def fbincoeff(n, k, integer=True): """ Computation of a single binomial coefficient n over k, returning a float (an OverflowError returns float('inf'), which would occur for n > 1029 for IEEE754 floating-point standard). """ assert is_posinteger(n), \ "n in n_over_k in fbincoeff must be a positive integer!" assert is_nonneginteger(k), \ "k in n_over_k in fbincoeff must be a non-negative integer!" assert n >= k, "n must be >= k in n_over_k in fbincoeff!" if integer: bico = _bicolongint(n, k) try: fbico = round(float(bico)) except OverflowError: fbico = float('inf') else: try: lnbico = lngamma(n+1) - lngamma(k+1) - lngamma(n-k+1) fbico = round(exp(lnbico)) except OverflowError: fbico = float('inf') return fbico
def ibincoeff(n, k, integer=True): """ Computation of a single binomial coefficient n over k, returning an integer (possibly long). For integer=True integer arithmetics is used throughout and there is no risk of overflow. For integer=False a floating- point gamma function approximation is used and the result converted to integer at the end (if an overflow occurs ERRCODE is returned). """ assert is_posinteger(n), \ "n in n_over_k in ibincoeff must be a positive integer!" assert is_nonneginteger(k), \ "k in n_over_k in ibincoeff must be a non-negative integer!" assert n >= k, "n must be >= k in n_over_k in ibincoeff!" if integer: ibico = _bicolongint(n, k) else: try: lnbico = lngamma(n+1) - lngamma(k+1) - lngamma(n-k+1) ibico = safeint(round(exp(lnbico)), 'ibincoeff') except OverflowError: ibico = ERRCODE return ibico
def ffactorial(n, integer=True): """ Computation of a factorial, returning a float. If integer=True a rounded float obtained from integer arithmetics is returned. If integer=False a rounded float obtained from floating-point arithmetics based on the function lngamma is returned. Rounding may cause the return of an approximate result rather than an exact. float('inf') is returned if an OverflowError occurs. """ assert is_nonneginteger(n), \ "the argument to ffactorial must be a non-negative integer!" if integer: fact = factorial(n) try: ffact = round(float(fact)) except OverflowError: ffact = float('inf') else: try: ffact = round(exp(lngamma(n + 1.0))) except OverflowError: ffact = float('inf') return ffact
def ibincoeff(n, k, integer=True): """ Computation of a single binomial coefficient n over k, returning an integer (possibly long). For integer=True integer arithmetics is used throughout and there is no risk of overflow. For integer=False a floating- point gamma function approximation is used and the result converted to integer at the end (if an overflow occurs ERRCODE is returned). """ assert is_posinteger(n), \ "n in n_over_k in ibincoeff must be a positive integer!" assert is_nonneginteger(k), \ "k in n_over_k in ibincoeff must be a non-negative integer!" assert n >= k, "n must be >= k in n_over_k in ibincoeff!" if integer: ibico = _bicolongint(n, k) else: try: lnbico = lngamma(n + 1) - lngamma(k + 1) - lngamma(n - k + 1) ibico = safeint(round(exp(lnbico)), 'ibincoeff') except OverflowError: ibico = ERRCODE return ibico
def fbincoeff(n, k, integer=True): """ Computation of a single binomial coefficient n over k, returning a float (an OverflowError returns float('inf'), which would occur for n > 1029 for IEEE754 floating-point standard). """ assert is_posinteger(n), \ "n in n_over_k in fbincoeff must be a positive integer!" assert is_nonneginteger(k), \ "k in n_over_k in fbincoeff must be a non-negative integer!" assert n >= k, "n must be >= k in n_over_k in fbincoeff!" if integer: bico = _bicolongint(n, k) try: fbico = round(float(bico)) except OverflowError: fbico = float('inf') else: try: lnbico = lngamma(n + 1) - lngamma(k + 1) - lngamma(n - k + 1) fbico = round(exp(lnbico)) except OverflowError: fbico = float('inf') return fbico
def ffactorial(n, integer=True): """ Computation of a factorial, returning a float. If integer=True a rounded float obtained from integer arithmetics is returned. If integer=False a rounded float obtained from floating-point arithmetics based on the function lngamma is returned. Rounding may cause the return of an approximate result rather than an exact. float('inf') is returned if an OverflowError occurs. """ assert is_nonneginteger(n), \ "the argument to ffactorial must be a non-negative integer!" if integer: fact = factorial(n) try: ffact = round(float(fact)) except OverflowError: ffact = float('inf') else: try: ffact = round(exp(lngamma(n+1.0))) except OverflowError: ffact = float('inf') return ffact
def _checkinput(self, cll, t, tnext, tolf, tola, maxitn, imprv): """ Used to check the values of the input parameters to the solver methods. cll is the name of the present method (a string). """ assert tnext > t, "time step must be positive in " + cll + "!" wtext1 = "tolerances smaller than machine epsilon are not recommended " wtext2 = "in " + cll + ". Machine epsilon is used instead" wtext = wtext1 + wtext2 if tolf < MACHEPS: tolf = MACHEPS warn(wtext) if tola < MACHEPS: tola = MACHEPS warn(wtext) assert is_posinteger(maxitn), \ "maxitn must be a positive integer in " + cll + "!" if not is_nonneginteger(imprv): imprv = 0 wtext1 = "imprv must be a non-negative integer in " wtext2 = cll + "! imprv=0 is used instead" wtext = wtext1 + wtext2 warn(wtext)
def qromberg(func, a, b, caller='caller', tolf=TWOMACHEPS, maxnsplits=16): """ Romberg integration of a function of one variable over the interval [a, b]. 'caller' is the name of the program, method or function calling qromberg. 'tolf' is the maximum allowed fractional difference between the two last "tail" integrals on the "T table diagonal". 'maxnsplits' is the maximum number of consecutive splits of the subintervals into halves. (cf. Davis-Rabinowitz and Dahlquist-Bjorck-Anderson). """ span = b - a assert span >= 0.0, \ "Integration limits must be in increasing order in qromberg!" assert is_nonneginteger(maxnsplits), \ "Max number of splits must be a nonnegative integer in qromberg!" m = 1; n = 1 summ = 0.5 * (func(a)+func(b)) intgrl = span * summ ttable = [[intgrl]] # First entry into T table (a nested list) previntgrl = (1.0 + 2.0*tolf)*intgrl # Ascertains intgrl != previntgrl... adiff = abs(intgrl-previntgrl) while (adiff > tolf*abs(intgrl)) and (m <= maxnsplits): n *= 2 # We have made a computation for all lower powers-of-2 starting with 1. # The below procedure sums up the new ordinates and then multiplies the # sum with the width of the trapezoids. nhalved = n // 2 # Integer division h = span / float(nhalved) x = a + 0.5*h subsum = 0.0 for k in range(0, nhalved): subsum += func(x + k*h) summ += subsum ttable.append([]) ttable[m].append(span * summ / n) # Interpolation Richardson style: for k in range(0, m): aux = float(4**(k+1)) y = (aux*ttable[m][k] - ttable[m-1][k]) / (aux - 1.0) ttable[m].append(y) intgrl = ttable[m][m] previntgrl = ttable[m-1][m-1] adiff = abs(intgrl-previntgrl) m += 1 if m > maxnsplits: wtxt1 = "qromberg called by " + caller + " failed to converge.\n" wtxt2 = "abs(intgrl-previntgrl) = " + str(adiff) wtxt3 = " for " + str(maxnsplits) + " splits" warn(wtxt1+wtxt2+wtxt3) return intgrl
def cpoisson(lam, tspan, n): """ The Poisson distribution: p(N=n) = exp(-lam*tspan) * (lam*tspan)**n / n! n = 0, 1,...., inf """ # Input check ----------- assert lam >= 0.0, "Poisson rate must not be negative in cpoisson!" assert tspan >= 0.0, "time span must not be negative in cpoisson!" assert is_nonneginteger(n), "variate must be a non-negative integer in cpoisson!" # ----------------------- cdf = 1.0 - cgamma(n + 1.0, lam, tspan) return cdf
def cpoisson(lam, tspan, n): """ The Poisson distribution: p(N=n) = exp(-lam*tspan) * (lam*tspan)**n / n! n = 0, 1,...., inf """ # Input check ----------- assert lam >= 0.0, "Poisson rate must not be negative in cpoisson!" assert tspan >= 0.0, "time span must not be negative in cpoisson!" assert is_nonneginteger(n), \ "variate must be a non-negative integer in cpoisson!" # ----------------------- cdf = 1.0 - cgamma(n + 1.0, lam, tspan) return cdf
def dpoisson(lam, tspan, n): """ The Poisson distribution: p(N=n) = exp(-lam*tspan) * (lam*tspan)**n / n! n = 0, 1,...., inf """ # Input check ----------- assert lam >= 0.0, "Poisson rate must not be negative in dpoisson!" assert tspan >= 0.0, "time span must not be negative in dpoisson!" assert is_nonneginteger(n), \ " must be a non-negative integer in dpoisson!" # ----------------------- lamtau = lam * tspan ln = lamtau + lngamma(n + 1) - n * log(lamtau) ln = kept_within(0.0, ln) pdf = exp(-ln) # Will always be >= 0.0 return pdf
def lnfactorial(n, integer=True): """ Computation of the natural logarithm of a factorial using integer arithmetics (integer=True) or floating point arithmetics using lngamma (integer=False). In both cases a floating-point number is returned, of course... """ assert is_nonneginteger(n), \ "the argument to lnfactorial must be a non-negative integer!" if integer: fact = factorial(n) lnfact = log(fact) else: lnfact = lngamma(n+1.0) return lnfact
def dpoisson(lam, tspan, n): """ The Poisson distribution: p(N=n) = exp(-lam*tspan) * (lam*tspan)**n / n! n = 0, 1,...., inf """ # Input check ----------- assert lam >= 0.0, "Poisson rate must not be negative in dpoisson!" assert tspan >= 0.0, "time span must not be negative in dpoisson!" assert is_nonneginteger(n), \ " must be a non-negative integer in dpoisson!" # ----------------------- lamtau = lam*tspan ln = lamtau + lngamma(n+1) - n*log(lamtau) ln = kept_within(0.0, ln) pdf = exp(-ln) # Will always be >= 0.0 return pdf
def lnfactorial(n, integer=True): """ Computation of the natural logarithm of a factorial using integer arithmetics (integer=True) or floating point arithmetics using lngamma (integer=False). In both cases a floating-point number is returned, of course... """ assert is_nonneginteger(n), \ "the argument to lnfactorial must be a non-negative integer!" if integer: fact = factorial(n) lnfact = log(fact) else: lnfact = lngamma(n + 1.0) return lnfact
def lnbincoeff(n, k, integer=True): """ Computation of the natural logarithm of a single binomial coefficient n over k """ assert is_posinteger(n), \ "n in n_over_k in lnbincoeff must be a positive integer!" assert is_nonneginteger(k), \ "k in n_over_k in lnbincoeff must be a non-negative integer!" assert n >= k, "n must be >= k in n_over_k in lnbincoeff!" if integer: bico = _bicolongint(n, k) lnbico = log(bico) else: lnbico = lngamma(n+1) - lngamma(k+1) - lngamma(n-k+1) return lnbico
def lnbincoeff(n, k, integer=True): """ Computation of the natural logarithm of a single binomial coefficient n over k """ assert is_posinteger(n), \ "n in n_over_k in lnbincoeff must be a positive integer!" assert is_nonneginteger(k), \ "k in n_over_k in lnbincoeff must be a non-negative integer!" assert n >= k, "n must be >= k in n_over_k in lnbincoeff!" if integer: bico = _bicolongint(n, k) lnbico = log(bico) else: lnbico = lngamma(n + 1) - lngamma(k + 1) - lngamma(n - k + 1) return lnbico
def ifactorial(n, integer=True): """ Computation of a factorial, returning an integer. For integer=True a long, exact integer is returned. For integer=False an integer obtained from floating-point arithmetics based on the function lngamma is returned - it is approximate and may not be exact but faster for large n. ERRCODE (cf. misclib.numbers) is returned if a floating-point OverflowError occurs and a warning is sent to stdout (no error can occur when integer=True). """ assert is_nonneginteger(n), \ "the argument to ifactorial must be a non-negative integer!" if integer: fact = factorial(n) else: try: fact = safeint(round(exp(lngamma(n + 1.0))), 'ifactorial') except OverflowError: fact = ERRCODE warn("OverflowError in ifactorial - ERRCODE is returned") return fact
def ifactorial(n, integer=True): """ Computation of a factorial, returning an integer. For integer=True a long, exact integer is returned. For integer=False an integer obtained from floating-point arithmetics based on the function lngamma is returned - it is approximate and may not be exact but faster for large n. ERRCODE (cf. misclib.numbers) is returned if a floating-point OverflowError occurs and a warning is sent to stdout (no error can occur when integer=True). """ assert is_nonneginteger(n), \ "the argument to ifactorial must be a non-negative integer!" if integer: fact = factorial(n) else: try: fact = safeint(round(exp(lngamma(n+1.0))), 'ifactorial') except OverflowError: fact = ERRCODE warn("OverflowError in ifactorial - ERRCODE is returned") return fact
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 znewton(fifi2fid, x0, caller='caller', tolf=FOURMACHEPS, \ tola=SQRTTINY, maxniter=64): """ Solves the equation fi(x) = 0 using the Newton-Raphson algorithm. NB. The function always returns a value but a warning is printed to stdout if the iteration procedure has not converged! Convergence is fast for the Newton algorithm - at the price of having to compute the derivative of the function. Convergence cannot be guaranteed for all functions and/or initial guesses, either... Arguments: ---------- fifi2fid Function having the desired root as its argument and 1: the value of fi, AND 2: the value of the ratio of its value to the value of its derivative given that root as its outputs, in that order x0 Initial guess as to the value of the root tolf Desired fractional accuracy of root (a combination of absolute and fractional will actually be used: tolf*abs(root) + tola) tola Desired max absolute difference of fi(root) from zero AND desired absolute accuracy of root (a combination of absolute and fractional will actually be used: tolf*abs(root) + tola) maxniter Maximum number of iterations Additional feature: ------------------- For maxniter == 0 the solution will be taken beyond tolf and tola (if possible) to the limit where either abs(fi(x)) or abs(fi(x)/fid(x)) has stopped shrinking. If convergence is not reached after 2048 iterations, the procedure is halted and the present estimate is returned anyway (a minimum of 8 iterations will be carried out anyhow). Returns: --------- Final value of root """ if tolf < MACHEPS: tolf = MACHEPS wtxt1 = "Fractional tolerance less than machine epsilon is not a " wtxt2 = "good idea in znewton. Machine epsilon will be used instead!" warn(wtxt1 + wtxt2) if tola < 0.0: tola = 0.0 wtxt1 = "Negative absolute tolerance is not a good idea " wtxt2 = "in znewton. 0.0 (zero) will be used instead!" warn(wtxt1 + wtxt2) assert is_nonneginteger(maxniter), \ "Maximum number of iterations must be a non-negative integer in znewton!" MAXMAX = 2048 MINNITER = 8 if maxniter == 0: limit = True maxnit = MAXMAX else: limit = False maxnit = maxniter x = x0 fi, fi2fid = fifi2fid(x) if fi == 0.0: return x af = abs(fi) if not limit: if af < tola: return x x -= fi2fid ah = abs(fi2fid) if not limit: if ah < tolf * abs(x) + tola: return x niter = 0 while True: niter += 1 afprev = af ahprev = ah fi, fi2fid = fifi2fid(x) if fi == 0.0: return x af = abs(fi) if limit: if af < tola and niter >= MINNITER and af >= afprev: return x else: if af < tola: return x x -= fi2fid ah = abs(fi2fid) if limit: if ah < tolf * abs(x) + tola and niter >= MINNITER and ah >= ahprev: \ return x else: if ah < tolf * abs(x) + tola: return x if niter >= maxnit: break wtxt1 = str(maxnit) + " iterations not sufficient in znewton called by " wtxt2 = caller + ". fi(x) = " + str(fi) + " for x = " + str(x) warn(wtxt1 + wtxt2) return x
def cemp_exp(values, npexp, x, ordered=False, check=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!!! """ nvalues = len(values) if check: assert is_nonneginteger(npexp), \ "No. of points for exp tail in cemp_exp must be a non-neg integer!" assert npexp <= nvalues, \ "Number of points for exponential tail in cemp_exp too large!" for v in values: assert value >= 0.0, \ "All inputs must be non-negative in cemp_exp!" if npexp == nvalues: mean = sum(values) / float(nvalues) return cexpo(mean, x) 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: vcopy.insert(0, 0.0) nindex = nvalues else: nindex = nvalues - 1 breaki = nindex - npexp # The last index of the piecewise linear part vcopyb = vcopy[breaki] if x > vcopyb: # Compute the mean of the shifted exponential theta = 0.0 k0 = breaki + 1 nip1 = nindex + 1 for k in range(k0, nip1): theta += vcopy[k] theta += vcopyb * (0.5 - npexp) theta /= npexp cdf = 1.0 - npexp * exp(-(x - vcopyb) / theta) / nindex else: # Find the right interval using a binary search left = bisect(vcopy, x) - 1 vcopyl = vcopy[left] try: cdf = left + (x - vcopyl) / (vcopy[left + 1] - vcopyl) except ZeroDivisionError: cdf = left cdf /= nindex cdf = kept_within(0.0, cdf, 1.0) return cdf
def zsteffen(func, x0, caller='caller', tolf=FOURMACHEPS, \ tola=SQRTTINY, maxniter=512): """ Solves the equation func(x) = 0 using Steffensen's method. Steffensen's method is a method with second order convergence (like Newton's method) at the price of two function evaluations per iteration. It does NOT require that the derivative be computed, as opposed to Newton's method. In practice zsteffen seems to require a rather clever initial guess as to the root and/or a lot of iterations since it starts slowly when the initial guess is too far away from the actual root, but it might anyway converge in the end. It is clearly inferior to znewton and should be avoided unless the derivative offers problems with numerics or speed. If computation of the derivative is slow, a good idea is to use znewton for a very small number of iterations to produce an initial guess that can be used as an input to zsteffen (may at times be very efficient). And/or: Try the additional feature of taking the solution to the practical limit (cf. below) rather than trying to guess the required number of iterations! For the theory behind Steffensen's method, consult Dahlquist-Bjorck- Anderson. NB. The function always returns a value but a warning is printed to stdout if the iteration procedure has not converged! Arguments: ---------- func Function having the proposed root as its argument x0 Initial guess as to the value of the root tolf Desired fractional accuracy of root (a combination of absolute and fractional will actually be used: tolf*abs(root) + tola) tola Desired max absolute difference of func(root) from zero AND desired absolute accuracy of root (a combination of absolute and fractional will actually be used: tolf*abs(root) + tola) maxniter Maximum number of iterations Additional feature: ------------------- For maxniter == 0 the solution will be taken beyond tolf and tola (if possible) to the limit where either abs(func(x)) or abs(h) - where h is the increment of the root estimate - has stopped shrinking. If convergence is not reached after 2**16 (= 65,536) iterations, the procedure is halted and the present estimate is returned anyway (a minimum of 16 iterations will be carried out anyhow). Returns: --------- Final value of root """ if tolf < MACHEPS: tolf = MACHEPS wtxt1 = "Fractional tolerance less than machine epsilon is not a " wtxt2 = "good idea in zsteffen. Machine epsilon will be used instead!" warn(wtxt1 + wtxt2) if tola < 0.0: tola = 0.0 wtxt1 = "Negative absolute tolerance is not a good idea " wtxt2 = "in zsteffen. 0.0 (zero) will be used instead!" warn(wtxt1 + wtxt2) assert is_nonneginteger(maxniter), \ "Maximum number of iterations must be a non-negative integer in zsteffen!" MAXMAX = 2**16 MINNITER = 16 if maxniter == 0: limit = True maxnit = MAXMAX else: limit = False maxnit = maxniter x = x0 f = func(x) if f == 0.0: return x af = abs(f) if not limit: if af < tola: return x g = (func(x + f) - f) / f h = f / g x -= h ah = abs(h) if not limit: if ah < tolf * abs(x) + tola: return x niter = 0 while True: niter += 1 afprev = af ahprev = ah f = func(x) if f == 0.0: return x af = abs(f) if limit: if af < tola and niter >= MINNITER and af >= afprev: return x else: if af < tola: return x g = (func(x + f) - f) / f h = f / g x -= h ah = abs(h) if limit: if ah < tolf * abs(x) + tola and niter >= MINNITER and ah >= ahprev: \ return x else: if ah < tolf * abs(x) + tola: return x if niter >= maxnit: break wtxt1 = str(maxnit) + " iterations not sufficient in zsteffen called by " wtxt2 = caller + ". func(x) = " + str(f) + " for x = " + str(x) warn(wtxt1 + wtxt2) return x
def zsteffen(func, x0, caller='caller', tolf=FOURMACHEPS, \ tola=SQRTTINY, maxniter=512): """ Solves the equation func(x) = 0 using Steffensen's method. Steffensen's method is a method with second order convergence (like Newton's method) at the price of two function evaluations per iteration. It does NOT require that the derivative be computed, as opposed to Newton's method. In practice zsteffen seems to require a rather clever initial guess as to the root and/or a lot of iterations since it starts slowly when the initial guess is too far away from the actual root, but it might anyway converge in the end. It is clearly inferior to znewton and should be avoided unless the derivative offers problems with numerics or speed. If computation of the derivative is slow, a good idea is to use znewton for a very small number of iterations to produce an initial guess that can be used as an input to zsteffen (may at times be very efficient). And/or: Try the additional feature of taking the solution to the practical limit (cf. below) rather than trying to guess the required number of iterations! For the theory behind Steffensen's method, consult Dahlquist-Bjorck- Anderson. NB. The function always returns a value but a warning is printed to stdout if the iteration procedure has not converged! Arguments: ---------- func Function having the proposed root as its argument x0 Initial guess as to the value of the root tolf Desired fractional accuracy of root (a combination of absolute and fractional will actually be used: tolf*abs(root) + tola) tola Desired max absolute difference of func(root) from zero AND desired absolute accuracy of root (a combination of absolute and fractional will actually be used: tolf*abs(root) + tola) maxniter Maximum number of iterations Additional feature: ------------------- For maxniter == 0 the solution will be taken beyond tolf and tola (if possible) to the limit where either abs(func(x)) or abs(h) - where h is the increment of the root estimate - has stopped shrinking. If convergence is not reached after 2**16 (= 65,536) iterations, the procedure is halted and the present estimate is returned anyway (a minimum of 16 iterations will be carried out anyhow). Returns: --------- Final value of root """ if tolf < MACHEPS: tolf = MACHEPS wtxt1 = "Fractional tolerance less than machine epsilon is not a " wtxt2 = "good idea in zsteffen. Machine epsilon will be used instead!" warn(wtxt1+wtxt2) if tola < 0.0: tola = 0.0 wtxt1 = "Negative absolute tolerance is not a good idea " wtxt2 = "in zsteffen. 0.0 (zero) will be used instead!" warn(wtxt1+wtxt2) assert is_nonneginteger(maxniter), \ "Maximum number of iterations must be a non-negative integer in zsteffen!" MAXMAX = 2**16 MINNITER = 16 if maxniter == 0: limit = True maxnit = MAXMAX else: limit = False maxnit = maxniter x = x0 f = func(x) if f == 0.0: return x af = abs(f) if not limit: if af < tola: return x g = (func(x+f) - f) / f h = f/g x -= h ah = abs(h) if not limit: if ah < tolf*abs(x) + tola: return x niter = 0 while True: niter += 1 afprev = af ahprev = ah f = func(x) if f == 0.0: return x af = abs(f) if limit: if af < tola and niter >= MINNITER and af >= afprev: return x else: if af < tola: return x g = (func(x+f) - f) / f h = f/g x -= h ah = abs(h) if limit: if ah < tolf*abs(x) + tola and niter >= MINNITER and ah >= ahprev: \ return x else: if ah < tolf*abs(x) + tola: return x if niter >= maxnit: break wtxt1 = str(maxnit) + " iterations not sufficient in zsteffen called by " wtxt2 = caller + ". func(x) = " + str(f) + " for x = " + str(x) warn(wtxt1+wtxt2) return x
def znewton(fifi2fid, x0, caller='caller', tolf=FOURMACHEPS, \ tola=SQRTTINY, maxniter=64): """ Solves the equation fi(x) = 0 using the Newton-Raphson algorithm. NB. The function always returns a value but a warning is printed to stdout if the iteration procedure has not converged! Convergence is fast for the Newton algorithm - at the price of having to compute the derivative of the function. Convergence cannot be guaranteed for all functions and/or initial guesses, either... Arguments: ---------- fifi2fid Function having the desired root as its argument and 1: the value of fi, AND 2: the value of the ratio of its value to the value of its derivative given that root as its outputs, in that order x0 Initial guess as to the value of the root tolf Desired fractional accuracy of root (a combination of absolute and fractional will actually be used: tolf*abs(root) + tola) tola Desired max absolute difference of fi(root) from zero AND desired absolute accuracy of root (a combination of absolute and fractional will actually be used: tolf*abs(root) + tola) maxniter Maximum number of iterations Additional feature: ------------------- For maxniter == 0 the solution will be taken beyond tolf and tola (if possible) to the limit where either abs(fi(x)) or abs(fi(x)/fid(x)) has stopped shrinking. If convergence is not reached after 2048 iterations, the procedure is halted and the present estimate is returned anyway (a minimum of 8 iterations will be carried out anyhow). Returns: --------- Final value of root """ if tolf < MACHEPS: tolf = MACHEPS wtxt1 = "Fractional tolerance less than machine epsilon is not a " wtxt2 = "good idea in znewton. Machine epsilon will be used instead!" warn(wtxt1+wtxt2) if tola < 0.0: tola = 0.0 wtxt1 = "Negative absolute tolerance is not a good idea " wtxt2 = "in znewton. 0.0 (zero) will be used instead!" warn(wtxt1+wtxt2) assert is_nonneginteger(maxniter), \ "Maximum number of iterations must be a non-negative integer in znewton!" MAXMAX = 2048 MINNITER = 8 if maxniter == 0: limit = True maxnit = MAXMAX else: limit = False maxnit = maxniter x = x0 fi, fi2fid = fifi2fid(x) if fi == 0.0: return x af = abs(fi) if not limit: if af < tola: return x x -= fi2fid ah = abs(fi2fid) if not limit: if ah < tolf*abs(x) + tola: return x niter = 0 while True: niter += 1 afprev = af ahprev = ah fi, fi2fid = fifi2fid(x) if fi == 0.0: return x af = abs(fi) if limit: if af < tola and niter >= MINNITER and af >= afprev: return x else: if af < tola: return x x -= fi2fid ah = abs(fi2fid) if limit: if ah < tolf*abs(x) + tola and niter >= MINNITER and ah >= ahprev: \ return x else: if ah < tolf*abs(x) + tola: return x if niter >= maxnit: break wtxt1 = str(maxnit) + " iterations not sufficient in znewton called by " wtxt2 = caller + ". fi(x) = " + str(fi) + " for x = " + str(x) warn(wtxt1+wtxt2) return x
def nelder_mead(objfunc, point0, spans, \ trace=False, tolf=SQRTMACHEPS, tola=SQRTTINY, maxniter=256, \ rho=1.0, xsi=2.0, gamma=0.5, sigma=0.5): """ The Nelder & Mead downhill simplex method is designed to find the minimum of an objective function that has a multi-dimensional input, (see for instance Lagarias et al. (1998), "Convergence Properties of the Nelder-Mead Simplex in Low Dimensions", SIAM J. Optim., Society for Industrial and Applied Mathematics Vol. 9, No. 1, pp. 112-147 for details). The algorithm is said to first have been presented by Nelder and Mead in Computer Journal, Vol. 7, pp. 308-313 (1965). The initial simplex must be entered by entering an initial point (an array of coordinates), plus an array of spans for the corresponding point coordinates. For trace=True a trace is printed to stdout consisting of the present number of iterations, the present low value of the objective function, the present value of the absolute value of difference between the high and the low value of the objective function, and the present list of vertices of the low value of the objective function = the present "best" point. tolf is the fractional tolerance and tola is the absolute tolerance of the absolute value of difference between the high and the low value of the objective function. maxniter is the maximum allowed number of iterations. rho, xsi, gamma and sigma are the parameters for reflection, expansion, contraction and shrinkage, respectively (cf. the references above). """ # Check the input parameters assert is_nonneginteger(maxniter), \ "max number of iterations must be a non-negative integer in nelder_mead!" if tolf < MACHEPS: tolf = MACHEPS wtext = "fractional tolerance smaller than machine epsilon is not " wtext += "recommended in nelder_mead. Machine epsilon is used instead" warn(wtext) assert rho > 0.0, "rho must be positive in nelder_mead!" assert xsi > 1.0, "xsi must be > 1.0 in nelder_mead!" assert xsi > rho, "xsi must be > rho in nelder_mead!" assert 0.0 < gamma < 1.0, "gamma must be in (0.0, 1.0) in nelder_mead!" assert 0.0 < sigma < 1.0, "sigma be in (0.0, 1.0) in nelder_mead!" assert tola >= 0.0, "absolute tolerance must be positive in nelder_mead!" # Prepare matrix of vertices ndim = len(point0) assert len(spans) == ndim vertices = Matrix() vertices.append(array('d', list(point0))) ndimp1 = ndim + 1 fndim = float(ndim) for j in range(0, ndim): vertices.append(array('d', list(point0))) for j in range(0, ndim): vertices[j+1][j] += spans[j] # Prepare a few variants of parameters oneprho = 1.0 + rho # LOOP!!!!!!!! niter = 0 while True: niter += 1 if niter > maxniter: txt1 = "nelder_mead did not converge. Absolute error = " txt2 = str(abs(high-low)) + " for " + str(niter-1) txt3 = " iterations. Consider new tols or maxniter!" raise Error(txt1+txt2+txt3) # Compute the objective function values for the vertices flist = array('d', []) for k in range(0, ndimp1): fk = objfunc(vertices[k]) flist.append(fk) # Establish the highest point, the next highest point and the lowest low = flist[0] high = nxhi = low ilow = 0 ihigh = 0 for k in range(1, ndimp1): fk = flist[k] if fk > high: nxhi = high high = fk ihigh = k elif fk < low: low = fk ilow = k if trace: print(niter, low, abs(high-low), list(vertices[ilow])) if low < tola: tol = tola else: tol = abs(low)*tolf if abs(high-low) < tol: return low, list(vertices[ilow]) # Reflect the high point # First find a new vertix = the centroid of the non-max vertices cntr = array('d', ndim*[float('nan')]) newr = array('d', ndim*[float('nan')]) for j in range(0, ndim): xsum = 0.0 for k in range(0, ndimp1): if k != ihigh: xsum += vertices[k][j] cntr[j] = xsum/fndim # Then move from the centroid in an away-from-max direction for j in range(0, ndim): newr[j] = oneprho*cntr[j] - rho*vertices[ihigh][j] # Check the new vertix accepted = False phir = objfunc(newr) if low <= phir < nxhi: # Everything is OK! if trace: print("Reflection sufficient") vertices[ihigh] = newr phi = phir accepted = True elif phir < low: # Expand: if trace: print("Expansion") newe = array('d', ndim*[float('nan')]) for j in range(0, ndim): newe[j] = cntr[j] + xsi*(newr[j]-cntr[j]) phie = objfunc(newe) if phie < phir: vertices[ihigh] = newe phi = phie else: vertices[ihigh] = newr phi = phir accepted = True elif phir >= nxhi: # Contract if phir < high: # -outside: if trace: print("Outside contraction") newo = array('d', ndim*[float('nan')]) for j in range(0, ndim): newo[j] = cntr[j] + gamma*(newr[j]-cntr[j]) phio = objfunc(newo) if phio <= phir: vertices[ihigh] = newo phi = phio accepted = True else: # -inside: if trace: print("Inside contraction") newi = array('d', ndim*[float('nan')]) for j in range(0, ndim): newi[j] = cntr[j] - gamma*(cntr[j]-vertices[ihigh][j]) phii = objfunc(newi) if phii <= high: vertices[ihigh] = newi phi = phii accepted = True if not accepted: # Shrink: if trace: print("Shrinkage") for k in range(0, ndimp1): for j in range(j, ndim): vertices[k][j] = vertices[ilow][j] + sigma*(vertices[k][j] - vertices[ilow][j]) # end of nelder_mead # ------------------------------------------------------------------------------
def cemp_exp(values, npexp, x, ordered=False, check=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!!! """ nvalues = len(values) if check: assert is_nonneginteger(npexp), "No. of points for exp tail in cemp_exp must be a non-neg integer!" assert npexp <= nvalues, "Number of points for exponential tail in cemp_exp too large!" for v in values: assert value >= 0.0, "All inputs must be non-negative in cemp_exp!" if npexp == nvalues: mean = sum(values) / float(nvalues) return cexpo(mean, x) 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: vcopy.insert(0, 0.0) nindex = nvalues else: nindex = nvalues - 1 breaki = nindex - npexp # The last index of the piecewise linear part vcopyb = vcopy[breaki] if x > vcopyb: # Compute the mean of the shifted exponential theta = 0.0 k0 = breaki + 1 nip1 = nindex + 1 for k in range(k0, nip1): theta += vcopy[k] theta += vcopyb * (0.5 - npexp) theta /= npexp cdf = 1.0 - npexp * exp(-(x - vcopyb) / theta) / nindex else: # Find the right interval using a binary search left = bisect(vcopy, x) - 1 vcopyl = vcopy[left] try: cdf = left + (x - vcopyl) / (vcopy[left + 1] - vcopyl) except ZeroDivisionError: cdf = left cdf /= nindex cdf = kept_within(0.0, cdf, 1.0) return cdf