Repository navigation
Quadratic_Programs
A scenario quadratic program (QP) is a scenario LP with a convex quadratic term added to the cost. The constraints are the same linear constraints as in the LP: scenario constraints built from the samples
QPs handle inputs, input modes and parameter sweeps exactly as LPs do. This page restates the essentials and adds what is specific to
The most general QP is the relaxed–regularized QP:
with
| Symbol | Meaning |
|---|---|
| decision vector | |
| symmetric positive semidefinite matrix of the quadratic cost | |
| linear cost vector | |
|
|
scenario constraints ( |
|
|
hard constraints ( |
| slack of scenario |
|
|
|
relaxation penalty; regularization weight, reference point and norm |
Each scenario counts as one constraint, so the complexity
| Variant |
option value |
Objective | Scenario constraints |
|---|---|---|---|
| Robust | robust |
||
| Regularized | regularization |
||
| Relaxed | relaxation |
|
|
| Relaxed–regularized | regularization-relaxation |
all four terms |
|
The variants relate to the general formulation as follows:
-
Robust:
$\tau = 0$ ,$\rho = 0$ and$\zeta_i = 0$ . -
Relaxed:
$\tau = 0$ . -
Regularized:
$\rho = 0$ and$\zeta_i = 0$ .
How to select a variant:
- Web interface: use the Select Option dropdown. It shows only the fields the selected variant uses, and the server ignores the parameters of the other variants.
-
Relaxed options with
$\rho = 0$ : the web interface keeps the slacks, so violations cost nothing; use$\rho > 0$ . -
Python:
solve_qpadds slack variables wheninclude_slack=True. With the defaultinclude_slack=None, it adds them wheneverrho != 0.
Norm p. Set it with the web field p, the JSON key p, or norm_type in Python; the default is 2. As for LPs, the term is the vector inf, or fro (the same as inf a QP solver such as OSQP can be used; other orders need a conic solver (MOSEK, CLARABEL, SCS).
| Symbol | Shape | Web interface | Program JSON key |
solve_qp argument |
|---|---|---|---|---|
|
|
integers | dimension fields d, 𝔪, 𝔫 |
n_x, rows_A, rows_G
|
inferred |
| Edit c | c |
c: (d, 1) or (d,)
|
||
delta
|
Edit Q (required) | Q |
Q: (d, d)
|
|
delta[k]
|
Edit A(δ) (symbolic mode) | A_d |
callable delta -> (m, d)
|
|
delta[k]
|
Edit b(δ) (symbolic mode) | b_d |
callable delta -> (m, 1) or (m,)
|
|
|
|
|
Edit G, Edit h (when 𝔫 > 0) |
G, h
|
arrays, or np.array([]) for none |
|
|
scalars ≥ 0; comma-separated lists in the web/JSON | τ ≥ 0, ρ ≥ 0 |
tau, rho
|
tau, rho (floats, default 0) |
| Edit x̄ | x_ref |
x_ref: scalar or d entries (default 0) |
||
any number inf, fro
|
p | p |
norm_type (default 2) |
|
| Confidence ... where β | confidence |
beta in quantify_risk(k, N, beta)
|
||
| scenarios |
|
Scenarios upload | separate file |
deltas: 2-D (N, q)
|
solve_qp checks AssertionError (Q must be symmetric or Q must be positive semi-definite (eigenvalues: ...)) if either check fails:
-
$Q$ must equal$Q^\top$ up to a small tolerance. - The smallest eigenvalue of
$Q$ must be$\ge 0$ , again up to a small tolerance. Rank-deficient matrices assembled in floating point, such as$D^\top D$ , whose zero eigenvalues come out as about$-10^{-16}$ , are accepted.
Both tolerances are
To drop the quadratic term, use a zero matrix. The QP tab always requires a
-
Expressions. Entries of
$A(\delta)$ and$b(\delta)$ are numbers or expressions indelta[0], …,delta[q-1], indexed from 0. They may use+ - * / // % **(and^for power), parentheses, functions such assin,exp,sqrtandabs, the constantspiande, andmath.functions such asmath.sin(delta[1]); the full list is in Linear Programs.$c$ ,$Q$ ,$G$ and$h$ cannot containdelta. -
JSON entries. In the program JSON, entries may be plain numbers or quoted strings (
0,"1","-delta[0]*delta[2]"); expressions must be quoted. -
Input modes.
-
Symbolic: the scenario file is
$N \times q$ , with one scenario per row. -
Numeric: row
$i$ is$A(\delta_i)$ flattened row by row, followed by$b(\delta_i)$ , so each row has$\mathfrak{m}d + \mathfrak{m}$ numbers.$d$ and$\mathfrak{m}$ must be given (corn_x, androws_A).
Details and an example are in Linear Programs.
-
Symbolic: the scenario file is
-
Sweeps. Comma-separated values of
$\rho$ and$\tau$ give$n_\tau n_\rho$ solves. The reported overall confidence is$1 - \beta n_\tau n_\rho$ (union bound). -
Python shapes.
deltasshould be 2-D,(N, q).b_dmay return(m, 1)or(m,)andhmay be(n, 1)or(n,); both are reshaped to columns.
solve_qp(deltas, A_d, b_d, G, h, c, Q, tau=0.0, x_ref=np.array([0.0]), rho=0.0, norm_type=2, solver=None, include_slack=None) returns (x, zeta, cost, N, complexity, constraints, degeneracy):
-
xhas shape(d, 1). -
costis the value of the full objective, including the$\tau$ and$\rho$ terms. The web interface instead reports$c^\top x^\ast + \tfrac12 x^{\ast\top} Q x^\ast$ .
See QP.py for implementation details.
The program JSON uses the same keys as for LPs (see Linear Programs), with "type": "QP" and the extra required key Q (
-
type; -
mode; -
option, which is inferred fromrho/tauif absent; -
c,Q; -
A_d,b_d, required in symbolic mode; -
G,h, optional; -
n_x,rows_A(required in numeric mode),rows_G; -
rho,tau,confidence,p,x_ref.
Optional fields that you do not use can be omitted or left empty ([], "" or null).
Benchmark: benchmarks/QP_Iris_minimal_3d.
The goal is to separate Iris setosa from the other two species using two features, petal length and petal width, with a hard-margin linear support vector machine (SVM). Each of the
-
$(\delta_i[0], \delta_i[1])$ are petal length and width in cm; -
$\delta_i[2] \in \lbrace -1, +1 \rbrace$ is the label:$-1$ for the 50 setosa samples,$+1$ for the other 100.
Write
is a robust QP with
so each scenario contributes one row (
data/program_symbolic.json (whitespace reformatted):
{
"type": "QP",
"A_d": [["-delta[0]*delta[2]", "-delta[1]*delta[2]", "-delta[2]"]],
"b_d": [["1"]],
"c": [["0"], ["0"], ["0"]],
"Q": [["1", "0", "0"],
["0", "1", "0"],
["0", "0", "0"]],
"rho": "0",
"tau": "0",
"confidence": "1e-06",
"mode": "symbolic",
"n_x": 3,
"rows_A": 1,
"rows_G": 0
}data/scenarios.csv has 150 rows of the form petal_length, petal_width, label. The first row is 1.4,0.2,-1.
-
Upload Program with
data/program_symbolic.json. This opens the QP tab with Robust,$d = 3$ , 𝔪 = 1 and 𝔫 = 0. - Upload
data/scenarios.csvunder Scenarios. - Choose a solver (CLARABEL below) and press Solve.
Results table:
- optimal cost 1.1765;
- optimal x = (1.2941, 0.8235, −3.7882);
- complexity 2;
- risk bounds [0.0000, 0.1441];
- confidence 0.999999;
- no degeneracy;
- total constraints 150.
Run from the root of the Scen-Opt repository:
import json
import numpy as np
from src.QP import solve_qp
from src.Risk import quantify_risk
from src.parsing import generate_matrix_function, generate_matrix
data = "benchmarks/QP_Iris_minimal_3d/data/"
prog = json.load(open(data + "program_symbolic.json"))
A_d = generate_matrix_function(json.dumps(prog["A_d"])) # callable: delta -> (1, 3) array
b_d = generate_matrix_function(json.dumps(prog["b_d"])) # callable: delta -> (1, 1) array
c = generate_matrix(json.dumps(prog["c"])) # (3, 1) array
Q = generate_matrix(json.dumps(prog["Q"])) # (3, 3) array, diag(1, 1, 0)
G, h = np.array([]), np.array([]) # no hard constraints
deltas = np.loadtxt(data + "scenarios.csv", delimiter=",", ndmin=2) # shape (150, 3)
# Robust QP: tau = 0 and rho = 0 (the defaults)
x, zeta, cost, N, k, constraints, degenerate = solve_qp(
deltas, A_d, b_d, G, h, c, Q, solver="CLARABEL")
eps_L, eps_U = quantify_risk(k, N, beta=1e-6)
w1, w2, b = x.ravel()
print("x* = [w1, w2, b]:", x.ravel())
print("cost (1/2)||w||^2:", cost, "| margin 2/||w||:", 2 / np.hypot(w1, w2))
print("N =", N, "| k =", k, "| degenerate:", degenerate)
print("risk bounds:", eps_L, eps_U)Output (CVXPY 1.7.2, CLARABEL):
x* = [w1, w2, b]: [ 1.29411765 0.82352941 -3.7882353 ]
cost (1/2)||w||^2: 1.1764705890324765 | margin 2/||w||: 1.3038404805987853
N = 150 | k = 2 | degenerate: False
risk bounds: 0.0 0.14409952527843412
-
Classifier: the separating line is
$1.2941 z_1 + 0.8235 z_2 = 3.7882$ . Points with$\mathbf{w}^\top z + b > 0$ are classified as not setosa. The margin width$2/\lVert \mathbf{w} \rVert$ is 1.3038. -
Complexity: two support vectors determine the solution, so
$k = 2$ . -
Risk: with confidence at least
$1 - 10^{-6}$ , a new iris drawn from the same population violates$y (\mathbf{w}^\top z + b) \ge 1$ (it lies inside the margin or is misclassified) with probability at most$\overline{\varepsilon} = 0.1441$ . -
MOSEK: the stored MOSEK result in
results/metrics.jsonagrees to three decimals.
If the classes were not linearly separable, the robust QP would be infeasible. The Relaxation option (with
For numeric mode, use data/program_numeric.json, which has no A_d/b_d and declares "rows_A": 1, with data/scenarios_numeric.csv. Each row is 1.4, 0.2, 1, 1. The results are identical to symbolic mode.
-
QP_cbf_robot_navigation_118d: trajectory of a point robot with double-integrator dynamics (20 steps) around a wall whose position is uncertain ($d = 118$ , 320 hard constraints,$N = 500$ ). -
QP_radiation_therapy_50d: prostate brachytherapy plan under catheter-placement uncertainty, built on TROTS dose-influence data ($d = 50$ , 80 dose constraints per scenario). It is a regularized QP with$\tau = 0.1$ . Degeneracy is detected: the published MOSEK result has$k = 5$ , and the web interface shows such a result's risk bounds as [N/A, 0.1414].
- Linear Programs: full description of the shared inputs, input modes and sweeps
- Semidefinite Programs
- Scenario Theory, QP.py, Risk.py