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 be open, with , , and . The inequalities are componentwise.
For subject to and , define equality indices and inequality indices . The active inequality index set at a feasible point is
If both types are collected into one set, use distinct constraint labels:
Equality and inequality are different constraints, even though their component indices are both 1. The inactive inequality indices are , 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 and is continuous. There is such that for every , where . 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.

The prepared example uses and . Its derivative is . At , , and . Every point in satisfies the inequality, so it is irrelevant to that local test. Removing it globally admits the other strict local minimum , 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 and replace just one inequality by
Positive tightens the right-hand side to ; negative relaxes it. Let be a nearby local minimizer branch, with , and put . This is a local branch value. It need not select the global minimum for every perturbed problem.

The drawing uses , , , and . The exact solution branch is for . It has , , inequality multiplier and equality multiplier .
Hypotheses for the following Taylor proof. The data are continuously differentiable; is differentiable at zero; there exists such that the same inequalities remain active for every . Thus
Taylor remainders are therefore , meaning their norm divided by tends to zero. The active-set statement alone does not justify dividing an uncontrolled approximation by .
Equalities are satisfied at both points:
Every unperturbed active inequality is still zero:
The selected one stays active at its changed bound:
Full objective substitution¶

At the unperturbed KKT point,
Use the objective expansion and substitute this identity:
All equality differences vanish. Inactive inequalities have . Untouched active inequalities have zero constraint difference. The selected constraint has difference , and its contribution is . Hence
This is the sign convention for . If instead the constraint is written , increasing relaxes it and the derivative with respect to is . For an equality written , the same substitution gives . With a differentiable multi-parameter branch,
Rescaling to , , changes the multiplier to . 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
| Inequality state | Constraint value | Multiplier |
|---|---|---|
| inactive | ||
| strongly active | ||
| weakly active |
Strict complementarity means every active inequality is strongly active, equivalently 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.

For subject to , and stationarity is . At , the bound is strongly active with . At , it is weakly active with . At , it is inactive with . 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¶
The Lagrangian and KKT conditions are
| Active subset | Stationarity/active equations | Result |
|---|---|---|
| neither | , | violates the lower bound |
| lower only | , | , accepted |
| upper only | , | , violates the multiplier sign |
| both | and | infeasible |
Thus , , . 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 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.

Perturb the lower bound to . For , it remains active:
The exact full value function is
For , the lower bound is inactive; at 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 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 subject to . Write
At , the upper bound is slack, so complementarity gives . The full stationarity equation then gives . The lower bound is active but weak, and strict complementarity fails. This derives the result without assuming 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 :
Tightening to changes the value by ; 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.

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 ( and ), with a change in the second derivative. Their feasibility-domain endpoints are separate: perturbing beyond 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 , with multipliers . 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
Here 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 and positive definiteness of on its null space. To verify it, consider a homogeneous solution :
Multiplying the first equation by gives . Positive curvature on the null space forces . Then , and full row rank forces . 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.

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
Its exact value is . At zero both constraints are active and stationarity is with nonnegative multipliers. Every pair , , 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 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: and exchange which active constraint is weak. The two gradients are both -1, so LICQ fails, even though a direction 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 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.
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 and equality . The imported inequality dual is , and the equality multiplier in 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, with , 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