Constrained Optimization

Why Constraints Are Essential

Real engineering problems always have constraints: power budgets, physical limits, resource allocations, interference thresholds. A solution that ignores constraints is useless in practice. SciPy's minimize supports equality and inequality constraints through several solvers, each with different strengths.

Definition:

General Constrained Optimization Problem

min⁑xf(x)subjectΒ togi(x)≀0,β€…β€Ši=1,…,m,hj(x)=0,β€…β€Šj=1,…,p\min_{\mathbf{x}} f(\mathbf{x}) \quad \text{subject to} \quad g_i(\mathbf{x}) \le 0, \; i = 1,\ldots,m, \quad h_j(\mathbf{x}) = 0, \; j = 1,\ldots,pwhere:βˆ’where: -fistheβˆ—βˆ—objectivefunctionβˆ—βˆ—βˆ’is the **objective function** -g_iareβˆ—βˆ—inequalityconstraintsβˆ—βˆ—(are **inequality constraints** (\le 0byconvention)βˆ’by convention) -h_jareβˆ—βˆ—equalityconstraintsβˆ—βˆ—Theβˆ—βˆ—feasiblesetβˆ—βˆ—isare **equality constraints** The **feasible set** is{\mathbf{x} : g_i(\mathbf{x}) \le 0, ; h_j(\mathbf{x}) = 0 ; \forall i,j}$.

SciPy uses the convention that inequality constraints are gi(x)β‰₯0g_i(\mathbf{x}) \ge 0 (opposite sign). Always check the docs.

Definition:

Lagrangian and KKT Conditions

The Lagrangian of a constrained problem is:

L(x,Ξ»,Ξ½)=f(x)+βˆ‘i=1mΞ»igi(x)+βˆ‘j=1pΞ½jhj(x)\mathcal{L}(\mathbf{x}, \boldsymbol{\lambda}, \boldsymbol{\nu}) = f(\mathbf{x}) + \sum_{i=1}^{m} \lambda_i g_i(\mathbf{x}) + \sum_{j=1}^{p} \nu_j h_j(\mathbf{x})

The Karush-Kuhn-Tucker (KKT) conditions are necessary for optimality (and sufficient for convex problems):

  1. Stationarity: βˆ‡xL=0\nabla_{\mathbf{x}} \mathcal{L} = \mathbf{0}
  2. Primal feasibility: gi(x)≀0g_i(\mathbf{x}) \le 0, hj(x)=0h_j(\mathbf{x}) = 0
  3. Dual feasibility: Ξ»iβ‰₯0\lambda_i \ge 0
  4. Complementary slackness: Ξ»igi(x)=0\lambda_i g_i(\mathbf{x}) = 0

The multipliers Ξ»i,Ξ½j\lambda_i, \nu_j represent the sensitivity of the optimal value to constraint perturbations.

Definition:

Bounds and LinearConstraint in SciPy

from scipy.optimize import minimize, Bounds, LinearConstraint

# Box constraints: lb <= x <= ub
bounds = Bounds(lb=[0, -np.inf], ub=[np.inf, 1])

# Linear constraint: lb <= A @ x <= ub
linear_con = LinearConstraint(
    A=np.array([[1, 1], [2, -1]]),
    lb=[0, -np.inf],
    ub=[10, 5],
)

# Nonlinear constraint (dict form for SLSQP)
nonlinear_con = {
    'type': 'ineq',  # g(x) >= 0
    'fun': lambda x: x[0]**2 + x[1]**2 - 1,
    'jac': lambda x: np.array([2*x[0], 2*x[1]]),
}

Use Bounds and LinearConstraint objects with trust-constr; use dict-form constraints with SLSQP.

Definition:

SLSQP (Sequential Least Squares Programming)

SLSQP solves nonlinear constrained problems by solving a sequence of quadratic programming (QP) subproblems. At each iteration:

  1. Approximate ff by a quadratic model around xk\mathbf{x}_k
  2. Linearize the constraints
  3. Solve the resulting QP to get a search direction
  4. Perform a line search
result = minimize(f, x0, method='SLSQP',
                  jac=grad_f,
                  bounds=bounds,
                  constraints=[eq_con, ineq_con])

Pros: Handles equality + inequality + bounds; well-tested. Cons: Can be slow for large problems; may fail on highly nonlinear constraints.

Definition:

trust-constr (Trust-Region Constrained)

The trust-constr method uses a trust-region approach with an interior-point or SQP algorithm. It is SciPy's most modern constrained solver and supports:

  • Sparse Jacobians and Hessians
  • Bounds, LinearConstraint, NonlinearConstraint objects
  • Verbose iteration output
from scipy.optimize import NonlinearConstraint

nlc = NonlinearConstraint(
    fun=lambda x: x[0]**2 + x[1]**2,
    lb=0, ub=1,
    jac=lambda x: np.array([2*x[0], 2*x[1]]),
)

result = minimize(f, x0, method='trust-constr',
                  jac=grad_f, hess=hess_f,
                  constraints=nlc, bounds=bounds)

Definition:

Linear Programming with scipy.optimize.linprog

A linear program (LP) has a linear objective and linear constraints:

min⁑xcTxs.t.Aubx≀bub,β€…β€ŠAeqx=beq,β€…β€Šl≀x≀u\min_{\mathbf{x}} \mathbf{c}^T \mathbf{x} \quad \text{s.t.} \quad \mathbf{A}_{\text{ub}} \mathbf{x} \le \mathbf{b}_{\text{ub}}, \; \mathbf{A}_{\text{eq}} \mathbf{x} = \mathbf{b}_{\text{eq}}, \; \mathbf{l} \le \mathbf{x} \le \mathbf{u}

from scipy.optimize import linprog

result = linprog(
    c,                      # cost vector
    A_ub=A_ub, b_ub=b_ub,  # inequality constraints
    A_eq=A_eq, b_eq=b_eq,  # equality constraints
    bounds=bounds,          # variable bounds
    method='highs',         # default: HiGHS solver
)

The HiGHS solver (default since SciPy 1.9) uses the revised simplex method or interior-point method and handles millions of variables.

Theorem: KKT Necessary Conditions

If x⋆\mathbf{x}^\star is a local minimizer of the constrained problem and a constraint qualification holds (e.g., LICQ: the active constraint gradients are linearly independent), then there exist multipliers Ξ»i⋆β‰₯0\lambda_i^\star \ge 0 and Ξ½j⋆\nu_j^\star such that the KKT conditions hold at (x⋆,λ⋆,ν⋆)(\mathbf{x}^\star, \boldsymbol{\lambda}^\star, \boldsymbol{\nu}^\star).

For convex problems (convex ff, gig_i; affine hjh_j), the KKT conditions are both necessary and sufficient for global optimality.

At the optimum, the negative gradient of ff must be expressible as a non-negative combination of active constraint gradients. If you could decrease ff without violating any active constraint, you haven't found the optimum yet.

Example: Mean-Variance Portfolio Optimization

Find the minimum-variance portfolio of n=5n=5 assets with expected return at least rmin⁑=0.08r_{\min} = 0.08, weights summing to 1, no short selling (wiβ‰₯0w_i \ge 0).

Example: Resource Allocation with Linear Programming

A factory produces two products. Product A yields $40/unit, Product B yields $50/unit. Each unit of A requires 2 hours of labor and 1 unit of material; each unit of B requires 1 hour of labor and 3 units of material. Available: 100 labor hours, 90 material units. Maximize profit.

Constrained Optimization in 2D

Visualize the feasible region, objective contours, and optimal point for a 2D constrained problem.

Parameters

Geometry of KKT Conditions

Geometry of KKT Conditions
At the optimal point, the negative gradient of the objective is a non-negative combination of active constraint gradients. The multiplier lambdai\\lambda_i measures constraint tightness.

Constrained Optimization Examples

python
SLSQP, trust-constr, and linprog examples including portfolio optimization and resource allocation.
# Code from: ch08/python/constrained_optimization.py
# Load from backend supplements endpoint

Quick Check

In SciPy's SLSQP, an inequality constraint {'type': 'ineq', 'fun': g} means:

g(x) <= 0

g(x) >= 0

g(x) == 0

g(x) != 0

Common Mistake: Wrong Sign Convention for Constraints

Mistake:

Writing {'type': 'ineq', 'fun': lambda x: 1 - x[0]**2 - x[1]**2} when you want x02+x12≀1x_0^2 + x_1^2 \le 1, then being surprised when the solver enforces x02+x12β‰₯1x_0^2 + x_1^2 \ge 1.

Correction:

SciPy's SLSQP enforces g(x) >= 0. For the constraint x02+x12≀1x_0^2 + x_1^2 \le 1, write {'type': 'ineq', 'fun': lambda x: 1 - x[0]**2 - x[1]**2}. This is correct because 1βˆ’x02βˆ’x12β‰₯0β€…β€ŠβŸΊβ€…β€Šx02+x12≀11 - x_0^2 - x_1^2 \ge 0 \iff x_0^2 + x_1^2 \le 1. With trust-constr, use NonlinearConstraint(fun, lb, ub) instead, which avoids sign confusion.

Feasible Set

The set of all points satisfying all constraints of an optimization problem.

Lagrange Multiplier

A scalar Ξ»i\lambda_i or Ξ½j\nu_j associated with a constraint that measures the rate of change of the optimal objective value with respect to the constraint bound.

Related: Feasible Set