def rbinomial(self, n, phi): """ The binomial distribution: p(N=k) = bincoeff * phi**k * (1-phi)**(n-k); n >= 1; k = 0, 1,...., n where phi is the frequency or "Bernoulli probability". Algorithm taken from ORNL-RSIC-38, Vol II (1973). """ assert is_posinteger(n), \ "n must be a positive integer in rbinomial!" assert 0.0 < phi and phi < 1.0, \ "frequency parameter is out of range in rbinomial!" normconst = 10.0 onemphi = 1.0 - phi if phi < 0.5: w = int(round(normconst * onemphi / phi)) else: w = int(round(normconst * phi / onemphi)) if n > w: #------------------------------------------------------- k = int(round(self.rnormal(n * phi, sqrt(n * phi * onemphi)))) else: #------------------------------------------------------- if phi < 0.25: k = -1 m = 0 phi = -safelog(onemphi) while m < n: r = self.rexpo(1.0) j = 1 + int(r / phi) m += j k += 1 if m == n: k += 1 elif phi > 0.75: k = n + 1 m = 0 phi = -safelog(phi) while m < n: r = self.rexpo(1.0) j = 1 + int(r / phi) m += j k -= 1 if m == n: k -= 1 else: # if 0.25 <= phi and phi <= 0.75: k = 0 m = 0 while m < n: r = self.runif01() if r < phi: k += 1 m += 1 k = kept_within(0, k, n) return k
def rtukeylambda_gen(self, lam1, lam2, lam3, lam4, pmin=0.0, pmax=1.0): """ The Friemer-Mudholkar-Kollia-Lin generalized Tukey lambda distribution. lam1 is a location parameter and lam2 a scale parameter. lam3 and lam4 are associated with the shape of the distribution. """ assert lam2 > 0.0, \ "shape parameter lam2 must be a positive float in rtukeylambda_gen!" assert 0.0 <= pmin < pmax, \ "pmin must be in [0.0, pmax) in rtukeylambda_gen!" assert pmin < pmax <= 1.0, \ "pmax must be in (pmin, 1.0] in rtukeylambda_gen!" p = pmin + (pmax-pmin)*self.runif01() if lam3 == 0.0: q3 = safelog(p) else: q3 = (p**lam3-1.0) / lam3 if lam4 == 0.0: q4 = safelog(1.0-p) else: q4 = ((1.0-p)**lam4 - 1.0) / lam4 x = lam1 + (q3-q4)/lam2 return x
def itukeylambda_gen(prob, lam1, lam2, lam3, lam4): """ The Friemer-Mudholkar-Kollia-Lin generalized Tukey-Lambda distribution. lam1 is a location parameter and lam2 is a scale parameter. lam3 and lam4 are associated with the shape. lam2 must be a positive number. """ _assertprob(prob, 'itukeylambda_gen') # --- assert lam2 > 0.0, \ "shape parameter lam2 must be a positive float in itukeylambda_gen!" if lam3 == 0.0: q3 = safelog(prob) else: q3 = (prob**lam3-1.0) / lam3 if lam4 == 0.0: q4 = safelog(1.0-prob) else: q4 = ((1.0-prob)**lam4 - 1.0) / lam4 x = lam1 + (q3-q4)/lam2 return x
def rbinomial(self, n, phi): """ The binomial distribution: p(N=k) = bincoeff * phi**k * (1-phi)**(n-k); n >= 1; k = 0, 1,...., n where phi is the frequency or "Bernoulli probability". Algorithm taken from ORNL-RSIC-38, Vol II (1973). """ assert is_posinteger(n), \ "n must be a positive integer in rbinomial!" assert 0.0 < phi and phi < 1.0, \ "frequency parameter is out of range in rbinomial!" normconst = 10.0 onemphi = 1.0 - phi if phi < 0.5: w = int(round(normconst * onemphi / phi)) else: w = int(round(normconst * phi / onemphi)) if n > w: #------------------------------------------------------- k = int(round(self.rnormal(n*phi, sqrt(n*phi*onemphi)))) else: #------------------------------------------------------- if phi < 0.25: k = -1 m = 0 phi = - safelog(onemphi) while m < n: r = self.rexpo(1.0) j = 1 + int(r/phi) m += j k += 1 if m == n: k += 1 elif phi > 0.75: k = n + 1 m = 0 phi = - safelog(phi) while m < n: r = self.rexpo(1.0) j = 1 + int(r/phi) m += j k -= 1 if m == n: k -= 1 else: # if 0.25 <= phi and phi <= 0.75: k = 0 m = 0 while m < n: r = self.runif01() if r < phi: k += 1 m += 1 k = kept_within(0, k, n) return k
def iextreme_I(prob, type='max', mu=0.0, scale=1.0): """ Extreme value distribution type I (aka the Gumbel distribution or Gumbel distribution type I): F = exp{-exp[-(x-mu)/scale]} (max variant) f = exp[-(x-mu)/scale] * exp{-exp[-(x-mu)/scale]} / scale F = 1 - exp{-exp[+(x-mu)/scale]} (min variant) f = exp[+(x-mu)/scale] * exp{-exp[+(x-mu)/scale]} / scale type must be 'max' or 'min' scale must be > 0.0 """ _assertprob(prob, 'iextreme_I') assert scale >= 0.0, "scale parameter must not be negative in iextreme_I!" if type == 'max': x = - scale*safelog(-safelog(prob)) + mu elif type == 'min': x = scale*safelog(-safelog(1.0-prob)) + mu else: raise Error("iextreme_I: type must be either 'max' or 'min'") return x
def rtukeylambda_gen(self, lam1, lam2, lam3, lam4, pmin=0.0, pmax=1.0): """ The Friemer-Mudholkar-Kollia-Lin generalized Tukey lambda distribution. lam1 is a location parameter and lam2 a scale parameter. lam3 and lam4 are associated with the shape of the distribution. """ assert lam2 > 0.0, \ "shape parameter lam2 must be a positive float in rtukeylambda_gen!" assert 0.0 <= pmin < pmax, \ "pmin must be in [0.0, pmax) in rtukeylambda_gen!" assert pmin < pmax <= 1.0, \ "pmax must be in (pmin, 1.0] in rtukeylambda_gen!" p = pmin + (pmax - pmin) * self.runif01() if lam3 == 0.0: q3 = safelog(p) else: q3 = (p**lam3 - 1.0) / lam3 if lam4 == 0.0: q4 = safelog(1.0 - p) else: q4 = ((1.0 - p)**lam4 - 1.0) / lam4 x = lam1 + (q3 - q4) / lam2 return x
def ikodlin(prob, gam, eta): """ The inverse of the Kodlin distribution, aka the linear hazard rate distribution: f = (gam + eta*x) * exp{-[gam*x + (1/2)*eta*x**2]} F = 1 - exp{-[gam*x + (1/2)*eta*x**2]} x, gam, eta >= 0 """ _assertprob(prob, 'ikodlin') assert gam >= 0.0, "no parameters in ikodlin must be negative!" assert eta >= 0.0, "no parameters in ikodlin must be negative!" # (1/2)*eta*x**2 + gam*x + ln(1-F) = 0 try: a = 0.5*eta b = gam c = safelog(1.0-prob) x1, x2 = z2nddeg_real(a, b, c) x = max(x1, x2) except ValueError: x = float('inf') x = kept_within(0.0, x) return x
def rerlang(self, nshape, phasemean, xmax=float('inf')): """ Generator of Erlang-distributed random variates. Represents the sum of nshape exponentially distributed random variables, each having the same mean value = phasemean. For nshape = 1 it works as a generator of exponentially distributed random numbers. """ assert is_posinteger(nshape), \ "shape parameter must be a positive integer in rerlang!" assert phasemean >= 0.0, "phasemean must not be negative in rerlang!" assert xmax >= 0.0, "variate max must be non-negative in rerlang!" if nshape < GeneralRandomStream.__ERLANG2GAMMA: while True: x = 1.0 for k in range(0, nshape): x *= self.runif01() # Might turn out to be zero... x = -phasemean * safelog(x) if x <= xmax: break else: # Gamma is OK while True: x = phasemean * self.rgamma(float(nshape), 1.0) if x <= xmax: break x = kept_within(0.0, x) return x
def rinhomexpo_cum(self, lamt, suplamt): """ Generates - cumulatively - inhomogeneously exponentially distributed interarrival times (due to an inhomogeneous Poisson process) from clock time = 0.0 (the algorithm is taken from Bratley, Fox & Schrage). 'lamt' is an externally defined function of clock time that returns the present arrival rate (arrival frequency). 'suplamt' is another externally defined function that returns the supremum of the arrival rate over the REMAINING time from the present clock time. NB. A SEPARATE STREAM MUST BE INSTANTIATED FOR EACH ONE OF THE VARIATES THAT ARE GENERATED USING THIS METHOD, EACH WITH A SEPARATE SEED!! """ errtxt1 = "All values from suplamt must be " errtxt1 += "non-negative floats in rinhomexpo_cum!" errtxt2 = "All values from lamt must be " errtxt2 += "non-negative floats in rinhomexpo_cum!" ticum = self.__ticum while True: lamsup = suplamt(ticum) assert lamsup >= 0.0, errortxt1 u = self.runif01() ticum -= safediv(1.0, lamsup) * safelog(self.runif01()) lam = lamt(ticum) assert lam >= 0.0, errortxt2 if u*lamsup <= lam: break self.__ticum = ticum return ticum
def rinhomexpo_cum(self, lamt, suplamt): """ Generates - cumulatively - inhomogeneously exponentially distributed interarrival times (due to an inhomogeneous Poisson process) from clock time = 0.0 (the algorithm is taken from Bratley, Fox & Schrage). 'lamt' is an externally defined function of clock time that returns the present arrival rate (arrival frequency). 'suplamt' is another externally defined function that returns the supremum of the arrival rate over the REMAINING time from the present clock time. NB. A SEPARATE STREAM MUST BE INSTANTIATED FOR EACH ONE OF THE VARIATES THAT ARE GENERATED USING THIS METHOD, EACH WITH A SEPARATE SEED!! """ errtxt1 = "All values from suplamt must be " errtxt1 += "non-negative floats in rinhomexpo_cum!" errtxt2 = "All values from lamt must be " errtxt2 += "non-negative floats in rinhomexpo_cum!" ticum = self.__ticum while True: lamsup = suplamt(ticum) assert lamsup >= 0.0, errortxt1 u = self.runif01() ticum -= safediv(1.0, lamsup) * safelog(self.runif01()) lam = lamt(ticum) assert lam >= 0.0, errortxt2 if u * lamsup <= lam: break self.__ticum = ticum return ticum
def rerlang(self, nshape, phasemean, xmax=float('inf')): """ Generator of Erlang-distributed random variates. Represents the sum of nshape exponentially distributed random variables, each having the same mean value = phasemean. For nshape = 1 it works as a generator of exponentially distributed random numbers. """ assert is_posinteger(nshape), \ "shape parameter must be a positive integer in rerlang!" assert phasemean >= 0.0, "phasemean must not be negative in rerlang!" assert xmax >= 0.0, "variate max must be non-negative in rerlang!" if nshape < GeneralRandomStream.__ERLANG2GAMMA: while True: x = 1.0 for k in range(0, nshape): x *= self.runif01() # Might turn out to be zero... x = - phasemean * safelog(x) if x <= xmax: break else: # Gamma is OK while True: x = phasemean * self.rgamma(float(nshape), 1.0) if x <= xmax: break x = kept_within(0.0, x) return x
def ilaplace(prob, loc=0.0, scale=1.0): """ The inverse of the Laplace distribution f = [(1/2)/s)]*exp(-abs([x-l]/s)) F = (1/2)*exp([x-l]/s) {x <= l}, F = 1 - (1/2)*exp(-[x-l]/s) {x >= l} s >= 0 """ _assertprob(prob, 'ilaplace') # --- assert scale >= 0.0, "scale parameter in ilaplace must not be negative!" if prob <= 0.5: x = safelog(2.0*prob) x = scale*x + loc else: x = - safelog(2.0*(1.0-prob)) x = scale*x + loc return x
def igeometric(prob, phi): """ The geometric distribution with p(K=k) = phi * (1-phi)**(k-1) and P(K>=k) = sum phi * (1-phi)**k = 1 - q**k where q = 1 - phi and 0 < phi <= 1 is the success frequency or "Bernoulli probability" and K >= 1 is the number of trials to the first success in a series of Bernoulli trials. It is easy to prove that P(k) = 1 - (1-phi)**k: let q = 1 - phi. p(k) = (1-q) * q**(k-1) = q**(k-1) - q**k. Then P(1) = p(1) = 1 - q. P(2) = p(1) + p(2) = 1 - q + q - q**2 = 1 - q**2. Induction can be used to show that P(k) = 1 - q**k = 1 - (1-phi)**k The algorithm is taken from ORNL-RSIC-38, Vol II (1973). """ _assertprob(prob, 'igeometric') assert 0.0 <= phi and phi <= 1.0, \ "success frequency must be in [0.0, 1.0] in igeometric!" if phi == 1.0: return 1 # Obvious... q = 1.0 - phi if phi < 0.25: # Use the direct inversion formula lnq = - safelog(q) ln1mp = - safelog(1.0 - prob) kg = 1 + int(ln1mp/lnq) else: # Looking for the passing point is more efficient for kg = 1 # phi >= 0.25 (it's still inversion) u = prob a = phi while True: u = u - a if u > 0.0: kg += 1 a *= q else: break return kg
def irayleigh(prob, sigma=1.0): """ The inverse of the Rayleigh distribution: f = (x/s**2) * exp[-x**2/(2*s**2)] F = 1 - exp[-x**2/(2*s**2)] x, s >= 0 """ _assertprob(prob, 'irayleigh') assert sigma >= 0.0, "parameter in irayleigh must not be negative!" return sigma * sqrt(2.0*(-safelog(1.0-prob))) # Will always be >= 0.0
def iextreme_gen(prob, type, shape, mu=0.0, scale=1.0): """ Generalized extreme value distribution: F = exp{-[1-shape*(x-mu)/scale]**(1/shape)} (max version) f = [1-shape*(x-mu)/scale]**(1/shape-1) * exp{-[1-shape*(x-mu)/scale]**(1/shape)} / scale F = 1 - exp{-[1+shape*(x-mu)/scale]**(1/shape)} (min version) f = [1+shape*(x-mu)/scale]**(1/shape-1) * exp{-[1+shape*(x-mu)/scale]**(1/shape)} / scale shape < 0 => Type II shape > 0 => Type III shape -> 0 => Type I - Gumbel type must be 'max' or 'min' scale must be > 0.0 A REASONABLE SCHEME SEEMS TO BE mu = scale WHICH SEEMS TO LIMIT THE DISTRIBUTION TO EITHER SIDE OF THE Y-AXIS! """ if shape == 0.0: x = iextreme_I(prob, type, mu, scale) else: _assertprob(prob, 'iextreme_gen') assert scale >= 0.0 if type == 'max': x = scale*(1.0-(-safelog(prob))**shape)/shape + mu elif type == 'min': x = scale*((-safelog(1.0-prob))**shape-1.0)/shape + mu else: raise Error("iextreme_gen: type must be either 'max' or 'min'") return x
def ilogistic(prob, mu=0.0, scale=1.0): """ The inverse of the logistic distribution: f = exp[-(x-m)/s] / (s*{1 + exp[-(x-m)/s]}**2) F = 1 / {1 + exp[-(x-m)/s]} x in R m is the mean and mode, s is a scale parameter (s >= 0) """ _assertprob(prob, 'ilogistic') assert scale >= 0.0, "scale parameter in ilogistic must not be negative!" x = mu - scale*safelog(safediv(1.0, prob) - 1.0) return x
def iexpo_gen(prob, a, b, c=0.0): """ The generalized continuous exponential distribution (x in R): x <= c: f = [a*b/(a+b)] * exp(+a*[x-c]) F = [b/(a+b)] * exp(+a*[x-c]) x >= c: f = [a*b/(a+b)] * exp(-b*[x-c]) F = 1 - [a/(a+b)]*exp(-b*[x-c]) a > 0, b > 0 NB The symmetrical double-sided exponential sits in ilaplace! """ _assertprob(prob, 'iexpo_gen') assert a > 0.0 assert b > 0.0 r = prob*(a+b)/b if r <= 1.0: x = c + safelog(r) / a else: x = c - safelog((a+b)*(1.0-prob)/a) / b return x
def rpiecexpo_cum(self, times, lamt): """ Generates - cumulatively - piecewise exponentially distributed interarrival times from clock time = 0.0 (the algorithm is taken from Bratley, Fox & Schrage). 'times' is a list or tuple containing the points at which the arrival rate (= arrival frequency) changes (the first break time point must be 0.0). 'lamt' is a list or tuple containing the arrival rates between break points. The number of elements in 'times' must be one more than the number of elements in 'lamt'! The algorithm cranking out the numbers is cyclic - the procedure starts over from time zero when the last break point is reached. THE PREVENT THE RESTART FROM TAKING PLACE, A (VERY) LARGE NUMBER MUST BE GIVEN AS THE LAST BREAK POINT (the cyclicity is rarely needed or desired in practice). NB. A SEPARATE STREAM MUST BE INSTANTIATED FOR EACH ONE OF THE VARIATES THAT ARE GENERATED USING THIS METHOD, EACH WITH A SEPARATE SEED!! """ ntimes = len(times) errtxt1 = "No. of arrival rates and no. of break points " errtxt2 = "are incompatible in rpiecexpo_cum!" errtext = errtxt1 + errtxt2 assert len(lamt) == ntimes - 1, errtext r = ntimes * [0.0] for k in range(1, ntimes): assert lamt[k-1] >= 0.0, \ "All lamt must be non-negative floats in rpiecexpo_cum!" r[k] = r[k - 1] + lamt[k - 1] * (times[k] - times[k - 1]) cumul = self.__cumul cumul -= safelog(self.runif01()) iseg = 0 while r[iseg + 1] <= cumul: iseg = iseg + 1 tcum = times[iseg] + safediv(cumul - r[iseg], lamt[iseg]) tcum = kept_within(0.0, tcum) self.__cumul = cumul # NB THIS IS NOT CUMULATIVE TIME! return tcum
def rexpo_cum(self, lam): """ Generates - cumulatively - exponentially distributed interarrival times with arrival rate (arrivale frequency) 'lam' from clock time = 0.0. NB. A SEPARATE STREAM MUST BE INSTANTIATED FOR EACH ONE OF THE VARIATES THAT ARE GENERATED USING THIS METHOD, EACH WITH A SEPARATE SEED!! """ assert lam >= 0.0, \ "Arrival rate must be a non-negative float in rexpo_cum!" tcum = self.__tcum tcum -= safediv(1.0, lam) * safelog(self.runif01()) self.__tcum = tcum return tcum
def rpiecexpo_cum(self, times, lamt): """ Generates - cumulatively - piecewise exponentially distributed interarrival times from clock time = 0.0 (the algorithm is taken from Bratley, Fox & Schrage). 'times' is a list or tuple containing the points at which the arrival rate (= arrival frequency) changes (the first break time point must be 0.0). 'lamt' is a list or tuple containing the arrival rates between break points. The number of elements in 'times' must be one more than the number of elements in 'lamt'! The algorithm cranking out the numbers is cyclic - the procedure starts over from time zero when the last break point is reached. THE PREVENT THE RESTART FROM TAKING PLACE, A (VERY) LARGE NUMBER MUST BE GIVEN AS THE LAST BREAK POINT (the cyclicity is rarely needed or desired in practice). NB. A SEPARATE STREAM MUST BE INSTANTIATED FOR EACH ONE OF THE VARIATES THAT ARE GENERATED USING THIS METHOD, EACH WITH A SEPARATE SEED!! """ ntimes = len(times) errtxt1 = "No. of arrival rates and no. of break points " errtxt2 = "are incompatible in rpiecexpo_cum!" errtext = errtxt1 + errtxt2 assert len(lamt) == ntimes-1, errtext r = ntimes*[0.0] for k in range(1, ntimes): assert lamt[k-1] >= 0.0, \ "All lamt must be non-negative floats in rpiecexpo_cum!" r[k] = r[k-1] + lamt[k-1] * (times[k]-times[k-1]) cumul = self.__cumul cumul -= safelog(self.runif01()) iseg = 0 while r[iseg+1] <= cumul: iseg = iseg + 1 tcum = times[iseg] + safediv(cumul-r[iseg], lamt[iseg]) tcum = kept_within(0.0, tcum) self.__cumul = cumul # NB THIS IS NOT CUMULATIVE TIME! return tcum
def rexpo(self, mean, xmax=float('inf')): """ Generator of exponentially distributed random variates with mean = 1.0/lambda: f = (1/mean) * exp(-x/mean) F = 1 - exp(-x/mean). mean >= 0.0 """ assert mean >= 0.0, "mean must be a non-negative float in rexpo!" assert xmax >= 0.0, "variate max must be a non-negative float in rexpo!" while True: x = - mean * safelog(self.runif01()) if x <= xmax: break return x
def rexpo(self, mean, xmax=float('inf')): """ Generator of exponentially distributed random variates with mean = 1.0/lambda: f = (1/mean) * exp(-x/mean) F = 1 - exp(-x/mean). mean >= 0.0 """ assert mean >= 0.0, "mean must be a non-negative float in rexpo!" assert xmax >= 0.0, "variate max must be a non-negative float in rexpo!" while True: x = -mean * safelog(self.runif01()) if x <= xmax: break return x
def iexpo(prob, mean=1.0): """ The inverse of the exponential distribution with mean = 1/lambda: f = (1/mean) * exp(-x/mean) F = 1 - exp(-x/mean) x >= 0, mean >= 0.0 """ _assertprob(prob, 'iexpo') # --- assert mean >= 0.0, "mean of variate in iexpo must be a non-negative float!" x = - mean * safelog(1.0-prob) #x = kept_within(0.0, x) # Not really needed return x
def iweibull(prob, c, scale=1.0): """ The inverse of the Weibull distribution: F = 1 - exp[-(x/s)**c] x >= 0, s >= 0, c >= 1 """ if c == 1.0: x = iexpo(prob, scale) else: _assertprob(prob, 'iweibull') # --- assert c >= 1.0, \ "shape parameter in iweibull must not be smaller than 1.0!" assert scale >= 0.0, "scale parameter in iweibull must not be negative!" x = scale * safepow(-safelog(1.0-prob), 1.0/c) #x = kept_within(0.0, x) # Not really needed return x
def rstable(self, alpha, beta, location, scale, \ xmin=float('-inf'), xmax=float('inf')): """ rstable generates random variates from any distribution of the stable family. 0 < alpha <= 2 is the tail index (the smaller alpha, the wider the distribution). -1 <= beta <= 1 is the skewness parameter (the greater absolute value, the more skewed the distribution, negative = skewed to the left, positive = skewed to the right). For beta = 0 and alpha = 2 the distribution is a Gaussian distribution - but scaled with sqrt(2) (the variance of the stable is twice that of the Gaussian). For beta = 0 and alpha = 1 the distribution is the Cauchy. For abs(beta) = 1 and alpha = 0.5 the distribution is the Levy. For alpha < 2.0 the variance and higher moments are infinite, and for alpha <= 1 all moments are infinite. A scale parameter and a location parameter are also applicable so that Y = scale*X + location is still a stable variate with the same alpha as that of X. E{Y} = location for alpha > 1. The algorithm is taken from Weron but the original version seems to be Chambers, Mallows and Stuck: Weron R. (1996): 'On the Chambers-Mallows-Stuck method for simulating skewed stable random variates', Statist. Probab. Lett. 28, 165-171. Chambers, J.M., Mallows, C.L. and Stuck, B.W. (1976): 'A Method for simulating stable random variables', J. Amer. Statist. Assoc. 71, 340-344. Weron, R. (1996): 'Correction to: On the Chambers-Mallows-Stuck Method for Simulating Skewed Stable Random Variables', Research Report HSC/96/1, Wroclaw University of Technology. """ assert 0.0 < alpha and alpha <= 2.0, \ "alpha must be in (0.0, 2.0] in rstable!" assert -1.0 <= beta and beta <= 1.0, \ "beta must be in [-1.0, 1.0] in rstable!" assert xmax > xmin, "xmax must be > xmin in rstable!" while True: v = self.runifab(-PIHALF, PIHALF) w = self.rexpo(1.0) if beta == 0 or beta == 0.0: oneoa = 1.0 / float(alpha) av = alpha * v x1 = sin(av) / cos(v)**oneoa x2 = (cos(v - av) / w)**(oneoa - 1.0) x = x1 * x2 x = scale * x + location elif alpha == 1 or alpha == 1.0: pihpbv = PIHALF + beta * v x1 = pihpbv * tan(v) x2 = beta * safelog(PIHALF * w * cos(v) / pihpbv) #x = (x1-x2) / PIHALF #x = scale*x + (1.0/PIHALF)*beta*scale*safelog(scale) + location x = 2.0 * PIINV * (x1 - x2) / PIHALF x = scale * x + ( 2.0 * PIINV) * beta * scale * safelog(scale) + location else: tpiha = tan(PIHALF * alpha) oneoa = 1.0 / float(alpha) bab = atan(beta * tan(PIHALF * alpha)) sab = (1.0 + beta * beta * tpiha * tpiha)**(0.5 * oneoa) vpbab = v + bab av = alpha * vpbab x1 = sin(av) / cos(v)**oneoa x2 = (cos(v - av) / w)**(oneoa - 1.0) x = sab * x1 * x2 x = scale * x + location if xmin <= x and x <= xmax: break return x
def rnormal(self, mu, sigma, xmin=float('-inf'), xmax=float('inf')): """ Generator of normally distributed random variates. Based on the Kinderman-Ramage algorithm as modified by Tirler, Dalgaard, Hoermann & Leydold. sigma >= 0.0 """ assert sigma >= 0.0, \ "standard deviation must be a non-negative float in rnormal!" assert xmax > xmin, "xmax must be > xmin in rnormal!" ksi = 2.2160358671 ksi2h = 0.5 * ksi * ksi p18 = 0.180025191068563 p48 = 0.479727404222441 while True: u = self.runif01() if u < 0.884070402298758: v = self.runif01() x = ksi * (1.131131635444180 * u + v - 1.0) elif u >= 0.973310954173898: while True: v = self.runif01() w = self.runif01() t = ksi2h - safelog(w) if v * v * t <= ksi2h: break if u < 0.986655477086949: x = sqrt(2.0 * t) else: x = -sqrt(2.0 * t) elif u >= 0.958720824790463: while True: v = self.runif01() w = self.runif01() z = v - w t = ksi - 0.630834801921960 * min(v, w) if max(v, w) <= 0.755591531667601: if z < 0.0: x = t else: x = -t break p = dnormal(0.0, 1.0, t) f = p - p18 * max(ksi - abs(t), 0.0) if 0.034240503750111 * abs(z) <= f: if z < 0.0: x = t else: x = -t break elif u >= 0.911312780288703: while True: v = self.runif01() w = self.runif01() z = v - w t = p48 + 1.105473661022070 * min(v, w) if max(v, w) <= 0.872834976671790: if z < 0.0: x = t else: x = -t break p = dnormal(0.0, 1.0, t) f = p - p18 * max(ksi - abs(t), 0.0) if 0.049264496373128 * abs(z) <= f: if z < 0.0: x = t else: x = -t break else: while True: v = self.runif01() w = self.runif01() z = v - w t = p48 - 0.595507138015940 * min(v, w) if t >= 0.0: if max(v, w) <= 0.805577924423817: if z < 0.0: x = t else: x = -t break p = dnormal(0.0, 1.0, t) f = p - p18 * max(ksi - abs(t), 0.0) if 0.053377549506886 * abs(z) <= f: if z < 0.0: x = t else: x = -t break x = sigma * x + mu if xmin <= x <= xmax: break return x
def rgamma(self, alpha, lam, xmax=float('inf')): """ The gamma distribution: f = lam * exp(-lam*x) * (lam*x)**(alpha-1) / gamma(alpha) The cdf is the integral = the incomplete gamma ratio. x, alpha >= 0; lam > 0.0 The generator is a slight modification of Python's built-in "gammavariate". """ assert alpha >= 0.0, "alpha must be non-negative in rgamma!" assert lam > 0.0, "lambda must be a positive float in rgamma!" assert xmax >= 0.0, \ "variate max must be a non-negative float in rgamma!" f, i = modf(alpha) if f == 0.0: if 1.0 <= i and i <= GeneralRandomStream.__GAMMA2ERLANG: return self.rerlang(int(i), 1.0 / lam, xmax) if alpha < 1.0: # Uses ALGORITHM GS of Statistical Computing - Kennedy & Gentle # (according to Python's "gammavariate") alphainv = 1.0 / alpha alpham1 = alpha - 1.0 while True: while True: u = self.runif01() b = (E + alpha) / E p = b * u if p <= 1.0: w = p**alphainv else: w = -safelog((b - p) * alphainv) u1 = self.runif01() if p > 1.0: if u1 <= w**(alpham1): break elif u1 <= exp(-w): break x = w / lam if x <= xmax: break else: # elif alpha > 1.0: # Uses R.C.H. Cheng, "The generation of Gamma # variables with non-integral shape parameters", # Applied Statistics, (1977), 26, No. 1, p71-74 # (according to Python's "gammavariate") ainv = sqrt(2.0 * alpha - 1.0) beta = 1.0 / ainv bbb = alpha - GeneralRandomStream.__LN4 ccc = alpha + ainv while True: while True: u1 = self.runif01() u2 = self.runif01() v = beta * safelog(safediv(u1, 1.0 - u1)) w = alpha * exp(v) c1 = u1 * u1 * u2 r = bbb + ccc * v - w c2 = r + 2.5040773967762742 - 4.5 * c1 # 2.5040773967762742 = 1.0 + log(4.5) if c2 >= 0.0 or r >= safelog(c1): break x = w / lam if x <= xmax: break x = kept_within(0.0, x) return x
def rexppower(self, loc, scale, alpha, \ xmin=float('-inf'), xmax=float('inf')): """ The exponential power distribution f = (a/s) * exp(-abs([x-l]/s)**a) / [2*gamma(1/a)] F = 1/2 * [1 + sgn(x-l) * Fgamma(1/a, abs([x-l]/s)**a)], x in R s, a > 0 where Fgamma is the gamma distribution cdf. A modified version of the rejection technique proposed in P.R. Tadikamalla; "Random Sampling From the Exponential Power Distribution", J. Am. Statistical Association 75(371), 1980, pp 683-686 is used (the gamma is used for alpha > 2.0 in place of the rejection procedure proposed by Tadikamalla - for purposes of speed). """ assert xmax > xmin, "xmax must be > xmin in rexppower!" assert scale > 0.0, \ "scale parameter must be a positive float in rexppower!" assert alpha > 0.0, \ "shape parameter alpha must be a positive float in rexppower!" sinv = 1.0 / scale ainv = 1.0 / alpha while True: if alpha < 1.0: # The gamma distribution rgam = self.rgamma(ainv, sinv) sign = self.rsign() x = loc + sign * rgam**ainv elif alpha == 1.0: # The Laplace distribution is used x = self.rlaplace(d, scale) elif 1.0 < alpha < 2.0: # Tadikamalla's rejection procedure ayay = ainv**ainv ayayi = 1.0 / ayay while True: u1 = self.runif01() if u1 > 0.5: x = -ayay * safelog(2.0 * (1.0 - u1)) else: x = ayay * safelog(2.0 * u1) ax = abs(x) lnu2 = safelog(self.runif01()) if lnu2 <= -ax**alpha + ayayi * ax - 1.0 + ainv: break x = loc + sinv * x elif alpha == 2.0: # The normal (Gaussian) distribution x = self.rnormal(d, scale) else: '''while True: # Tadikamalla is slower than the gamma!!! ayay = ainv**ainv ayayi2 = 1.0/(ayay*ayay) x = self.rnormal(0.0, ayay) ax = abs(x) lnu = safelog(self.runif01()) if lnu <= - ax**alpha + 0.5*ayayi2*x*x + ainv - 0.5: break''' rgam = self.rgamma(ainv, sinv) # The gamma distribution sign = self.rsign() x = loc + sign * rgam**ainv if xmin <= x <= xmax: break return x
def rbeta(self, a, b, x1, x2): """ The beta distribution f = x**(a-1) * (1-x)**(b-1) / beta(a, b) The cdf is the integral = the incomplete beta or the incomplete beta/complete beta depending on how the incomplete beta function is defined. x, a, b >= 0; x2 > x1 The algorithm is due to Berman (1970)/Jonck (1964) (for a and b < 1.0) and Cheng (1978) as described in Bratley, Fox and Schrage. """ assert a > 0.0, \ "shape parameters a and b must both be > 0.0 in rbeta!" assert b > 0.0, \ "shape parameters a and b must both be > 0.0 in rbeta!" assert x2 >= x1, \ "support span must not be negative in rbeta!" if x2 == x1: return x1 if a == 1.0 and b == 1.0: y = self.runif01() elif is_posinteger(a) and is_posinteger(b): nstop = int(a + b - 1.0) r = [] for k in range(0, nstop): r.append(self.runif01()) r.sort() y = r[int(a-1.0)] elif a < 1.0 and b < 1.0: a1 = 1.0 / a b1 = 1.0 / b while True: u = pow(self.runif01(), a1) v = pow(self.runif01(), b1) if u + v <= 1.0: y = u / (u+v) break else: alpha = a + b if min(a, b) <= 1.0: beta = 1.0 / min(a, b) else: beta = sqrt((alpha-2.0)/(2.0*a*b-alpha)) gamma = a + 1.0/beta while True: u1 = self.runif01() u2 = self.runif01() u1 = kept_within(TINY, u1, ONEMMACHEPS) u2 = kept_within(TINY, u2, HUGE) comp1 = safelog(u1*u1*u2) v = beta * safelog(safediv(u1, 1.0-u1)) w = a * exp(v) comp2 = alpha*safelog(alpha/(b+w)) + gamma*v - \ GeneralRandomStream.__LN4 if comp2 >= comp1: y = w / (b+w) break x = y*(x2-x1) + x1 x = kept_within(x1, x, x2) return x
def rexppower(self, loc, scale, alpha, \ xmin=float('-inf'), xmax=float('inf')): """ The exponential power distribution f = (a/s) * exp(-abs([x-l]/s)**a) / [2*gamma(1/a)] F = 1/2 * [1 + sgn(x-l) * Fgamma(1/a, abs([x-l]/s)**a)], x in R s, a > 0 where Fgamma is the gamma distribution cdf. A modified version of the rejection technique proposed in P.R. Tadikamalla; "Random Sampling From the Exponential Power Distribution", J. Am. Statistical Association 75(371), 1980, pp 683-686 is used (the gamma is used for alpha > 2.0 in place of the rejection procedure proposed by Tadikamalla - for purposes of speed). """ assert xmax > xmin, "xmax must be > xmin in rexppower!" assert scale > 0.0, \ "scale parameter must be a positive float in rexppower!" assert alpha > 0.0, \ "shape parameter alpha must be a positive float in rexppower!" sinv = 1.0/scale ainv = 1.0/alpha while True: if alpha < 1.0: # The gamma distribution rgam = self.rgamma(ainv, sinv) sign = self.rsign() x = loc + sign*rgam**ainv elif alpha == 1.0: # The Laplace distribution is used x = self.rlaplace(d, scale) elif 1.0 < alpha < 2.0: # Tadikamalla's rejection procedure ayay = ainv**ainv ayayi = 1.0/ayay while True: u1 = self.runif01() if u1 > 0.5: x = - ayay * safelog(2.0*(1.0-u1)) else: x = ayay * safelog(2.0*u1) ax = abs(x) lnu2 = safelog(self.runif01()) if lnu2 <= - ax**alpha + ayayi*ax - 1.0 + ainv: break x = loc + sinv*x elif alpha == 2.0: # The normal (Gaussian) distribution x = self.rnormal(d, scale) else: '''while True: # Tadikamalla is slower than the gamma!!! ayay = ainv**ainv ayayi2 = 1.0/(ayay*ayay) x = self.rnormal(0.0, ayay) ax = abs(x) lnu = safelog(self.runif01()) if lnu <= - ax**alpha + 0.5*ayayi2*x*x + ainv - 0.5: break''' rgam = self.rgamma(ainv, sinv) # The gamma distribution sign = self.rsign() x = loc + sign*rgam**ainv if xmin <= x <= xmax: break return x
def rbeta(self, a, b, x1, x2): """ The beta distribution f = x**(a-1) * (1-x)**(b-1) / beta(a, b) The cdf is the integral = the incomplete beta or the incomplete beta/complete beta depending on how the incomplete beta function is defined. x, a, b >= 0; x2 > x1 The algorithm is due to Berman (1970)/Jonck (1964) (for a and b < 1.0) and Cheng (1978) as described in Bratley, Fox and Schrage. """ assert a > 0.0, \ "shape parameters a and b must both be > 0.0 in rbeta!" assert b > 0.0, \ "shape parameters a and b must both be > 0.0 in rbeta!" assert x2 >= x1, \ "support span must not be negative in rbeta!" if x2 == x1: return x1 if a == 1.0 and b == 1.0: y = self.runif01() elif is_posinteger(a) and is_posinteger(b): nstop = int(a + b - 1.0) r = [] for k in range(0, nstop): r.append(self.runif01()) r.sort() y = r[int(a - 1.0)] elif a < 1.0 and b < 1.0: a1 = 1.0 / a b1 = 1.0 / b while True: u = pow(self.runif01(), a1) v = pow(self.runif01(), b1) if u + v <= 1.0: y = u / (u + v) break else: alpha = a + b if min(a, b) <= 1.0: beta = 1.0 / min(a, b) else: beta = sqrt((alpha - 2.0) / (2.0 * a * b - alpha)) gamma = a + 1.0 / beta while True: u1 = self.runif01() u2 = self.runif01() u1 = kept_within(TINY, u1, ONEMMACHEPS) u2 = kept_within(TINY, u2, HUGE) comp1 = safelog(u1 * u1 * u2) v = beta * safelog(safediv(u1, 1.0 - u1)) w = a * exp(v) comp2 = alpha*safelog(alpha/(b+w)) + gamma*v - \ GeneralRandomStream.__LN4 if comp2 >= comp1: y = w / (b + w) break x = y * (x2 - x1) + x1 x = kept_within(x1, x, x2) return x
def rgamma(self, alpha, lam, xmax=float('inf')): """ The gamma distribution: f = lam * exp(-lam*x) * (lam*x)**(alpha-1) / gamma(alpha) The cdf is the integral = the incomplete gamma ratio. x, alpha >= 0; lam > 0.0 The generator is a slight modification of Python's built-in "gammavariate". """ assert alpha >= 0.0, "alpha must be non-negative in rgamma!" assert lam > 0.0, "lambda must be a positive float in rgamma!" assert xmax >= 0.0, \ "variate max must be a non-negative float in rgamma!" f, i = modf(alpha) if f == 0.0: if 1.0 <= i and i <= GeneralRandomStream.__GAMMA2ERLANG: return self.rerlang(int(i), 1.0/lam, xmax) if alpha < 1.0: # Uses ALGORITHM GS of Statistical Computing - Kennedy & Gentle # (according to Python's "gammavariate") alphainv = 1.0 / alpha alpham1 = alpha - 1.0 while True: while True: u = self.runif01() b = (E+alpha) / E p = b * u if p <= 1.0: w = p ** alphainv else: w = -safelog((b-p)*alphainv) u1 = self.runif01() if p > 1.0: if u1 <= w ** (alpham1): break elif u1 <= exp(-w): break x = w / lam if x <= xmax: break else: # elif alpha > 1.0: # Uses R.C.H. Cheng, "The generation of Gamma # variables with non-integral shape parameters", # Applied Statistics, (1977), 26, No. 1, p71-74 # (according to Python's "gammavariate") ainv = sqrt(2.0*alpha - 1.0) beta = 1.0 / ainv bbb = alpha - GeneralRandomStream.__LN4 ccc = alpha + ainv while True: while True: u1 = self.runif01() u2 = self.runif01() v = beta * safelog(safediv(u1, 1.0-u1)) w = alpha * exp(v) c1 = u1 * u1 * u2 r = bbb + ccc*v - w c2 = r + 2.5040773967762742 - 4.5*c1 # 2.5040773967762742 = 1.0 + log(4.5) if c2 >= 0.0 or r >= safelog(c1): break x = w / lam if x <= xmax: break x = kept_within(0.0, x) return x
def rnormal(self, mu, sigma, xmin=float('-inf'), xmax=float('inf')): """ Generator of normally distributed random variates. Based on the Kinderman-Ramage algorithm as modified by Tirler, Dalgaard, Hoermann & Leydold. sigma >= 0.0 """ assert sigma >= 0.0, \ "standard deviation must be a non-negative float in rnormal!" assert xmax > xmin, "xmax must be > xmin in rnormal!" ksi = 2.2160358671 ksi2h = 0.5*ksi*ksi p18 = 0.180025191068563 p48 = 0.479727404222441 while True: u = self.runif01() if u < 0.884070402298758: v = self.runif01() x = ksi * (1.131131635444180*u + v - 1.0) elif u >= 0.973310954173898: while True: v = self.runif01() w = self.runif01() t = ksi2h - safelog(w) if v*v*t <= ksi2h: break if u < 0.986655477086949: x = sqrt(2.0*t) else: x = -sqrt(2.0*t) elif u >= 0.958720824790463: while True: v = self.runif01() w = self.runif01() z = v - w t = ksi - 0.630834801921960*min(v, w) if max(v, w) <= 0.755591531667601: if z < 0.0: x = t else: x = -t break p = dnormal(0.0, 1.0, t) f = p - p18*max(ksi-abs(t), 0.0) if 0.034240503750111*abs(z) <= f: if z < 0.0: x = t else: x = -t break elif u >= 0.911312780288703: while True: v = self.runif01() w = self.runif01() z = v - w t = p48 + 1.105473661022070*min(v, w) if max(v, w) <= 0.872834976671790: if z < 0.0: x = t else: x = -t break p = dnormal(0.0, 1.0, t) f = p - p18*max(ksi-abs(t), 0.0) if 0.049264496373128*abs(z) <= f: if z < 0.0: x = t else: x = -t break else: while True: v = self.runif01() w = self.runif01() z = v - w t = p48 - 0.595507138015940*min(v, w) if t >= 0.0: if max(v, w) <= 0.805577924423817: if z < 0.0: x = t else: x = -t break p = dnormal(0.0, 1.0, t) f = p - p18*max(ksi-abs(t), 0.0) if 0.053377549506886*abs(z) <= f: if z < 0.0: x = t else: x = -t break x = sigma*x + mu if xmin <= x <= xmax: break return x
def iemp_exp(prob, values, npexp=0, ordered=False): """ The mixed expirical/exponential distribution from Bratley, Fox and Schrage. A polygon (piecewise linearly interpolated cdf with equal probability for each interval between the ) is used together with a (shifted) exponential for the tail. The distribution is designed so as to preserve the mean of the input sample. The input is a tuple/list of observed points and an integer (npexp) corresponding to the number of (the largest) points that will be used to formulate the exponential tail (the default value of npexp will raise an assertion error so something >= 0 must be prescribed). NB it is assumed that x is in [0.0, inf) !!!!!!!!!!!! The function may also be used for a piecewise linear cdf without the exponential tail (setting npexp = 0) - corrections are made to maintain the mean in this case as well!!! """ _assertprob(prob, 'iemp_exp') assert is_nonneginteger(npexp), \ "No. of points for exp. tail in iemp_exp must be a non-neg integer!" nvalues = len(values) for k in range(0, nvalues): assert values[k] >= 0.0, \ "All values in list must be non-negative in iemp_exp!" if npexp == nvalues: mean = sum(values) return iexpo(prob, mean) vcopy = list(values) if not ordered: valueskm1 = values[0] for k in range(1, nvalues): valuesk = values[k] if valuesk >= valueskm1: valueskm1 = valuesk else: vcopy.sort() break if vcopy[0] == 0.0: # Remove the first zero if any - it will be del vcopy[0] # returned later on!!!!!!!! nvalues = nvalues - 1 errtxt2 = "Number of points for exponential tail in iemp_exp too large!" assert npexp <= nvalues, errtxt2 pcomp = 1.0 - prob nred = pcomp*nvalues if npexp > pcomp*nvalues: breaki = nvalues - npexp - 1 # The last ix of the piecewise linear part breakp = vcopy[breaki] # The last value of the piecewise linear part summ = 0.0 k0 = breaki + 1 for k in range(k0, nvalues): summ += vcopy[k] - breakp theta = (0.5*breakp + summ) / npexp q = npexp / float(nvalues) try: x = breakp - theta*safelog(nred/npexp) except ValueError: x = float('inf') else: vcopy.insert(0, 0.0) # A floor value must be inserted v = nvalues*prob i = int(v) x = vcopy[i] + (v-i)*(vcopy[i+1]-vcopy[i]) if npexp == 0: # Correction to maintain mean when there is no exp tail x = (nvalues+1.0) * x / nvalues x = kept_within(0.0, x) return x
def rstable(self, alpha, beta, location, scale, \ xmin=float('-inf'), xmax=float('inf')): """ rstable generates random variates from any distribution of the stable family. 0 < alpha <= 2 is the tail index (the smaller alpha, the wider the distribution). -1 <= beta <= 1 is the skewness parameter (the greater absolute value, the more skewed the distribution, negative = skewed to the left, positive = skewed to the right). For beta = 0 and alpha = 2 the distribution is a Gaussian distribution - but scaled with sqrt(2) (the variance of the stable is twice that of the Gaussian). For beta = 0 and alpha = 1 the distribution is the Cauchy. For abs(beta) = 1 and alpha = 0.5 the distribution is the Levy. For alpha < 2.0 the variance and higher moments are infinite, and for alpha <= 1 all moments are infinite. A scale parameter and a location parameter are also applicable so that Y = scale*X + location is still a stable variate with the same alpha as that of X. E{Y} = location for alpha > 1. The algorithm is taken from Weron but the original version seems to be Chambers, Mallows and Stuck: Weron R. (1996): 'On the Chambers-Mallows-Stuck method for simulating skewed stable random variates', Statist. Probab. Lett. 28, 165-171. Chambers, J.M., Mallows, C.L. and Stuck, B.W. (1976): 'A Method for simulating stable random variables', J. Amer. Statist. Assoc. 71, 340-344. Weron, R. (1996): 'Correction to: On the Chambers-Mallows-Stuck Method for Simulating Skewed Stable Random Variables', Research Report HSC/96/1, Wroclaw University of Technology. """ assert 0.0 < alpha and alpha <= 2.0, \ "alpha must be in (0.0, 2.0] in rstable!" assert -1.0 <= beta and beta <= 1.0, \ "beta must be in [-1.0, 1.0] in rstable!" assert xmax > xmin, "xmax must be > xmin in rstable!" while True: v = self.runifab(-PIHALF, PIHALF) w = self.rexpo(1.0) if beta == 0 or beta == 0.0: oneoa = 1.0 / float(alpha) av = alpha * v x1 = sin(av) / cos(v)**oneoa x2 = (cos(v-av)/w) ** (oneoa-1.0) x = x1 * x2 x = scale*x + location elif alpha == 1 or alpha == 1.0: pihpbv = PIHALF + beta*v x1 = pihpbv * tan(v) x2 = beta * safelog(PIHALF*w*cos(v)/pihpbv) #x = (x1-x2) / PIHALF #x = scale*x + (1.0/PIHALF)*beta*scale*safelog(scale) + location x = 2.0*PIINV * (x1-x2) / PIHALF x = scale*x + (2.0*PIINV)*beta*scale*safelog(scale) + location else: tpiha = tan(PIHALF*alpha) oneoa = 1.0 / float(alpha) bab = atan(beta*tan(PIHALF*alpha)) sab = (1.0 + beta*beta*tpiha*tpiha) ** (0.5*oneoa) vpbab = v + bab av = alpha * vpbab x1 = sin(av) / cos(v)**oneoa x2 = (cos(v-av) / w) ** (oneoa-1.0) x = sab * x1 * x2 x = scale*x + location if xmin <= x and x <= xmax: break return x
def inormal(prob, mu=0.0, sigma=1.0): """ Returns the inverse of the cumulative normal distribution function. Reference: Boris Moro "The Full Monte", Risk Magazine, 8(2) (February): 57-58, 1995, where Moro improves on the Beasley-Springer algorithm (J. D. Beasley and S. G. Springer, Applied Statistics, vol. 26, 1977, pp. 118-121). This is further refined by Shaw, c. f. below. Max relative error is claimed to be less than 2.6e-9 """ _assertprob(prob, 'inormal') assert sigma >= 0.0, "sigma must not be negative in inormal!" #a = ( 2.50662823884, -18.61500062529, \ # 41.39119773534, -25.44106049637) # Moro #b = (-8.47351093090, 23.08336743743, \ # -21.06224101826, 3.13082909833) # Moro # The a and b below are claimed to be better by William Shaw in a # Mathematica working report: "Refinement of the Normal Quantile - # A benchmark Normal quantile based on recursion, and an appraisal # of the Beasley-Springer-Moro, Acklam, and Wichura (AS241) methods" # (William Shaw, Financial Mathematics Group, King's College, London; # [email protected]). # Max RELATIVE error is claimed to be reduced from 1.4e-8 to 2.6e-9 # over the central region a = ( 2.5066282682076065359, -18.515898959450185753, \ 40.864622120467790785, -24.820209533706798850) # Moro/Shaw b = ( -8.4339736056039657294, 22.831834928541562628, \ -20.641301545177201274, 3.0154847661978822127) # Moro/Shaw c = (0.3374754822726147, 0.9761690190917186, 0.1607979714918209, \ 0.0276438810333863, 0.0038405729373609, 0.0003951896511919, \ 0.0000321767881768, 0.0000002888167364, 0.0000003960315187) # Moro x = prob - 0.5 if abs(x) < 0.42: # A rational approximation for the central region... r = x * x r = x * (((a[3]*r+a[2])*r+a[1])*r+a[0]) / ((((b[3]*r+b[2])*r+\ b[1])*r+b[0])*r+1.0) r = sigma*r + mu else: # ...and a polynomial for the tails r = prob if x > 0.0: r = 1.0 - prob try: r = safelog(-safelog(r)) r = c[0] + r*(c[1] + r*(c[2] + r*(c[3] + r*(c[4] + r*(c[5] +\ r*(c[6] + r*(c[7] + r*c[8]))))))) if x < 0.0: r = -r r = sigma*r + mu except ValueError: r = fsign(x) * float('inf') return r