4.4. Second-Order Cone Program

Second-order cone programming is the standard convex formulation for Euclidean norm constraints of the form

\[\|A_i x + b_i\|_2 \le c_i^\top x + d_i.\]

Together with affine equalities, this gives the standard SOCP template

\[\begin{split}\begin{array}{ll} \min\limits_x & f^\top x \\ \text{s.t.} & \|A_i x + b_i\|_2 \le c_i^\top x + d_i,\quad i=1,\ldots,m, \\ & Fx = g. \end{array}\end{split}\]

Here \(x \in \mathbb{R}^n\) is the decision variable, \(f \in \mathbb{R}^n\) defines the linear objective \(f^\top x\), \(A_i \in \mathbb{R}^{n_i \times n}\), \(b_i \in \mathbb{R}^{n_i}\), \(c_i \in \mathbb{R}^n\), \(d_i \in \mathbb{R}\), and \(F \in \mathbb{R}^{p \times n}\), \(g \in \mathbb{R}^p\) define the affine equality system \(F x = g\). The left-hand side of each cone constraint is a Euclidean norm, while the right-hand side is affine.

SOCPs appear whenever “stay inside a norm cone” is the natural modeling idea. They are common in robust optimization, control, and geometric design problems.

Step 1: Generate a feasible cone instance

This example is built from a known feasible point \(x_0\). For each cone constraint, we choose \(d_i = \|A_i x_0 + b_i\|_2 - c_i^\top x_0\), so the inequality is satisfied at \(x_0\) with equality. We also set g = F @ x0 so the affine equalities hold at the same reference point.

import numpy as np
import admm

np.random.seed(1)

m = 3
n = 10
p = 5
n_i = 5

f = np.random.randn(n)
A = []
b = []
c = []
d = []
x0 = np.random.randn(n)
for i in range(m):
    A.append(np.random.randn(n_i, n))
    b.append(np.random.randn(n_i))
    c.append(np.random.randn(n))
    d.append(np.linalg.norm(A[i] @ x0 + b[i], 2) - c[i].T @ x0)
F = np.random.randn(p, n)
g = F @ x0

Step 2: Create the model and variable

This problem has one vector decision variable \(x \in \mathbb{R}^{10}\).

model = admm.Model()
x = admm.Var("x", n)

Step 3: Write the objective

The objective is the linear form \(f^\top x\), which maps directly to f.T @ x in the symbolic API.

model.setObjective(f.T @ x)

Step 4: Add the cone constraints one by one

This is the central modeling idea in an SOCP. For each index i, the code

admm.norm(A[i] @ x + b[i], ord=2) <= c[i].T @ x + d[i]

is a literal translation of \(\|A_i x + b_i\|_2 \le c_i^\top x + d_i\). We add those constraints in a loop because the problem has several cone inequalities, not just one. After that, we add the affine equality block \(Fx = g\).

for i in range(m):
    model.addConstr(admm.norm(A[i] @ x + b[i], ord=2) <= c[i].T @ x + d[i])
model.addConstr(F @ x == g)

Step 5: Solve and inspect the result

Once the cone and equality constraints are in place, we solve and print the standard solver outputs.

model.optimize()

print(" * model.ObjVal: ", model.ObjVal)        # Expected: 2.06815161777782
print(" * model.StatusString: ", model.StatusString)  # Expected: SOLVE_OPT_SUCCESS

Complete runnable example:

import numpy as np
import admm

np.random.seed(1)

m = 3
n = 10
p = 5
n_i = 5

f = np.random.randn(n)
A = []
b = []
c = []
d = []
x0 = np.random.randn(n)
for i in range(m):
    A.append(np.random.randn(n_i, n))
    b.append(np.random.randn(n_i))
    c.append(np.random.randn(n))
    d.append(np.linalg.norm(A[i] @ x0 + b[i], 2) - c[i].T @ x0)
F = np.random.randn(p, n)
g = F @ x0

model = admm.Model()
x = admm.Var("x", n)
model.setObjective(f.T @ x)
for i in range(m):
    model.addConstr(admm.norm(A[i] @ x + b[i], ord=2) <= c[i].T @ x + d[i])
model.addConstr(F @ x == g)
model.optimize()

print(" * model.ObjVal: ", model.ObjVal)        # Expected: 2.06815161777782
print(" * model.StatusString: ", model.StatusString)  # Expected: SOLVE_OPT_SUCCESS

This example is available as a standalone script in the examples/ folder of the ADMM repository:

python examples/second_order_cone_program.py

Each second-order cone constraint \(\|A_i x + b_i\|_2 \le c_i^\top x + d_i\) maps to a single addConstr(...) call. No auxiliary variables or epigraph reformulations are needed — ADMM accepts norm constraints directly.