OSQP: Operator Splitting Quadratic Program
OSQP 是一种基于算子分裂法的二次规划求解器,通过 ADMM 算法将大规模稀疏 QP 问题分解为简单的子问题,具有高效的求解速度和内存效率。
📖 1. 概述
OSQP(Operator Splitting Quadratic Program)是一种一阶算子分裂求解器,专门用于求解凸二次规划问题。它基于 ADMM(Alternating Direction Method of Multipliers,交替方向乘子法)框架,将复杂的 QP 问题分解为一系列简单的子问题迭代求解。
算子分裂
将复杂问题分解为简单的近端算子
稀疏友好
利用稀疏矩阵结构,内存效率高
嵌入式就绪
适用于实时控制、嵌入式系统
开源免费
BSD 许可证,支持多种语言绑定
📏 2. 二次规划问题
2.1 标准形式
OSQP 求解的 QP 问题标准形式为:
其中各变量含义如下:
| 符号 | 维度 | 含义 |
|---|---|---|
| \(x\) | \(n \times 1\) | 决策变量(待求解) |
| \(P\) | \(n \times n\) | 二次成本矩阵(对称半正定) |
| \(q\) | \(n \times 1\) | 线性成本向量 |
| \(A\) | \(m \times n\) | 约束矩阵(通常稀疏) |
| \(l\) | \(m \times 1\) | 约束下界 |
| \(u\) | \(m \times 1\) | 约束上界 |
2.2 直观示例
考虑一个简单的二维 QP 问题:
对应 OSQP 标准形式:
数据矩阵
$$P = \begin{bmatrix} 2 & 0 \\ 0 & 1 \end{bmatrix}, \quad q = \begin{bmatrix} 1 \\ 1 \end{bmatrix}$$约束数据
$$A = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}, \quad l = \begin{bmatrix} 0 \\ 0 \end{bmatrix}, \quad u = \begin{bmatrix} 2 \\ 3 \end{bmatrix}$$🔄 3. ADMM 算法
3.1 基本原理
ADMM(Alternating Direction Method of Multipliers)是解决可分离凸优化问题的一阶方法,结合了对偶分解和增广拉格朗日方法的优点。
考虑如下可分离问题:
ADMM 的增广拉格朗日函数为:
其中 \(\rho > 0\) 为惩罚参数,\(y\) 为对偶变量(拉格朗日乘子)。
3.2 分裂策略
ADMM 将原问题分裂为三个交替更新步骤:
x 更新(近端梯度步)
$$x^{k+1} = \arg\min_x \left\{ f(x) + \frac{\rho}{2}\|Ax + Bz^k - c + y^k/\rho\|_2^2 \right\}$$当 \(f\) 为简单函数(如二次函数)时,此子问题可通过求解线性方程组高效求解。
z 更新(近端算子步)
$$z^{k+1} = \arg\min_z \left\{ g(z) + \frac{\rho}{2}\|Ax^{k+1} + Bz - c + y^k/\rho\|_2^2 \right\}$$当 \(g\) 为简单函数(如指示函数)时,z 更新可闭式求解。
对偶变量更新(乘子步)
$$y^{k+1} = y^k + \rho(Ax^{k+1} + Bz^{k+1} - c)$$简单的梯度上升步,更新对偶变量以惩罚约束违反。
3.3 收敛性分析
收敛条件
- \(f\) 和 \(g\) 为凸函数
- \(Ax + Bz = c\) 的可行域非空
- 对偶问题有解
收敛速度
- 线性收敛速度 \(O(1/k)\)
- 取决于问题条件数
- 预处理可显著加速
1. 无需二阶导数(Hessian),只需一阶梯度
2. 子问题可并行求解
3. 对大规模稀疏问题高效
4. 数值稳定性好
🔧 4. QP 到 ADMM 重构
OSQP 的核心创新在于将标准 QP 问题重构为 ADMM 可高效求解的形式。
4.1 第一步:变量分裂
引入辅助变量 \(z\),将原问题等价转化为:
其中 \(\delta_{[l,u]}(z)\) 是区间 \([l,u]\) 的指示函数:
4.2 第二步:增广拉格朗日
对约束 \(Ax = z\) 引入对偶变量 \(y\),构造增广拉格朗日函数:
增广项 \(\frac{\rho}{2}\|Ax - z\|_2^2\) 的作用:
- 保证子问题严格凸,提高数值稳定性
- 使收敛不依赖于原始函数的凸性
- \(\rho\) 越大,对约束违反的惩罚越重
4.3 第三步:算子分裂
将增广拉格朗日函数按变量 \(x\) 和 \(z\) 分裂:
f(x) 部分
$$f(x) = \frac{1}{2}x^TPx + q^Tx$$二次函数,可微,梯度为 \(\nabla f = Px + q\)
g(z) 部分
$$g(z) = \delta_{[l,u]}(z)$$指示函数,不可微但近端算子简单(投影到区间)
🧩 5. 求解流程
基于上述重构,OSQP 的 ADMM 迭代步骤如下:
5.1 x 更新(线性方程组求解)
这是 x 更新的核心公式。令 \(\mathcal{H} = P + \rho A^T A\)(常称为组合 Hessian),则:
5.2 z 更新(区间投影)
投影操作为逐元素裁剪:
5.3 对偶变量更新
这是简单的梯度上升步,对偶变量 \(\rho^{-1}y\) 收敛到约束 \(Ax = z\) 的拉格朗日乘子。
5.4 终止条件
OSQP 同时监控原残差和对偶残差:
原残差 (Primal Residual)
$$r^k = Ax^{k+1} - z^{k+1}$$衡量约束 \(Ax = z\) 的满足程度
对偶残差 (Dual Residual)
$$s^k = P x^{k+1} + q + A^T y^{k+1}$$衡量 KKT 条件的满足程度
$$\|r^k\| \leq \epsilon_{\text{primal}} = \epsilon_{\text{abs}} + \epsilon_{\text{rel}} \cdot \max\{\|Ax^{k+1}\|, \|z^{k+1}\|\}$$ $$\|s^k\| \leq \epsilon_{\text{dual}} = \epsilon_{\text{abs}} + \epsilon_{\text{rel}} \cdot \|Px^{k+1} + q\|$$
5.5 完整算法流程
初始化
设置 \(x^0 = 0, z^0 = 0, y^0 = 0, \rho > 0\),预处理得到 \(\mathcal{H}\) 的分解
循环迭代
- 求解 \(\mathcal{H} x^{k+1} = -q + A^T(\rho z^k - y^k)\)
- \(z^{k+1} = \text{proj}_{[l,u]}(Ax^{k+1} + y^k / \rho)\)
- \(y^{k+1} = y^k + \rho(Ax^{k+1} - z^{k+1})\)
- 计算残差 \(r^k, s^k\),检查终止条件
- (可选)自适应调整 \(\rho\)
输出
返回最优解 \(x^*\), 对偶变量 \(y^*\), 迭代次数, 残差历史
💻 6. 代码实现
6.1 Python 接口示例
import osqp
import numpy as np
from scipy import sparse
# 定义 QP 问题数据
P = sparse.csc_matrix(np.array([[2.0, 0.0], [0.0, 1.0]]))
q = np.array([1.0, 1.0])
A = sparse.csc_matrix(np.array([[1.0, 0.0], [0.0, 1.0]]))
l = np.array([0.0, 0.0])
u = np.array([2.0, 3.0])
# 创建求解器并求解
solver = osqp.OSQP()
solver.setup(P, q, A, l, u, alpha=1.6, eps_abs=1e-6, eps_rel=1e-6)
result = solver.solve()
print(f"最优解: x = {result.x}")
print(f"最优值: p = {result.info.obj_val}")
print(f"迭代次数: {result.info.iterations}")
6.2 手动实现 OSQP 核心
import numpy as np
from scipy import sparse
from scipy.sparse.linalg import spsolve
def osqp_solve(P, q, A, l, u, rho=1.0, max_iter=4000,
eps_abs=1e-4, eps_rel=1e-4, alpha=1.6):
n = P.shape[0]
m = A.shape[0]
# 预处理: 计算 H = P + rho * A^T A
H = P + rho * A.T @ A
H = H.tocsc()
# 初始化
x = np.zeros(n)
z = np.zeros(m)
y = np.zeros(m)
for k in range(max_iter):
# x 更新: H x = -q + A^T(rho*z - y)
b = -q + A.T @ (rho * z - y)
x = spsolve(H, b)
# z 更新: 区间投影
Ax = A @ x
z_old = z.copy()
z = np.clip(Ax + y / rho, l, u)
# 对偶变量更新
y = y + rho * (Ax - z)
# 计算残差
primal_res = np.linalg.norm(Ax - z)
dual_res = np.linalg.norm(rho * A.T @ (z - z_old))
# 检查终止条件
eps_pri = eps_abs + eps_rel * max(np.linalg.norm(Ax), np.linalg.norm(z))
eps_dual = eps_abs + eps_rel * np.linalg.norm(rho * A.T @ z)
if primal_res < eps_pri and dual_res < eps_dual:
print(f"收敛于 {k+1} 次迭代")
break
return x, y
6.3 C 语言核心循环
void osqp_solve(Workspace *work) {
int n = work->n;
int m = work->m;
for (int iter = 0; iter < work->settings->max_iter; iter++) {
// x 更新
vec_add_scaled(work->x, work->z, work->y, 1.0, -1.0/rho, m);
mat_vec_mul(work->A, work->x, work->tmp, m, n);
vec_add_scaled(work->rhs, work->q, work->y, -1.0, 1.0, n);
// 求解 H * x_new = rhs (使用预计算的 Cholesky 分解)
ldl_solve(work->chol, work->x, work->rhs);
// z 更新 (区间投影)
mat_vec_mul(work->A, work->x, work->Ax, m, n);
for (int i = 0; i < m; i++) {
work->z[i] = proj(work->Ax[i] + work->y[i]/rho,
work->l[i], work->u[i]);
}
// 对偶变量更新
vec_add_scaled(work->y, work->Ax, work->z, 1.0, -1.0, m);
// 残差检查
if (check_convergence(work)) break;
}
}
float proj(float val, float lo, float hi) {
return fmax(lo, fmin(hi, val));
}
1. 使用稀疏矩阵格式(CSC/CSR)存储 \(P\) 和 \(A\)
2. 预处理阶段一次性完成 Cholesky 分解
3. x 更新使用前向/后向代入而非直接求逆
4. \(\alpha = 1.6\)(松弛参数)通常比 \(\alpha = 1.0\) 收敛更快
🚀 7. 应用场景
MPC 每步求解一个有限时域 QP,OSQP 的热启动和预处理使其在实时控制中表现优异。
# MPC 问题: 每步求解 QP
# min sum_{k=0}^{N-1} (x_k' Q x_k + u_k' R u_k)
# s.t. x_{k+1} = A x_k + B u_k
# x_min <= x_k <= x_max
# u_min <= u_k <= u_max
# 将 MPC 问题转化为标准 QP 形式
# P: 分块对角矩阵 (Q, R)
# A: 状态转移约束矩阵
# l, u: 变量上下界
solver = osqp.OSQP()
solver.setup(P, q, A, l, u, warm_start=True)
# 每个控制周期只需很少的迭代次数(热启动)
result = solver.solve()
将碰撞避免、关节限制等约束编码为 QP,实时求解机器人轨迹。
- 逆运动学:求解满足关节限制的配置
- 力控制:接触力约束下的力矩优化
- 轨迹优化:动力学约束下的平滑轨迹
Markowitz 均值-方差优化及其变体。
\(\Sigma\) 为协方差矩阵(稀疏),\(\mu\) 为期望收益,\(w\) 为资产权重。
稀疏信号恢复、总变分去噪等。
- LASSO:\(l_1\) 正则化回归
- 基追踪:稀疏表示
- TV 去噪:图像总变分最小化
带等式/不等式约束的最小二乘问题。
展开后为标准 QP:\(P = C^TC, q = -C^Td, A = G, l = -\infty, u = h\)
📊 8. 对比
| 方法 | 算法类型 | 每步复杂度 | 内存 | 适用场景 |
|---|---|---|---|---|
| OSQP | 一阶 (ADMM) | \(O(\text{nnz}(\mathcal{H}))\) | 低 | 大规模稀疏 QP |
| ECOS | 内点法 | \(O(n^3)\) | 中 | 中小规模 SOCP |
| qpOASES | 活动集 | \(O(n^2)\) | 高 | 实时 MPC |
| CVXOPT | 内点法 | \(O(n^3)\) | 高 | 通用凸优化 |
| Gurobi | 混合 | 可变 | 高 | 大规模 QP/MIP |
OSQP 优势
无需矩阵分解、稀疏友好、内存低、嵌入式就绪
OSQP 局限
一阶方法收敛较慢、对病态问题可能退化
选型建议
实时 MPC + 稀疏结构 → OSQP
通用优化 → Gurobi / MOSEK
生态
Python / C / MATLAB / R / Julia
支持热启动和参数调优
❓ 9. 常见问题
9.1 为什么 OSQP 不需要矩阵分解?
9.2 \(\rho\) 和 \(\alpha\) 如何选择?
惩罚参数 \(\rho\)
- 默认 \(\rho = 1.0\),通常无需调整
- 过小:收敛慢,迭代次数多
- 过大:z 更新步长过小
- OSQP 支持自适应调节 \(\rho\)
松弛参数 \(\alpha\)
- \(\alpha = 1.0\):标准 ADMM
- \(\alpha = 1.6\):OSQP 默认(推荐)
- 过松弛加速收敛但可能不稳定
- \(\alpha \in (1, 1.8)\) 通常安全
9.3 如何处理非凸 QP?
1. 添加正则化:\(\hat{P} = P + \delta I\),\(\delta\) 为小正数
2. 使用特征值分解截断负特征值
3. 对于混合整数问题,使用分支定界 + OSQP 子问题
9.4 热启动的原理?
热启动(Warm Start)利用上一次 QP 的解作为当前 QP 的初始点:
在 MPC 等连续时间步问题中,相邻 QP 的解通常接近,热启动可将迭代次数从数百次减少到 10-50 次。
9.5 OSQP 与 cvxpy 的关系?
CVXPY 是一个建模语言,自动将问题转化为标准形式并选择后端求解器:
指定 OSQP:
problem.solve(solver=cp.OSQP)
📚 10. 参考文献
- Stellato, B., et al. (2020). OSQP: An Operator Splitting Solver for Quadratic Programs. Mathematical Programming Computation, 12, 637-672.
- Boyd, S., et al. (2011). Distributed Optimization and Statistical Learning via the Alternating Direction Method of Multipliers. Foundations and Trends in Machine Learning, 3(1), 1-122.
- Goldfarb, D., et al. (2017). Operator Splitting Methods for Large Scale Convex Optimization. IMA Journal of Numerical Analysis.
- Parikh, N., & Boyd, S. (2014). Proximal Algorithms. Foundations and Trends in Optimization, 1(3), 127-239.
- osqp.org: OSQP 官方文档与教程.
← 返回首页 | Last updated: 2026-06-25 | Made with love for convex optimization