TP

Gradient descent and quadratic penalization

Continuous optimization — ENT 305

We study the projection of $(0,1)$ onto the line $x=y$:

$$\min_{(x,y)\in\mathbb R^2} f(x,y)=\frac12\bigl[x^2+(y-1)^2\bigr],\qquad h(x,y)=y-x=0.$$

import numpy as np
import matplotlib.pyplot as plt

1. Build the quadratic penalty

For $c>0$, define

$$Q_c(x,y)=f(x,y)+\frac c2,h(x,y)^2.$$

Exercise 1. Compute $\nabla f$, $\nabla h$, $\nabla Q_c$ and $\nabla^2 Q_c$ by hand. Complete the functions below.

def f(x, y):
    return ...

def grad_f(x, y):
    return ...

def h(x, y):
    return ...

def grad_h(x, y):
    return ...

def Q(xy, c):
    ...
    return ...

def grad_Q(xy, c):
    x, y = ...
    return ...

def hessian_Q(c):
    return ...

At each iteration, use the descent direction $d_k=-\nabla Q_c(x_k)$ and choose a step $\alpha_k>0$ satisfying

$$Q_c(x_k+\alpha_k d_k)\le Q_c(x_k)+\sigma\alpha_k\nabla Q_c(x_k)^\top d_k,\qquad 0<\sigma<1.$$

Exercise 2. Complete the code and compare the trajectories for $c=1$ and $c=20$.

def armijo_step(x, direction, c, alpha0=1.0, shrink=0.5, sigma=1e-4):
    ...

def gradient_descent(x0, c, tol=1e-8, max_iter=30000):
    ...
    return np.asarray(trajectory)

for c in [1.0, 20.0]:
    trajectory = gradient_descent([1.5, -0.5], c)
    print(f'c={c:g}: iterations={len(trajectory)-1}, '
          f'point={trajectory[-1]}, |h|={abs(h(*trajectory[-1])):.3g}')
c=1: iterations=29, converged=True, point=[0.33333333 0.66666667], |h|=0.333
c=20: iterations=20, converged=True, point=[0.48780488 0.51219512], |h|=0.0244

3. Analytical solution of the penalized problem

Exercise 3. Show that the descent gradient algorithm converges to

$$\boxed{(x_c,y_c)=\left(\frac{c}{1+2c},\frac{1+c}{1+2c}\right).}$$

def exact_penalty_solution(c):
    return ...

for c in [0.5, 1, 5, 20, 100]:
    path = ...
    x_exact = ...
    print(f'c={c:5g} | GD={path[-1]} | exact={x_exact} | error={np.linalg.norm(path[-1]-x_exact):.2e}')

4. Effect of the penalty parameter

The solution of the original constrained problem is $\bar z=(1/2,1/2)$. Show that

$$|h(x_c,y_c)|=\frac1{1+2c},\qquad | (x_c,y_c)-\bar z|=\frac1{\sqrt2(1+2c)}.$$

Exercise 4. Plot these two errors as functions of $c$. What happens as $c\to\infty$? Does any finite $c$ enforce the constraint exactly?

c_values = np.logspace(-1, 3, 100)
...
fig, ax = plt.subplots(figsize=(8, 4.5))
ax.loglog(c_values, feasibility, label=r'$|y_c-x_c|$')
ax.loglog(c_values, distance, label=r'$\|(x_c,y_c)-(1/2,1/2)\|$')
ax.set(xlabel='Penalty parameter c', ylabel='Error', title='Accuracy versus penalty parameter')
ax.grid(True, which='both', alpha=0.3)
ax.legend(); plt.show()

5. Visualize the iterates

Exercise 5. Superimpose the trajectories of gradient descent on level curves of $Q_c$, and draw the line $x=y$.


fig, axes = plt.subplots(1, 2, figsize=(11, 4.5))

xx, yy = np.meshgrid(
    np.linspace(-0.6, 1.7, 150),
    np.linspace(-0.8, 1.7, 150)
)

for ax, c in zip(axes, [1.0, 20.0]):

    values = ...
    ax.contour(xx, yy, values, levels=20)

    path= ...

    # Trajectory
    ax.plot(path[:, 0], path[:, 1], '-',
            linewidth=1, alpha=0.7)

    # All iterations
    ax.scatter(path[:, 0], path[:, 1],
               s=22, zorder=4, label='GD iterates')

    # Initial and final points
    ax.scatter(*path[0], s=90, marker='s',
               zorder=5, label='Initial point')
    ax.scatter(*path[-1], s=110, marker='*',
               zorder=6, label='Final iterate')

    # Constraint and constrained minimizer
    ax.plot([-0.8, 1.7], [-0.8, 1.7], '--',
            label='Constraint x=y')
    ax.scatter(0.5, 0.5, s=80, marker='x',
               linewidths=2, zorder=7,
               label='Constrained minimizer')

    ax.set(
        xlim=(-0.6, 1.7),
        ylim=(-0.8, 1.7),
        xlabel='x',
        ylabel='y',
        title=f'c={c:g}'
    )
    ax.set_aspect('equal', adjustable='box')
    ax.legend(fontsize=7)

plt.tight_layout()
plt.show()

6. Conditioning: the limitation of the method

The Hessian of $Q_c$ is

$$\nabla^2 Q_c=\begin{pmatrix}1+c&-c\-c&1+c\end{pmatrix}.$$

Exercise 6. Show that its eigenvalues are $1$ and $1+2c$. Deduce that $Q_c$ is strongly convex, but that

$$\kappa_2(\nabla^2 Q_c)=1+2c\longrightarrow+\infty.$$

Measure the number of gradient-descent iterations needed as $c$ increases. Explain the compromise between constraint accuracy and numerical conditioning.

test_c = np.array([0.5, 1, 2, 5, 10, 20, 50, 100], dtype=float)
rows = []
for c in test_c:
    path = ...
    rows.append((c, ..., len(path)-1, abs(h(*path[-1]))))
print('     c   condition number    GD iterations      |h|     ')
for c, cond, iters, violation in rows:
    print(f'{c:7.1f} {cond:17.1f} {iters:16d} {violation:12.5g}   ')

7. Quadratic-penalty algorithm

For a prescribed feasibility tolerance $\varepsilon_{\rm feas}$, increase $c$ progressively, using the previous solution as the initial point for the next unconstrained minimization.

Exercise 7. Inspect the effect of changing the growth factor and feasibility tolerance. Why is it usually preferable to increase $c$ gradually rather than choosing an extremely large value at the start?

def penalty_method(x0, c0=0.5, growth=5.0, feasibility_tol=1e-3, max_outer=12):
    x = np.asarray(x0, dtype=float)
    c = c0
    history = []
    ...
    return x, history

x_result, history = penalty_method([1.5, -0.5])
for c, x, violation, iterations in history:
    print(f'c={c:8.2f} x={x} |h|={violation:.3e} GD iterations={iterations} ')
print('Final constrained-solution error:', np.linalg.norm(x_result-[0.5,0.5]))
Elise Grosjean
Elise Grosjean
Assistant Professor

My research interests include numerics, P.D.E analysis, Reduced basis methods, inverse problems.