Dual Variables

CVXPYlayers can return constraint dual variables (Lagrange multipliers) alongside the primal solution. Dual variables represent the sensitivity of the optimal objective value to changes in the constraint right-hand side.

Basic Usage

Include a constraint’s dual variable in the variables list:

import cvxpy as cp
import torch
from cvxpylayers.torch import CvxpyLayer

n = 2
x = cp.Variable(n)
c = cp.Parameter(n)
b = cp.Parameter()

# Define constraint and store reference
eq_con = cp.sum(x) == b
prob = cp.Problem(cp.Minimize(c @ x), [eq_con, x >= 0])

# Request primal variable AND the constraint's dual
layer = CvxpyLayer(
    prob,
    parameters=[c, b],
    variables=[x, eq_con.dual_variables[0]],
)

c_t = torch.tensor([1.0, 2.0], requires_grad=True)
b_t = torch.tensor(1.0, requires_grad=True)

# Forward pass returns both
x_opt, eq_dual = layer(c_t, b_t)

# Gradients flow through dual variables
loss = eq_dual.sum()
loss.backward()

Multiple Dual Variables

You can request duals from multiple constraints:

eq_con = cp.sum(x) == b
ineq_con = x >= 0
prob = cp.Problem(cp.Minimize(c @ x), [eq_con, ineq_con])

layer = CvxpyLayer(
    prob,
    parameters=[c, b],
    variables=[x, eq_con.dual_variables[0], ineq_con.dual_variables[0]],
)

x_opt, eq_dual, ineq_dual = layer(c_t, b_t)

Dual-Only Output

You can request only dual variables without primal:

layer = CvxpyLayer(
    prob,
    parameters=[c, b],
    variables=[eq_con.dual_variables[0]],
)

(eq_dual,) = layer(c_t, b_t)

Batching

Dual variables work with batched parameters:

# Batch of 32 problems
c_batch = torch.randn(32, n, requires_grad=True)
b_batch = torch.randn(32, requires_grad=True)

x_opt, eq_dual = layer(c_batch, b_batch)
# x_opt: (32, n)
# eq_dual: (32,) for scalar constraint

Accessing Dual Variables

Every CVXPY constraint has a dual_variables attribute:

con = A @ x == b
dual = con.dual_variables[0]  # First (usually only) dual variable

Most constraints have a single dual variable. Some conic constraints created explicitly (like cp.SOC(t, x) or cp.PowCone3D(...)) have multiple dual variables that you can access by index.