Skip to content

Commit

Permalink
Update random for py3
Browse files Browse the repository at this point in the history
Python random stdlib has deprecated .jumpahead.
Numpy.random is being updated (formerly randomgen),
providing method recommendations for parallelisable monty-
carlo simulations.

Outstanding issues:
 - The track generation still appears to invoke np.random.normal
   during the main loop, which is likely a bug (defeating
   repeatability and parallelisation-independence).
   (Should replace with another call on PRNG object.)
 - Could update track generation to directly use random
   generator, rather than obfuscating with wrapper named
   like standard module. (No additional capabilities required.)
 - Should pass differently-initialised PRNGs explicitly,
   rather than rely on process-local global-state mutations.
   (This will support repeatability with multi-threading.)
 - lognormvariate currently implements a non-standard definition,
   and is only called once (with constant arguments). Should
   migrate to use conventional formulation.
 - Some unused functions (Cauchy and NCT) have been commented out
   pending removal. (To simplify future maintenance/refactors.)
  • Loading branch information
benjimin committed Aug 19, 2019
1 parent b575cef commit 58ab461
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 59 deletions.
8 changes: 4 additions & 4 deletions TrackGenerator/TrackGenerator.py
Expand Up @@ -1636,7 +1636,7 @@ def dumpAllCellCoefficients(self):
# library as it provides the ability to `jumpahead` in the stream (as
# opposed to `numpy.random`).

PRNG = random.Random()
PRNG = random.Random(seed=1234, stream=0)


def normal(mean=0.0, stddev=1.0):
Expand Down Expand Up @@ -1874,9 +1874,9 @@ def run(configFile, callback=None):
callback(sim.index, N)

if sim.seed:
PRNG.seed(sim.seed)
PRNG.jumpahead(sim.jumpahead)
log.debug('seed %i jumpahead %i', sim.seed, sim.jumpahead)
global PRNG #TODO: explicitly-pass rather than mutate global state
PRNG = random.Random(seed=sim.seed, stream=sim.index)
log.debug('seed %i stream %i', sim.seed, sim.index)

trackFile = pjoin(trackPath, sim.outfile)
tracks = tg.generateTracks(sim.ntracks, sim.index)
Expand Down
106 changes: 63 additions & 43 deletions Utilities/tcrandom.py
Expand Up @@ -13,21 +13,42 @@
.. |gamma| unicode:: U+003B3 .. GREEK SMALL LETTER GAMMA
"""
import random
import math
from scipy.special import nctdtrit, ndtri

try:
from numpy.random import Generator, Philox
except ImportError: # prior to numpy 1.18
try:
from randomgen import Generator, Philox
except ImportError: # prior to randomgen 1.17
from randomgen import RandomGenerator as Generator, Philox

#pylint: disable-msg=R0904

class Random(random.Random):
class Random:
"""
An extension of the standard :mod:`random` library to
allow sampling from additional distributions.
Pseudorandom number generator.
Expect each simulation to instantiate this class with the same
seed integer, and with a unique stream integer (drawn from e.g. an
enumeration of all the simulations)
"""

def __init__(self, value=None):
random.Random.__init__(self, value)
# numpy 1.18 recommends Philox for independent pseudorandom streams
def __init__(self, seed, stream):
self.PRNG = Generator(Philox(key=seed + stream))
def normalvariate(self, loc=0, scale=1, shape=None):
return self.PRNG.normal(loc, scale, shape)
def uniform(self, low=0, high=1, shape=None):
return self.PRNG.uniform(low, high, shape)
def random(self): # TODO: refactor elsewhere to call .uniform() directly
return self.uniform()
# TODO: migrate to use library implementations,
# rather than custom implementations:
# def logisticvariate(self, loc, sigma):
# return self.PRNG.logistic(loc, sigma)
# def lognormvariate:
# return self.PRNG.lognormal(mean, sigma)

def logisticvariate(self, mu, sigma):
"""
Expand All @@ -39,54 +60,53 @@ def logisticvariate(self, mu, sigma):
:returns: A random variate from the logistic distribution.
"""

u1 = self.random()
if sigma <= 0.0:
raise ValueError("Invalid input parameter: `sigma` must be positive")
return mu + sigma * math.log(u1 / (1 - u1))

def cauchyvariate(self, mu, sigma):
"""
Random variate from the Cauchy distribution.
:param float mu: Location parameter.
:param float sigma: Scale parameter (|sigma| > 0)
:returns: A random variate from the Cauchy distribution.
"""
u1 = self.random()
if sigma <= 0.0:
raise ValueError("Invalid input parameter: `sigma` must be positive")
return mu + sigma * math.tan(math.pi * (u1 - 0.5))

def nctvariate(self, df, nc, mu=0.0, sigma=1.0):
"""
Random variate from the non-central T distribution.
:param float df: degrees of freedom for the distribution.
:param float nc: non-centrality parameter.
:param float mu: Location parameter.
:param float sigma: Scale parameter.
:returns: A random variate from the non-central T distribution.
"""
if df <= 0.0:
raise ValueError("Invalid input parameter: `df` must be positive")
if sigma <= 0.0:
raise ValueError("Invalid input parameter: `sigma` must be positive")

u1 = self.random()
return mu + sigma * nctdtrit(df, nc, u1)
# def cauchyvariate(self, mu, sigma):
# """
# Random variate from the Cauchy distribution.
#
# :param float mu: Location parameter.
# :param float sigma: Scale parameter (|sigma| > 0)
#
# :returns: A random variate from the Cauchy distribution.
#
# """
# u1 = self.random()
# if sigma <= 0.0:
# raise ValueError("Invalid input parameter: `sigma` must be positive")
# return mu + sigma * math.tan(math.pi * (u1 - 0.5))

# def nctvariate(self, df, nc, mu=0.0, sigma=1.0):
# """
# Random variate from the non-central T distribution.
#
# :param float df: degrees of freedom for the distribution.
# :param float nc: non-centrality parameter.
# :param float mu: Location parameter.
# :param float sigma: Scale parameter.
#
# :returns: A random variate from the non-central T distribution.
# """
# if df <= 0.0:
# raise ValueError("Invalid input parameter: `df` must be positive")
# if sigma <= 0.0:
# raise ValueError("Invalid input parameter: `sigma` must be positive")
#
# u1 = self.random()
# return mu + sigma * nctdtrit(df, nc, u1)

def lognormvariate(self, xi, mu=0.0, sigma=1.0):
"""
Random variate from the lognormal distribution.
:param float xi: Shape parameter
:param float mu: Location parameter
:param float sigma: Scale paramter (|sigma| > 0)
:returns: A random variate from the lognormal distribution
"""
if xi <= 0.0:
Expand Down
28 changes: 16 additions & 12 deletions tests/test_tcrandom.py
Expand Up @@ -15,27 +15,30 @@ class TestRandom(unittest.TestCase):
def setUp(self):

self.seed = 1
self.prng = Random()

self.prng = Random(self.seed, stream=0)

@unittest.skip("Generator implementation specific")
def testLogistic(self):
"""Testing logistic variates"""
self.prng.seed(self.seed)
result = self.prng.logisticvariate(0, 1)
assert_almost_equal(result, -1.86290986)

@unittest.skip("Generator implementation specific")
def testNormal(self):
"""Testing normal variates"""
self.prng.seed(self.seed)
result = self.prng.normalvariate(0, 1)
assert_almost_equal(result, 0.607455857)

@unittest.skip("Generator implementation specific")
def testCauchy(self):
"""Testing cauchy variates"""
self.prng.seed(self.seed)
result = self.prng.cauchyvariate(0, 1)
assert_almost_equal(result, -2.22660116)

@unittest.skip("Generator implementation specific")
def testNCT(self):
self.prng.seed(self.seed)
result = self.prng.nctvariate(1, 0)
Expand All @@ -48,16 +51,17 @@ def testLogisticInvalidParams(self):
self.assertRaises(ValueError, self.prng.logisticvariate,
0, -1)

def testCauchyInvalidParams(self):
self.assertRaises(ValueError, self.prng.cauchyvariate,
0, -1)

def testNCTInvalidParams(self):
self.assertRaises(ValueError, self.prng.nctvariate,
-5, 0)
self.assertRaises(ValueError, self.prng.nctvariate,
1, 0, 1, -1)

# def testCauchyInvalidParams(self):
# self.assertRaises(ValueError, self.prng.cauchyvariate,
# 0, -1)
#
# def testNCTInvalidParams(self):
# self.assertRaises(ValueError, self.prng.nctvariate,
# -5, 0)
# self.assertRaises(ValueError, self.prng.nctvariate,
# 1, 0, 1, -1)

@unittest.skip("Generator implementation specific")
def testLognorm(self):
"""
Testing lognorm variates
Expand Down

0 comments on commit 58ab461

Please sign in to comment.