import matplotlib.pyplot as plt
import numpy as np
from scipy import linalg
from scipy.optimize import root
import sys
if "google.colab" in sys.modules:
!wget "https://raw.githubusercontent.com/ndcbe/optimization/main/notebooks/helper.py"
else:
sys.path.insert(0, "../")
import helper
from helper import set_plotting_style
set_plotting_style()Local and Global Solutions¶
Necessary Conditions for Optimality¶
Sufficient Conditions for Optimality¶
Definitions and geometric proof guide¶
Quantified minimizer definitions¶
For , let with . A global minimizer satisfies for every . A local minimizer satisfies this inequality for every for some . Strict local minimality requires for every other point in that ball. Isolation means that is the only local minimizer in some ball.

Isolated implies strict¶
Let be a local minimizer isolated among local minimizers. Choose a radius for local minimality and a radius for isolation. If strictness fails, every sufficiently small neighborhood contains a distinct with ; choose it inside both balls. Choose . Every belongs to by the triangle inequality, and therefore . This makes a second local minimizer inside the isolating ball, a contradiction. The converse fails in the oscillatory example below.

Infimum and existence¶
For nonempty bounded below, means for all , and for each there is with . The second condition rules out a larger lower bound. If is unbounded below, . A minimum is attained; for an objective on , apply this definition to . For on , because and as , but no point attains it.
Weierstrass says that a continuous function on a nonempty compact set attains its minimum and maximum (see the Real Analysis Review lecture). If a continuous objective has a bounded sublevel set , then is nonempty, closed, bounded and hence compact in . Its minimizer is global: all points outside have value larger than . A coercive continuous objective satisfies this hypothesis because as .
Convexity certificates¶
Convexity on a convex domain means for all points and . Strict convexity replaces by for distinct points. Stationarity means .
If is local but not global, choose with ; need not be local. For , the distance is smaller than the local radius for sufficiently close to one. Convexity then gives , contradicting local minimality.

Two distinct global minimizers of a strictly convex function cannot tie at : their midpoint has value less than . This proves uniqueness if a minimizer exists. Convex differentiable functions also obey the tangent inequality . At stationarity the last term is zero, proving global optimality.

Exact identities and necessary conditions¶
Assume on a neighborhood of the segment. Set . The scalar mean-value theorem gives some with . Scalar Taylor’s theorem gives some with . The intermediate points can differ. The fundamental theorem of calculus applied componentwise gives the vector identity ; it does not require a common vector mean-value point.

At a local minimum, if , choose . The scalar slope is continuous and starts at . It remains negative for all sufficiently small positive . The scalar mean-value theorem gives with ; choose to remain inside the local minimizing ball. This contradiction proves stationarity.
If the Hessian has a direction with , continuity keeps that scalar curvature negative along a short segment. At stationarity the linear Taylor term vanishes, leaving a negative quadratic remainder and the same contradiction. Thus the Hessian must be positive semidefinite. A zero curvature direction gives no such sign guarantee: and the constant zero function all have zero gradient and Hessian at zero, but different classifications.

Sufficient conditions: strictness and isolation¶
Suppose and . Continuity keeps the Hessian positive definite in a sufficiently small ball (the smallest eigenvalue remains positive). For every nonzero displacement staying in that ball, the second Taylor identity gives a strictly positive objective change. This proves strict local minimality.
If another local minimizer lay in that ball, its gradient would also be zero. With , convexity of the ball keeps the whole segment inside it. Multiplying the gradient integral identity by gives , a contradiction. This proves isolation. The certificate is local and gives no comparison with distant basins.

Sources: Biegler (2010), Chapter 2, Def. 2.15 and Thms. 2.14, 2.16–2.18, pp. 27–30, with errata; Nocedal and Wright (2006), §2.1, pp. 12–17.
Example 2.19 in Biegler (2010)¶
Main Idea: Use the necessary and sufficient conditions to classify a candidate solution in the following example.
with
Biegler reports the rounded candidate with .
At the underlying stationary point, the book reports the Hessian
which has eigenvalues and .
The square roots require . The model is smooth in the interior . The printed candidate is rounded: its computed gradient is small but not zero. We refine it below before comparing derivative values; the figure window is not the full domain.
The Test Function¶
## Define Python function
def my_f(x, verbose=False):
"""Evaluate function given above at point x
Inputs:
x - vector with 2 elements
Outputs:
f - function value (scalar)
"""
# Constants
a = np.array([0.3, 0.6, 0.2])
b = np.array([5, 26, 3])
c = np.array([40, 1, 10])
# Intermediates. Recall Python indices start at 0
u = x[0] - 0.8
s = np.sqrt(1 - u)
s2 = np.sqrt(1 + u)
v = x[1] - (a[0] + a[1] * u**2 * s - a[2] * u)
alpha = (
-b[0] + b[1] * u**2 * s2 + b[2] * u
) # September 5, 2018: changed 's' to 's2'
beta = c[0] * v**2 * (1 - c[1] * v) / (1 + c[2] * u**2)
f = alpha * np.exp(-beta)
if verbose:
print("##### my_f at x = ", x, "#####")
print("u = ", u)
print("sqrt(1-u) = ", s)
print("sqrt(1+u) = ", s2)
print("v = ", v)
print("alpha = ", alpha)
print("beta = ", beta)
print("f(x) = ", f)
print("##### Done. #####\n")
return f## Define candidate point and check against value reported in book
xtest = np.array([0.7395, 0.3144])
ftest = my_f(xtest)
my_f(xtest, verbose=True)
## Evaluate a two-dimensional grid
x1 = np.arange(0.0, 1.1, 0.05)
x2 = np.arange(0.0, 1.0, 0.05)
# Create a matrix of all points to sample
X1, X2 = np.meshgrid(x1, x2)
n1 = len(x1)
n2 = len(x2)
# Notice the order. This was wrong in quadratic.ipynb and has been corrected in quadratic_update.ipynb
F = np.zeros([n2, n1])
xtemp = np.zeros(2)
# Evaluate f(x) over grid
for i in range(0, n1):
xtemp[0] = x1[i]
for j in range(0, n2):
xtemp[1] = x2[j]
F[j, i] = my_f(xtemp)
##### my_f at x = [0.7395 0.3144] #####
u = -0.0605
sqrt(1-u) = 1.029805806936434
sqrt(1+u) = 0.9692780818733084
v = 3.839197709659681e-05
alpha = -5.089257197421404
beta = 5.687377052495847e-08
f(x) = -5.089256907976166
##### Done. #####
Discussion: The two-dimensional contour plot below shows one basin. A picture alone does not prove convexity or global optimality. Compare the local derivative test with the definitions above.
Finite Difference Gradient and Hessian¶
We need to calculate and to analyze a given point. Below are functions that implement a central finite difference. Another option is to analytically calculate the derivatives or use automatic differentiation.
## Gradient
def my_grad(x, verbose=True, eps=1e-6):
"""
Calculate gradient of function my_f using central difference formula
Inputs:
x - point for which to evaluate gradient
Outputs:
grad - gradient (vector)
Assumptions:
1. my_f is defined
2. input x has the correct number of elements for my_f
3. my_f is continuous and differentiable
"""
n = len(x)
grad = np.zeros(n)
if verbose:
print("***** my_grad at x = ", x, "*****")
for i in range(0, n):
# Create vector of zeros except eps in position i
e = np.zeros(n)
e[i] = eps
# Finite difference formula
my_f_plus = my_f(x + e)
my_f_minus = my_f(x - e)
# Diagnostics
if verbose:
print("e[", i, "] = ", e)
print("f(x + e[", i, "]) = ", my_f_plus)
print("f(x - e[", i, "]) = ", my_f_minus)
grad[i] = (my_f_plus - my_f_minus) / (2 * eps)
if verbose:
print("***** Done. ***** \n")
return grad
grad = my_grad(xtest)
print(grad)***** my_grad at x = [0.7395 0.3144] *****
e[ 0 ] = [1.e-06 0.e+00]
f(x + e[ 0 ]) = -5.089256904035737
f(x - e[ 0 ]) = -5.089256911839596
e[ 1 ] = [0.e+00 1.e-06]
f(x + e[ 1 ]) = -5.089256892701673
f(x - e[ 1 ]) = -5.089256922857939
***** Done. *****
[0.00390193 0.01507813]
Discussion: According to the book, . Is the above answer reasonable?
Note: Before fixing the mistake in my_f, the gradient was [-0.28182046 0.01506107]. The version we discussed in class had the mistake in my_f.
## Hessian
def my_hes(x, eps=1e-4, grad_eps=1e-6):
"""
Calculate Hessian of function my_f using central difference formula and my_grad
Inputs:
x - point for which to evaluate gradient
Outputs:
H - Hessian (matrix)
Assumptions:
1. my_f and my_grad are defined
2. input x has the correct number of elements for my_f
3. my_f is continuous and twice differentiable
4. No mistakes in my_grad
"""
n = len(x)
H = np.zeros([n, n])
for i in range(0, n):
# Create vector of zeros except eps in position i
e = np.zeros(n)
e[i] = eps
# Evaluate gradient twice
grad_plus = my_grad(x + e, verbose=False, eps=grad_eps)
grad_minus = my_grad(x - e, verbose=False, eps=grad_eps)
# Notice we are building the Hessian by column (or row)
H[:, i] = (grad_plus - grad_minus) / (2 * eps)
"""
Note: This is not very efficient. You can probably think of several
performance improvements. We will learn more practical ways to approximate
the Hessian for optimization algorithms in the next few lectures.
"""
return H
H = my_hes(xtest)
print(H)[[ 76.99959337 108.341347 ]
[108.34137365 392.71895957]]
Primary Discussion: How does this compare to the answer given in the book? Which elements are very close to the given answer? Which have greater error? Why does this make sense?
Secondary Discussion: What happens if you set eps to different values in my_grad and my_hes? Is H still symmetric?
Here the gradient difference step is and the outer Hessian step is . Taking both extremely small can amplify subtraction error. The computed Hessian need not be exactly symmetric; report its asymmetry before using for a symmetric eigenvalue calculation. Compare several step sizes below.
Analysis of Optimality Conditions¶
Activity
Calculate the eigenvalues of H.
Is a
stationary point
local maximizer
strict global maximizer
global maximizer
local minimizer
strict local minimizer
global minimizer
Answer Yes, No, or Possibly for each bullet point.
# Add your solution hereRefine the point and show the calculations¶
The book’s eigenvalues above are supplied comparison data. The following calculation evaluates them independently.
Central differences use the coordinate vector :
A root solve for the numerical gradient refines the candidate. Check the termination message and residual; a numerical solve is not a proof of exact stationarity.
# Refine using the same objective and finite-difference gradient defined above.
refinement = root(lambda x: my_grad(x, verbose=False), xtest, tol=1e-10)
xref = refinement.x
ref_residual = float(np.linalg.norm(my_grad(xref, verbose=False)))
if not refinement.success and ref_residual > 1e-7:
raise RuntimeError(refinement.message)
raw_H = my_hes(xref)
sym_H = (raw_H + raw_H.T) / 2
values, vectors = np.linalg.eigh(sym_H)
if np.any(values <= 0):
raise RuntimeError("The local quadratic ellipse requires positive eigenvalues")
delta_f = 0.25
semi_axes = np.sqrt(2 * delta_f / values)
print("Refinement:", refinement.message)
print("Residual norm:", ref_residual)
my_f(xref, verbose=True)
print("Hessian asymmetry norm:", np.linalg.norm(raw_H - raw_H.T))
print("Compare Hessian steps and eigenvalues:")
for k in [3e-4, 1e-4, 3e-5]:
Hk = my_hes(xref, eps=k)
print(k, np.linalg.eigvalsh((Hk + Hk.T) / 2))
# The complete grid and quadratic geometry are archived for the handout figure.
xx = np.linspace(0, 1.1, 151)
yy = np.linspace(0, 1.0, 151)
zz = np.array([[my_f(np.array([x,y])) for x in xx] for y in yy])
results = dict(x=xx.tolist(), y=yy.tolist(), objective=zz.tolist(),
rounded=xtest.tolist(), refined=xref.tolist(), objective_refined=float(my_f(xref)),
gradient_refined=my_grad(xref, verbose=False).tolist(),
hessian=sym_H.tolist(), eigenvalues=values.tolist(), eigenvectors=vectors.tolist(),
delta_f=delta_f, semi_axes=semi_axes.tolist())
helper.save_results("optimality-example-2-19", results,
notebook="notebooks/6-dev/Optimality.ipynb", source_tag=None,
source_cells=[("notebooks/6-dev/Optimality.ipynb", tag) for tag in
["results:uo-example-model", "results:uo-gradient", "results:uo-hessian", "results:uo-example-data"]],
description="Full Biegler Example 2.19 objective and refined local quadratic ellipse", quiet=True)
Refinement: The solution converged.
Residual norm: 0.0
##### my_f at x = [0.73950546 0.3143601 ] #####
u = -0.060494538339870285
sqrt(1-u) = 1.029803155141734
sqrt(1+u) = 0.969280899254767
v = 5.669353875248362e-13
alpha = -5.089257198124354
beta = 1.2402740014872362e-23
f(x) = -5.089257198124354
##### Done. #####
Hessian asymmetry norm: 4.39625888431457e-05
Compare Hessian steps and eigenvalues:
0.0003 [ 43.41741485 426.3602212 ]
0.0001 [ 43.41742864 426.36146937]
3e-05 [ 43.41744101 426.36161168]
print("Central differences: h=1e-6 (gradient), k=1e-4 (Hessian)")
print(f"Rounded x=({xtest[0]:.4f}, {xtest[1]:.4f}), ||g||={np.linalg.norm(my_grad(xtest,False)):.3e}")
print(f"Refined x=({xref[0]:.8f}, {xref[1]:.8f}), f={my_f(xref):.8f}")
print(f"||g(refined)||={ref_residual:.3e}")
print(f"H rows: [{sym_H[0,0]:.3f}, {sym_H[0,1]:.3f}]; [{sym_H[1,0]:.3f}, {sym_H[1,1]:.3f}]")
print(f"Eigenvalues: {values[0]:.3f}, {values[1]:.3f}; semi-axes: {semi_axes[0]:.4f}, {semi_axes[1]:.4f}")
Central differences: h=1e-6 (gradient), k=1e-4 (Hessian)
Rounded x=(0.7395, 0.3144), ||g||=1.557e-02
Refined x=(0.73950546, 0.31436010), f=-5.08925720
||g(refined)||=0.000e+00
H rows: [77.012, 108.334]; [108.334, 392.767]
Eigenvalues: 43.417, 426.361; semi-axes: 0.1073, 0.0342
# One plotting cell serves the notebook, archive renderer, and printed handout.
fig, axes = plt.subplots(1,2,figsize=(10,2.7),layout='constrained')
x=np.asarray(results['x']); y=np.asarray(results['y']); z=np.asarray(results['objective'])
xr=np.asarray(results['refined']); Q=np.asarray(results['eigenvectors'])
radii=np.asarray(results['semi_axes']); lam=np.asarray(results['eigenvalues'])
cs=axes[0].contour(x,y,z,levels=[-5,-4,-2,0,2,5,10,15],colors='black',linewidths=.7)
axes[0].clabel(cs,fontsize=8,inline=True)
axes[0].plot(xr[0],xr[1],marker='o',color='#D55E00',markeredgecolor='black',markersize=5)
axes[0].set(title='Objective contours: one basin',xlim=(0,1.1),ylim=(0,1),xlabel='$x_1$',ylabel='$x_2$')
t=np.linspace(0,2*np.pi,300)
ellipse=xr[:,None]+Q @ (radii[:,None]*np.array([np.cos(t),np.sin(t)]))
axes[1].plot(ellipse[0],ellipse[1],color='black',linewidth=1.6)
for j,(color,style) in enumerate([('#0072B2','--'),('#D55E00',':')]):
ends=xr[:,None]+Q[:,j,None]*radii[j]*np.array([[-1,1]])
axes[1].plot(ends[0],ends[1],color=color,linestyle=style,linewidth=2.4,
label=rf'$\lambda_{j+1}={lam[j]:.1f}$')
axes[1].plot(*xr,marker='o',color='black',markersize=4)
axes[1].set(title=r'Quadratic level: $\Delta f=0.25$',
xlim=(xr[0]-.14,xr[0]+.14),ylim=(xr[1]-.14,xr[1]+.14),xlabel='$x_1$',ylabel='$x_2$')
axes[1].legend(loc='upper right',fontsize=8,frameon=False)
for ax in axes:
ax.set_aspect('equal',adjustable='box')
ax.tick_params(labelsize=9)
ax.xaxis.label.set_size(12);ax.yaxis.label.set_size(12)
ax.title.set_size(11)
helper.save_figure(fig,"optimality-example-2-19",quiet=True)
plt.show()

The left panel displays the actual objective. The right panel displays the local quadratic model, not an exact objective contour and not a statistical confidence region. If , the quadratic level equation is
Each eigenvector segment ends on this ellipse: its semi-axis is . Thus the weaker-curvature direction is longer, with ratio . This is the same inverse-square-root geometry used for covariance/confidence ellipsoids, with different meaning for the level.
Positive eigenvalues at an exactly stationary interior point give a strict, isolated local minimum. Here the derivative values and residual provide numerical evidence for that certificate. They do not establish global optimality of the nonconvex model.
Continuous Optimization Algorithms¶
Derivative-free methods use objective values with less derivative structure; classical direct search generally scales poorly as the number of variables grows. Gradient-based methods exploit smoothness to choose steps and often solve local problems quickly. Neither family name alone guarantees a global solution; the assumptions and convergence result for the particular method matter.
Gradient-based Algorithms
Conjugate gradient methods
Sequential quadratic programming methods
Sequential linear programming methods
Interior point methods
Derivative-free optimization
Deterministic algorithms
Nelder–Mead method
Stochastic algorithms
Simulated Annealing
Genetic Algorithms
Strict but not isolated: detailed counterexample¶
The handout uses
Since , every nonzero point has . Thus zero is the strict global minimizer. Strictness compares objective values with nearby points; isolation instead excludes other local minimizers from some neighborhood.
Away from zero,
Put and . Fix a small and take . For all sufficiently large ,
because the sine terms are and , whereas the other term tends to zero. In the corresponding interval,
is negative at the left endpoint and positive at the right endpoint. The minimum on this compact interval is therefore attained in its interior, giving a local minimizer. These disjoint intervals approach zero. Hence every neighborhood of zero contains another local minimizer, although each has a value greater than .
The uncorrected expression does not have a local minimum at zero: at it is negative, arbitrarily close to zero. The added repairs the value comparison while preserving the oscillations.
Here , but does not approach zero. The function is not near zero, so a theorem assuming a continuous Hessian cannot be applied there.
Sources: Biegler (2010), Definition 2.15, p. 28, with the SIAM errata; Nocedal and Wright (2006), p. 13. The derivative/sign argument above expands the lecture’s prepared zoom rather than treating every stationary point as a minimum.
Function-value complexity versus stationarity¶
A global guarantee and a small gradient are different targets. For an objective on that is -Lipschitz in the infinity norm, a function-value oracle must, in the worst case, use at least
calls to guarantee objective error strictly below . A uniform grid gives a matching-order upper bound with points per coordinate. An unsampled box can contain a Lipschitz depression consistent with every observed value; the guarantee must account for that possibility.
For , , , the lower bound is calls. The tolerance is an absolute objective gap; calling it a percentage requires an objective scale. One more coordinate multiplies this bound by 100; doubling the tolerance divides it by .
Nesterov’s historical illustration budgets arithmetic operations per call and 106 arithmetic operations per second. At , this is seconds, or about 31.25 million years using the book’s seconds per year. It is an oracle illustration, not a benchmark of current hardware. At , the floor gives 1210 calls (about 7.2 days under that budget); the grid upper bound gives 1310 calls (about 16 days). The book’s “two weeks” is a rough illustration, not the exact floored lower-bound arithmetic.
Nesterov also discusses global lower bounds for smoother classes and higher-order oracles on p. 15; those use their own hypotheses. The displayed function-value bound is tied to its stated oracle and problem class.
For a different task, assume a globally Lipschitz gradient with constant and . Gradient descent with step gives
Summing and taking the smallest gradient norm yields
This iteration bound has no explicit dimension factor, but a gradient evaluation still has a dimension-dependent cost. It certifies small gradient, not a minimum or global objective accuracy. Its oracle and smoothness assumptions also differ from the function-value global problem.
Source: Nesterov (2018), Lectures on Convex Optimization, 2nd edition, §1.1.3, Theorem 1.1.2 and Example 1.1.4, pp. 10–15; §1.2.3, pp. 31–32. Book DOI.
- Nesterov, Y. (2018). Lectures on Convex Optimization. In Springer Optimization and Its Applications. Springer International Publishing. 10.1007/978-3-319-91578-4