Source code for gammapy.utils.distributions.general_random

# Licensed under a 3-clause BSD style license - see LICENSE.rst
from __future__ import absolute_import, division, print_function, unicode_literals
import numpy as np
from astropy.extern.six.moves import range
from ...utils.random import get_random_state

__all__ = ['GeneralRandom']


[docs]class GeneralRandom(object): """Fast random number generation with an arbitrary pdf of a continuous variable x. Linear interpolation is applied between points pdf(x) at which the pdf is specified. I started with the recipy 576556, removed some unnecessary stuff and added some useful stuff. Recipe 576556: Generating random numbers with arbitrary distribution http://code.activestate.com/recipes/576556/ Note: This class can only handle 1D distributions. Note: Should it be required the cdf could be deleted after computing to inversecdf to free memory since it is not required for random number generation.""" def __init__(self, pdf, min_range, max_range, ninversecdf=None, ran_res=1e3): """Initialize the lookup table Inputs: x: random number values pdf: probability density profile at that point ninversecdf: number of reverse lookup values Lookup is computed and stored in: cdf: cumulative pdf inversecdf: the inverse lookup table delta_inversecdf: difference of inversecdf""" self.ran_res = ran_res # Resolution of the PDF x = np.linspace(min_range, max_range, ran_res) # This is a good default for the number of reverse # lookups to not loose much information in the pdf if ninversecdf is None: ninversecdf = 5 * x.size self.nx = x.size self.x = x self.pdf = pdf(x) # old solution has problems with first bin: # self.pdf = pdf/float(pdf.sum()) #normalize it # self.cdf = self.pdf.cumsum() self.cdf = np.empty(self.nx, dtype=float) self.cdf[0] = 0 for i in range(1, self.nx): self.cdf[i] = self.cdf[i - 1] + (self.pdf[i] + self.pdf[i - 1]) * (self.x[i] - self.x[i - 1]) / 2 self.pdf = self.pdf / self.cdf.max() # normalize pdf self.cdf = self.cdf / self.cdf.max() # normalize cdf self.ninversecdf = ninversecdf y = np.arange(ninversecdf) / float(ninversecdf) # delta = 1.0/ninversecdf self.inversecdf = np.empty(ninversecdf) self.inversecdf[0] = self.x[0] cdf_idx = 0 for n in range(1, self.ninversecdf): while self.cdf[cdf_idx] < y[n] and cdf_idx < ninversecdf: cdf_idx += 1 self.inversecdf[n] = self.x[cdf_idx - 1] + \ (self.x[cdf_idx] - self.x[cdf_idx - 1]) * \ (y[n] - self.cdf[cdf_idx - 1]) / \ (self.cdf[cdf_idx] - self.cdf[cdf_idx - 1]) if cdf_idx >= ninversecdf: break self.delta_inversecdf = \ np.concatenate((np.diff(self.inversecdf), [0]))
[docs] def draw(self, N=1000, random_state='random-seed'): """Returns an array of random numbers with the requested distribution. The random numbers x are generated using the lookups inversecdf and delta_inversecdf. Parameters ---------- N : int array length random_state : {int, 'random-seed', 'global-rng', `~numpy.random.RandomState`} Defines random number generator initialisation. Passed to `~gammapy.utils.random.get_random_state`. Returns ------- x : `~numpy.ndarray` random numbers """ random_state = get_random_state(random_state) # Generate uniform random float index in range [0, ninversecdf-1] idx_f = random_state.uniform(size=N, high=self.ninversecdf - 1) # Round down to next integer idx = np.array(idx_f, 'i') # Use the inversecdf lookup to get the corresponding x # and the delta_inversecdf lookup for linear interpolation x = self.inversecdf[idx] + (idx_f - idx) * self.delta_inversecdf[idx] return x
[docs] def make_plots(self, N=1e5): """Plot the pdf, cdf and inversecdf and a random distribution of sample size N. Useful for illustrating the interpolation and debugging.""" import matplotlib.pyplot as plt # Plot the cdf plt.figure() plt.plot(self.x, self.cdf) plt.title('cdf(x)') # Plot the inverse cdf plt.figure() y = np.arange(self.ninversecdf) / float(self.ninversecdf) plt.plot(y, self.inversecdf) plt.title('inversecdf(y)') # Plot the pdf and a random sample distribution plt.figure() x = self.draw(N) # Use the same binning as self.x binedges = self.x plt.hist(x, bins=binedges, normed=True) # x1 = 0.5*(edges[0:-1] + edges[1:]) # plot(x1, p1/p1.sum(),label='hist of random draws') plt.plot(self.x, self.pdf, label='pdf')