Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Analysis of KKT Conditions

Reference: Biegler (2010), Chapter 4, §4.3, pp.72–74; Rockafellar (1993), Lagrange Multipliers and Optimality, §9, pp.224–225. The log-model and its nonlinear KKT system have their complete home in Local Optimality Conditions. Constraint Qualifications develops multiplier existence/uniqueness and the link to actual feasible directions.

Active Sets

Let D⊂RnD\subset\mathbb R^n be open, with f:D→Rf:D\to\mathbb R, g:D→Rr\mathbf g:D\to\mathbb R^r, and h:D→Rm\mathbf h:D\to\mathbb R^m. The inequalities are componentwise.

For min⁡f(x)\min f(\mathbf{x}) subject to g(x)≤0\mathbf{g}(\mathbf{x})\le\mathbf{0} and h(x)=0\mathbf{h}(\mathbf{x})=\mathbf{0}, define equality indices E={1,…,m}\mathcal{E}=\{1,\ldots,m\} and inequality indices I={1,…,r}\mathcal{I}=\{1,\ldots,r\}. The active inequality index set at a feasible point is

AI(x)={i∈I:gi(x)=0}.\mathcal{A}_I(\mathbf{x})=\{i\in\mathcal{I}:g_i(\mathbf{x})=0\}.

If both types are collected into one set, use distinct constraint labels:

A(x)={(h,j):j∈E}∪{(g,i):i∈AI(x)}.\mathcal{A}(\mathbf{x})=\{(h,j):j\in\mathcal{E}\}\cup\{(g,i):i\in\mathcal{A}_I(\mathbf{x})\}.

Equality h1h_1 and inequality g1g_1 are different constraints, even though their component indices are both 1. The inactive inequality indices are I∖AI\mathcal{I}\setminus\mathcal{A}_I, rather than a difference with untagged equality indices. All equalities belong to the combined active set at every feasible point.

Why an inactive constraint drops out locally. Suppose gi(x∗)<0g_i(\mathbf{x}^*)<0 and gig_i is continuous. There is ρi>0\rho_i>0 such that gi(x)<0g_i(\mathbf x)<0 for every x∈B(x∗,ρi)⊂D\mathbf x\in\mathcal B(\mathbf x^*,\rho_i)\subset D, where B(x∗,ρi)={x:∥x−x∗∥<ρi}\mathcal B(\mathbf x^*,\rho_i)=\{\mathbf x:\|\mathbf x-\mathbf x^*\|<\rho_i\}. For finitely many inactive inequalities, take the intersection of these neighborhoods. Removing them leaves exactly the same feasible comparisons in that neighborhood. Their KKT multipliers are zero by complementarity, so they contribute nothing to gradient balance either. This is a local argument, not permission to remove them from the optimization model.

An inactive constraint leaves the local neighborhood unchanged but excludes a lower distant value

The prepared example uses f(x)=(x2−1)2+0.1(3x−x3)f(x)=(x^2-1)^2+0.1(3x-x^3) and g=−x≤0g=-x\le0. Its derivative is (x2−1)(4x−0.3)(x^2-1)(4x-0.3). At x=1x=1, f′′=7.4>0f''=7.4>0, g=−1g=-1 and f=0.2f=0.2. Every point in (0.7,1.3)(0.7,1.3) satisfies the inequality, so it is irrelevant to that local test. Removing it globally admits the other strict local minimum x=−1x=-1, with value -0.2. A constraint that is inactive at the current solution can still exclude a better distant solution.

Source: Biegler p.72 for the active/local characterization; explicit index spaces, continuity proof and polynomial example are independently specified here.

Sensitivity Analysis

A smooth local branch and a moving constraint

Choose ı^∈AI(x∗)\hat\imath\in\mathcal{A}_I(\mathbf{x}^*) and replace just one inequality by

gı^(x)+ϵ≤0.g_{\hat\imath}(\mathbf{x})+\epsilon\le0.

Positive ϵ\epsilon tightens the right-hand side to −ϵ-\epsilon; negative ϵ\epsilon relaxes it. Let xϵ\mathbf{x}^{\epsilon} be a nearby local minimizer branch, with x0=x∗\mathbf{x}^0=\mathbf{x}^*, and put V(ϵ)=f(xϵ)V(\epsilon)=f(\mathbf{x}^{\epsilon}). This is a local branch value. It need not select the global minimum for every perturbed problem.

A fixed equality rail and a moving active inequality fence

The drawing uses f=x12+x22f=x_1^2+x_2^2, g=b−x1≤0g=b-x_1\le0, h=x2−x1=0h=x_2-x_1=0, and b=1b=1. The exact solution branch is xϵ=(b+ϵ,b+ϵ)T\mathbf{x}^{\epsilon}=(b+\epsilon,b+\epsilon)^T for b+ϵ>0b+\epsilon>0. It has V(ϵ)=2(b+ϵ)2V(\epsilon)=2(b+\epsilon)^2, V′(0)=4bV'(0)=4b, inequality multiplier u∗=4bu^*=4b and equality multiplier v∗=−2bv^*=-2b.

Hypotheses for the following Taylor proof. The data are continuously differentiable; xϵ\mathbf{x}^{\epsilon} is differentiable at zero; there exists δ>0\delta>0 such that the same inequalities remain active for every ∣ϵ∣<δ|\epsilon|<\delta. Thus

Δx:=xϵ−x∗=ϵx′(0)+o(ϵ)=O(ϵ).\Delta\mathbf{x}:=\mathbf{x}^{\epsilon}-\mathbf{x}^*=\epsilon\mathbf{x}'(0)+o(\epsilon)=O(\epsilon).

Taylor remainders are therefore o(ϵ)o(\epsilon), meaning their norm divided by ∣ϵ∣|\epsilon| tends to zero. The active-set statement alone does not justify dividing an uncontrolled approximation by ϵ\epsilon.

  1. Equalities are satisfied at both points:

0=h(xϵ)−h(x∗)=Jh(x∗)Δx+o(ϵ).\mathbf{0}=\mathbf{h}(\mathbf{x}^{\epsilon})-\mathbf{h}(\mathbf{x}^*)=\mathbf{J}_h(\mathbf{x}^*)\Delta\mathbf{x}+o(\epsilon).
  1. Every unperturbed active inequality is still zero:

0=gi(xϵ)−gi(x∗)=∇gi(x∗)TΔx+o(ϵ),i∈AI∖{ı^}.0=g_i(\mathbf{x}^{\epsilon})-g_i(\mathbf{x}^*)=\nabla g_i(\mathbf{x}^*)^T\Delta\mathbf{x}+o(\epsilon),\qquad i\in\mathcal{A}_I\setminus\{\hat\imath\}.
  1. The selected one stays active at its changed bound:

−ϵ=gı^(xϵ)−gı^(x∗)=∇gı^(x∗)TΔx+o(ϵ).-\epsilon=g_{\hat\imath}(\mathbf{x}^{\epsilon})-g_{\hat\imath}(\mathbf{x}^*)=\nabla g_{\hat\imath}(\mathbf{x}^*)^T\Delta\mathbf{x}+o(\epsilon).

Full objective substitution

Constraint changes multiplied by minus their multipliers leave one first-order term

At the unperturbed KKT point,

∇f=−JhTv∗−JgTu∗.\nabla f=-\mathbf{J}_h^T\mathbf{v}^*-\mathbf{J}_g^T\mathbf{u}^*.

Use the objective expansion and substitute this identity:

V(ϵ)−V(0)=∇f(x∗)TΔx+o(ϵ)=−∑j∈Evj∗∇hjTΔx−∑i∈Iui∗∇giTΔx+o(ϵ)=−∑j∈Evj∗(hj(xϵ)−hj(x∗))−∑i∈Iui∗(gi(xϵ)−gi(x∗))+o(ϵ)=uı^∗ϵ+o(ϵ).\begin{aligned} V(\epsilon)-V(0)&=\nabla f(\mathbf{x}^*)^T\Delta\mathbf{x}+o(\epsilon)\\ &=-\sum_{j\in\mathcal E}v_j^*\nabla h_j^T\Delta\mathbf{x}-\sum_{i\in\mathcal I}u_i^*\nabla g_i^T\Delta\mathbf{x}+o(\epsilon)\\ &=-\sum_{j\in\mathcal E}v_j^*\bigl(h_j(\mathbf{x}^{\epsilon})-h_j(\mathbf{x}^*)\bigr) \\&\quad-\sum_{i\in\mathcal I}u_i^*\bigl(g_i(\mathbf{x}^{\epsilon})-g_i(\mathbf{x}^*)\bigr)+o(\epsilon)\\ &=u_{\hat\imath}^*\epsilon+o(\epsilon). \end{aligned}

All equality differences vanish. Inactive inequalities have ui∗=0u_i^*=0. Untouched active inequalities have zero constraint difference. The selected constraint has difference −ϵ-\epsilon, and its contribution is −uı^∗(−ϵ)-u_{\hat\imath}^*(-\epsilon). Hence

V′(0)=lim⁡ϵ→0V(ϵ)−V(0)ϵ=uı^∗.V'(0)=\lim_{\epsilon\to0}\frac{V(\epsilon)-V(0)}{\epsilon}=u_{\hat\imath}^*.

This is the sign convention for g+ϵ≤0g+\epsilon\le0. If instead the constraint is written g≤bg\le b, increasing bb relaxes it and the derivative with respect to bb is −u∗-u^*. For an equality written h+η=0h+\eta=0, the same substitution gives ∂V/∂η=v∗\partial V/\partial\eta=v^*. With a differentiable multi-parameter branch,

dV=u∗Tdϵ+v∗Tdη.dV=\mathbf{u}^{*T}d\boldsymbol\epsilon+\mathbf{v}^{*T}d\boldsymbol\eta.

Rescaling gg to cgc g, c>0c>0, changes the multiplier to u/cu/c. Its units and numerical magnitude depend on the constraint’s scaling; the product of multiplier and the corresponding constraint perturbation is invariant. A large multiplier is not comparable across arbitrary scalings.

Source: Biegler p.73 for the Taylor argument. The branch/remainder scope and sign/scaling extensions are stated explicitly; Rockafellar Thm.9.1 p.224 distinguishes an ordinary derivative from a general subgradient interpretation.

Strong activity, weak activity, and strict complementarity

For a chosen KKT multiplier vector, the active inequality indices split into

As={i∈AI:ui∗>0},Aw={i∈AI:ui∗=0}.\mathcal{A}_s=\{i\in\mathcal{A}_I:u_i^*>0\},\qquad\mathcal{A}_w=\{i\in\mathcal{A}_I:u_i^*=0\}.
Inequality stateConstraint valueMultiplier
inactivegi(x∗)<0g_i(\mathbf{x}^*)<0ui∗=0u_i^*=0
strongly activegi(x∗)=0g_i(\mathbf{x}^*)=0ui∗>0u_i^*>0
weakly activegi(x∗)=0g_i(\mathbf{x}^*)=0ui∗=0u_i^*=0

Strict complementarity means every active inequality is strongly active, equivalently Aw\mathcal A_w is empty. If there are no active inequalities it holds vacuously. Equality multipliers are unrestricted and are not classified by this inequality definition. Nonunique multiplier vectors can yield different strong/weak classifications for the same primal point; uniqueness is a separate regularity question.

Three lower-bound examples for classifying active states

For min⁡(x−a)2\min(x-a)^2 subject to −x≤0-x\le0, x∗=max⁡(a,0)x^*=\max(a,0) and stationarity is 2(x∗−a)−u∗=02(x^*-a)-u^*=0. At a=−1a=-1, the bound is strongly active with u∗=2u^*=2. At a=0a=0, it is weakly active with u∗=0u^*=0. At a=1a=1, it is inactive with u∗=0u^*=0. Zero multiplier does not imply slack.

Source: Biegler Def.4.6 and the preceding sets, p.73; the explicit prepared examples supply the distinctions.

Example 4.7: enumerate and test the bound states

min⁡xx2s.t.12−x≤0,x−1≤0.\min_x x^2\quad\text{s.t.}\quad \tfrac12-x\le0,\quad x-1\le0.

The Lagrangian and KKT conditions are

L=x2+uL(12−x)+uU(x−1),L=x^2+u_L(\tfrac12-x)+u_U(x-1),
2x−uL+uU=0,uL(12−x)=0,uU(x−1)=0,2x-u_L+u_U=0,\quad u_L(\tfrac12-x)=0,\quad u_U(x-1)=0,
12≤x≤1,uL,uU≥0.\tfrac12\le x\le1,\qquad u_L,u_U\ge0.
Active subsetStationarity/active equationsResult
neitheruL=uU=0u_L=u_U=0, 2x=02x=0x=0x=0 violates the lower bound
lower onlyx=1/2x=1/2, uU=0u_U=0uL=1u_L=1, accepted
upper onlyx=1x=1, uL=0u_L=0uU=−2u_U=-2, violates the multiplier sign
bothx=1/2x=1/2 and x=1x=1infeasible

Thus x∗=1/2x^*=1/2, uL∗=1u_L^*=1, uU∗=0u_U^*=0. The lower bound is strongly active and strict complementarity holds. The objective and bounds are convex, so this candidate is the global minimum. Enumeration considers up to 2r2^r subsets; infeasible or sign-inconsistent branches are rejected, rather than accepted just because some stationarity equations have a root. Active-set and interior-point methods avoid exhaustive enumeration.

Decision-space geometry of the strong and weak bound examples

Perturb the lower bound to 1/2+ϵ1/2+\epsilon. For −1/2<ϵ<1/2-1/2<\epsilon<1/2, it remains active:

xϵ=12+ϵ,Vs(ϵ)=14+ϵ+ϵ2,Vs′(0)=1=uL∗.x^{\epsilon}=\tfrac12+\epsilon,\qquad V_s(\epsilon)=\tfrac14+\epsilon+\epsilon^2,\qquad V_s'(0)=1=u_L^*.

The exact full value function is

Vs(ϵ)={0,ϵ≤−1/2,(1/2+ϵ)2,−1/2<ϵ≤1/2,+∞,ϵ>1/2.V_s(\epsilon)=\begin{cases}0,&\epsilon\le-1/2,\\(1/2+\epsilon)^2,&-1/2<\epsilon\le1/2,\\+\infty,&\epsilon>1/2.\end{cases}

For ϵ<−1/2\epsilon<-1/2, the lower bound is inactive; at ϵ=1/2\epsilon=1/2 both bounds coincide and the feasible set has one point. Beyond that the problem is infeasible. The original slope 1 predicts only the local linear term; for ϵ=0.1\epsilon=0.1 the true increase is 0.11, not 0.10. Continuing that old line far enough can even predict a negative value for a squared objective.

Weak-activity variation: derive the zero multiplier

Now use min⁡x2\min x^2 subject to 0≤x≤10\le x\le1. Write

L=x2−uLx+uU(x−1),2x−uL+uU=0.L=x^2-u_Lx+u_U(x-1),\qquad 2x-u_L+u_U=0.

At x∗=0x^*=0, the upper bound is slack, so complementarity gives uU∗=0u_U^*=0. The full stationarity equation then gives uL∗=0u_L^*=0. The lower bound is active but weak, and strict complementarity fails. This derives the result without assuming uL=0u_L=0 in advance. The 2018 scan’s case-ii premise states that value; keeping it in the reference as a source reading does not make it a necessary premise for the derivation.

Perturb the lower bound to x≥ϵx\ge\epsilon:

xϵ=max⁡(ϵ,0),Vw(ϵ)={0,ϵ≤0,ϵ2,0<ϵ≤1,+∞,ϵ>1.x^{\epsilon}=\max(\epsilon,0),\qquad V_w(\epsilon)=\begin{cases}0,&\epsilon\le0,\\\epsilon^2,&0<\epsilon\le1,\\+\infty,&\epsilon>1.\end{cases}

Tightening to ϵ>0\epsilon>0 changes the value by ϵ2\epsilon^2; loosening keeps the unconstrained minimizer and changes the value by zero. Both one-sided first derivatives at zero are zero, matching the multiplier. Their second-order behavior differs.

Exact optimum-value functions compared with the original multiplier predictions

An active-set change requires rechecking the smooth-branch interpretation; it does not automatically destroy the value derivative. Both value functions are continuously differentiable at the slack-to-active transitions shown in the plot (ϵ=−1/2\epsilon=-1/2 and ϵ=0\epsilon=0), with a change in the second derivative. Their feasibility-domain endpoints are separate: perturbing beyond 1/21/2 or 1, respectively, makes the model infeasible. In the weak example the solution branch has different one-sided derivatives, but the value still has derivative zero. For a finite perturbation, recompute the relevant active state and multiplier rather than extrapolating the old slope indefinitely.

Source: Biegler Example 4.7 and variation, pp.73–74. Case enumeration, complete parameter domains and the two-sided qualifications are explicit here.

Computing a branch derivative with the active KKT matrix

For twice continuously differentiable data, collect equalities and selected active inequalities into constraint rows JA\mathbf J_A, with multipliers λ\boldsymbol\lambda. A constant right-hand-side perturbation changes one active equation but not its gradient. Differentiating the active stationarity/equality equations of a smooth branch gives

[WJATJA0][x′(0)λ′(0)]=[0−eı^],W=∇xx2L(x∗,λ∗).\begin{bmatrix}\mathbf W&\mathbf J_A^T\\\mathbf J_A&\mathbf0\end{bmatrix} \begin{bmatrix}\mathbf x'(0)\\\boldsymbol\lambda'(0)\end{bmatrix} =\begin{bmatrix}\mathbf0\\-\mathbf e_{\hat\imath}\end{bmatrix},\qquad \mathbf W=\nabla^2_{xx}L(\mathbf x^*,\boldsymbol\lambda^*).

Here eı^\mathbf e_{\hat\imath} is the unit vector in the stacked row corresponding to the perturbed inequality. Its row position includes the equality rows; it is not an unadjusted original inequality index.

This is a derivative of the active KKT equations, not an optimization algorithm. A nonsingular matrix gives one branch derivative. Nocedal & Wright (2006), Appendix A.2, Theorem A.2, p.631, gives the implicit function theorem used here: it supplies a nearby smooth root branch when this active KKT Jacobian is nonsingular; feasibility, multiplier signs and a local sufficiency test are still needed to identify it as the intended minimizer branch. Strictly positive active multipliers and strictly slack inactive constraints persist under small continuous changes; weak activity needs special care.

One sufficient nonsingularity test is full row rank of JA\mathbf J_A and positive definiteness of W\mathbf W on its null space. To verify it, consider a homogeneous solution (p,q)(\mathbf p,\mathbf q):

Wp+JATq=0,JAp=0.\mathbf W\mathbf p+\mathbf J_A^T\mathbf q=\mathbf0,\qquad\mathbf J_A\mathbf p=\mathbf0.

Multiplying the first equation by pT\mathbf p^T gives pTWp=0\mathbf p^T\mathbf W\mathbf p=0. Positive curvature on the null space forces p=0\mathbf p=\mathbf0. Then JATq=0\mathbf J_A^T\mathbf q=\mathbf0, and full row rank forces q=0\mathbf q=\mathbf0. If the active Jacobian is square and nonsingular, its null space is already zero. This explains why the moving-fence/rail toy problem has a well-defined branch even before a nonzero tangent-curvature direction is available.

Constraint-row movement and transposed-multiplier balance are the two actions in the bordered system

The matrix construction and nonsingularity argument are independently derived from the displayed KKT equations; the extended reduced-Hessian treatment is in Second Order Optimality Conditions.

When the value has two one-sided slopes

Consider the convex problem

min⁡xxs.t.ϵ−x≤0,−x≤0.\min_x x\quad\text{s.t.}\quad\epsilon-x\le0,\quad -x\le0.

Its exact value is V(ϵ)=max⁡(ϵ,0)V(\epsilon)=\max(\epsilon,0). At zero both constraints are active and stationarity is 1−u1−u2=01-u_1-u_2=0 with nonnegative multipliers. Every pair (u1,u2)=(t,1−t)(u_1,u_2)=(t,1-t), 0≤t≤10\le t\le1, is valid. The same primal point therefore has nonunique multipliers. The right value derivative is 1, the left derivative is 0, and there is no ordinary two-sided derivative. The selected constraint multiplier u1∈[0,1]u_1\in[0,1] is a subgradient, not an arbitrary choice of a common slope.

This example also shows why strong/weak labels can depend on the multiplier vector: (1,0)(1,0) and (0,1)(0,1) exchange which active constraint is weak. The two gradients are both -1, so LICQ fails, even though a direction d>0d>0 strictly decreases both active inequality linearizations.

Rockafellar Thm.9.1 p.224 states a regular-subgradient interpretation under its hypotheses; ordinary partial derivatives follow when the relevant value function is differentiable. Thm.9.3 p.225 gives the convex directional/subgradient relation under its stated existence and lower-semicontinuity hypotheses. In that convex setting, the right and left coordinate slopes are the largest and smallest compatible coordinate multipliers. Those statements should not be read as a global smooth sensitivity theorem for arbitrary nonconvex problems.

The local Taylor proof, the weak squared-value example and this kink example serve different purposes: a smooth branch gives a derivative; a branch switch can leave the value differentiable; nonunique multipliers can describe a range of one-sided sensitivities.

Multipliers in Pyomo

Reference: Pyomo Suffix documentation, especially importing solver data and exporting warm-start information. IMPORT retrieves solver-supported suffixes, EXPORT sends them, and IMPORT_EXPORT does both. Names and support depend on the solver/interface. The existing example below uses the Ipopt NL interface: dual for constraint multipliers and ipopt_zL_out/ipopt_zU_out for variable-bound multipliers. The _in suffixes provide the bound-multiplier warm start. Check termination before interpreting any stored suffix, and check the model’s stationarity/sign convention when comparing it with g≤0g\le0 mathematics.

The warm start below solves the same model again, using the first primal/dual point. It is a demonstration of passing initialization, not a guarantee that warm starting always reduces iterations or selects a global minimum.

min⁡x1,...,x4x1⋅x4⋅(x1+x2+x3)+x3s.t.x1⋅x2⋅x3⋅x4≥25x12+x22+x32+x42=40\begin{align} \min_{x_1,...,x_4} \quad & x_1 \cdot x_4 \cdot (x_1 + x_2 + x_3) + x_3 \\ \mathrm{s.t.} \quad & x_1 \cdot x_2 \cdot x_3 \cdot x_4 \geq 25 \\ & x_1^2 + x_2^2 + x_3^2 + x_4^2 = 40 \end{align}
import pyomo.environ as pyo

# Example from
# https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Suffixes.html#exporting-suffix-data

model = pyo.ConcreteModel()
model.x1 = pyo.Var(bounds=(1, 5), initialize=1.0)
model.x2 = pyo.Var(bounds=(1, 5), initialize=5.0)
model.x3 = pyo.Var(bounds=(1, 5), initialize=5.0)
model.x4 = pyo.Var(bounds=(1, 5), initialize=1.0)
model.obj = pyo.Objective(
    expr=model.x1 * model.x4 * (model.x1 + model.x2 + model.x3) + model.x3
)
model.inequality = pyo.Constraint(
    expr=model.x1 * model.x2 * model.x3 * model.x4 >= 25.0
)
model.equality = pyo.Constraint(
    expr=model.x1**2 + model.x2**2 + model.x3**2 + model.x4**2 == 40.0
)

### Declare all suffixes
# Ipopt bound multipliers (obtained from solution)
model.ipopt_zL_out = pyo.Suffix(direction=pyo.Suffix.IMPORT)
model.ipopt_zU_out = pyo.Suffix(direction=pyo.Suffix.IMPORT)
# Ipopt bound multipliers (sent to solver)
model.ipopt_zL_in = pyo.Suffix(direction=pyo.Suffix.EXPORT)
model.ipopt_zU_in = pyo.Suffix(direction=pyo.Suffix.EXPORT)
# Obtain dual solutions from first solve and send to warm start
model.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT_EXPORT)

ipopt = pyo.SolverFactory("ipopt")

Solve without warm starting

results = ipopt.solve(model, tee=True)
assert pyo.check_optimal_termination(results), (
    f"Solve failed: status={results.solver.status}, "
    f"termination={results.solver.termination_condition}"
)
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...:        4
Number of nonzeros in inequality constraint Jacobian.:        4
Number of nonzeros in Lagrangian Hessian.............:       10

Total number of variables............................:        4
                     variables with only lower bounds:        0
                variables with lower and upper bounds:        4
                     variables with only upper bounds:        0
Total number of equality constraints.................:        1
Total number of inequality constraints...............:        1
        inequality constraints with only lower bounds:        1
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:        0

iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
   0  1.6109693e+01 1.12e+01 5.28e-01  -1.0 0.00e+00    -  0.00e+00 0.00e+00   0
   1  1.6982239e+01 7.30e-01 1.02e+01  -1.0 6.11e-01    -  7.19e-02 1.00e+00f  1
   2  1.7318411e+01 3.60e-02 5.05e-01  -1.0 1.61e-01    -  1.00e+00 1.00e+00h  1
   3  1.6849424e+01 2.78e-01 6.68e-02  -1.7 2.85e-01    -  7.94e-01 1.00e+00h  1
   4  1.7051199e+01 4.71e-03 2.78e-03  -1.7 6.06e-02    -  1.00e+00 1.00e+00h  1
   5  1.7011979e+01 7.19e-03 8.50e-03  -3.8 3.66e-02    -  9.45e-01 9.98e-01h  1
   6  1.7014271e+01 1.74e-05 9.78e-06  -3.8 3.33e-03    -  1.00e+00 1.00e+00h  1
   7  1.7014021e+01 1.23e-07 1.82e-07  -5.7 2.69e-04    -  1.00e+00 1.00e+00h  1
   8  1.7014017e+01 1.77e-11 2.52e-11  -8.6 3.32e-06    -  1.00e+00 1.00e+00h  1

Number of Iterations....: 8

                                   (scaled)                 (unscaled)
Objective...............:   1.7014017145179164e+01    1.7014017145179164e+01
Dual infeasibility......:   2.5167821044254762e-11    2.5167821044254762e-11
Constraint violation....:   1.7706724975141697e-11    1.7706724975141697e-11
Complementarity.........:   2.5277100427932987e-09    2.5277100427932987e-09
Overall NLP error.......:   2.5277100427932987e-09    2.5277100427932987e-09


Number of objective function evaluations             = 9
Number of objective gradient evaluations             = 9
Number of equality constraint evaluations            = 9
Number of inequality constraint evaluations          = 9
Number of equality constraint Jacobian evaluations   = 9
Number of inequality constraint Jacobian evaluations = 9
Number of Lagrangian Hessian evaluations             = 8
Total CPU secs in IPOPT (w/o function evaluations)   =      0.001
Total CPU secs in NLP function evaluations           =      0.000

EXIT: Optimal Solution Found.

Inspect dual variables for lower bound

model.ipopt_zL_out.display()
ipopt_zL_out : Direction=IMPORT, Datatype=FLOAT
    Key : Value
     x1 :    1.0878712258659022
     x2 : 6.693166200639301e-10
     x3 : 8.887657145295419e-10
     x4 : 6.570872591660427e-09

Inspect dual variables for upper bound

model.ipopt_zU_out.display()
ipopt_zU_out : Direction=IMPORT, Datatype=FLOAT
    Key : Value
     x1 : -6.262653086171725e-10
     x2 : -9.788835007031796e-09
     x3 : -2.122849252064015e-09
     x4 : -6.925197858855533e-10

Solve with warm starting

### Set Ipopt options for warm-start
# The current values on the ipopt_zU_out and ipopt_zL_out suffixes will
# be used as initial conditions for the bound multipliers to solve the
# new problem
model.ipopt_zL_in.update(model.ipopt_zL_out)
model.ipopt_zU_in.update(model.ipopt_zU_out)
ipopt.options["warm_start_init_point"] = "yes"
ipopt.options["warm_start_bound_push"] = 1e-6
ipopt.options["warm_start_mult_bound_push"] = 1e-6
ipopt.options["mu_init"] = 1e-6

results = ipopt.solve(model, tee=True)
assert pyo.check_optimal_termination(results), (
    f"Solve failed: status={results.solver.status}, "
    f"termination={results.solver.termination_condition}"
)
Ipopt 3.13.2: warm_start_init_point=yes
warm_start_bound_push=1e-06
warm_start_mult_bound_push=1e-06
mu_init=1e-06


******************************************************************************
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...:        4
Number of nonzeros in inequality constraint Jacobian.:        4
Number of nonzeros in Lagrangian Hessian.............:       10

Total number of variables............................:        4
                     variables with only lower bounds:        0
                variables with lower and upper bounds:        4
                     variables with only upper bounds:        0
Total number of equality constraints.................:        1
Total number of inequality constraints...............:        1
        inequality constraints with only lower bounds:        1
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:        0

iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
   0  1.7014032e+01 2.00e-06 4.07e-06  -6.0 0.00e+00    -  0.00e+00 0.00e+00   0
   1  1.7014019e+01 3.65e-12 1.00e-11  -6.0 2.50e-01    -  1.00e+00 1.00e+00h  1
   2  1.7014017e+01 4.48e-12 6.43e-12  -9.0 1.92e-06    -  1.00e+00 1.00e+00h  1

Number of Iterations....: 2

                                   (scaled)                 (unscaled)
Objective...............:   1.7014017142195414e+01    1.7014017142195414e+01
Dual infeasibility......:   6.4260438785999374e-12    6.4260438785999374e-12
Constraint violation....:   4.4835246626462322e-12    4.4835246626462322e-12
Complementarity.........:   1.0057206694668722e-09    1.0057206694668722e-09
Overall NLP error.......:   1.0057206694668722e-09    1.0057206694668722e-09


Number of objective function evaluations             = 3
Number of objective gradient evaluations             = 3
Number of equality constraint evaluations            = 3
Number of inequality constraint evaluations          = 4
Number of equality constraint Jacobian evaluations   = 3
Number of inequality constraint Jacobian evaluations = 3
Number of Lagrangian Hessian evaluations             = 2
Total CPU secs in IPOPT (w/o function evaluations)   =      0.000
Total CPU secs in NLP function evaluations           =      0.000

EXIT: Optimal Solution Found.

Translate solver suffixes and check gradient balance

For this minimization model and Ipopt NL interface, use the canonical inequality g=25−x1x2x3x4≤0g=25-x_1x_2x_3x_4\le0 and equality h=∑ixi2−40=0h=\sum_i x_i^2-40=0. The imported inequality dual is uu, and the equality multiplier in L=f+ug+vhL=f+u g+v h is the negative of the imported equality dual. The canonical lower-bound multiplier is ipopt_zL_out; the canonical upper-bound multiplier is the negative of the signed ipopt_zU_out suffix. Lower-bound multipliers enter stationarity with a minus sign, upper-bound multipliers with a plus sign. Keep the raw signed suffix values when passing a warm start back to the solver. The calculation below checks these signs against independently written analytic gradients, rather than assuming that every solver/interface uses the same suffix convention.

A small residual establishes approximate KKT consistency for this returned point, not a global-minimum certificate for the nonconvex model. The warm-start demonstration repeats the same problem; timing and iteration counts can change with the solver version.

The existing four-variable model has inactive upper bounds. A separate one-variable check, min⁡−x\min -x with 0≤x≤10\le x\le1, makes the upper bound strongly active and verifies its sign with a nonzero multiplier.

import numpy as np

x = np.array([pyo.value(model.x1), pyo.value(model.x2),
              pyo.value(model.x3), pyo.value(model.x4)])
variables = [model.x1, model.x2, model.x3, model.x4]
u = float(model.dual[model.inequality])
v = -float(model.dual[model.equality])
z_lower = np.array([float(model.ipopt_zL_out.get(var, 0.0)) for var in variables])
z_upper = -np.array([float(model.ipopt_zU_out.get(var, 0.0)) for var in variables])

# Analytic derivatives of the displayed objective and canonical constraints.
gradient_f = np.array([x[3] * (2*x[0] + x[1] + x[2]),
                       x[0]*x[3], x[0]*x[3] + 1,
                       x[0]*(x[0] + x[1] + x[2])])
gradient_g = -np.prod(x) / x
gradient_h = 2*x
stationarity = gradient_f + u*gradient_g + v*gradient_h - z_lower + z_upper
primal_residual = max(0.0, 25-np.prod(x), abs(np.dot(x, x)-40),
                      float(np.max(1-x)), float(np.max(x-5)))
complementarity = max(abs(u*(25-np.prod(x))),
                      float(np.max(np.abs(z_lower*(1-x)))),
                      float(np.max(np.abs(z_upper*(x-5)))))
assert np.max(np.abs(stationarity)) < 1e-5
assert primal_residual < 1e-5 and complementarity < 1e-5
assert u >= -1e-7 and np.min(z_lower) >= -1e-7 and np.min(z_upper) >= -1e-7

print("Primal point:", np.round(x, 8))
print(f"Objective: {pyo.value(model.obj):.8f}")
print(f"Canonical inequality multiplier u: {u:.8f}")
print(f"Canonical equality multiplier v: {v:.8f}")
print("Lower-bound multipliers:", np.round(z_lower, 8))
print("Upper-bound multipliers:", np.round(z_upper, 8))
print(f"Max stationarity residual: {np.max(np.abs(stationarity)):.3e}")
print(f"Max primal residual: {primal_residual:.3e}")
print(f"Max complementarity residual: {complementarity:.3e}")

# A nonzero upper-bound multiplier checks the signed suffix conversion.
probe = pyo.ConcreteModel()
probe.x = pyo.Var(bounds=(0, 1), initialize=0.5)
probe.obj = pyo.Objective(expr=-probe.x)
probe.ipopt_zL_out = pyo.Suffix(direction=pyo.Suffix.IMPORT)
probe.ipopt_zU_out = pyo.Suffix(direction=pyo.Suffix.IMPORT)
probe_result = pyo.SolverFactory("ipopt").solve(probe)
assert pyo.check_optimal_termination(probe_result)
raw_upper = float(probe.ipopt_zU_out[probe.x])
canonical_upper = -raw_upper
canonical_lower = float(probe.ipopt_zL_out[probe.x])
probe_stationarity = -1 - canonical_lower + canonical_upper
assert raw_upper < -0.5 and canonical_upper > 0.5
assert abs(probe_stationarity) < 1e-6
assert abs(pyo.value(probe.x)-1) < 1e-6
print(f"Active-upper-bound raw suffix: {raw_upper:.8f}")
print(f"Active-upper-bound canonical multiplier: {canonical_upper:.8f}")
print(f"Active-upper-bound stationarity residual: {abs(probe_stationarity):.3e}")
Primal point: [1.         4.74299964 3.82114998 1.37940829]
Objective: 17.01401727
Canonical inequality multiplier u: 0.55229366
Canonical equality multiplier v: 0.16146856
Lower-bound multipliers: [1.08787122 0.         0.         0.        ]
Upper-bound multipliers: [0. 0. 0. 0.]
Max stationarity residual: 2.796e-08
Max primal residual: 2.133e-08
Max complementarity residual: 1.178e-08
Active-upper-bound raw suffix: -1.00000000
Active-upper-bound canonical multiplier: 1.00000000
Active-upper-bound stationarity residual: 2.220e-16