Optimization

Root-finding with the secant method (secant)

The function secant locates a root of a given function using the secant method. A simple example use of the secant method is to compute pi as the root of sin(x) closest to x = 3:

>>> from mpmath import *
>>> mp.dps = 30
>>> print secant(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
>>> print secant(lambda x: x**3 + 2*x + 1, j)
(0.226698825758202 + 1.46771150871022j)

A good initial guess for the location of the root is required for the method to be effective, so it is somewhat more appropriate to think of the secant method as a root-polishing method than a root-finding method. When the rough location of the root is known, the secant method can be used to refine it to very high precision in only a few steps. If the root is a first-order root, only roughly log(prec) iterations are required. (The secant method is far less efficient for double roots.) It may be worthwhile to compute the initial approximation to a root using a machine precision solver (for example using one of SciPy’s many solvers), and then refining it to high precision using mpmath’s secant method.

Neat examples

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
>>> print secant(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
>>> print secant(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 exp(w), given the first term of the solution’s asymptotic expansion as the initial value:

>>> def lambert(x):
...     return secant(lambda w: w*exp(w) - x, log(1+x))
...
>>> mp.dps = 15
>>> print lambert(1)
0.567143290409784
>>> print lambert(1000)
5.2496028524016

Options

Strictly speaking, the secant method requires two initial values. By default, you only have to provide the first point x0; secant automatically sets the second point (somewhat arbitrarily) to x0 + 1/4. Manually providing also the second point can help in some cases if secant fails to converge.

By default, secant performs a maximum of 20 steps, which can be increased or decreased using the maxsteps keyword argument. You can pass secant the option verbose=True to show detailed progress.

Finding all roots of a polynomial (polyroots)

The function polyroots computes all n real or complex roots of an n-th degree polynomial. It will for example successfully compute the two real roots of 3x^2 - 7x + 2

>>> mp.dps = 15
>>> for r in polyroots([2,-7,3]):
...     print r
...
0.333333333333333
2.0

or the three roots of x^3 - x^2 - 14x + 24:

>>> polyroots([24,-14,-1,1])
[mpf('-4.0'), mpf('2.0'), mpf('3.0')]

The following example computes all the 5th roots of unity; i.e. the roots of x^5 - 1:

>>> mp.dps = 20
>>> for r in polyroots([-1, 0, 0, 0, 0, 1]):
...     print r
...
1.0
(-0.8090169943749474241 + 0.58778525229247312917j)
(-0.8090169943749474241 - 0.58778525229247312917j)
(0.3090169943749474241 + 0.95105651629515357212j)
(0.3090169943749474241 - 0.95105651629515357212j)

Although all roots are internally calculated using complex arithmetic, any root found to have an imaginary part smaller than the estimated numerical error is truncated to a real number. Real roots are placed first in the returned list, sorted by value. The remaining complex numbers are sorted by real their parts so that conjugate roots end up next to each other.

Provided there are no repeated roots, polyroots can typically compute all roots with high precision:

>>> mp.dps = 70
>>> for r in polyroots([1, 0, -10, 0, 1]):
...     print r
...
-3.146264369941972342329135065715570445512477129187328701232486717442666
-0.3178372451957822447257576172961742883731333784334325548791272414612005
0.3178372451957822447257576172961742883731333784334325548791272414612005
3.146264369941972342329135065715570445512477129187328701232486717442666
>>>
>>> print sqrt(3) + sqrt(2)
3.146264369941972342329135065715570445512477129187328701232486717442665
>>> print sqrt(3) - sqrt(2)
0.3178372451957822447257576172961742883731333784334325548791272414612005

Ill-conditioned polynomials

If a root with multiplicity M is present, it can typically only be computed accurately to dps/M digits (an sometimes less, due to slow convergence). For example, a triple root can be expected to be computed to roughly 5 accurate digits at standard 15-digit precision:

>>> mp.dps = 15
>>> for r in polyroots([1,3,3,1]):
...     print r
...
(-1.00000007263211 - 1.9031226565599e-7j)
(-0.999999852005337 + 2.06642147088233e-7j)
(-1.00000162463939 - 1.50181300875847e-6j)

An example of an extremely ill-conditioned polynomial is Wilkinson’s polynomial which has roots at the integers 1 through 20.

>>> W = [2432902008176640000, -8752948036761600000, 13803759753640704000,
...     -12870931245150988800, 8037811822645051776, -3599979517947607200,
...     1206647803780373360, -311333643161390640, 63030812099294896,
...     -10142299865511450, 1307535010540395, -135585182899530,
...     11310276995381, -756111184500, 40171771630, -1672280820, 53327946,
...     -1256850, 20615, -210, 1]
...
>>> roots = polyroots(W)
>>> for r in roots:
...     print r
...
1.0
2.0
3.0
4.00000000000184
4.99999999994666
6.00000000011394
7.00000001128901
7.99999998684772
8.99999991431934
9.99999897717367
10.9999998763699
11.9999947045394
13.0000024841578
13.9999973809886
15.0000157682936
16.000000498559
17.0000017669947
18.0000008200121
18.999999711431
20.0000000125722

All roots have been separated out, but polyroots fails to converge to high accuracy. The roots can however effectively be polished using the secant method:

>>> for r in roots:
...     print secant(extraprec(50)(lambda x: polyval(W, x)), r)
...
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0
10.0
11.0
12.0
13.0
14.0
15.0
16.0
17.0
18.0
19.0
20.0

The extra precision ensures that the evaluation of the polynomial is accurate.

Table Of Contents

Previous topic

Differentiation

Next topic

Limits

This Page