| from __future__ import print_function |
|
|
| from copy import copy |
|
|
| from ..libmp.backend import xrange |
|
|
| class OptimizationMethods(object): |
| def __init__(ctx): |
| pass |
|
|
| |
| |
| |
|
|
| class Newton: |
| """ |
| 1d-solver generating pairs of approximative root and error. |
| |
| Needs starting points x0 close to the root. |
| |
| Pro: |
| |
| * converges fast |
| * sometimes more robust than secant with bad second starting point |
| |
| Contra: |
| |
| * converges slowly for multiple roots |
| * needs first derivative |
| * 2 function evaluations per iteration |
| """ |
| maxsteps = 20 |
|
|
| def __init__(self, ctx, f, x0, **kwargs): |
| self.ctx = ctx |
| if len(x0) == 1: |
| self.x0 = x0[0] |
| else: |
| raise ValueError('expected 1 starting point, got %i' % len(x0)) |
| self.f = f |
| if not 'df' in kwargs: |
| def df(x): |
| return self.ctx.diff(f, x) |
| else: |
| df = kwargs['df'] |
| self.df = df |
|
|
| def __iter__(self): |
| f = self.f |
| df = self.df |
| x0 = self.x0 |
| while True: |
| x1 = x0 - f(x0) / df(x0) |
| error = abs(x1 - x0) |
| x0 = x1 |
| yield (x1, error) |
|
|
| class Secant: |
| """ |
| 1d-solver generating pairs of approximative root and error. |
| |
| Needs starting points x0 and x1 close to the root. |
| x1 defaults to x0 + 0.25. |
| |
| Pro: |
| |
| * converges fast |
| |
| Contra: |
| |
| * converges slowly for multiple roots |
| """ |
| maxsteps = 30 |
|
|
| def __init__(self, ctx, f, x0, **kwargs): |
| self.ctx = ctx |
| if len(x0) == 1: |
| self.x0 = x0[0] |
| self.x1 = self.x0 + 0.25 |
| elif len(x0) == 2: |
| self.x0 = x0[0] |
| self.x1 = x0[1] |
| else: |
| raise ValueError('expected 1 or 2 starting points, got %i' % len(x0)) |
| self.f = f |
|
|
| def __iter__(self): |
| f = self.f |
| x0 = self.x0 |
| x1 = self.x1 |
| f0 = f(x0) |
| while True: |
| f1 = f(x1) |
| l = x1 - x0 |
| if not l: |
| break |
| s = (f1 - f0) / l |
| if not s: |
| break |
| x0, x1 = x1, x1 - f1/s |
| f0 = f1 |
| yield x1, abs(l) |
|
|
| class MNewton: |
| """ |
| 1d-solver generating pairs of approximative root and error. |
| |
| Needs starting point x0 close to the root. |
| Uses modified Newton's method that converges fast regardless of the |
| multiplicity of the root. |
| |
| Pro: |
| |
| * converges fast for multiple roots |
| |
| Contra: |
| |
| * needs first and second derivative of f |
| * 3 function evaluations per iteration |
| """ |
| maxsteps = 20 |
|
|
| def __init__(self, ctx, f, x0, **kwargs): |
| self.ctx = ctx |
| if not len(x0) == 1: |
| raise ValueError('expected 1 starting point, got %i' % len(x0)) |
| self.x0 = x0[0] |
| self.f = f |
| if not 'df' in kwargs: |
| def df(x): |
| return self.ctx.diff(f, x) |
| else: |
| df = kwargs['df'] |
| self.df = df |
| if not 'd2f' in kwargs: |
| def d2f(x): |
| return self.ctx.diff(df, x) |
| else: |
| d2f = kwargs['df'] |
| self.d2f = d2f |
|
|
| def __iter__(self): |
| x = self.x0 |
| f = self.f |
| df = self.df |
| d2f = self.d2f |
| while True: |
| prevx = x |
| fx = f(x) |
| if fx == 0: |
| break |
| dfx = df(x) |
| d2fx = d2f(x) |
| |
| x -= fx / (dfx - fx * d2fx / dfx) |
| error = abs(x - prevx) |
| yield x, error |
|
|
| class Halley: |
| """ |
| 1d-solver generating pairs of approximative root and error. |
| |
| Needs a starting point x0 close to the root. |
| Uses Halley's method with cubic convergence rate. |
| |
| Pro: |
| |
| * converges even faster the Newton's method |
| * useful when computing with *many* digits |
| |
| Contra: |
| |
| * needs first and second derivative of f |
| * 3 function evaluations per iteration |
| * converges slowly for multiple roots |
| """ |
|
|
| maxsteps = 20 |
|
|
| def __init__(self, ctx, f, x0, **kwargs): |
| self.ctx = ctx |
| if not len(x0) == 1: |
| raise ValueError('expected 1 starting point, got %i' % len(x0)) |
| self.x0 = x0[0] |
| self.f = f |
| if not 'df' in kwargs: |
| def df(x): |
| return self.ctx.diff(f, x) |
| else: |
| df = kwargs['df'] |
| self.df = df |
| if not 'd2f' in kwargs: |
| def d2f(x): |
| return self.ctx.diff(df, x) |
| else: |
| d2f = kwargs['df'] |
| self.d2f = d2f |
|
|
| def __iter__(self): |
| x = self.x0 |
| f = self.f |
| df = self.df |
| d2f = self.d2f |
| while True: |
| prevx = x |
| fx = f(x) |
| dfx = df(x) |
| d2fx = d2f(x) |
| x -= 2*fx*dfx / (2*dfx**2 - fx*d2fx) |
| error = abs(x - prevx) |
| yield x, error |
|
|
| class Muller: |
| """ |
| 1d-solver generating pairs of approximative root and error. |
| |
| Needs starting points x0, x1 and x2 close to the root. |
| x1 defaults to x0 + 0.25; x2 to x1 + 0.25. |
| Uses Muller's method that converges towards complex roots. |
| |
| Pro: |
| |
| * converges fast (somewhat faster than secant) |
| * can find complex roots |
| |
| Contra: |
| |
| * converges slowly for multiple roots |
| * may have complex values for real starting points and real roots |
| |
| http://en.wikipedia.org/wiki/Muller's_method |
| """ |
| maxsteps = 30 |
|
|
| def __init__(self, ctx, f, x0, **kwargs): |
| self.ctx = ctx |
| if len(x0) == 1: |
| self.x0 = x0[0] |
| self.x1 = self.x0 + 0.25 |
| self.x2 = self.x1 + 0.25 |
| elif len(x0) == 2: |
| self.x0 = x0[0] |
| self.x1 = x0[1] |
| self.x2 = self.x1 + 0.25 |
| elif len(x0) == 3: |
| self.x0 = x0[0] |
| self.x1 = x0[1] |
| self.x2 = x0[2] |
| else: |
| raise ValueError('expected 1, 2 or 3 starting points, got %i' |
| % len(x0)) |
| self.f = f |
| self.verbose = kwargs['verbose'] |
|
|
| def __iter__(self): |
| f = self.f |
| x0 = self.x0 |
| x1 = self.x1 |
| x2 = self.x2 |
| fx0 = f(x0) |
| fx1 = f(x1) |
| fx2 = f(x2) |
| while True: |
| |
| |
| fx2x1 = (fx1 - fx2) / (x1 - x2) |
| fx2x0 = (fx0 - fx2) / (x0 - x2) |
| fx1x0 = (fx0 - fx1) / (x0 - x1) |
| w = fx2x1 + fx2x0 - fx1x0 |
| fx2x1x0 = (fx1x0 - fx2x1) / (x0 - x2) |
| if w == 0 and fx2x1x0 == 0: |
| if self.verbose: |
| print('canceled with') |
| print('x0 =', x0, ', x1 =', x1, 'and x2 =', x2) |
| break |
| x0 = x1 |
| fx0 = fx1 |
| x1 = x2 |
| fx1 = fx2 |
| |
| r = self.ctx.sqrt(w**2 - 4*fx2*fx2x1x0) |
| if abs(w - r) > abs(w + r): |
| r = -r |
| x2 -= 2*fx2 / (w + r) |
| fx2 = f(x2) |
| error = abs(x2 - x1) |
| yield x2, error |
|
|
| |
| class Bisection: |
| """ |
| 1d-solver generating pairs of approximative root and error. |
| |
| Uses bisection method to find a root of f in [a, b]. |
| Might fail for multiple roots (needs sign change). |
| |
| Pro: |
| |
| * robust and reliable |
| |
| Contra: |
| |
| * converges slowly |
| * needs sign change |
| """ |
| maxsteps = 100 |
|
|
| def __init__(self, ctx, f, x0, **kwargs): |
| self.ctx = ctx |
| if len(x0) != 2: |
| raise ValueError('expected interval of 2 points, got %i' % len(x0)) |
| self.f = f |
| self.a = x0[0] |
| self.b = x0[1] |
|
|
| def __iter__(self): |
| f = self.f |
| a = self.a |
| b = self.b |
| l = b - a |
| fb = f(b) |
| while True: |
| m = self.ctx.ldexp(a + b, -1) |
| fm = f(m) |
| sign = fm * fb |
| if sign < 0: |
| a = m |
| elif sign > 0: |
| b = m |
| fb = fm |
| else: |
| yield m, self.ctx.zero |
| l /= 2 |
| yield (a + b)/2, abs(l) |
|
|
| def _getm(method): |
| """ |
| Return a function to calculate m for Illinois-like methods. |
| """ |
| if method == 'illinois': |
| def getm(fz, fb): |
| return 0.5 |
| elif method == 'pegasus': |
| def getm(fz, fb): |
| return fb/(fb + fz) |
| elif method == 'anderson': |
| def getm(fz, fb): |
| m = 1 - fz/fb |
| if m > 0: |
| return m |
| else: |
| return 0.5 |
| else: |
| raise ValueError("method '%s' not recognized" % method) |
| return getm |
|
|
| class Illinois: |
| """ |
| 1d-solver generating pairs of approximative root and error. |
| |
| Uses Illinois method or similar to find a root of f in [a, b]. |
| Might fail for multiple roots (needs sign change). |
| Combines bisect with secant (improved regula falsi). |
| |
| The only difference between the methods is the scaling factor m, which is |
| used to ensure convergence (you can choose one using the 'method' keyword): |
| |
| Illinois method ('illinois'): |
| m = 0.5 |
| |
| Pegasus method ('pegasus'): |
| m = fb/(fb + fz) |
| |
| Anderson-Bjoerk method ('anderson'): |
| m = 1 - fz/fb if positive else 0.5 |
| |
| Pro: |
| |
| * converges very fast |
| |
| Contra: |
| |
| * has problems with multiple roots |
| * needs sign change |
| """ |
| maxsteps = 30 |
|
|
| def __init__(self, ctx, f, x0, **kwargs): |
| self.ctx = ctx |
| if len(x0) != 2: |
| raise ValueError('expected interval of 2 points, got %i' % len(x0)) |
| self.a = x0[0] |
| self.b = x0[1] |
| self.f = f |
| self.tol = kwargs['tol'] |
| self.verbose = kwargs['verbose'] |
| self.method = kwargs.get('method', 'illinois') |
| self.getm = _getm(self.method) |
| if self.verbose: |
| print('using %s method' % self.method) |
|
|
| def __iter__(self): |
| method = self.method |
| f = self.f |
| a = self.a |
| b = self.b |
| fa = f(a) |
| fb = f(b) |
| m = None |
| while True: |
| l = b - a |
| if l == 0: |
| break |
| s = (fb - fa) / l |
| z = a - fa/s |
| fz = f(z) |
| if abs(fz) < self.tol: |
| |
| if self.verbose: |
| print('canceled with z =', z) |
| yield z, l |
| break |
| if fz * fb < 0: |
| a = b |
| fa = fb |
| b = z |
| fb = fz |
| else: |
| m = self.getm(fz, fb) |
| b = z |
| fb = fz |
| fa = m*fa |
| if self.verbose and m and not method == 'illinois': |
| print('m:', m) |
| yield (a + b)/2, abs(l) |
|
|
| def Pegasus(*args, **kwargs): |
| """ |
| 1d-solver generating pairs of approximative root and error. |
| |
| Uses Pegasus method to find a root of f in [a, b]. |
| Wrapper for illinois to use method='pegasus'. |
| """ |
| kwargs['method'] = 'pegasus' |
| return Illinois(*args, **kwargs) |
|
|
| def Anderson(*args, **kwargs): |
| """ |
| 1d-solver generating pairs of approximative root and error. |
| |
| Uses Anderson-Bjoerk method to find a root of f in [a, b]. |
| Wrapper for illinois to use method='pegasus'. |
| """ |
| kwargs['method'] = 'anderson' |
| return Illinois(*args, **kwargs) |
|
|
| |
| class Ridder: |
| """ |
| 1d-solver generating pairs of approximative root and error. |
| |
| Ridders' method to find a root of f in [a, b]. |
| Is told to perform as well as Brent's method while being simpler. |
| |
| Pro: |
| |
| * very fast |
| * simpler than Brent's method |
| |
| Contra: |
| |
| * two function evaluations per step |
| * has problems with multiple roots |
| * needs sign change |
| |
| http://en.wikipedia.org/wiki/Ridders'_method |
| """ |
| maxsteps = 30 |
|
|
| def __init__(self, ctx, f, x0, **kwargs): |
| self.ctx = ctx |
| self.f = f |
| if len(x0) != 2: |
| raise ValueError('expected interval of 2 points, got %i' % len(x0)) |
| self.x1 = x0[0] |
| self.x2 = x0[1] |
| self.verbose = kwargs['verbose'] |
| self.tol = kwargs['tol'] |
|
|
| def __iter__(self): |
| ctx = self.ctx |
| f = self.f |
| x1 = self.x1 |
| fx1 = f(x1) |
| x2 = self.x2 |
| fx2 = f(x2) |
| while True: |
| x3 = 0.5*(x1 + x2) |
| fx3 = f(x3) |
| x4 = x3 + (x3 - x1) * ctx.sign(fx1 - fx2) * fx3 / ctx.sqrt(fx3**2 - fx1*fx2) |
| fx4 = f(x4) |
| if abs(fx4) < self.tol: |
| |
| if self.verbose: |
| print('canceled with f(x4) =', fx4) |
| yield x4, abs(x1 - x2) |
| break |
| if fx4 * fx2 < 0: |
| x1 = x4 |
| fx1 = fx4 |
| else: |
| x2 = x4 |
| fx2 = fx4 |
| error = abs(x1 - x2) |
| yield (x1 + x2)/2, error |
|
|
| class ANewton: |
| """ |
| EXPERIMENTAL 1d-solver generating pairs of approximative root and error. |
| |
| Uses Newton's method modified to use Steffensens method when convergence is |
| slow. (I.e. for multiple roots.) |
| """ |
| maxsteps = 20 |
|
|
| def __init__(self, ctx, f, x0, **kwargs): |
| self.ctx = ctx |
| if not len(x0) == 1: |
| raise ValueError('expected 1 starting point, got %i' % len(x0)) |
| self.x0 = x0[0] |
| self.f = f |
| if not 'df' in kwargs: |
| def df(x): |
| return self.ctx.diff(f, x) |
| else: |
| df = kwargs['df'] |
| self.df = df |
| def phi(x): |
| return x - f(x) / df(x) |
| self.phi = phi |
| self.verbose = kwargs['verbose'] |
|
|
| def __iter__(self): |
| x0 = self.x0 |
| f = self.f |
| df = self.df |
| phi = self.phi |
| error = 0 |
| counter = 0 |
| while True: |
| prevx = x0 |
| try: |
| x0 = phi(x0) |
| except ZeroDivisionError: |
| if self.verbose: |
| print('ZeroDivisionError: canceled with x =', x0) |
| break |
| preverror = error |
| error = abs(prevx - x0) |
| |
| if error and abs(error - preverror) / error < 1: |
| if self.verbose: |
| print('converging slowly') |
| counter += 1 |
| if counter >= 3: |
| |
| phi = steffensen(phi) |
| counter = 0 |
| if self.verbose: |
| print('accelerating convergence') |
| yield x0, error |
|
|
| |
|
|
| |
| |
| |
|
|
| def jacobian(ctx, f, x): |
| """ |
| Calculate the Jacobian matrix of a function at the point x0. |
| |
| This is the first derivative of a vectorial function: |
| |
| f : R^m -> R^n with m >= n |
| """ |
| x = ctx.matrix(x) |
| h = ctx.sqrt(ctx.eps) |
| fx = ctx.matrix(f(*x)) |
| m = len(fx) |
| n = len(x) |
| J = ctx.matrix(m, n) |
| for j in xrange(n): |
| xj = x.copy() |
| xj[j] += h |
| Jj = (ctx.matrix(f(*xj)) - fx) / h |
| for i in xrange(m): |
| J[i,j] = Jj[i] |
| return J |
|
|
| |
| class MDNewton: |
| """ |
| Find the root of a vector function numerically using Newton's method. |
| |
| f is a vector function representing a nonlinear equation system. |
| |
| x0 is the starting point close to the root. |
| |
| J is a function returning the Jacobian matrix for a point. |
| |
| Supports overdetermined systems. |
| |
| Use the 'norm' keyword to specify which norm to use. Defaults to max-norm. |
| The function to calculate the Jacobian matrix can be given using the |
| keyword 'J'. Otherwise it will be calculated numerically. |
| |
| Please note that this method converges only locally. Especially for high- |
| dimensional systems it is not trivial to find a good starting point being |
| close enough to the root. |
| |
| It is recommended to use a faster, low-precision solver from SciPy [1] or |
| OpenOpt [2] to get an initial guess. Afterwards you can use this method for |
| root-polishing to any precision. |
| |
| [1] http://scipy.org |
| |
| [2] http://openopt.org/Welcome |
| """ |
| maxsteps = 10 |
|
|
| def __init__(self, ctx, f, x0, **kwargs): |
| self.ctx = ctx |
| self.f = f |
| if isinstance(x0, (tuple, list)): |
| x0 = ctx.matrix(x0) |
| assert x0.cols == 1, 'need a vector' |
| self.x0 = x0 |
| if 'J' in kwargs: |
| self.J = kwargs['J'] |
| else: |
| def J(*x): |
| return ctx.jacobian(f, x) |
| self.J = J |
| self.norm = kwargs['norm'] |
| self.verbose = kwargs['verbose'] |
|
|
| def __iter__(self): |
| f = self.f |
| x0 = self.x0 |
| norm = self.norm |
| J = self.J |
| fx = self.ctx.matrix(f(*x0)) |
| fxnorm = norm(fx) |
| cancel = False |
| while not cancel: |
| |
| fxn = -fx |
| Jx = J(*x0) |
| s = self.ctx.lu_solve(Jx, fxn) |
| if self.verbose: |
| print('Jx:') |
| print(Jx) |
| print('s:', s) |
| |
| l = self.ctx.one |
| x1 = x0 + s |
| while True: |
| if x1 == x0: |
| if self.verbose: |
| print("canceled, won't get more excact") |
| cancel = True |
| break |
| fx = self.ctx.matrix(f(*x1)) |
| newnorm = norm(fx) |
| if newnorm < fxnorm: |
| |
| fxnorm = newnorm |
| x0 = x1 |
| break |
| l /= 2 |
| x1 = x0 + l*s |
| yield (x0, fxnorm) |
|
|
| |
| |
| |
|
|
| str2solver = {'newton':Newton, 'secant':Secant, 'mnewton':MNewton, |
| 'halley':Halley, 'muller':Muller, 'bisect':Bisection, |
| 'illinois':Illinois, 'pegasus':Pegasus, 'anderson':Anderson, |
| 'ridder':Ridder, 'anewton':ANewton, 'mdnewton':MDNewton} |
|
|
| def findroot(ctx, f, x0, solver='secant', tol=None, verbose=False, verify=True, **kwargs): |
| r""" |
| Find an approximate solution to `f(x) = 0`, using *x0* as starting point or |
| interval for *x*. |
| |
| Multidimensional overdetermined systems are supported. |
| You can specify them using a function or a list of functions. |
| |
| Mathematically speaking, this function returns `x` such that |
| `|f(x)|^2 \leq \mathrm{tol}` is true within the current working precision. |
| If the computed value does not meet this criterion, an exception is raised. |
| This exception can be disabled with *verify=False*. |
| |
| For interval arithmetic (``iv.findroot()``), please note that |
| the returned interval ``x`` is not guaranteed to contain `f(x)=0`! |
| It is only some `x` for which `|f(x)|^2 \leq \mathrm{tol}` certainly holds |
| regardless of numerical error. This may be improved in the future. |
| |
| **Arguments** |
| |
| *f* |
| one dimensional function |
| *x0* |
| starting point, several starting points or interval (depends on solver) |
| *tol* |
| the returned solution has an error smaller than this |
| *verbose* |
| print additional information for each iteration if true |
| *verify* |
| verify the solution and raise a ValueError if `|f(x)|^2 > \mathrm{tol}` |
| *solver* |
| a generator for *f* and *x0* returning approximative solution and error |
| *maxsteps* |
| after how many steps the solver will cancel |
| *df* |
| first derivative of *f* (used by some solvers) |
| *d2f* |
| second derivative of *f* (used by some solvers) |
| *multidimensional* |
| force multidimensional solving |
| *J* |
| Jacobian matrix of *f* (used by multidimensional solvers) |
| *norm* |
| used vector norm (used by multidimensional solvers) |
| |
| solver has to be callable with ``(f, x0, **kwargs)`` and return an generator |
| yielding pairs of approximative solution and estimated error (which is |
| expected to be positive). |
| You can use the following string aliases: |
| 'secant', 'mnewton', 'halley', 'muller', 'illinois', 'pegasus', 'anderson', |
| 'ridder', 'anewton', 'bisect' |
| |
| See mpmath.calculus.optimization for their documentation. |
| |
| **Examples** |
| |
| The function :func:`~mpmath.findroot` locates a root of a given function using the |
| secant method by default. A simple example use of the secant method is to |
| compute `\pi` as the root of `\sin x` closest to `x_0 = 3`:: |
| |
| >>> from mpmath import * |
| >>> mp.dps = 30; mp.pretty = True |
| >>> findroot(sin, 3) |
| 3.14159265358979323846264338328 |
| |
| The secant method can be used to find complex roots of analytic functions, |
| although it must in that case generally be given a nonreal starting value |
| (or else it will never leave the real line):: |
| |
| >>> mp.dps = 15 |
| >>> findroot(lambda x: x**3 + 2*x + 1, j) |
| (0.226698825758202 + 1.46771150871022j) |
| |
| A nice application is to compute nontrivial roots of the Riemann zeta |
| function with many digits (good initial values are needed for convergence):: |
| |
| >>> mp.dps = 30 |
| >>> findroot(zeta, 0.5+14j) |
| (0.5 + 14.1347251417346937904572519836j) |
| |
| The secant method can also be used as an optimization algorithm, by passing |
| it a derivative of a function. The following example locates the positive |
| minimum of the gamma function:: |
| |
| >>> mp.dps = 20 |
| >>> findroot(lambda x: diff(gamma, x), 1) |
| 1.4616321449683623413 |
| |
| Finally, a useful application is to compute inverse functions, such as the |
| Lambert W function which is the inverse of `w e^w`, given the first |
| term of the solution's asymptotic expansion as the initial value. In basic |
| cases, this gives identical results to mpmath's built-in ``lambertw`` |
| function:: |
| |
| >>> def lambert(x): |
| ... return findroot(lambda w: w*exp(w) - x, log(1+x)) |
| ... |
| >>> mp.dps = 15 |
| >>> lambert(1); lambertw(1) |
| 0.567143290409784 |
| 0.567143290409784 |
| >>> lambert(1000); lambert(1000) |
| 5.2496028524016 |
| 5.2496028524016 |
| |
| Multidimensional functions are also supported:: |
| |
| >>> f = [lambda x1, x2: x1**2 + x2, |
| ... lambda x1, x2: 5*x1**2 - 3*x1 + 2*x2 - 3] |
| >>> findroot(f, (0, 0)) |
| [-0.618033988749895] |
| [-0.381966011250105] |
| >>> findroot(f, (10, 10)) |
| [ 1.61803398874989] |
| [-2.61803398874989] |
| |
| You can verify this by solving the system manually. |
| |
| Please note that the following (more general) syntax also works:: |
| |
| >>> def f(x1, x2): |
| ... return x1**2 + x2, 5*x1**2 - 3*x1 + 2*x2 - 3 |
| ... |
| >>> findroot(f, (0, 0)) |
| [-0.618033988749895] |
| [-0.381966011250105] |
| |
| |
| **Multiple roots** |
| |
| For multiple roots all methods of the Newtonian family (including secant) |
| converge slowly. Consider this example:: |
| |
| >>> f = lambda x: (x - 1)**99 |
| >>> findroot(f, 0.9, verify=False) |
| 0.918073542444929 |
| |
| Even for a very close starting point the secant method converges very |
| slowly. Use ``verbose=True`` to illustrate this. |
| |
| It is possible to modify Newton's method to make it converge regardless of |
| the root's multiplicity:: |
| |
| >>> findroot(f, -10, solver='mnewton') |
| 1.0 |
| |
| This variant uses the first and second derivative of the function, which is |
| not very efficient. |
| |
| Alternatively you can use an experimental Newtonian solver that keeps track |
| of the speed of convergence and accelerates it using Steffensen's method if |
| necessary:: |
| |
| >>> findroot(f, -10, solver='anewton', verbose=True) |
| x: -9.88888888888888888889 |
| error: 0.111111111111111111111 |
| converging slowly |
| x: -9.77890011223344556678 |
| error: 0.10998877665544332211 |
| converging slowly |
| x: -9.67002233332199662166 |
| error: 0.108877778911448945119 |
| converging slowly |
| accelerating convergence |
| x: -9.5622443299551077669 |
| error: 0.107778003366888854764 |
| converging slowly |
| x: 0.99999999999999999214 |
| error: 10.562244329955107759 |
| x: 1.0 |
| error: 7.8598304758094664213e-18 |
| ZeroDivisionError: canceled with x = 1.0 |
| 1.0 |
| |
| **Complex roots** |
| |
| For complex roots it's recommended to use Muller's method as it converges |
| even for real starting points very fast:: |
| |
| >>> findroot(lambda x: x**4 + x + 1, (0, 1, 2), solver='muller') |
| (0.727136084491197 + 0.934099289460529j) |
| |
| |
| **Intersection methods** |
| |
| When you need to find a root in a known interval, it's highly recommended to |
| use an intersection-based solver like ``'anderson'`` or ``'ridder'``. |
| Usually they converge faster and more reliable. They have however problems |
| with multiple roots and usually need a sign change to find a root:: |
| |
| >>> findroot(lambda x: x**3, (-1, 1), solver='anderson') |
| 0.0 |
| |
| Be careful with symmetric functions:: |
| |
| >>> findroot(lambda x: x**2, (-1, 1), solver='anderson') #doctest:+ELLIPSIS |
| Traceback (most recent call last): |
| ... |
| ZeroDivisionError |
| |
| It fails even for better starting points, because there is no sign change:: |
| |
| >>> findroot(lambda x: x**2, (-1, .5), solver='anderson') |
| Traceback (most recent call last): |
| ... |
| ValueError: Could not find root within given tolerance. (1.0 > 2.16840434497100886801e-19) |
| Try another starting point or tweak arguments. |
| |
| """ |
| prec = ctx.prec |
| try: |
| ctx.prec += 20 |
|
|
| |
| if tol is None: |
| tol = ctx.eps * 2**10 |
|
|
| kwargs['verbose'] = kwargs.get('verbose', verbose) |
|
|
| if 'd1f' in kwargs: |
| kwargs['df'] = kwargs['d1f'] |
|
|
| kwargs['tol'] = tol |
| if isinstance(x0, (list, tuple)): |
| x0 = [ctx.convert(x) for x in x0] |
| else: |
| x0 = [ctx.convert(x0)] |
|
|
| if isinstance(solver, str): |
| try: |
| solver = str2solver[solver] |
| except KeyError: |
| raise ValueError('could not recognize solver') |
|
|
| |
| if isinstance(f, (list, tuple)): |
| f2 = copy(f) |
| def tmp(*args): |
| return [fn(*args) for fn in f2] |
| f = tmp |
|
|
| |
| try: |
| fx = f(*x0) |
| multidimensional = isinstance(fx, (list, tuple, ctx.matrix)) |
| except TypeError: |
| fx = f(x0[0]) |
| multidimensional = False |
| if 'multidimensional' in kwargs: |
| multidimensional = kwargs['multidimensional'] |
| if multidimensional: |
| |
| solver = MDNewton |
| if not 'norm' in kwargs: |
| norm = lambda x: ctx.norm(x, 'inf') |
| kwargs['norm'] = norm |
| else: |
| norm = kwargs['norm'] |
| else: |
| norm = abs |
|
|
| |
| if norm(fx) == 0: |
| if multidimensional: |
| return ctx.matrix(x0) |
| else: |
| return x0[0] |
|
|
| |
| iterations = solver(ctx, f, x0, **kwargs) |
| if 'maxsteps' in kwargs: |
| maxsteps = kwargs['maxsteps'] |
| else: |
| maxsteps = iterations.maxsteps |
| i = 0 |
| for x, error in iterations: |
| if verbose: |
| print('x: ', x) |
| print('error:', error) |
| i += 1 |
| if error < tol * max(1, norm(x)) or i >= maxsteps: |
| break |
| else: |
| if not i: |
| raise ValueError('Could not find root using the given solver.\n' |
| 'Try another starting point or tweak arguments.') |
| if not isinstance(x, (list, tuple, ctx.matrix)): |
| xl = [x] |
| else: |
| xl = x |
| if verify and norm(f(*xl))**2 > tol: |
| raise ValueError('Could not find root within given tolerance. ' |
| '(%s > %s)\n' |
| 'Try another starting point or tweak arguments.' |
| % (norm(f(*xl))**2, tol)) |
| return x |
| finally: |
| ctx.prec = prec |
|
|
|
|
| def multiplicity(ctx, f, root, tol=None, maxsteps=10, **kwargs): |
| """ |
| Return the multiplicity of a given root of f. |
| |
| Internally, numerical derivatives are used. This might be inefficient for |
| higher order derviatives. Due to this, ``multiplicity`` cancels after |
| evaluating 10 derivatives by default. You can be specify the n-th derivative |
| using the dnf keyword. |
| |
| >>> from mpmath import * |
| >>> multiplicity(lambda x: sin(x) - 1, pi/2) |
| 2 |
| |
| """ |
| if tol is None: |
| tol = ctx.eps ** 0.8 |
| kwargs['d0f'] = f |
| for i in xrange(maxsteps): |
| dfstr = 'd' + str(i) + 'f' |
| if dfstr in kwargs: |
| df = kwargs[dfstr] |
| else: |
| df = lambda x: ctx.diff(f, x, i) |
| if not abs(df(root)) < tol: |
| break |
| return i |
|
|
| def steffensen(f): |
| """ |
| linear convergent function -> quadratic convergent function |
| |
| Steffensen's method for quadratic convergence of a linear converging |
| sequence. |
| Don not use it for higher rates of convergence. |
| It may even work for divergent sequences. |
| |
| Definition: |
| F(x) = (x*f(f(x)) - f(x)**2) / (f(f(x)) - 2*f(x) + x) |
| |
| Example |
| ....... |
| |
| You can use Steffensen's method to accelerate a fixpoint iteration of linear |
| (or less) convergence. |
| |
| x* is a fixpoint of the iteration x_{k+1} = phi(x_k) if x* = phi(x*). For |
| phi(x) = x**2 there are two fixpoints: 0 and 1. |
| |
| Let's try Steffensen's method: |
| |
| >>> f = lambda x: x**2 |
| >>> from mpmath.calculus.optimization import steffensen |
| >>> F = steffensen(f) |
| >>> for x in [0.5, 0.9, 2.0]: |
| ... fx = Fx = x |
| ... for i in xrange(9): |
| ... try: |
| ... fx = f(fx) |
| ... except OverflowError: |
| ... pass |
| ... try: |
| ... Fx = F(Fx) |
| ... except ZeroDivisionError: |
| ... pass |
| ... print('%20g %20g' % (fx, Fx)) |
| 0.25 -0.5 |
| 0.0625 0.1 |
| 0.00390625 -0.0011236 |
| 1.52588e-05 1.41691e-09 |
| 2.32831e-10 -2.84465e-27 |
| 5.42101e-20 2.30189e-80 |
| 2.93874e-39 -1.2197e-239 |
| 8.63617e-78 0 |
| 7.45834e-155 0 |
| 0.81 1.02676 |
| 0.6561 1.00134 |
| 0.430467 1 |
| 0.185302 1 |
| 0.0343368 1 |
| 0.00117902 1 |
| 1.39008e-06 1 |
| 1.93233e-12 1 |
| 3.73392e-24 1 |
| 4 1.6 |
| 16 1.2962 |
| 256 1.10194 |
| 65536 1.01659 |
| 4.29497e+09 1.00053 |
| 1.84467e+19 1 |
| 3.40282e+38 1 |
| 1.15792e+77 1 |
| 1.34078e+154 1 |
| |
| Unmodified, the iteration converges only towards 0. Modified it converges |
| not only much faster, it converges even to the repelling fixpoint 1. |
| """ |
| def F(x): |
| fx = f(x) |
| ffx = f(fx) |
| return (x*ffx - fx**2) / (ffx - 2*fx + x) |
| return F |
|
|
| OptimizationMethods.jacobian = jacobian |
| OptimizationMethods.findroot = findroot |
| OptimizationMethods.multiplicity = multiplicity |
|
|
| if __name__ == '__main__': |
| import doctest |
| doctest.testmod() |
|
|