For data near a feasible point, constraint qualifications connect the actual feasible set to its first-order model. We use row-oriented Jacobians and the canonical form , . The KKT reference owns active/strong/weak terminology and sensitivity; Local Optimality Conditions owns convex KKT sufficiency.
The definitions, full proof arguments, and diagnostics below supplement Lecture 20. Biegler (2010), Sections 4.3.1–4.3.2, pp. 74–79, supplies the core sequence; Nocedal and Wright (2006), Sections 12.2, 12.4 and 12.6, supplies the tangent-cone formulation and proof details.
On open , use , , , and . Inequalities are componentwise.
Feasible sequences, unit directions, and the tangent cone¶
A feasible sequence approaching has , , and feasible terms for all sufficiently large . If
then is its unit limiting direction. A feasible sequence need not have a direction limit: it can alternate between two approach directions. The definition applies to sequences or subsequences for which the limit exists.
The tangent cone uses arbitrary positive scales:
It includes . Every nonzero tangent vector gives a unit limiting direction after normalization, and positive multiples of a unit limiting direction lie in . Thus unit limiting directions are , rather than the entire cone. This distinction resolves the normalization shorthand in Biegler’s Lemma 4.13. See Nocedal and Wright, Definition 12.2, p. 316, and Lemma 12.2, pp. 323–325.

Here , , . Its normalized chord is . The direction points from the limit toward nearby feasible points. A ball travelling toward the limit moves in the opposite direction; the ball is an analogy, not an assumption about an algorithm’s iterates.
First-order necessity along the actual feasible set¶
If is differentiable and is a local minimum on , then
Proof for a unit limiting direction. Put and . Differentiability gives
Local minimality makes the left side nonnegative eventually. A negative limit would force arbitrarily nearby feasible points with a smaller objective, a contradiction. Multiplication by a nonnegative scalar extends the result to every tangent vector. Biegler, Theorem 4.8 and proof, pp. 74–75; Nocedal and Wright, Theorem 12.3, pp. 325–326.

This is necessary, not sufficient. For subject to , the origin has and . Yet is feasible and improves the objective for every nonzero . The loss appears at second order (Nocedal and Wright, Example (12.44), p. 326).
Linearization and constraint indices¶
Use separate index sets: for equalities and for active inequalities. The combined row labels are tagged pairs and ; equality 1 and inequality 1 are distinct rows. Stack
The linearized cone is
Containment proof. For , write . Expand an equality about the feasible point and divide by : . For an active inequality, . Taking limits proves . Inactive inequalities stay negative nearby by continuity and impose no first-order restriction. The remainder is , not .

Linearized feasibility alone does not assert that is feasible for a nonlinear constraint. A curved correction can be necessary.

Affine constraints: exact first-order geometry¶
For , , take with and for every active row. The straight path satisfies the equalities and active inequalities exactly. Each inactive row has positive slack ; choose small enough to preserve all finitely many slacks. Hence , even if the constraint rows are dependent.
At a local minimum, no solves , , . The theorem of alternatives gives
Set inactive multipliers to zero to obtain the complete KKT conditions. This is Biegler’s Theorem 4.11, pp. 75–76; Nocedal and Wright’s Lemma 12.7, pp. 338–339. Independence is not required for this affine-constraint argument. With a convex objective the existing convex KKT certificate proves global sufficiency; it does not require LICQ.
A cusp where KKT fails¶
Both constraints imply , so ; at they force . The origin is the unique global solution. This is Biegler’s (4.44), Figure 4.8, p. 77.
import sys
from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt
if "google.colab" in sys.modules:
import urllib.request
urllib.request.urlretrieve(
"https://raw.githubusercontent.com/ndcbe/optimization/main/notebooks/helper.py",
"helper.py",
)
import helper
helper.easy_install()
else:
sys.path.insert(0, str(Path("..").resolve()))
import helper
plt.style.use("../../figures/dowling.mplstyle" if Path("../../figures/dowling.mplstyle").exists()
else "https://raw.githubusercontent.com/ndcbe/optimization/main/figures/dowling.mplstyle")
x1_grid = np.linspace(-1.2, 1.2, 401)
cusp_data = {"x1": x1_grid.tolist(), "upper": (x1_grid**3).tolist(),
"lower": (-x1_grid**3).tolist(), "gradients": [[0, 1], [0, -1]]}
helper.save_results("licq-cusp", cusp_data,
notebook="notebooks/7-dev/Constraint-Qualifications.ipynb",
source_cells=[("notebooks/7-dev/Constraint-Qualifications.ipynb", "results:cq-cusp-data")],
description="Analytic cusp boundaries and active gradients; no optimization solve.", quiet=True)
results = cusp_data
HATCH_CYCLE = ["///", "\\\\"]
SHADE_ALPHA = 0.18
XLO, XHI = -1.2, 1.2
YLO, YHI = -1.35, 1.35
# One scale for both gradient arrows. They are unit vectors of equal length in
# opposite directions; drawing them at equal length is the whole point.
SCALE = 0.62
def _arrow(ax, base, vec, label, *, color, offset, ha="left", scale=SCALE):
"""One gradient, drawn AND named. The name is the greyscale-safe identity."""
tip = np.asarray(base) + scale * np.asarray(vec)
ax.arrow(
base[0],
base[1],
scale * vec[0],
scale * vec[1],
width=0.022,
length_includes_head=True,
color=color,
zorder=6,
)
ax.annotate(
label,
xy=(tip[0] + offset[0], tip[1] + offset[1]),
fontsize=13,
ha=ha,
va="center",
zorder=7,
bbox=dict(facecolor="white", edgecolor="none", pad=1.0),
)
x1 = np.asarray(results["x1"])
upper = np.asarray(results["upper"]) # x2 = x1^3, boundary of g1 <= 0
lower = np.asarray(results["lower"]) # x2 = -x1^3, boundary of g2 <= 0
fig, ax = plt.subplots(figsize=(5.2, 5.0))
# g1 <= 0 is everything BELOW x2 = x1^3.
ax.fill_between(
x1,
YLO,
np.clip(upper, YLO, YHI),
facecolor="0.55",
alpha=SHADE_ALPHA,
hatch=HATCH_CYCLE[0],
edgecolor=plt.rcParams["hatch.color"],
linewidth=0.0,
zorder=0,
)
# g2 <= 0 is everything ABOVE x2 = -x1^3.
ax.fill_between(
x1,
np.clip(lower, YLO, YHI),
YHI,
facecolor="0.55",
alpha=SHADE_ALPHA,
hatch=HATCH_CYCLE[1],
edgecolor=plt.rcParams["hatch.color"],
linewidth=0.0,
zorder=0,
)
ax.plot(x1, upper, color="#0072B2", linestyle="-", linewidth=2.5, zorder=3)
ax.plot(x1, lower, color="#E69F00", linestyle="--", linewidth=2.5, zorder=3)
ax.annotate(
"$g_1(\\mathbf{x}) = x_2 - x_1^3 \\leq 0$",
xy=(-1.15, -1.22),
fontsize=12,
bbox=dict(facecolor="white", edgecolor="none", pad=1.0),
zorder=4,
)
ax.annotate(
"$g_2(\\mathbf{x}) = -x_1^3 - x_2 \\leq 0$",
xy=(-1.15, 1.10),
fontsize=12,
bbox=dict(facecolor="white", edgecolor="none", pad=1.0),
zorder=4,
)
# The feasible set: the doubly hatched sliver, x1 >= 0, between the curves.
ax.annotate(
"feasible set\n(both hatches)",
xy=(0.55, 0.02),
xytext=(0.86, -0.72),
fontsize=12,
ha="center",
va="center",
arrowprops=dict(arrowstyle="->", color="black", linewidth=1.2),
bbox=dict(facecolor="white", edgecolor="none", pad=1.0),
zorder=9,
)
# x* = (0,0): the cusp.
ax.plot([0.0], [0.0], marker="*", markersize=18, color="black", linestyle="none", zorder=8)
ax.annotate(
"$\\mathbf{x}^*$",
xy=(0.09, -0.17),
fontsize=14,
bbox=dict(facecolor="white", edgecolor="none", pad=1.0),
zorder=8,
)
# The two active gradients at x*. grad g1 = [0,1]^T, grad g2 = [0,-1]^T:
# antiparallel, so the active Jacobian has rank 1 and LICQ fails.
_arrow(
ax,
(0.0, 0.0),
(0.0, 1.0),
"$\\nabla g_1(\\mathbf{x}^*)$\n$= (0,\\, 1)^{T}$",
color="#0072B2",
offset=(-0.10, -0.02),
ha="right",
)
_arrow(
ax,
(0.0, 0.0),
(0.0, -1.0),
"$\\nabla g_2(\\mathbf{x}^*)$\n$= (0,\\, -1)^{T}$",
color="#E69F00",
offset=(-0.10, 0.02),
ha="right",
)
ax.axhline(0.0, color="black", linewidth=0.8, zorder=1)
ax.axvline(0.0, color="black", linewidth=0.8, zorder=1)
ax.set_xlim(XLO, XHI)
ax.set_ylim(YLO, YHI)
ax.set_xlabel("$x_1$")
ax.set_ylabel("$x_2$")
ax.set_aspect("equal", adjustable="box")
fig.tight_layout()
helper.save_figure(fig, "licq-cusp", quiet=True)

Explain the geometry. The overlap of both hatches is feasible. Why is the origin optimal even though the objective gradient is nonzero? The constraints restrict to be nonnegative and permit only when .
Full KKT case analysis¶
With , stationarity is
The remaining conditions are , , . At the origin the first stationarity component is 1 for every finite multiplier vector.
Neither multiplier positive: gives gradient , so stationarity fails. Zero multipliers do not remove the constraints or make this feasible problem unbounded.
Only : the second stationarity row forces , a contradiction.
Only : the second row forces , again a contradiction.
Both positive: the second row gives ; the first gives . Complementarity requires both constraints active, which forces , contradicting the first row. The algebraic relation has both square-root signs; feasibility selects , but no finite feasible KKT point results.
An approximate stationarity path illustrates the difficulty: for , choose , . Stationarity holds and the point is feasible, but both inequalities are slack and . As , complementarity residuals vanish while multipliers diverge. This is not a family of exact KKT points. The original scan’s denominator is replaced by the verified relation.
Actual and linearized directions at the cusp¶
At the origin the active row Jacobian is
Its inequalities impose and , hence . In contrast, and imply every normalized nonzero feasible chord tends to : . Therefore .

Biegler calls and . The left-pointing has but cannot be realized by a feasible sequence. Two handwritten sketches reverse these names; the algebra and geometry fix the labels. A constraint qualification eliminates this extra first-order direction.
LICQ and feasible curved corrections¶
LICQ holds at a feasible point when all equality gradients and all active inequality gradients are jointly independent: . It is a property of the constraint representation at a point, not a requirement that the point already be optimal. Equalities, weakly active inequalities, and active bounds all count. If there are more active rows than variables, LICQ cannot hold. Biegler, Definition 4.12 at the foot of p. 77; Nocedal and Wright, Definition 12.4, p. 320.
The cusp has two antiparallel nonzero rows, rank 1 rather than 2, so LICQ fails. Nonzero individual gradients are not enough.

Reverse inclusion under LICQ. Let stack all equalities and active inequalities in the tagged row order, and let have basis columns for . For any , consider
At its derivative with respect to is , a square nonsingular matrix: a vector in its null space belongs to and is orthogonal to that space, hence is zero. The C1 implicit function theorem requires a C1 residual map, a zero residual at the base point and an invertible derivative with respect to the unknowns; these follow from the stated neighborhood data, feasibility and the displayed null-space argument. It yields a path , and differentiation at zero gives . Thus .
The construction gives exactly and for active inequalities; continuity preserves the inactive inequalities for small positive . These exact identities come from solving , not merely truncating a Taylor expansion. Consequently . This adapts Nocedal and Wright, Lemma 12.2, pp. 323–325, to and row Jacobians; Biegler, Lemma 4.13, p. 78, requires the errata’s subscript.
For the circle equality at , is tangent. The straight step has . The corrected path is feasible and differs from the straight step by . LICQ promises a nearby feasible path, not feasibility of the straight step.
From no feasible descent to KKT¶
At a local minimum with LICQ, , so no direction satisfies
The finite-dimensional linear alternative says that absence of a direction satisfying that system is equivalent to existence of a nonnegative active inequality multiplier and unrestricted equality multiplier balancing the objective gradient. Thus it gives , unrestricted , with . Inactive multipliers are zero, yielding dual feasibility and complementarity. Feasibility is already a hypothesis. This proves KKT necessity (Biegler, Theorem 4.14, p. 78; Nocedal and Wright, Theorem 12.1, p. 321, and proof pp. 323–329).

Conversely, a KKT vector implies for every . This algebra needs no CQ. It is still a first-order necessary test, not general sufficiency. The separate convex certificate is at the Local Optimality reference.
Multiplier uniqueness and its limits¶
At a fixed KKT point, stack active multipliers in the same order as , with equality components unrestricted and inequality components nonnegative. If and both satisfy stationarity, then
Full row rank makes injective, so the difference is zero. Equivalently, multiply by : the row Gram matrix is positive definite. This is the row-oriented version of Biegler’s Theorem 4.15 proof, p. 79; his column-oriented Gram matrix has the factors in the opposite order.

Failure of LICQ does not force multiple KKT multipliers. For subject to , , at stationarity and nonnegativity require , hence uniquely despite dependent rows. Without LICQ the unconstrained linear stationarity system has a null direction, but the multiplier sign restrictions may remove all nonzero displacements.
For redundant equalities the situation differs: if , equality multipliers can move freely as without sign restrictions. The active equality KKT matrix has null vector , whatever the Hessian.
Uniqueness alone does not establish a differentiable solution branch or a shadow price. The sensitivity reference states the branch, sign, and second-order conditions and gives examples of nonsmooth values.
MFCQ, geometry, and bounded multipliers¶
MFCQ holds when has full row rank and and for every . With no active inequalities the strict condition is vacuous; equality independence still matters. This tests a feasible point, not just a solution. Biegler, Definition 4.16, p. 79; Nocedal and Wright, Definition 12.6, p. 339.
LICQ implies MFCQ: full row rank allows solving . The equality part is zero and every active inequality decreases strictly.
Strictly weaker: take , , at the origin. The three active rows have rank 2, so LICQ fails; satisfies MFCQ. For , stationarity gives , , . The multiplier set is the closed line segment from to : bounded, nonunique.

At the cusp, MFCQ would require and , which is impossible. No finite KKT multiplier exists there.
Why MFCQ bounds a nonempty KKT multiplier set. If a sequence of KKT multipliers were unbounded, omit its identically zero inactive components, normalize it by its diverging norm, and take a convergent subsequence on the compact unit sphere in . Dividing stationarity by that norm gives
Dot with the MFCQ direction. Each active inequality derivative is strictly negative, so a zero sum forces . Equality independence then forces , contradicting unit norm. The fixed-point KKT multiplier set is closed, so it is compact if nonempty. MFCQ also ensures KKT existence at a local minimum.
Conversely, failure of MFCQ yields a nonzero homogeneous multiplier with : equality dependence gives one immediately; otherwise the strict-direction theorem of alternatives gives one. Add its nonnegative ray to any existing KKT vector to obtain an unbounded multiplier set. Thus at a fixed KKT point with a nonempty multiplier set, MFCQ is equivalent to boundedness. The nonempty condition matters: subject to has sole feasible point 0, fails MFCQ, and has no KKT vector; the empty set is bounded in the set-theoretic sense. See Nocedal and Wright, p. 339, with this scope made explicit.


What each qualification guarantees¶
For data at a local minimum:
| Condition | Geometry | KKT multipliers |
|---|---|---|
| LICQ | ; all active rows independent | Exist and are unique |
| MFCQ | ; common strict inward direction with equality tangency | Exist; nonempty compact set, possibly nonunique |
| All active constraints affine | without independence | Exist, possibly unbounded |
| Abadie’s CQ | The statement itself | Existence by the same alternative argument; no general uniqueness or boundedness |
These are sufficient hypotheses; their failure is not proof that KKT fails. Example: subject to has a KKT vector at zero although LICQ and MFCQ fail. Its multiplier set is .
MFCQ gives : for any , add with a strict MFCQ direction . All active inequalities then decrease strictly. Full row rank of gives, by the implicit function theorem, an equality-feasible path ; strict inequality derivatives make it feasible for small . Thus . Let and use closedness of the tangent cone to get .
Peterson (1973), SIAM Review 15, pp. 639–654, DOI, Table 1 pp. 643–644 and Figure 4 p. 645, maps a broader CQ hierarchy. Table 1 puts MFCQ on p. 643 and LICQ on the continuation p. 644. His principal formulation uses inequalities and a general set ; do not encode equalities as two opposite inequalities when translating MFCQ. His arrows point toward weaker conditions. The equality-aware definitions here follow the textbooks.
Example revisited: solve with Pyomo¶
The following original experiment intentionally approaches the degenerate cusp with Ipopt. Keep its iterates and reported termination distinct from an exact KKT certificate: there is no finite multiplier vector at the true solution. Solver versions and tolerances can change the numerical output.
import pyomo.environ as pyo
from pyomo.environ import (
ConcreteModel,
Var,
Objective,
Constraint,
SolverFactory,
Suffix,
minimize,
value,
)
## Create concrete Pyomo model
m = ConcreteModel()
## Set up to extract dual variables after model solve.
m.dual = Suffix(direction=Suffix.IMPORT)
## Declare variables with initial values
m.x1 = Var(initialize=1)
m.x2 = Var(initialize=1)
## Declare objective
m.OBJ = Objective(expr=m.x1, sense=minimize)
m.g1 = Constraint(expr=m.x2 <= m.x1**3)
m.g2 = Constraint(expr=-m.x1**3 <= m.x2)
## Specify IPOPT as solver
solver = SolverFactory("ipopt")
## Solve the model
results = solver.solve(m, tee=True)
assert pyo.check_optimal_termination(results), (
f"Solve failed: status={results.solver.status}, "
f"termination={results.solver.termination_condition}"
)
## Return the solution
print("x1 = ", value(m.x1))
print("x2 = ", value(m.x2))
print("\n")
## Inspect dual variables
m.dual.display()Ipopt 3.13.2:
******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
Ipopt is released as open source code under the Eclipse Public License (EPL).
For more information visit http://projects.coin-or.org/Ipopt
This version of Ipopt was compiled from source code available at
https://github.com/IDAES/Ipopt as part of the Institute for the Design of
Advanced Energy Systems Process Systems Engineering Framework (IDAES PSE
Framework) Copyright (c) 2018-2019. See https://github.com/IDAES/idaes-pse.
This version of Ipopt was compiled using HSL, a collection of Fortran codes
for large-scale scientific computation. All technical papers, sales and
publicity material resulting from use of the HSL codes within IPOPT must
contain the following acknowledgement:
HSL, a collection of Fortran codes for large-scale scientific
computation. See http://www.hsl.rl.ac.uk.
******************************************************************************
This is Ipopt version 3.13.2, running with linear solver ma27.
Number of nonzeros in equality constraint Jacobian...: 0
Number of nonzeros in inequality constraint Jacobian.: 4
Number of nonzeros in Lagrangian Hessian.............: 1
Total number of variables............................: 2
variables with only lower bounds: 0
variables with lower and upper bounds: 0
variables with only upper bounds: 0
Total number of equality constraints.................: 0
Total number of inequality constraints...............: 2
inequality constraints with only lower bounds: 0
inequality constraints with lower and upper bounds: 0
inequality constraints with only upper bounds: 2
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
0 1.0000000e+00 0.00e+00 7.89e-01 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0
1 9.7574511e-01 0.00e+00 1.04e-02 -1.0 2.54e-01 - 1.00e+00 1.00e+00f 1
2 6.6105230e-01 0.00e+00 2.39e-01 -1.7 4.64e+00 - 1.00e+00 3.73e-01f 1
3 4.6924687e-01 0.00e+00 2.74e-01 -1.7 1.92e-01 - 1.00e+00 1.00e+00h 1
4 3.2820073e-01 0.00e+00 3.14e-01 -1.7 1.62e-01 - 1.00e+00 8.72e-01h 1
5 2.6957767e-01 0.00e+00 1.60e-01 -1.7 5.86e-02 - 1.00e+00 1.00e+00f 1
6 1.8935274e-01 0.00e+00 2.24e+00 -2.5 2.21e-01 - 1.00e+00 3.63e-01f 1
7 1.3091124e-01 0.00e+00 3.19e-01 -2.5 5.84e-02 - 1.00e+00 1.00e+00h 1
8 9.6126564e-02 0.00e+00 2.03e+00 -2.5 4.69e-02 - 1.00e+00 7.41e-01h 1
9 7.1009142e-02 0.00e+00 2.58e-01 -2.5 2.51e-02 - 1.00e+00 1.00e+00f 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
10 5.1777995e-02 0.00e+00 1.21e+01 -2.5 3.18e-02 - 1.00e+00 6.04e-01h 1
11 4.0824512e-02 0.00e+00 1.97e-01 -2.5 1.10e-02 - 1.00e+00 1.00e+00f 1
12 3.4583313e-02 0.00e+00 5.91e+01 -2.5 1.80e-02 - 1.00e+00 3.47e-01h 2
13 2.6914569e-02 0.00e+00 1.79e-01 -2.5 7.67e-03 - 1.00e+00 1.00e+00h 1
14 2.0308733e-02 0.00e+00 2.01e-01 -2.5 6.61e-03 - 1.00e+00 1.00e+00h 1
15 1.9046691e-02 0.00e+00 3.31e-02 -2.5 1.26e-03 - 1.00e+00 1.00e+00h 1
16 1.2746908e-02 0.00e+00 5.71e+02 -3.8 1.83e-02 - 1.00e+00 3.44e-01f 1
17 8.7118644e-03 0.00e+00 3.19e-01 -3.8 4.04e-03 - 1.00e+00 1.00e+00f 1
18 7.5197334e-03 0.00e+00 1.09e+03 -3.8 3.10e-03 - 1.00e+00 3.85e-01f 2
19 5.1147446e-03 0.00e+00 3.19e-01 -3.8 2.40e-03 - 1.00e+00 1.00e+00f 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
20 3.3178215e-03 0.00e+00 3.78e-01 -3.8 1.80e-03 - 1.00e+00 1.00e+00f 1
21 2.2893616e-03 0.00e+00 3.40e-01 -3.8 1.03e-03 - 1.00e+00 1.00e+00f 1
22 1.8283227e-03 0.00e+00 8.34e+04 -3.8 4.48e-03 - 1.00e+00 1.03e-01f 2
23 4.5313818e-04 0.00e+00 8.82e-01 -3.8 1.38e-03 - 1.00e+00 1.00e+00f 1
24 3.0653152e-04 0.00e+00 3.16e+05 -5.7 1.47e-04 4.0 2.32e-01 1.00e+00h 1
25 -5.5285928e-04 0.00e+00 1.00e+00 -5.7 8.59e-04 - 1.00e+00 1.00e+00f 1
26 -1.1223885e-02 1.40e-06 1.36e+04 -5.7 7.96e-01 - 1.00e+00 1.34e-02f 1
27 -1.1107786e-02 1.36e-06 1.16e+03 -5.7 3.72e-03 - 1.00e+00 3.12e-02h 6
28 -7.4290268e-03 4.00e-07 4.64e-01 -5.7 3.68e-03 - 1.00e+00 1.00e+00h 1
29 -5.8687786e-03 1.92e-07 8.66e+02 -5.7 2.41e-03 - 1.00e+00 6.47e-01h 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
30 -5.8542172e-03 1.91e-07 3.64e+03 -5.7 1.86e-03 - 1.00e+00 7.81e-03f 8
31 -3.9974633e-03 5.39e-08 3.37e-01 -5.7 1.86e-03 - 1.00e+00 1.00e+00h 1
32 -2.9534118e-03 1.58e-08 5.23e+02 -5.7 1.12e-03 - 1.00e+00 9.29e-01h 1
33 -2.9344353e-03 1.53e-08 1.06e+04 -5.7 6.07e-04 - 1.00e+00 3.12e-02f 6
34 -2.3405157e-03 2.82e-09 1.76e-01 -5.7 5.94e-04 - 1.00e+00 1.00e+00h 1
35 -2.1660008e-03 1.62e-10 3.83e-02 -5.7 1.75e-04 - 1.00e+00 1.00e+00h 1
36 -2.1508378e-03 0.00e+00 6.75e-04 -5.7 1.52e-05 - 1.00e+00 1.00e+00h 1
37 -2.1544258e-03 0.00e+00 6.10e-06 -8.6 3.59e-06 - 1.00e+00 1.00e+00f 1
38 -2.1544329e-03 0.00e+00 7.78e-12 -9.0 7.05e-09 - 1.00e+00 1.00e+00h 1
Number of Iterations....: 38
(scaled) (unscaled)
Objective...............: -2.1544328718688505e-03 -2.1544328718688505e-03
Dual infeasibility......: 7.7819972688075723e-12 7.7819972688075723e-12
Constraint violation....: 0.0000000000000000e+00 0.0000000000000000e+00
Complementarity.........: 9.0909382315597638e-10 9.0909382315597638e-10
Overall NLP error.......: 2.5317795697139416e-12 9.0909382315597638e-10
Number of objective function evaluations = 64
Number of objective gradient evaluations = 39
Number of equality constraint evaluations = 0
Number of inequality constraint evaluations = 64
Number of equality constraint Jacobian evaluations = 0
Number of inequality constraint Jacobian evaluations = 39
Number of Lagrangian Hessian evaluations = 38
Total CPU secs in IPOPT (w/o function evaluations) = 0.002
Total CPU secs in NLP function evaluations = 0.000
EXIT: Optimal Solution Found.
x1 = -0.0021544328718688505
x2 = 4.26637029618013e-25
dual : Direction=IMPORT, Datatype=FLOAT
Key : Value
g1 : -35907.30543965533
g2 : -35907.30543965533
Discussion. Inspect the iteration count, final primal residuals, point, and imported duals. Why is the point only approximately zero? Compare the growth of multiplier magnitudes with , rather than treating the solver’s output as a finite limiting KKT vector.
The raw Pyomo dual suffix uses the solver interface’s constraint orientation. It is not automatically the nonnegative of the canonical form. For this pair of upper/lower constraints, check the mapping against stationarity before interpreting the signs; the worked suffix checks illustrate this procedure. A large negative imported value does not by itself violate canonical dual feasibility.
For diagnosing larger models, continue to NLP Diagnostics with Degeneracy Hunter: full row-dependence theory, QR/MILP workflow, scaling and case-study context accompany the executable diagnostic example.
Check the original model, rather than only the termination banner¶
The current default run reports an optimal termination at a slightly negative . Exact cusp feasibility requires . Re-evaluate the original inequalities and multiplier products below. Both imported suffixes convert as for this model; the stationarity residual checks that mapping.
Ipopt’s options reference describes bound relaxation and its limits. In an inequality formulation with slack variables, the internal feasibility and complementarity report can differ from evaluation of the original inequality. The numerical output here establishes that difference directly: small primal violation multiplied by a very large multiplier need not produce a small original-model complementarity residual. Keep the original experiment unchanged; an optimal solver status is not a proof of exact feasibility or KKT satisfaction at the cusp.
x = np.array([value(m.x1), value(m.x2)])
g = np.array([x[1] - x[0]**3, -x[0]**3 - x[1]])
u = -np.array([m.dual[m.g1], m.dual[m.g2]])
stationarity = np.array([1 - 3 * (u[0] + u[1]) * x[0]**2, u[0] - u[1]])
print(f"Original-model primal violation: {max(0.0, float(g.max())):.6e}")
print(f"Canonical multipliers: {u[0]:.6e}, {u[1]:.6e}")
print(f"Stationarity infinity norm: {np.linalg.norm(stationarity, np.inf):.6e}")
print(f"Original complementarity infinity norm: {np.linalg.norm(u * g, np.inf):.6e}")
assert np.all(u >= 0) and np.linalg.norm(stationarity, np.inf) < 1e-6
Original-model primal violation: 9.999975e-09
Canonical multipliers: 3.590731e+04, 3.590731e+04
Stationarity infinity norm: 7.782219e-12
Original complementarity infinity norm: 3.590721e-04