凸优化 · 数值求解

OSQP: Operator Splitting Quadratic Program

OSQP 是一种基于算子分裂法的二次规划求解器,通过 ADMM 算法将大规模稀疏 QP 问题分解为简单的子问题,具有高效的求解速度和内存效率。

📖 1. 概述

OSQP(Operator Splitting Quadratic Program)是一种一阶算子分裂求解器,专门用于求解凸二次规划问题。它基于 ADMM(Alternating Direction Method of Multipliers,交替方向乘子法)框架,将复杂的 QP 问题分解为一系列简单的子问题迭代求解。

⚙️

算子分裂

将复杂问题分解为简单的近端算子

📊

稀疏友好

利用稀疏矩阵结构,内存效率高

嵌入式就绪

适用于实时控制、嵌入式系统

🔗

开源免费

BSD 许可证,支持多种语言绑定

OSQP 求解器架构 QP 问题 min 0.5x'Px+q'x 重构 ADMM 形式 s.t. Ax=z 迭代 子问题求解 x-update / z-update 预处理 缩放 + 对角预处理 热启动 利用上一次解 终止检测 原残差 + 对偶残差
💡
核心优势:OSQP 不需要矩阵分解(如 Cholesky 或 LU),只需矩阵-向量乘法和前/后向代入,特别适合稀疏结构的 QP 问题。在 MPC、机器人控制、金融优化等领域广泛应用。

📏 2. 二次规划问题

2.1 标准形式

OSQP 求解的 QP 问题标准形式为:

$$\begin{aligned} \min_{x} \quad & \frac{1}{2} x^T P x + q^T x \\ \text{s.t.} \quad & l \leq Ax \leq u \end{aligned}$$

其中各变量含义如下:

符号维度含义
\(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 问题:

$$\begin{aligned} \min_{x_1, x_2} \quad & \frac{1}{2}(2x_1^2 + x_2^2) + x_1 + x_2 \\ \text{s.t.} \quad & 0 \leq x_1 \leq 2 \\ & 0 \leq x_2 \leq 3 \end{aligned}$$

对应 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}$$
QP 问题的几何意义 x₁ x₂ 可行域 最优解 等高线:目标函数值 红点:最优点

🔄 3. ADMM 算法

3.1 基本原理

ADMM(Alternating Direction Method of Multipliers)是解决可分离凸优化问题的一阶方法,结合了对偶分解和增广拉格朗日方法的优点。

考虑如下可分离问题:

$$\begin{aligned} \min_{x,z} \quad & f(x) + g(z) \\ \text{s.t.} \quad & Ax + Bz = c \end{aligned}$$

ADMM 的增广拉格朗日函数为:

$$\mathcal{L}_\rho(x,z,y) = f(x) + g(z) + y^T(Ax + Bz - c) + \frac{\rho}{2}\|Ax + Bz - c\|_2^2$$

其中 \(\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)$$

简单的梯度上升步,更新对偶变量以惩罚约束违反。

ADMM 交替更新示意图 x 更新
\(\min f(x) + \text{penalty}\)
约束检查
\(Ax + Bz \stackrel{?}{=} c\)
z 更新
\(\min g(z) + \text{penalty}\)
对偶变量更新
\(y \leftarrow y + \rho(Ax + Bz - c)\)
重复迭代直到收敛

3.3 收敛性分析

收敛条件

  • \(f\) 和 \(g\) 为凸函数
  • \(Ax + Bz = c\) 的可行域非空
  • 对偶问题有解

收敛速度

  • 线性收敛速度 \(O(1/k)\)
  • 取决于问题条件数
  • 预处理可显著加速
ADMM 的关键优势
1. 无需二阶导数(Hessian),只需一阶梯度
2. 子问题可并行求解
3. 对大规模稀疏问题高效
4. 数值稳定性好

🔧 4. QP 到 ADMM 重构

OSQP 的核心创新在于将标准 QP 问题重构为 ADMM 可高效求解的形式。

4.1 第一步:变量分裂

引入辅助变量 \(z\),将原问题等价转化为:

$$\begin{aligned} \min_{x,z} \quad & \frac{1}{2} x^T P x + q^T x + \delta_{[l,u]}(z) \\ \text{s.t.} \quad & Ax = z \end{aligned}$$

其中 \(\delta_{[l,u]}(z)\) 是区间 \([l,u]\) 的指示函数

$$\delta_{[l,u]}(z) = \begin{cases} 0 & \text{if } l \leq z \leq u \\ +\infty & \text{otherwise} \end{cases}$$

4.2 第二步:增广拉格朗日

对约束 \(Ax = z\) 引入对偶变量 \(y\),构造增广拉格朗日函数:

$$\mathcal{L}_\rho(x,z,y) = \frac{1}{2}x^TPx + q^Tx + \delta_{[l,u]}(z) + y^T(Ax - z) + \frac{\rho}{2}\|Ax - z\|_2^2$$

增广项 \(\frac{\rho}{2}\|Ax - z\|_2^2\) 的作用:

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)$$

指示函数,不可微但近端算子简单(投影到区间)

⚠️
关键洞察:通过将约束 \(l \leq Ax \leq u\) 分裂到指示函数中,z 更新步骤退化为简单的区间投影操作,这是 OSQP 高效的关键。

🧩 5. 求解流程

基于上述重构,OSQP 的 ADMM 迭代步骤如下:

5.1 x 更新(线性方程组求解)

$$x^{k+1} = (P + \rho A^T A)^{-1}(-q + A^T(\rho z^k - y^k))$$

这是 x 更新的核心公式。令 \(\mathcal{H} = P + \rho A^T A\)(常称为组合 Hessian),则:

$$\mathcal{H} x^{k+1} = -q + A^T(\rho z^k - y^k)$$
效率关键:\(\mathcal{H}\) 在整个迭代过程中保持不变。OSQP 在预处理阶段对 \(\mathcal{H}\) 进行稀疏 Cholesky 分解,之后每次迭代只需前向/后向代入求解线性方程组。

5.2 z 更新(区间投影)

$$z^{k+1} = \text{proj}_{[l,u]}(Ax^{k+1} + y^k / \rho)$$

投影操作为逐元素裁剪:

$$z_i^{k+1} = \min\left\{u_i, \max\left\{l_i, (Ax^{k+1})_i + y_i^k/\rho\right\}\right\}$$
区间投影示意图 l u 可行区间 [l, u] 在区间外 → 投影到 l 在区间内 → 保持不变 在区间外 → 投影到 u

5.3 对偶变量更新

$$y^{k+1} = y^k + \rho(Ax^{k+1} - z^{k+1})$$

这是简单的梯度上升步,对偶变量 \(\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}\) 的分解

循环迭代

  1. 求解 \(\mathcal{H} x^{k+1} = -q + A^T(\rho z^k - y^k)\)
  2. \(z^{k+1} = \text{proj}_{[l,u]}(Ax^{k+1} + y^k / \rho)\)
  3. \(y^{k+1} = y^k + \rho(Ax^{k+1} - z^{k+1})\)
  4. 计算残差 \(r^k, s^k\),检查终止条件
  5. (可选)自适应调整 \(\rho\)

输出

返回最优解 \(x^*\), 对偶变量 \(y^*\), 迭代次数, 残差历史

💻 6. 代码实现

6.1 Python 接口示例

Python 使用 osqp 库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 核心

OSQP 核心 ADMM 迭代Python
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 语言核心循环

OSQP C 风格核心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)

MPC 每步求解一个有限时域 QP,OSQP 的热启动和预处理使其在实时控制中表现优异。

MPC QP 构造Python
# 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 均值-方差优化及其变体。

$$\begin{aligned} \min_{w} \quad & \frac{1}{2} w^T \Sigma w - \lambda \mu^T w \\ \text{s.t.} \quad & w^T \mathbf{1} = 1 \\ & 0 \leq w_i \leq w_{\max} \end{aligned}$$

\(\Sigma\) 为协方差矩阵(稀疏),\(\mu\) 为期望收益,\(w\) 为资产权重。

⚙️ 信号处理与图像重建

稀疏信号恢复、总变分去噪等。

  • LASSO:\(l_1\) 正则化回归
  • 基追踪:稀疏表示
  • TV 去噪:图像总变分最小化
🎯 约束最小二乘

带等式/不等式约束的最小二乘问题。

$$\min_x \|Cx - d\|_2^2 \quad \text{s.t.} \quad Gx \leq h$$

展开后为标准 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
支持热启动和参数调优

实践联系:OSQP 在嵌入式 MPC(如自动驾驶、无人机控制)中表现突出。其热启动功能使得连续时间步的 QP 求解只需 10-50 次迭代,远少于冷启动的数百次。

9. 常见问题

9.1 为什么 OSQP 不需要矩阵分解?

💡
OSQP 的 x 更新需要求解 \(\mathcal{H}x = b\),其中 \(\mathcal{H} = P + \rho A^T A\)。在预处理阶段一次性计算 \(\mathcal{H}\) 的稀疏 Cholesky 分解 \(\mathcal{H} = LL^T\),之后每次迭代只需前向/后向代入,复杂度为 \(O(\text{nnz}(L))\) 而非 \(O(n^3)\)。

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?

⚠️
OSQP 要求 \(P\) 为半正定。若 \(P\) 非正定:
1. 添加正则化:\(\hat{P} = P + \delta I\),\(\delta\) 为小正数
2. 使用特征值分解截断负特征值
3. 对于混合整数问题,使用分支定界 + OSQP 子问题

9.4 热启动的原理?

热启动(Warm Start)利用上一次 QP 的解作为当前 QP 的初始点:

$$x^0_{\text{new}} = x^*_{\text{old}}, \quad z^0_{\text{new}} = z^*_{\text{old}}, \quad y^0_{\text{new}} = y^*_{\text{old}}$$

在 MPC 等连续时间步问题中,相邻 QP 的解通常接近,热启动可将迭代次数从数百次减少到 10-50 次。

9.5 OSQP 与 cvxpy 的关系?

CVXPY 是一个建模语言,自动将问题转化为标准形式并选择后端求解器:

CVXPY 模型 → 标准 QP/SDP → OSQP / ECOS / Gurobi

指定 OSQP:
problem.solve(solver=cp.OSQP)

📚 10. 参考文献

  1. Stellato, B., et al. (2020). OSQP: An Operator Splitting Solver for Quadratic Programs. Mathematical Programming Computation, 12, 637-672.
  2. 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.
  3. Goldfarb, D., et al. (2017). Operator Splitting Methods for Large Scale Convex Optimization. IMA Journal of Numerical Analysis.
  4. Parikh, N., & Boyd, S. (2014). Proximal Algorithms. Foundations and Trends in Optimization, 1(3), 127-239.
  5. osqp.org: OSQP 官方文档与教程.

← 返回首页 | Last updated: 2026-06-25 | Made with love for convex optimization