from calculus import defun #----------------------------------------------------------------------------# # Differentiation # #----------------------------------------------------------------------------# @defun def difference(ctx, s, n): r""" Given a sequence `(s_k)` containing at least `n+1` items, returns the `n`-th forward difference, .. math :: \Delta^n = \sum_{k=0}^{\infty} (-1)^{k+n} {n \choose k} s_k. """ n = int(n) d = ctx.zero b = (-1) ** (n & 1) for k in xrange(n+1): d += b * s[k] b = (b * (k-n)) // (k+1) return d def hsteps(ctx, f, x, n, prec, **options): singular = options.get('singular') addprec = options.get('addprec', 10) direction = options.get('direction', 0) workprec = (prec+2*addprec) * (n+1) orig = ctx.prec try: ctx.prec = workprec h = options.get('h') if h is None: if options.get('relative'): hextramag = int(ctx.mag(x)) else: hextramag = 0 h = ctx.ldexp(1, -prec-addprec-hextramag) else: h = ctx.convert(h) # Directed: steps x, x+h, ... x+n*h direction = options.get('direction', 0) if direction: h *= ctx.sign(direction) steps = xrange(n+1) norm = h # Central: steps x-n*h, x-(n-2)*h ..., x, ..., x+(n-2)*h, x+n*h else: steps = xrange(-n, n+1, 2) norm = (2*h) # Perturb if singular: x += 0.5*h values = [f(x+k*h) for k in steps] return values, norm, workprec finally: ctx.prec = orig @defun def diff(ctx, f, x, n=1, **options): r""" Numerically computes the derivative of `f`, `f'(x)`, or generally for an integer `n \ge 0`, the `n`-th derivative `f^{(n)}(x)`. A few basic examples are:: >>> from mpmath import * >>> mp.dps = 15; mp.pretty = True >>> diff(lambda x: x**2 + x, 1.0) 3.0 >>> diff(lambda x: x**2 + x, 1.0, 2) 2.0 >>> diff(lambda x: x**2 + x, 1.0, 3) 0.0 >>> nprint([diff(exp, 3, n) for n in range(5)]) # exp'(x) = exp(x) [20.0855, 20.0855, 20.0855, 20.0855, 20.0855] Even more generally, given a tuple of arguments `(x_1, \ldots, x_k)` and order `(n_1, \ldots, n_k)`, the partial derivative `f^{(n_1,\ldots,n_k)}(x_1,\ldots,x_k)` is evaluated. For example:: >>> diff(lambda x,y: 3*x*y + 2*y - x, (0.25, 0.5), (0,1)) 2.75 >>> diff(lambda x,y: 3*x*y + 2*y - x, (0.25, 0.5), (1,1)) 3.0 **Options** The following optional keyword arguments are recognized: ``method`` Supported methods are ``'step'`` or ``'quad'``: derivatives may be computed using either a finite difference with a small step size `h` (default), or numerical quadrature. ``direction`` Direction of finite difference: can be -1 for a left difference, 0 for a central difference (default), or +1 for a right difference; more generally can be any complex number. ``addprec`` Extra precision for `h` used to account for the function's sensitivity to perturbations (default = 10). ``relative`` Choose `h` relative to the magnitude of `x`, rather than an absolute value; useful for large or tiny `x` (default = False). ``h`` As an alternative to ``addprec`` and ``relative``, manually select the step size `h`. ``singular`` If True, evaluation exactly at the point `x` is avoided; this is useful for differentiating functions with removable singularities. Default = False. ``radius`` Radius of integration contour (with ``method = 'quad'``). Default = 0.25. A larger radius typically is faster and more accurate, but it must be chosen so that `f` has no singularities within the radius from the evaluation point. A finite difference requires `n+1` function evaluations and must be performed at `(n+1)` times the target precision. Accordingly, `f` must support fast evaluation at high precision. With integration, a larger number of function evaluations is required, but not much extra precision is required. For high order derivatives, this method may thus be faster if f is very expensive to evaluate at high precision. **Further examples** The direction option is useful for computing left- or right-sided derivatives of nonsmooth functions:: >>> diff(abs, 0, direction=0) 0.0 >>> diff(abs, 0, direction=1) 1.0 >>> diff(abs, 0, direction=-1) -1.0 More generally, if the direction is nonzero, a right difference is computed where the step size is multiplied by sign(direction). For example, with direction=+j, the derivative from the positive imaginary direction will be computed:: >>> diff(abs, 0, direction=j) (0.0 - 1.0j) With integration, the result may have a small imaginary part even even if the result is purely real:: >>> diff(sqrt, 1, method='quad') # doctest:+ELLIPSIS (0.5 - 4.59...e-26j) >>> chop(_) 0.5 Adding precision to obtain an accurate value:: >>> diff(cos, 1e-30) 0.0 >>> diff(cos, 1e-30, h=0.0001) -9.99999998328279e-31 >>> diff(cos, 1e-30, addprec=100) -1.0e-30 """ partial = False try: orders = list(n) x = list(x) partial = True except TypeError: pass if partial: x = map(ctx.convert, x) return _partial_diff(ctx, f, x, orders, options) method = options.get('method', 'step') if n == 0 and method != 'quad' and not options.get('singular'): return f(ctx.convert(x)) prec = ctx.prec try: if method == 'step': values, norm, workprec = hsteps(ctx, f, x, n, prec, **options) ctx.prec = workprec v = ctx.difference(values, n) / norm**n elif method == 'quad': ctx.prec += 10 radius = ctx.convert(options.get('radius', 0.25)) def g(t): rei = radius*ctx.expj(t) z = x + rei return f(z) / rei**n d = ctx.quadts(g, [0, 2*ctx.pi]) v = d * ctx.factorial(n) / (2*ctx.pi) else: raise ValueError("unknown method: %r" % method) finally: ctx.prec = prec return +v def _partial_diff(ctx, f, xs, orders, options): if not orders: return f() if not sum(orders): return f(*xs) i = 0 for i in range(len(orders)): if orders[i]: break order = orders[i] def fdiff_inner(*f_args): def inner(t): return f(*(f_args[:i] + (t,) + f_args[i+1:])) return ctx.diff(inner, f_args[i], order, **options) orders[i] = 0 return _partial_diff(ctx, fdiff_inner, xs, orders, options) @defun def diffs(ctx, f, x, n=None, **options): r""" Returns a generator that yields the sequence of derivatives .. math :: f(x), f'(x), f''(x), \ldots, f^{(k)}(x), \ldots With ``method='step'``, :func:`~mpmath.diffs` uses only `O(k)` function evaluations to generate the first `k` derivatives, rather than the roughly `O(k^2)` evaluations required if one calls :func:`~mpmath.diff` `k` separate times. With `n < \infty`, the generator stops as soon as the `n`-th derivative has been generated. If the exact number of needed derivatives is known in advance, this is further slightly more efficient. Options are the same as for :func:`~mpmath.diff`. **Examples** >>> from mpmath import * >>> mp.dps = 15 >>> nprint(list(diffs(cos, 1, 5))) [0.540302, -0.841471, -0.540302, 0.841471, 0.540302, -0.841471] >>> for i, d in zip(range(6), diffs(cos, 1)): print i, d ... 0 0.54030230586814 1 -0.841470984807897 2 -0.54030230586814 3 0.841470984807897 4 0.54030230586814 5 -0.841470984807897 """ if n is None: n = ctx.inf else: n = int(n) if options.get('method', 'step') != 'step': k = 0 while k < n: yield ctx.diff(f, x, k, **options) k += 1 return singular = options.get('singular') if singular: yield ctx.diff(f, x, 0, singular=True) else: yield f(ctx.convert(x)) if n < 1: return if n == ctx.inf: A, B = 1, 2 else: A, B = 1, n+1 while 1: callprec = ctx.prec y, norm, workprec = hsteps(ctx, f, x, B, callprec, **options) for k in xrange(A, B): try: ctx.prec = workprec d = ctx.difference(y, k) / norm**k finally: ctx.prec = callprec yield +d if k >= n: return A, B = B, int(A*1.4+1) B = min(B, n) @defun def differint(ctx, f, x, n=1, x0=0): r""" Calculates the Riemann-Liouville differintegral, or fractional derivative, defined by .. math :: \,_{x_0}{\mathbb{D}}^n_xf(x) \frac{1}{\Gamma(m-n)} \frac{d^m}{dx^m} \int_{x_0}^{x}(x-t)^{m-n-1}f(t)dt where `f` is a given (presumably well-behaved) function, `x` is the evaluation point, `n` is the order, and `x_0` is the reference point of integration (`m` is an arbitrary parameter selected automatically). With `n = 1`, this is just the standard derivative `f'(x)`; with `n = 2`, the second derivative `f''(x)`, etc. With `n = -1`, it gives `\int_{x_0}^x f(t) dt`, with `n = -2` it gives `\int_{x_0}^x \left( \int_{x_0}^t f(u) du \right) dt`, etc. As `n` is permitted to be any number, this operator generalizes iterated differentiation and iterated integration to a single operator with a continuous order parameter. **Examples** There is an exact formula for the fractional derivative of a monomial `x^p`, which may be used as a reference. For example, the following gives a half-derivative (order 0.5):: >>> from mpmath import * >>> mp.dps = 15; mp.pretty = True >>> x = mpf(3); p = 2; n = 0.5 >>> differint(lambda t: t**p, x, n) 7.81764019044672 >>> gamma(p+1)/gamma(p-n+1) * x**(p-n) 7.81764019044672 Another useful test function is the exponential function, whose integration / differentiation formula easy generalizes to arbitrary order. Here we first compute a third derivative, and then a triply nested integral. (The reference point `x_0` is set to `-\infty` to avoid nonzero endpoint terms.):: >>> differint(lambda x: exp(pi*x), -1.5, 3) 0.278538406900792 >>> exp(pi*-1.5) * pi**3 0.278538406900792 >>> differint(lambda x: exp(pi*x), 3.5, -3, -inf) 1922.50563031149 >>> exp(pi*3.5) / pi**3 1922.50563031149 However, for noninteger `n`, the differentiation formula for the exponential function must be modified to give the same result as the Riemann-Liouville differintegral:: >>> x = mpf(3.5) >>> c = pi >>> n = 1+2*j >>> differint(lambda x: exp(c*x), x, n) (-123295.005390743 + 140955.117867654j) >>> x**(-n) * exp(c)**x * (x*c)**n * gammainc(-n, 0, x*c) / gamma(-n) (-123295.005390743 + 140955.117867654j) """ m = max(int(ctx.ceil(ctx.re(n)))+1, 1) r = m-n-1 g = lambda x: ctx.quad(lambda t: (x-t)**r * f(t), [x0, x]) return ctx.diff(g, x, m) / ctx.gamma(m-n) @defun def diffun(ctx, f, n=1, **options): r""" Given a function `f`, returns a function `g(x)` that evaluates the nth derivative `f^{(n)}(x)`:: >>> from mpmath import * >>> mp.dps = 15; mp.pretty = True >>> cos2 = diffun(sin) >>> sin2 = diffun(sin, 4) >>> cos(1.3), cos2(1.3) (0.267498828624587, 0.267498828624587) >>> sin(1.3), sin2(1.3) (0.963558185417193, 0.963558185417193) The function `f` must support arbitrary precision evaluation. See :func:`~mpmath.diff` for additional details and supported keyword options. """ if n == 0: return f def g(x): return ctx.diff(f, x, n, **options) return g @defun def taylor(ctx, f, x, n, **options): r""" Produces a degree-`n` Taylor polynomial around the point `x` of the given function `f`. The coefficients are returned as a list. >>> from mpmath import * >>> mp.dps = 15; mp.pretty = True >>> nprint(chop(taylor(sin, 0, 5))) [0.0, 1.0, 0.0, -0.166667, 0.0, 0.00833333] The coefficients are computed using high-order numerical differentiation. The function must be possible to evaluate to arbitrary precision. See :func:`~mpmath.diff` for additional details and supported keyword options. Note that to evaluate the Taylor polynomial as an approximation of `f`, e.g. with :func:`~mpmath.polyval`, the coefficients must be reversed, and the point of the Taylor expansion must be subtracted from the argument: >>> p = taylor(exp, 2.0, 10) >>> polyval(p[::-1], 2.5 - 2.0) 12.1824939606092 >>> exp(2.5) 12.1824939607035 """ gen = enumerate(ctx.diffs(f, x, n, **options)) if options.get("chop", True): return [ctx.chop(d)/ctx.factorial(i) for i, d in gen] else: return [d/ctx.factorial(i) for i, d in gen] @defun def pade(ctx, a, L, M): r""" Computes a Pade approximation of degree `(L, M)` to a function. Given at least `L+M+1` Taylor coefficients `a` approximating a function `A(x)`, :func:`~mpmath.pade` returns coefficients of polynomials `P, Q` satisfying .. math :: P = \sum_{k=0}^L p_k x^k Q = \sum_{k=0}^M q_k x^k Q_0 = 1 A(x) Q(x) = P(x) + O(x^{L+M+1}) `P(x)/Q(x)` can provide a good approximation to an analytic function beyond the radius of convergence of its Taylor series (example from G.A. Baker 'Essentials of Pade Approximants' Academic Press, Ch.1A):: >>> from mpmath import * >>> mp.dps = 15; mp.pretty = True >>> one = mpf(1) >>> def f(x): ... return sqrt((one + 2*x)/(one + x)) ... >>> a = taylor(f, 0, 6) >>> p, q = pade(a, 3, 3) >>> x = 10 >>> polyval(p[::-1], x)/polyval(q[::-1], x) 1.38169105566806 >>> f(x) 1.38169855941551 """ # To determine L+1 coefficients of P and M coefficients of Q # L+M+1 coefficients of A must be provided assert(len(a) >= L+M+1) if M == 0: if L == 0: return [ctx.one], [ctx.one] else: return a[:L+1], [ctx.one] # Solve first # a[L]*q[1] + ... + a[L-M+1]*q[M] = -a[L+1] # ... # a[L+M-1]*q[1] + ... + a[L]*q[M] = -a[L+M] A = ctx.matrix(M) for j in range(M): for i in range(min(M, L+j+1)): A[j, i] = a[L+j-i] v = -ctx.matrix(a[(L+1):(L+M+1)]) x = ctx.lu_solve(A, v) q = [ctx.one] + list(x) # compute p p = [0]*(L+1) for i in range(L+1): s = a[i] for j in range(1, min(M,i) + 1): s += q[j]*a[i-j] p[i] = s return p, q