MTH 337: Week 8

In [1]:
%pylab inline
Populating the interactive namespace from numpy and matplotlib

Finding Roots of Equations

We're solving the equation $\cos(x) = x$ numerically.

Rewrite as the function $f(x) = \cos(x) - x$. This then becomes the "root-finding problem" of finding a value $x$ for which $f(x) = 0$.

In [2]:
def myf(x):
    return cos(x) - x

Bisection Method

  • Start with a "bracket" [a, b] with $sign(f(a)) \neq sign(f(b))$.
  • Evaluate $f$ at the midpoint c = (a + b)/2.
  • "Replace" either a or b by c, to keep $sign(f(a)) \neq sign(f(b))$.
  • Repeat while bracket width b - a > tolerance.

This is guaranteed to work, but is slow.

In [3]:
def mybisection(f, a, b, tol):
    sign_a = sign(f(a))
    sign_b = sign(f(b))
    # First check that we don't already have a root
    if sign_a == 0:
        return a
    elif sign_b == 0:
        return b
    # Then check that we do actually have a bracket.
    elif sign_a == sign_b:
        print "Not a bracket!"
        return
    # Keep track of the current step
    step = 0
    # Keep bisecting the interval [a, b]
    while b - a > tol:
        print step, a, b
        step += 1
        c = (a + b)/2.
        sign_c = sign(f(c))
        if sign_c == 0:
            return c
        elif sign_a == sign_c:
            a = c
        else:
            b = c
    return (a + b)/2.
In [4]:
mybisection(myf, 0, pi/2., 1e-5)
0 0 1.57079632679
1 0 0.785398163397
2 0.392699081699 0.785398163397
3 0.589048622548 0.785398163397
4 0.687223392973 0.785398163397
5 0.736310778185 0.785398163397
6 0.736310778185 0.760854470791
7 0.736310778185 0.748582624488
8 0.736310778185 0.742446701337
9 0.736310778185 0.739378739761
10 0.737844758973 0.739378739761
11 0.738611749367 0.739378739761
12 0.738995244564 0.739378739761
13 0.738995244564 0.739186992162
14 0.738995244564 0.739091118363
15 0.739043181464 0.739091118363
16 0.739067149913 0.739091118363
17 0.739079134138 0.739091118363
Out[4]:
0.7390881223069241

Functional Iteration

  • Start with some guess $x_0$ at the solution.
  • Generate a series of approximations $x_1, x_2, x_3, \ldots$ by $x_{i + 1} = f(x_i) + x_i$
In [5]:
def func_iter(f, x, tol):
    step = 0
    fval = f(x)
    while abs(fval) > tol:
        print step, x
        fval = f(x)
        x = fval + x
        step += 1
    return x
In [6]:
func_iter(myf, 1., 1e-5)
0 1.0
1 0.540302305868
2 0.857553215846
3 0.654289790498
4 0.793480358743
5 0.701368773623
6 0.763959682901
7 0.722102425027
8 0.750417761764
9 0.731404042423
10 0.744237354901
11 0.735604740436
12 0.74142508661
13 0.737506890513
14 0.740147335568
15 0.738369204122
16 0.739567202212
17 0.738760319874
18 0.739303892397
19 0.738937756715
20 0.739184399771
21 0.739018262427
22 0.73913017653
23 0.739054790747
24 0.739105571927
25 0.739071365299
26 0.739094407379
27 0.739078885995
28 0.739089341403
Out[6]:
0.73908229852240237

Newton's Method

  • Assume that f is differentiable, and that values are available for both f(x) and f'(x).
  • Find the tangent to f at the current estimate $x_i$.
  • Let $x_{i + 1}$ be the intersection of the tangent with the x-axis.

Then: $x_{i + 1} = x_i - \dfrac{f(x_i)}{f'(x_i)}$

This is very fast when it works, and converges quickly. But, the method is a little "fragile", and needs a good initial guess.

In [7]:
# Return both the function and the derivative
def myfdf(x):
    return cos(x) -  x, -sin(x) - 1
In [8]:
def newton(fdf, x, tol):
    while True:
        f, df = fdf(x)
        print x, f
        s = - f/df
        x += s
        if abs(s) <= tol:
            return x
In [9]:
newton(myfdf, .5, 1e-10)
0.5 0.37758256189
0.755222417106 -0.0271033118575
0.73914166615 -9.46153806177e-05
0.739085133921 -1.18097787105e-09
0.739085133215 0.0
Out[9]:
0.73908513321516067

Problems with Newton's Method

We use Newton's Method to find the root of $f(x) = tanh(x)$.

The graph of this function is shown below.

In [10]:
x = linspace(-2, 2)
plot(x, tanh(x))
axhline(color='k')
axvline(color='k')
Out[10]:
<matplotlib.lines.Line2D at 0x7f19b7d9d390>

Define a function which returns the values f(x), f'(x)

In [11]:
def tanhdf(x):
    return tanh(x), 1./cosh(x)**2

Modify the "newton" function to stop after a maximum number of steps

In [12]:
def newton(fdf, x, tol, maxsteps=10):
    for i in range(maxsteps):
        f, df = fdf(x)
        print x, f
        s = - f/df
        x += s
        if abs(s) <= tol:
            return x

Testing, we find that initial values |x| < 1.08 converge, |x| > 1.09 diverge

In [13]:
newton(tanhdf, 1.08, 1e-5)
1.08 0.793199097084
-1.05895313436 -0.785262860062
0.989404207298 0.757108162177
-0.784566773086 -0.655320106679
0.36399816111 0.348730850749
-0.0330146961372 -0.0330027063533
2.3995252668e-05 2.39952526634e-05
-9.21053462449e-15 -9.21053462449e-15
Out[13]:
0.0
In [14]:
newton(tanhdf, 1.09, 1e-5)
1.09 0.796878144205
-1.09331618202 -0.798085307174
1.10490354324 0.802253480143
-1.14615550788 -0.816476470971
1.30303261823 0.86250181868
-2.06492300238 -0.9683385739
13.4731428006 0.999999999996
-126055892893.0 -1.0
inf 1.0
nan nan
/home/adam/anaconda/lib/python2.7/site-packages/IPython/kernel/__main__.py:2: RuntimeWarning: overflow encountered in cosh
  from IPython.kernel.zmq import kernelapp as app
/home/adam/anaconda/lib/python2.7/site-packages/IPython/kernel/__main__.py:5: RuntimeWarning: divide by zero encountered in double_scalars
/home/adam/anaconda/lib/python2.7/site-packages/IPython/kernel/__main__.py:6: RuntimeWarning: invalid value encountered in double_scalars

We can find the initial value of x that neither converges nor diverges by solving:

$x_{i + 1} = -x_i$

$\Rightarrow x_i - \tanh(x_i)\cosh^2(x_i) = -x_i$

$\Rightarrow \sinh(x_i)\cosh(x_i) - 2 x_i = 0$

This is itself a root-finding problem, with $f(x) = \sinh(x)\cosh(x) - 2 x$

So, Newton's method can be used to find the initial values of x for which Newton's method does not converge!

In [15]:
def myfdf(x):
    return sinh(x)*cosh(x) - 2*x, cosh(2*x) - 2

newton(myfdf, 1, 1e-6)
1 -0.186569796076
1.1058734833 0.0437895311237
1.08916363476 0.00124532170727
1.08865994045 1.10556890354e-06
Out[15]:
1.0886594924830075

Colors

The RGB model

In the RGB model, colors are represented in terms of their three components - red, green, and blue.

Each component is represented as a number between fully off and fully on.

These components can be integers from 0 to 255, or floats in the interval [0, 1]

Images are 3D arrays of size (rows, columns, 3)

  • Image height is the number of rows.
  • Image width is the number of columns
  • The '3' indicates the three color components

The "dtype" for the array needs to be either a float in the interval [0, 1], or an 8-bit integer of type 'uint8'. We'll use floats for report 7.

A pure red square

In [16]:
a = zeros((10, 10, 3))
a[:,:,0] = 1
imshow(a)
Out[16]:
<matplotlib.image.AxesImage at 0x7f19b7939610>

A pure green square

In [17]:
a = zeros((10, 10, 3))
a[:,:,1] = 1
imshow(a)
Out[17]:
<matplotlib.image.AxesImage at 0x7f19b78762d0>

A pure blue square

In [18]:
a = zeros((10, 10, 3))
a[:,:,2] = 1
imshow(a)
Out[18]:
<matplotlib.image.AxesImage at 0x7f19b781f4d0>

Slicing can be used to set the color components in different parts of the image

Modern art?

In [19]:
n = 100
a = zeros((n, n, 3))
for i in range(100):
    w, x, y, z = random.randint(0, n, size=4)
    dim = random.randint(0, 3)
    a[min(w,x):max(w,x), min(y,z):max(y,z), dim] = random.rand()
imshow(a)
Out[19]:
<matplotlib.image.AxesImage at 0x7f19b775e310>

Boolean Indexing

An array can be indexed using another array.

If a boolean array is used as an index, only the values with a matching True in the boolean array are returned.

In [20]:
a = arange(5)
print a
b = array([True, True, False, False, True])
print b
print a[b]
[0 1 2 3 4]
[ True  True False False  True]
[0 1 4]

Boolean indexing can be used to select just those array elements which pass some test

In [21]:
a = arange(5)
print a
print a > 2     # This is the test
print a[a > 2]  # Only the elements passing the test are returned
[0 1 2 3 4]
[False False False  True  True]
[3 4]

Boolean indexing can also be used to modify just those array elements which pass some test

In [22]:
a = arange(5)
print a
b = a % 2 == 0 # This is the test, which is only passed by even numbers 
print b
a[b] = 100     # Set all even numbers to 100
print a
[0 1 2 3 4]
[ True False  True False  True]
[100   1 100   3 100]