Source code for sbp_env.randomness

import math

import numpy as np
import scipy
from SALib.sample import (
    saltelli,
    sobol_sequence,
    latin,
    finite_diff,
    fast_sampler,
)

#: the size of bucket to draw random samples
NUM_DATA_POINTS = 10000
SUPPORTED_RANDOM_METHODS_TITLES = {
    "pseudo_random": "Pseudo Random",
    "sobol_sequence": "Sobol sequence",
    "saltelli": "Saltelli's extension of Sobol sequence",
    "latin_hypercube": "Latin hypercube",
    "finite_differences": "Finite differences",
    "fast": "Fourier Amplitude Sensitivity Test (FAST)",
}
SUPPORTED_RANDOM_METHODS = tuple(t for t in SUPPORTED_RANDOM_METHODS_TITLES)
"""Supported methods to draw random numbers in samplers. Supported random methods are 
as follows:

* ``pseudo_random`` from numpy
    **Pseudo Random**
* ``sobol_sequence`` from *SALib*
    **Sobol sequence**
* ``saltelli`` from *SALib*
    **Saltelli's extension of Sobol sequence**
* ``latin_hypercube`` from *SALib*
    **Latin hypercube**
* ``finite_differences`` from *SALib*
    **Finite differences**
* ``fast`` from *SALib*
    **Fourier Amplitude Sensitivity Test (FAST)**

"""


[docs]class NormalRandomnessManager: """ Randomness Manager that draw a bucket of **normally distributed** random numbers and refill the bucket on-demand when it is exhausted. """ def __init__(self): # draws of normal distribution self.normal_draws_reserve = None # draws of half normal distribution self.half_normal_draws_reserve = None
[docs] def redraw_normal(self, kappa, sigma, use_vonmises=True): r"""Redraw the bucket of normally distributed random numbers :param kappa: the kappa :math:`\kappa` parameter to be used to draw from a von Mises distribution .. math:: x \sim f_\text{VonMises}( x | \mu, \kappa) :param sigma: the sigma :math:`\sigma` parameter to be used to draw from a normal distribution .. math:: x \sim \mathcal{N}( x | \mu, \sigma^2) :param use_vonmises: (Default value = True) """ if use_vonmises: assert kappa is not None dist = np.random.vonmises(0, kappa, NUM_DATA_POINTS) else: assert sigma is not None dist = np.random.normal(0, sigma, NUM_DATA_POINTS) self.normal_draws_reserve = dist
[docs] def draw_normal( self, origin: np.ndarray, use_vonmises: bool = True, kappa: float = 1, sigma: float = math.pi / 4, ) -> np.ndarray: r"""Draw from the normal distribution :param origin: the origin (mean) for the random number, which will be used to shift the number that this function returns :param use_vonmises: use vom-Mises distribution :param kappa: the :math:`\kappa` value :param sigma: the :math:`\sigma` value """ if self.normal_draws_reserve is None or self.normal_draws_reserve.size < 1: self.redraw_normal( use_vonmises=use_vonmises, kappa=kappa, sigma=math.pi / 4 ) # draw from samples draw = self.normal_draws_reserve[-1] self.normal_draws_reserve = self.normal_draws_reserve[:-1] # shift location return draw + origin
[docs] def redraw_half_normal(self, start_at: np.ndarray, scale: float): """Re-draw from a half normal distribution :param start_at: the origin :param scale: the scale of the half normal """ dist = scipy.stats.halfnorm.rvs(loc=start_at, scale=scale, size=NUM_DATA_POINTS) self.half_normal_draws_reserve = dist
[docs] def draw_half_normal(self, start_at: np.ndarray, scale: float = 1): """Draw from a half normal distribution :param start_at: the origin :param scale: the scale of the half normal """ if ( self.half_normal_draws_reserve is None or self.half_normal_draws_reserve.size < 1 ): self.redraw_half_normal(start_at, scale) # draw from samples draw = self.half_normal_draws_reserve[-1] self.half_normal_draws_reserve = self.half_normal_draws_reserve[:-1] return draw
[docs]class RandomnessManager: """ A random number manager that draw a buckets of random numbers at a number and redraw when the bucket is exhausted. """ def __init__(self, num_dim: int, bucket_size: int = NUM_DATA_POINTS): """ :param num_dim: the number of dimension """ # draws of random numbers self.random_draws = {} self.num_dim = num_dim self.bucket_size = bucket_size
[docs] def redraw(self, random_method: str): """Redraw the random number with the given method :param random_method: the random method to use """ problem = { "num_vars": self.num_dim, "names": list(range(self.num_dim)), "bounds": [[0, 1]] * self.num_dim, } if random_method == "pseudo_random": seq = np.random.random((self.bucket_size, self.num_dim)) elif random_method == "sobol_sequence": seq = sobol_sequence.sample(self.bucket_size, self.num_dim) elif random_method == "saltelli": seq = saltelli.sample(problem, self.bucket_size, calc_second_order=False) elif random_method == "latin_hypercube": seq = latin.sample(problem, self.bucket_size) elif random_method == "finite_differences": seq = finite_diff.sample(problem, self.bucket_size) elif random_method == "fast": seq = fast_sampler.sample(problem, self.bucket_size, M=45) else: raise ValueError(f"Unknown random method {random_method}") self.random_draws[random_method] = seq
[docs] def get_random(self, random_method): """Get one sample of random number :math:`r` where :math:`0 \le r \le 1` :param random_method: The kind of random number method to use, must be one of the choice in :data:`SUPPORTED_RANDOM_METHODS` """ if ( random_method not in self.random_draws or self.random_draws[random_method].size < 1 ): self.redraw(random_method) last = self.random_draws[random_method][-1] self.random_draws[random_method] = self.random_draws[random_method][:-1] return last
if __name__ == "__main__": # show presentation of plotting different qrsai-random numbers import matplotlib.pyplot as plt from matplotlib.pyplot import figure def show_fig(x, y, title=None): figure(num=1, figsize=(8, 6), dpi=200) plt.title(title) plt.plot(x, y, "r.") plt.show() random_numbers = RandomnessManager(num_dim=2) for _ in range(1): for m in SUPPORTED_RANDOM_METHODS: title = SUPPORTED_RANDOM_METHODS_TITLES[m] random_numbers.redraw(m) seq = random_numbers.random_draws[m][:NUM_DATA_POINTS] show_fig(seq.T[0], seq.T[1], title)