import math
__all__ = ['RNG']
from .primitive import PrimitiveRandomNumberGenerator
from .lcg import LCG1
[docs]class RNG(object):
"""
A class that uses a PrimitiveRandomNumberGenerator to provide several
higher-level random number generator methods.
"""
[docs] def __init__(self, generator_or_seed):
"""
Construct an RNG using the specified generator, if generatorOrSeed
is an instance of :py:class:`PrimitiveRandomNumberGenerator`. If
generator_or_seed is an integer, create a
:py:class:`.LCG1` using that
seed.
:type generator_or_seed: int or concrete instance of
:py:class:`PrimitiveRandomNumberGenerator`
"""
if isinstance(generator_or_seed, int):
self._generator = LCG1(generator_or_seed)
elif isinstance(generator_or_seed, PrimitiveRandomNumberGenerator):
self._generator = generator_or_seed
else:
raise ValueError(
"generatorOrSeed must be an int or a "
"PrimitiveRandomNumberGenerator")
self._nextNormal = None
# primitives: boolean, sign, integer, float
[docs] def b(self):
""" return a random boolean: True or False """
return self._generator.next_bool()
[docs] def sign(self):
"""
return a random sign: -1 or +1
"""
return 1 if self._generator.next_bool() else -1
[docs] def i(self, mx):
"""
return a random int in the range [0,mx-1]
"""
return math.floor(self._generator.next_float() * mx)
[docs] def f(self, mx=1.0):
"""
return a random float in the range [0,mx)
"""
return self._generator.next_float() * mx
# ranges: int, float
[docs] def ir(self, mn, mx):
"""
return a random int in the range [mn,mx-1]
"""
return mn + self.i(mx - mn)
[docs] def fr(self, mn, mx):
"""
return a random float in the range [mn, mx)
"""
return mn + self._generator.next_float() * (mx - mn)
[docs] def normal(self, std_dev=1.0):
"""
return a random float with a normal distribution having mean=0.0 and
the specified standard deviation.
:param std_dev: the standard deviation of the distribution.
:type std_dev: float
"""
if self._nextNormal is not None:
d = self._nextNormal * std_dev
self._nextNormal = None
return d
r2, v1, v2 = 2, 0, 0
while r2 >= 1.0 or r2 == 0.0:
v1 = self.f(2.0) - 1.0
v2 = self.f(2.0) - 1.0
r2 = v1 * v1 + v2 * v2
src = math.sqrt(-2 * math.log(r2) / r2)
self._nextNormal = v1 * src
return v2 * src * std_dev
[docs] def exp(self, beta):
"""
Exponential random deviate.
:param beta: survival parameter. This is the reciprocal of lambda,
the rate parameter.
:type beta: float
:return: a random float with an exponential distribution.
:rtype: float
"""
u = 0.0
while u == 0.0:
u = self.f()
return -math.log(u) / beta
[docs] def logistic(self, mean, scale):
"""
Logistic random deviate.
:param mean: mean value
:type mean: float
:param scale: the scale parameter. The variance of the distribution
is related to the scale by variance = (scale*pi)**2/3r
:type scale: float > 0
:return: a random float with a logistic distribution.
:rtype: float
"""
u = 0.0
while u * (1.0 - u) == 0:
u = self.f()
return mean + 0.551328895421792050 * scale * math.log(u / (1.0 - u))
[docs] def dice(self, num_dice, die_size):
"""
Roll [num_dice] x [die_size]-sided dice and return the sum.
The dice are what you would expect in the real world: an integer in the
range [1,die_size].
"""
return num_dice + self.i_sum(num_dice, die_size)
[docs] def f2(self, half_width=1.0, center=0.0):
"""
Return sum of two uniform random deviates. Result will lie in
[center-half_width, center+half_width) and have a triangular
distribution with a standard deviation of approximately 0.41*half_width.
:param half_width: half width of the resulting distribution
:type half_width: float
:param center: the center (mean value) of the distribution
:type center: float
:return: random float in the specified region
:rtype: float
"""
return center + (self.f() + self.f()) * half_width - half_width
[docs] def f3(self, half_width=1.0, center=0.0):
"""
Return sum of three uniform random deviates. The result has a
distribution similar to a normal distribution, but has a finite range:
[center-half_width, center+half_width) and approximate standard
deviation 0.33*half_width
:param half_width: half width of the resulting distribution
:type half_width: float
:param center: the center (mean value) of the distribution
:type center: float
:return: random float in the specified region
:rtype: float
"""
return center + (
self.f() + self.f() + self.f()) * half_width / \
1.5 - \
half_width
[docs] def f4(self, half_width=1.0, center=0.0):
"""
Return sum of four uniform random deviates. The result has a
distribution similar to a normal distribution, but has a finite range:
[center-half_width, center+half_width) and approximate standard
deviation 0.29*half_width
:param half_width: half width of the resulting distribution
:type half_width: float
:param center: the center (mean value) of the distribution
:type center: float
:return: random float in the specified region
:rtype: float
"""
return center + (self.f() + self.f() +
self.f() + self.f()) * half_width / 2.0 - half_width
[docs] def f5(self, half_width=1.0, center=0.0):
"""
Return sum of five uniform random deviates. The result has a
distribution similar to a normal distribution, but has a finite range:
[center-half_width, center+half_width) and approximate standard
deviation 0.26*half_width
:param half_width: half width of the resulting distribution
:type half_width: float
:param center: the center (mean value) of the distribution
:type center: float
:return: random float in the specified region
:rtype: float
"""
return center + (self.f() + self.f() + self.f() +
self.f() + self.f()) * half_width / 2.5 - half_width
[docs] def f6(self, half_width=1.0, center=0.0):
"""
Return sum of six uniform random deviates. The result has a
distribution similar to a normal distribution, but has a finite range:
[center-half_width, center+half_width) and approximate standard
deviation 0.24*half_width
:param half_width: half width of the resulting distribution
:type half_width: float
:param center: the center (mean value) of the distribution
:type center: float
:return: random float in the specified region
:rtype: float
"""
return center + (self.f() + self.f() + self.f() +
self.f() + self.f() + self.f()) * half_width / 3.0 - \
half_width
[docs] def f2r(self, a, b):
"""
Return a random deviate in the range [a, b) with an approximately
normal distribution.
:param a: left endpoint of range
:type a: float
:param b: right endpoint of range
:type b: float
:return: random float in [a, b)
:rtype: float
"""
return self.f2((b - a) / 2.0, (a + b) / 2.0)
[docs] def f3r(self, a, b):
"""
Return a random deviate in the range [a, b) with an approximately
normal distribution.
:param a: left endpoint of range
:type a: float
:param b: right endpoint of range
:type b: float
:return: random float in [a, b)
:rtype: float
"""
return self.f3((b - a) / 2.0, (a + b) / 2.0)
[docs] def f4r(self, a, b):
"""
Return a random deviate in the range [a, b) with an approximately
normal distribution.
:param a: left endpoint of range
:type a: float
:param b: right endpoint of range
:type b: float
:return: random float in [a, b)
:rtype: float
"""
return self.f4((b - a) / 2.0, (a + b) / 2.0)
[docs] def f5r(self, a, b):
"""
Return a random deviate in the range [a, b) with an approximately
normal distribution.
:param a: left endpoint of range
:type a: float
:param b: right endpoint of range
:type b: float
:return: random float in [a, b)
:rtype: float
"""
return self.f5((b - a) / 2.0, (a + b) / 2.0)
[docs] def f6r(self, a, b):
"""
Return a random deviate in the range [a, b) with an approximately
normal distribution.
:param a: left endpoint of range
:type a: float
:param b: right endpoint of range
:type b: float
:return: random float in [a, b)
:rtype: float
"""
return self.f6((b - a) / 2.0, (a + b) / 2.0)
[docs] def f2a(self, left, center, right):
"""
Return a random deviate in the range [left, right) with peak
probability at center. Half of the values will lie to the right of
center, and half will le to the left.
:param left: left endpoint of range
:type left: float
:param right: right endpoint of range
:type right: float
:param center: left endpoint of range
:type center: float
:return: random float in [left, right)
:rtype: float
"""
x = self.f2(1.0)
if x > 0:
return center + x * (right - center)
return center + x * (center - left)
[docs] def f3a(self, left, center, right):
"""
Return a random deviate in the range [left, right) with peak
probability at center. Half of the values will lie to the right of
center, and half will le to the left.
:param left: left endpoint of range
:type left: float
:param right: right endpoint of range
:type right: float
:param center: left endpoint of range
:type center: float
:return: random float in [left, right)
:rtype: float
"""
x = self.f3(1.0)
if x > 0:
return center + x * (right - center)
return center + x * (center - left)
[docs] def f4a(self, left, center, right):
"""
Return a random deviate in the range [left, right) with peak
probability at center. Half of the values will lie to the right of
center, and half will le to the left.
:param left: left endpoint of range
:type left: float
:param right: right endpoint of range
:type right: float
:param center: left endpoint of range
:type center: float
:return: random float in [left, right)
:rtype: float
"""
x = self.f4(1.0)
if x > 0:
return center + x * (right - center)
return center + x * (center - left)
[docs] def f5a(self, left, center, right):
"""
Return a random deviate in the range [left, right) with peak
probability at center. Half of the values will lie to the right of
center, and half will le to the left.
:param left: left endpoint of range
:type left: float
:param right: right endpoint of range
:type right: float
:param center: left endpoint of range
:type center: float
:return: random float in [left, right)
:rtype: float
"""
x = self.f5(1.0)
if x > 0:
return center + x * (right - center)
return center + x * (center - left)
[docs] def f6a(self, left, center, right):
"""
Return a random deviate in the range [left, right) with peak
probability at center. Half of the values will lie to the right of
center, and half will le to the left.
:param left: left endpoint of range
:type left: float
:param right: right endpoint of range
:type right: float
:param center: left endpoint of range
:type center: float
:return: random float in [left, right)
:rtype: float
"""
x = self.f6(1.0)
if x > 0:
return center + x * (right - center)
return center + x * (center - left)
[docs] def i_sum(self, n, r):
"""
generate [n] random ints in the range [0,r) and return the sum.
"""
result = 0
for i in range(n):
result += self.i(r)
return result
[docs] def in_triangle(self, a, b):
"""
Return a uniformly distributed random point within a right triangle
with measurements [a]x[b].
One leg of the triangle lies on the x-axis: [0,a].
The other leg of the triangle lies on the y-axis: [0,b].
:param a: the width of the triangle
:type a: float
:param b: the height of the triangle
:type b: float
:return: A point inside the triangle.
:rtype: (float, float)
"""
x, y = self.f(a), self.f(b)
if y > b - (b / a) * x:
x, y = a - x, b - y
return x, y
[docs] def choose(self, items):
"""
choose a random element of items.
:param items: the items to choose from.
:type items: Seq
"""
return items[self.i(len(items))]
[docs] def shuffle(self, items):
"""
Shuffle the elements of an array/list. Items is shuffled in-place -
no value is returned.
:param items: the items to shuffle.
:type items: Seq
"""
m = len(items)
# While there remain elements to shuffle...
while m != 0:
# Pick a remaining element?
i = self.i(m)
m -= 1
# And swap it with the current element.
t = items[m]
items[m] = items[i]
items[i] = t
[docs] def normal_asym(self,
center,
left_stddev,
right_stddev):
"""
Asymmetric normal distribution. This distribution stretches the left
and right sides of the distribution so that the resulting
distribution is asymmetric.
:param left_stddev: standard
deviation of left half
:type left_stddev: float
:param center: the center of the distribution. This is not the same
as the mean unless left_stddev == right_stddev.
:type center: float
:param right_stddev: standard deviation of right half
:type right_stddev: float
:return: random deviate with asymmetric normal distribution.
:rtype: float
"""
x = self.normal(1.0)
if x < 0.0:
return center + x * left_stddev
return center + x * right_stddev