def diff(f, x, direction=0): """ Compute f'(x) using a simple finite difference approximation. With direction = 0, use the central difference f(x-h), f(x+h) With direction = 1, use the forward difference f(x), f(x+h) With direction = -1, use the backward difference f(x-h), f(x) >>> print diff(cos, 1) -0.841470984807897 >>> print diff(abs, 0, 0) 0.0 >>> print diff(abs, 0, 1) 1.0 >>> print diff(abs, 0, -1) -1.0 The step size is taken similar to the epsilon of the precision. To eliminate cancellation errors, diff temporarily doubles the working precision while calculating the function values. """ prec = mp.prec extra = 5 h = ldexp(1, -prec-extra) try: mp.prec = 2*(prec+extra) if direction == 0: return (f(x+h) - f(x-h)) * ldexp(1, prec+extra-1) elif direction == 1: return (f(x+h) - f(x)) * ldexp(1, prec+extra) elif direction == -1: return (f(x) - f(x-h)) * ldexp(1, prec+extra) else: raise ValueError("invalid difference direction: %r" % direction) finally: mp.prec = prec
def calc_nodes(cls, prec, level, verbose=False): # It is important that the epsilon is set lower than the # "real" epsilon epsilon = ldexp(1, -prec-8) # Fairly high precision might be required for accurate # evaluation of the roots orig = mp.prec mp.prec = int(prec*1.5) nodes = [] n = 3*2**(level-1) upto = n//2 + 1 for j in xrange(1, upto): # Asymptotic formula for the roots r = mpf(math.cos(math.pi*(j-0.25)/(n+0.5))) # Newton iteration while 1: t1, t2 = 1, 0 # Evaluates the Legendre polynomial using its defining # recurrence relation for j1 in xrange(1,n+1): t3, t2, t1 = t2, t1, ((2*j1-1)*r*t1 - (j1-1)*t2)/j1 t4 = n*(r*t1- t2)/(r**2-1) t5 = r a = t1/t4 r = r - a if abs(a) < epsilon: break x = r w = 2/((1-r**2)*t4**2) if verbose and j % 30 == 15: print "Computing nodes (%i of %i)" % (j, upto) nodes.append((x, w)) mp.prec = orig return nodes
def calc_nodes(cls, prec, level, verbose=False): # It is important that the epsilon is set lower than the # "real" epsilon epsilon = ldexp(1, -prec - 8) # Fairly high precision might be required for accurate # evaluation of the roots orig = mp.prec mp.prec = int(prec * 1.5) nodes = [] n = 3 * 2**(level - 1) upto = n // 2 + 1 for j in xrange(1, upto): # Asymptotic formula for the roots r = mpf(math.cos(math.pi * (j - 0.25) / (n + 0.5))) # Newton iteration while 1: t1, t2 = 1, 0 # Evaluates the Legendre polynomial using its defining # recurrence relation for j1 in xrange(1, n + 1): t3, t2, t1 = t2, t1, ((2 * j1 - 1) * r * t1 - (j1 - 1) * t2) / j1 t4 = n * (r * t1 - t2) / (r**2 - 1) t5 = r a = t1 / t4 r = r - a if abs(a) < epsilon: break x = r w = 2 / ((1 - r**2) * t4**2) if verbose and j % 30 == 15: print "Computing nodes (%i of %i)" % (j, upto) nodes.append((x, w)) mp.prec = orig return nodes
def __iter__(self): f = self.f a = self.a b = self.b l = b - a fb = f(b) while True: m = ldexp(a + b, -1) fm = f(m) if fm * fb < 0: a = m else: b = m fb = fm l /= 2 yield (a + b) / 2, abs(l)
def __iter__(self): f = self.f a = self.a b = self.b l = b - a fb = f(b) while True: m = ldexp(a + b, -1) fm = f(m) if fm * fb < 0: a = m else: b = m fb = fm l /= 2 yield (a + b)/2, abs(l)
def calc_nodes(self, degree, prec, verbose=False): """ Calculates the abscissas and weights for Gauss-Legendre quadrature of degree of given degree (actually `3 \cdot 2^m`). """ # It is important that the epsilon is set lower than the # "real" epsilon epsilon = ldexp(1, -prec-8) # Fairly high precision might be required for accurate # evaluation of the roots orig = mp.prec mp.prec = int(prec*1.5) if degree == 1: x = mpf(3)/5 w = mpf(5)/9 nodes = [(-x,w),(mpf(0),mpf(8)/9),(x,w)] mp.prec = orig return nodes nodes = [] n = 3*2**(degree-1) upto = n//2 + 1 for j in xrange(1, upto): # Asymptotic formula for the roots r = mpf(math.cos(math.pi*(j-0.25)/(n+0.5))) # Newton iteration while 1: t1, t2 = 1, 0 # Evaluates the Legendre polynomial using its defining # recurrence relation for j1 in xrange(1,n+1): t3, t2, t1 = t2, t1, ((2*j1-1)*r*t1 - (j1-1)*t2)/j1 t4 = n*(r*t1- t2)/(r**2-1) t5 = r a = t1/t4 r = r - a if abs(a) < epsilon: break x = r w = 2/((1-r**2)*t4**2) if verbose and j % 30 == 15: print "Computing nodes (%i of %i)" % (j, upto) nodes.append((x, w)) nodes.append((NEG(x), w)) mp.prec = orig return nodes
def ODE_step_rk4(x, y, h, derivs): """ Advances the solution y(x) from x to x+h using the 4th-order Runge-Kutta method. derivs .... a python function f(x, (y1, y2, y3, ...)) returning a tuple (y1', y2', y3', ...) where y1' is the derivative of y1 at x. """ h2 = ldexp(h, -1) third = mpf(1)/3 k1 = smul(h, derivs(y, x)) k2 = smul(h, derivs(vadd(y, smul(half, k1)), x+h2)) k3 = smul(h, derivs(vadd(y, smul(half, k2)), x+h2)) k4 = smul(h, derivs(vadd(y, k3), x+h)) v = [] for i in range(len(y)): v.append(y[i] + third*(k2[i]+k3[i] + half*(k1[i]+k4[i]))) return v
def ode_taylor(derivs, x0, y0, tol_prec, n): h = tol = ldexp(1, -tol_prec) dim = len(y0) xs = [x0] ys = [y0] x = x0 y = y0 orig = mp.prec try: mp.prec = orig*(1+n) # Use n steps with Euler's method to get # evaluation points for derivatives for i in range(n): fxy = derivs(x, y) y = [y[i]+h*fxy[i] for i in xrange(len(y))] x += h xs.append(x) ys.append(y) # Compute derivatives ser = [[] for d in range(dim)] for j in range(n+1): s = [0]*dim b = (-1) ** (j & 1) k = 1 for i in range(j+1): for d in range(dim): s[d] += b * ys[i][d] b = (b * (j-k+1)) // (-k) k += 1 scale = h**(-j) / fac(j) for d in range(dim): s[d] = s[d] * scale ser[d].append(s[d]) finally: mp.prec = orig # Estimate radius for which we can get full accuracy. # XXX: do this right for zeros radius = mpf(1) for ts in ser: if ts[-1]: radius = min(radius, nthroot(tol/abs(ts[-1]), n)) radius /= 2 # XXX return ser, x0+radius
def calc_nodes(cls, prec, level, verbose=False): """ The abscissas and weights for tanh-sinh quadrature are given by x[k] = tanh(pi/2 * sinh(t)) w[k] = pi/2 * cosh(t) / cosh(pi/2 sinh(t))**2 Here t varies uniformly with k: t0, t0+h, t0+2*h, ... The list of nodes is actually infinite, but the weights die off so rapidly that only a few are needed. """ nodes = [] extra = 20 mp.prec += extra eps = ldexp(1, -prec-10) pi4 = pi/4 # For simplicity, we work in steps h = 1/2^n, with the first point # offset so that we can reuse the sum from the previous level # We define level 1 to include the "level 0" steps, including # the point x = 0. (It doesn't work well otherwise; not sure why.) t0 = ldexp(1, -level) if level == 1: nodes.append((mpf(0), pi4)) h = t0 else: h = t0*2 # Since h is fixed, we can compute the next exponential # by simply multiplying by exp(h) expt0 = exp(t0) a = pi4 * expt0 b = pi4 / expt0 udelta = exp(h) urdelta = 1/udelta for k in xrange(0, 20*2**level+1): # Reference implementation: # t = t0 + k*h # x = tanh(pi/2 * sinh(t)) # w = pi/2 * cosh(t) / cosh(pi/2 * sinh(t))**2 # Fast implementation. Note that c = exp(pi/2 * sinh(t)) c = exp(a-b) d = 1/c co = (c+d)/2 si = (c-d)/2 x = si / co w = (a+b) / co**2 diff = abs(x-1) if diff <= eps: break nodes.append((x, w)) a *= udelta b *= urdelta if verbose and k % 300 == 150: # Note: the number displayed is rather arbitrary. Should # figure out how to print something that looks more like a # percentage print "Calculating nodes:", nstr(-log(diff, 10) / prec) mp.prec -= extra return nodes
def calc_nodes(self, degree, prec, verbose=False): r""" The abscissas and weights for tanh-sinh quadrature of degree `m` are given by .. math:: x_k = \tanh(\pi/2 \sinh(t_k)) w_k = \pi/2 \cosh(t_k) / \cosh(\pi/2 \sinh(t_k))^2 where `t_k = t_0 + hk` for a step length `h \sim 2^{-m}`. The list of nodes is actually infinite, but the weights die off so rapidly that only a few are needed. """ nodes = [] extra = 20 mp.prec += extra eps = ldexp(1, -prec-10) pi4 = pi/4 # For simplicity, we work in steps h = 1/2^n, with the first point # offset so that we can reuse the sum from the previous degree # We define degree 1 to include the "degree 0" steps, including # the point x = 0. (It doesn't work well otherwise; not sure why.) t0 = ldexp(1, -degree) if degree == 1: #nodes.append((mpf(0), pi4)) #nodes.append((-mpf(0), pi4)) nodes.append((mpf(0), pi/2)) h = t0 else: h = t0*2 # Since h is fixed, we can compute the next exponential # by simply multiplying by exp(h) expt0 = exp(t0) a = pi4 * expt0 b = pi4 / expt0 udelta = exp(h) urdelta = 1/udelta for k in xrange(0, 20*2**degree+1): # Reference implementation: # t = t0 + k*h # x = tanh(pi/2 * sinh(t)) # w = pi/2 * cosh(t) / cosh(pi/2 * sinh(t))**2 # Fast implementation. Note that c = exp(pi/2 * sinh(t)) c = exp(a-b) d = 1/c co = (c+d)/2 si = (c-d)/2 x = si / co w = (a+b) / co**2 diff = abs(x-1) if diff <= eps: break nodes.append((x, w)) nodes.append((NEG(x), w)) a *= udelta b *= urdelta if verbose and k % 300 == 150: # Note: the number displayed is rather arbitrary. Should # figure out how to print something that looks more like a # percentage print "Calculating nodes:", nstr(-log(diff, 10) / prec) mp.prec -= extra return nodes
def polyroots(coeffs, maxsteps=50, cleanup=True, extraprec=10, error=False): """ Numerically locate all (complex) roots of a polynomial using the Durand-Kerner method. With error=True, this function returns a tuple (roots, err) where roots is a list of complex numbers sorted by absolute value, and err is an estimate of the maximum error. The polynomial should be given as a list of coefficients, in the same format as accepted by polyval(). The leading coefficient must be nonzero. These are the roots of x^3 - x^2 - 14*x + 24 and 4x^2 + 3x + 2: >>> nprint(polyroots([1,-1,-14,24]), 4) [-4.0, 2.0, 3.0] >>> nprint(polyroots([4,3,2], error=True)) ([(-0.375 - 0.599479j), (-0.375 + 0.599479j)], 2.22045e-16) """ if len(coeffs) <= 1: if not coeffs or not coeffs[0]: raise ValueError("Input to polyroots must not be the zero polynomial") # Constant polynomial with no roots return [] orig = mp.prec weps = +eps try: mp.prec += 10 deg = len(coeffs) - 1 # Must be monic lead = convert_lossless(coeffs[0]) if lead == 1: coeffs = map(convert_lossless, coeffs) else: coeffs = [c/lead for c in coeffs] f = lambda x: polyval(coeffs, x) roots = [mpc((0.4+0.9j)**n) for n in xrange(deg)] err = [mpf(1) for n in xrange(deg)] for step in xrange(maxsteps): if max(err).ae(0): break for i in xrange(deg): if not err[i].ae(0): p = roots[i] x = f(p) for j in range(deg): if i != j: try: x /= (p-roots[j]) except ZeroDivisionError: continue roots[i] = p - x err[i] = abs(x) if cleanup: for i in xrange(deg): if abs(roots[i].imag) < weps: roots[i] = roots[i].real elif abs(roots[i].real) < weps: roots[i] = roots[i].imag * 1j roots.sort(key=lambda x: (abs(x.imag), x.real)) finally: mp.prec = orig if error: err = max(err) err = max(err, ldexp(1, -orig+1)) return [+r for r in roots], +err else: return [+r for r in roots]
def calc_nodes(cls, prec, level, verbose=False): """ The abscissas and weights for tanh-sinh quadrature are given by x[k] = tanh(pi/2 * sinh(t)) w[k] = pi/2 * cosh(t) / cosh(pi/2 sinh(t))**2 Here t varies uniformly with k: t0, t0+h, t0+2*h, ... The list of nodes is actually infinite, but the weights die off so rapidly that only a few are needed. """ nodes = [] extra = 20 mp.prec += extra eps = ldexp(1, -prec - 10) pi4 = pi / 4 # For simplicity, we work in steps h = 1/2^n, with the first point # offset so that we can reuse the sum from the previous level # We define level 1 to include the "level 0" steps, including # the point x = 0. (It doesn't work well otherwise; not sure why.) t0 = ldexp(1, -level) if level == 1: nodes.append((mpf(0), pi4)) h = t0 else: h = t0 * 2 # Since h is fixed, we can compute the next exponential # by simply multiplying by exp(h) expt0 = exp(t0) a = pi4 * expt0 b = pi4 / expt0 udelta = exp(h) urdelta = 1 / udelta for k in xrange(0, 20 * 2**level + 1): # Reference implementation: # t = t0 + k*h # x = tanh(pi/2 * sinh(t)) # w = pi/2 * cosh(t) / cosh(pi/2 * sinh(t))**2 # Fast implementation. Note that c = exp(pi/2 * sinh(t)) c = exp(a - b) d = 1 / c co = (c + d) / 2 si = (c - d) / 2 x = si / co w = (a + b) / co**2 diff = abs(x - 1) if diff <= eps: break nodes.append((x, w)) a *= udelta b *= urdelta if verbose and k % 300 == 150: # Note: the number displayed is rather arbitrary. Should # figure out how to print something that looks more like a # percentage print "Calculating nodes:", nstr(-log(diff, 10) / prec) mp.prec -= extra return nodes