The KKT conditions
State the KKT conditions and use them to solve inequality-constrained problems.
The Karush-Kuhn-Tucker (KKT) conditions are the inequality-constrained generalization of "set the gradient to zero". They are necessary at any optimum of a constrained problem with strong duality, and for convex problems they are also sufficient, turning optimization into solving a system of equations and inequalities. They characterize the solution of every SVM and convex program.
The four conditions
For the problem subject to and , a point with multipliers satisfies the KKT conditions if:
- Stationarity. The Lagrangian gradient vanishes:
- Primal feasibility. and .
- Dual feasibility. for all inequality multipliers.
- Complementary slackness. for each [1].
Complementary slackness
The fourth condition is the new ingredient. For each inequality, either the constraint is active (, the optimum sits on its boundary) or its multiplier is zero (, the constraint does not push). A constraint cannot simultaneously be slack () and exert force ().
Why it holds (under strong duality). At the optimum . Tracing the weak-duality proof with equalities, the slack terms must vanish; since each term is (product of and ), each must be exactly zero. The active set, the constraints with , is what determines the solution.
Sufficiency for convex problems
For a convex problem (convex and , affine ), the KKT conditions are not only necessary but sufficient: any point satisfying all four is a global optimum[2].
Why. Stationarity makes a minimizer of the convex Lagrangian . Then for any feasible , , where the last equality uses complementary slackness. So solving the KKT system is solving the problem.
Worked example
Minimize subject to , written . Stationarity: , so . If the constraint is active, gives , so (dual feasible) and . Complementary slackness holds since . All four conditions check out, certifying the global optimum.
Check it on arrays
import numpy as np
# min 0.5 ||x||^2 s.t. a^T x >= 1 -> g(x) = 1 - a^T x <= 0
a = np.array([1.0, 2.0, 2.0])
norm2 = a @ a
lam = 1.0 / norm2 # candidate multiplier
x = lam * a # from stationarity x = lam a
# Verify all four KKT conditions
stationarity = np.allclose(x - lam * a, 0)
primal_feasible = (1 - a @ x) <= 1e-9
dual_feasible = lam >= 0
slackness = np.isclose(lam * (1 - a @ x), 0)
print("stationarity: ", stationarity)
print("primal feasibility: ", primal_feasible)
print("dual feasibility: ", dual_feasible)
print("compl. slackness: ", slackness)
print("x* =", np.round(x, 4), " f* =", round(0.5 * x @ x, 4))
# Inactive-constraint case: a^T x >= -1 is already satisfied at x=0, so lam=0.
# Then x* = 0 and the constraint is slack (g < 0) with zero multiplier.
print("\ninactive constraint: x*=0, lam=0, slackness:",
np.isclose(0.0 * (-1 - a @ np.zeros(3)), 0))All four KKT conditions hold at the certified optimum, and the inactive-constraint case shows complementary slackness with a zero multiplier.
Exercises
Exercise 1. Solve subject to with by hand, using the KKT conditions. Report , , and .
Solution
Step 1. Write . Stationarity of gives , so .
Step 2. Guess the constraint is active, . Substituting gives , so . With , , so dual feasibility holds.
Step 3. Then . Complementary slackness holds since , and primal feasibility holds (). All four conditions check out.
Step 4. The objective value is
Exercise 2. Using complementary slackness, prove that if the constraint is strictly slack at the optimum (), then its multiplier must be zero, and conversely a nonzero multiplier forces the constraint active.
Solution
Step 1. Complementary slackness states . This is a product of two numbers equal to zero, so at least one factor is zero.
Step 2. Suppose (strictly slack). Then , so the product can be zero only if . A constraint that does not bind exerts no force.
Step 3. Conversely, suppose . Then , so the product forces : the constraint is active. A constraint that pushes must sit on its boundary. The two cases are the active set and the inactive set, with no point both slack and forceful.
Exercise 3 (code). A box-constrained least squares: minimize subject to componentwise, with and . The KKT-optimal solution clips each coordinate at its upper bound, . Verify the four KKT conditions hold at this clipped point.
Solution
import numpy as np
c = np.array([2.0, -1.0, 3.0])
u = np.array([1.0, 1.0, 1.0])
# Constraints g_i(x) = x_i - u_i <= 0. Gradient of f is x - c.
# KKT solution clips at the bound where c_i > u_i.
x = np.minimum(c, u)
lam = np.maximum(c - u, 0.0) # multiplier active where the bound binds
g = x - u
stationarity = np.allclose((x - c) + lam, 0) # grad f + sum lam_i grad g_i = 0
primal_feasible = np.all(g <= 1e-12)
dual_feasible = np.all(lam >= 0)
slackness = np.allclose(lam * g, 0)
assert stationarity and primal_feasible and dual_feasible and slackness
print("x* =", x, " lambda =", lam)
print("all four KKT conditions hold:",
stationarity and primal_feasible and dual_feasible and slackness)Each coordinate either sits below its bound with a zero multiplier or clips to the bound with a positive multiplier; complementary slackness holds coordinatewise, certifying the clipped point as the global optimum.
The next lesson returns to unconstrained methods but uses curvature: Newton's method.
References
- [1]Boyd, S., & Vandenberghe, L. (2004). Convex Optimization, §5.5. Cambridge University Press. The KKT conditions and complementary slackness.↩
- [2]Bertsekas, D. P. (1999). Nonlinear Programming (2nd ed.), §3.3. Athena Scientific. Necessity and, for convex problems, sufficiency of the KKT conditions.↩