From cd804108548e1939bd8646634ed52ef388ee9f44 Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Thu, 25 Jan 2001 20:25:57 +0000 Subject: Cosmetic changes after some sleep; no change in semantics. --- Lib/random.py | 146 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 82 insertions(+), 64 deletions(-) diff --git a/Lib/random.py b/Lib/random.py index 1b91886..d098d39 100644 --- a/Lib/random.py +++ b/Lib/random.py @@ -27,7 +27,10 @@ Translated from anonymously contributed C/C++ source. Multi-threading note: the random number generator used here is not 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. @@ -71,9 +74,11 @@ class Random: self.seed(x) self.gauss_next = None +## -------------------- core generator ------------------- + # Specific to Wichmann-Hill generator. Subclasses wishing to use a # 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): """Set the Wichmann-Hill seed from (x, y, z). @@ -96,10 +101,43 @@ class Random: # Zero is a poor seed, so substitute 1 self._seed = (x or 1, y or 1, z or 1) + def random(self): + """Get the next random number in the range [0.0, 1.0).""" + + # Wichman-Hill random number generator. + # + # Wichmann, B. A. & Hill, I. D. (1982) + # Algorithm AS 183: + # An efficient and portable pseudo-random number generator + # Applied Statistics 31 (1982) 188-190 + # + # see also: + # Correction to Algorithm AS 183 + # Applied Statistics 33 (1984) 123 + # + # McLeod, A. I. (1985) + # A remark on Algorithm AS 183 + # Applied Statistics 34 (1985),198-200 + + # This part is thread-unsafe: + # BEGIN CRITICAL SECTION + x, y, z = self._seed + x = (171 * x) % 30269 + y = (172 * y) % 30307 + z = (170 * z) % 30323 + self._seed = x, y, z + # END CRITICAL SECTION + + # Note: on a platform using IEEE-754 double arithmetic, this can + # 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 + def seed(self, a=None): - """Seed from hashable value + """Seed from hashable object's hash code. - None or no argument seeds from current time. + 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: @@ -118,9 +156,6 @@ class Random: """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] @@ -131,9 +166,6 @@ class Random: "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. @@ -156,36 +188,18 @@ class Random: z = int(z * pow(170, n, 30323)) % 30323 self._seed = x, y, z - def random(self): - """Get the next random number in the range [0.0, 1.0).""" +## ---- Methods below this point do not need to be overridden when +## ---- subclassing for the purpose of using a different core generator. - # Wichman-Hill random number generator. - # - # Wichmann, B. A. & Hill, I. D. (1982) - # Algorithm AS 183: - # An efficient and portable pseudo-random number generator - # Applied Statistics 31 (1982) 188-190 - # - # see also: - # Correction to Algorithm AS 183 - # Applied Statistics 33 (1984) 123 - # - # McLeod, A. I. (1985) - # A remark on Algorithm AS 183 - # Applied Statistics 34 (1985),198-200 +## -------------------- pickle support ------------------- - # This part is thread-unsafe: - # BEGIN CRITICAL SECTION - x, y, z = self._seed - x = (171 * x) % 30269 - y = (172 * y) % 30307 - z = (170 * z) % 30323 - self._seed = x, y, z - # END CRITICAL SECTION + def __getstate__(self): # for pickle + return self.getstate() - # Note: on a platform using IEEE-754 double arithmetic, this can - # 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 + def __setstate__(self, state): # for pickle + self.setstate(state) + +## -------------------- integer methods ------------------- def randrange(self, start, stop=None, step=1, int=int, default=None): """Choose a random item from range(start, stop[, step]). @@ -227,14 +241,15 @@ class Random: return istart + istep*int(self.random() * n) def randint(self, a, b): - """Get a random integer in the range [a, b] including - both end points. + """Return random integer in range [a, b], including both end points. - (Deprecated; use randrange below.) + (Deprecated; use randrange(a, b+1).) """ return self.randrange(a, b+1) +## -------------------- sequence methods ------------------- + def choice(self, seq): """Choose a random element from a non-empty sequence.""" return seq[int(self.random() * len(seq))] @@ -254,17 +269,19 @@ class Random: if random is None: random = self.random 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)) x[i], x[j] = x[j], x[i] -# -------------------- uniform distribution ------------------- +## -------------------- real-valued distributions ------------------- + +## -------------------- uniform distribution ------------------- def uniform(self, a, b): """Get a random number in the range [a, b).""" return a + (b-a) * self.random() -# -------------------- normal distribution -------------------- +## -------------------- normal distribution -------------------- def normalvariate(self, mu, sigma): # mu = mean, sigma = standard deviation @@ -284,12 +301,12 @@ class Random: break return mu + z*sigma -# -------------------- lognormal distribution -------------------- +## -------------------- lognormal distribution -------------------- def lognormvariate(self, mu, sigma): return _exp(self.normalvariate(mu, sigma)) -# -------------------- circular uniform -------------------- +## -------------------- circular uniform -------------------- def cunifvariate(self, mean, arc): # mean: mean angle (in radians between 0 and pi) @@ -297,7 +314,7 @@ class Random: return (mean + arc * (self.random() - 0.5)) % _pi -# -------------------- exponential distribution -------------------- +## -------------------- exponential distribution -------------------- def expovariate(self, lambd): # lambd: rate lambd = 1/mean @@ -309,7 +326,7 @@ class Random: u = random() return -_log(u)/lambd -# -------------------- von Mises distribution -------------------- +## -------------------- von Mises distribution -------------------- def vonmisesvariate(self, mu, kappa): # mu: mean angle (in radians between 0 and 2*pi) @@ -351,7 +368,7 @@ class Random: return theta -# -------------------- gamma distribution -------------------- +## -------------------- gamma distribution -------------------- def gammavariate(self, alpha, beta): # beta times standard gamma @@ -410,7 +427,7 @@ class Random: return x -# -------------------- Gauss (faster alternative) -------------------- +## -------------------- Gauss (faster alternative) -------------------- def gauss(self, mu, sigma): @@ -443,7 +460,7 @@ class Random: return mu + z*sigma -# -------------------- beta -------------------- +## -------------------- beta -------------------- def betavariate(self, alpha, beta): @@ -453,7 +470,7 @@ class Random: z = self.expovariate(1.0/beta) return z/(y+z) -# -------------------- Pareto -------------------- +## -------------------- Pareto -------------------- def paretovariate(self, alpha): # Jain, pg. 495 @@ -461,7 +478,7 @@ class Random: u = self.random() return 1.0 / pow(u, 1.0/alpha) -# -------------------- Weibull -------------------- +## -------------------- Weibull -------------------- def weibullvariate(self, alpha, beta): # Jain, pg. 499; bug fix courtesy Bill Arms @@ -469,7 +486,7 @@ class Random: u = self.random() return alpha * pow(-_log(u), 1.0/beta) -# -------------------- test program -------------------- +## -------------------- test program -------------------- def _test_generator(n, funccall): import time @@ -493,17 +510,6 @@ def _test_generator(n, funccall): print 'avg %g, stddev %g, min %g, max %g' % \ (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): print 'TWOPI =', TWOPI print 'LOG4 =', LOG4 @@ -526,6 +532,18 @@ def _test(N=200): _test_generator(N, 'paretovariate(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. _inst = Random() seed = _inst.seed -- cgit v0.12