Like in root finding, you will often have some idea of where the optimum is. In similar fashion, we can iteratively improve our brackets until they are close enough.
import numpy as np
import scipy.optimize as optimize
try:
import executable_engineering as exe
except ImportError:
%pip install -q executable_engineering
import executable_engineering as exe
from executable_engineering import ScalarTracker, plot_bracketed_progressionGolden section search¶
In the bisection root finding algorithm, two points were sufficient since they identify a sign change. For minimization, we need to determine a minimum, so we need 3 points.
Given 3 points
Choose a point
Evaluate the function at
If , select .
If , select .
Repeat until a tolerance is met
How do we choose ?

The Golden ratio¶
To make a robust method, we want both of these paths to result in the same reduction of the search interval, and ideally reuse the function evaluations. This self-similarity implies we want to maintain the ratio of subdivision:
In the triplet with , we want .
In the triplet with , we want .
Using these two equations, we can eliminate and find,
and ignoring the negative root,
the Golden ratio!
Convergence rate¶
Each iteration of the Golden section method reduces the bracketed interval by a constant factor of .
The Golden section method is guaranteed to converge, doing so reliably at a linear rate ( with rate constant ).
Let’s see this on the unimodal function (where the true minimum is at ):
def f(x):
return x**4 + 10 * x
x_true = -(10 / 4)**(1 / 3)
tracker = ScalarTracker(f)
result = optimize.minimize_scalar(tracker, bracket=(-5, 0, 5), method='Golden')
print("Minimum found at x =", result.x)
print("Minimum function value =", result.fun)
plot_bracketed_progression(f, tracker, result, x_range=(-5, 5), title="Golden Section Search on f(x) = x^4 + 10x")Brent’s method¶
We’ve seen this concept before in root finding with bisection. Later we saw the value of using the function values to approximate the derivative and guess at the root. Since we had two points, we fit a line (which approximated the derivative) which is the Secant method.
Now we have 3 points so we can fit a parabola and find !
Parabolic interpolation¶
Fitting a parabola through the three points , , and allows us to jump directly to the vertex of that parabola as our next estimate :
(Note: For root finding, where we want where , Brent’s root-finding algorithm uses inverse quadratic interpolation to fit . For minimization, where we want , Brent’s minimization method uses successive parabolic interpolation as written above!)
The convergence order of parabolic interpolation on smooth functions is superlinear ().
Combination with Golden section¶
Brent’s method combines the robustness of the Golden Section method with the acceleration of parabolic approximation (still without any derivative information!).
Candidate steps from parabolic interpolation and Golden section are generated and compared against various criteria for robustness and progression.
This decision tree makes the exact convergence behavior adaptive, achieving superlinear convergence () for well-behaved functions while guaranteeing at least the linear convergence rate of Golden section search.
Example: Unimodal minimization¶
Minimize using Brent’s method and compare the evaluation count and convergence curve against Golden section search:
tracker = ScalarTracker(f)
result = optimize.minimize_scalar(tracker, bracket=(-5, 0, 5), method='Brent')
print("Minimum found at x =", result.x)
print("Minimum function value =", result.fun)
plot_bracketed_progression(f, tracker, result, x_range=(-5, 5), title="Brent's Method on f(x) = x^4 + 10x")Example: Non-differentiable function¶
Let’s try a non-differentiable function: :
def nondiff_func(x):
return np.abs(x)
# What should we use for the bracket? What happens if we use a different method?
bracket = (-5, 1, 5)
tracker = ScalarTracker(nondiff_func)
result = optimize.minimize_scalar(tracker, bracket=bracket, method='Brent')
print("Minimum found at x =", result.x)
print("Minimum function value =", result.fun)
plot_bracketed_progression(nondiff_func, tracker, result, x_range=(-5, 5), title="Brent's Method on f(x) = |x|")Example: A more treacherous function¶
Minimize
def complicated_nondiff_func(x):
return np.abs(np.sin(x)) + np.abs(np.cos(x)) + 0.1 * x**2
tracker = ScalarTracker(complicated_nondiff_func)
result = optimize.minimize_scalar(tracker, bracket=(-5, 1, 5), method='Brent')
print("Minimum found at x =", result.x)
print("Minimum function value =", result.fun)
plot_bracketed_progression(complicated_nondiff_func, tracker, result, x_range=(-5, 5), title="Brent's Method on a Complicated Non-Differentiable Function")