In our question to produce the perfect (i.e. winning) bracket, we'll need to optimize. So I guess we should look into that!
Optimization is the process of finding maxima, minima, and their locations. It can be constrained, unconstrained, or a combination.
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import minimize, newton
Here's a fun example to play with:
def f(x): return np.sin(8*x)+np.cos(7*x)
x = np.linspace(-2,4,500)
y = f(x)
plt.plot(x,y)
We know how to find roots so, if we can find roots of a derivative, we oughtta be able to optimize. Just be sure to apply Newton's method to the derivative of our function!
def fp(x): return 8*np.cos(8*x)-7*np.sin(7*x)
def fpp(x): return -64*np.sin(8*x) - 49*np.cos(7*x)
xmin = newton(fp,1,fprime=fpp)
ymin = f(xmin)
print((xmin,ymin))
plt.plot(x,y)
plt.plot(xmin,ymin,'ok')
Well, we sure found a local max, but maybe not an absolute. If $f'$ is a pain to find for some reason, we can use a numerical approximation. We'll talk about how that works later!
from scipy.misc import derivative
def fp(x): return derivative(f,x, 10**-5)
newton(fp,1)
The build in command for this is the minimize
command:
minimize(lambda x: -f(x), 1)
There are a couple of nice references for the golden section search on Wikipedia and here. Here's the implementation from Wikipedia:
gr = (np.sqrt(5) + 1) / 2
def gss(f, a, b, tol=10**(-10)):
c = b - (b - a) / gr
d = a + (b - a) / gr
while abs(c - d) > tol:
if f(c) < f(d):
b = d
else:
a = c
c = b - (b - a) / gr
d = a + (b - a) / gr
return (b + a) / 2
The golden-section search is a bracketing method for minimization and requires both endpoints of an interval to proceed.
xmin = gss(f, 0, 3)
ymin = f(xmin)
print((xmin,ymin))
plt.plot(x,y)
plt.plot(xmin,ymin,'ok')
Here's a fun function!
def peaks(x,y): return 3*(1-x)**2*np.exp(-(x**2) - \
(y+1)**2) - 10*(x/5 - x**3 - y**5)*np.exp(-x**2-y**2) - \
1/3*np.exp(-(x+1)**2 - y**2)
Let's take a look at it!
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X = np.arange(-3, 3, 0.05)
Y = np.arange(-3, 3, 0.05)
X, Y = np.meshgrid(X, Y)
Z = peaks(X,Y)
ax.plot_surface(X, Y, Z, lw=0.5)
fig.set_size_inches(12,12)
ax.set_aspect(0.7)
A contour plot might be more helpful, at least if we want a clue as to where the roots are.
cs = plt.contour(X,Y,Z,10)
ax=plt.gca()
ax.set_aspect(1)
fig=plt.gcf()
fig.set_size_inches(8,8)
The minimize
command expects a vector. Here's how we use it.
def ppeaks(xy): return peaks(xy[0],xy[1])
minimize(ppeaks, [0,-2])
cs = plt.contour(X,Y,Z,10)
ax=plt.gca()
ax.set_aspect(1)
fig=plt.gcf()
fig.set_size_inches(8,8)
plt.plot(0.228, -1.625, 'ok')
from scipy.optimize import approx_fprime
approx_fprime([1.2,2.2], ppeaks,10**-5)
Implementation of the basic algorithm is not particularly hard. Here's a gradient ascent to a maximum.
current = np.array([1,2.2])
trajectory = [current]
grad = approx_fprime(current, ppeaks,10**-5)
norm = np.linalg.norm(grad)
cnt = 0
while norm > 0.01 and cnt <1000:
step = 0.01*grad/norm
current = current + step
trajectory.append(current)
grad = approx_fprime(current, ppeaks,10**-5)
norm = np.linalg.norm(grad)
cnt = cnt + 1
cs = plt.contour(X,Y,Z,10)
xs = [xy[0] for xy in trajectory]
ys = [xy[1] for xy in trajectory]
plt.plot(xs,ys, 'k')
plt.plot(trajectory[0][0], trajectory[0][1],'og')
plt.plot(trajectory[-1][0], trajectory[-1][1],'or')
ax=plt.gca()
ax.set_aspect(1)
fig=plt.gcf()
fig.set_size_inches(8,8)