用 MATLAB + CVX 调用 MOSEK/SeDuMi/SDPT3 求解二维 LP 与 QP,并读懂命令行输出
CVX 是 MATLAB 里的建模层:用接近数学形式的语句写出目标函数和约束,CVX 会把它转换成求解器(MOSEK / SeDuMi / SDPT3 等)能处理的标准形式,然后调用底层求解器求解。 这篇博客用两个二维决策变量的小例子串起来:
- 线性规划(LP,Linear Programming):如何在
MATLAB中用CVX调用 MOSEK(也给出如何切换到SeDuMi/SDPT3)。 - 二次规划(QP,Quadratic Programming):如何用
CVX求解凸 QP。
0. 环境准备与基本流程
安装并初始化
CVX(只需一次):1
cvx_setup在 MATLAB 中,CVX 的典型结构是:
1
2
3
4
5
6
7cvx_begin
cvx_solver mosek % 或 sdpt3 / sedumi ...
variable x(2)
minimize( ... )
subject to
...
cvx_end- 想看求解器命令行输出:不要写
cvx_begin quiet - 切换求解器:改
cvx_solver mosek/cvx_solver sdpt3/cvx_solver sedumi- CVX 会在每个模型开始时读取当时的设置/或使用块内设置
- 可以像上面这样写在
cvx_begin ... cvx_end里面,也可以在cvx_begin之前设置- 写在
cvx_begin ... cvx_end里面:本次模型专用的设置,通常不会影响后面其他cvx_begin的solver - 写在
cvx_begin之前:全局设置,后面所有的CVX模型默认都用它,直到再次cvx_solver xxx改掉
- 写在
- 想看求解器命令行输出:不要写
1 二维 LP 示例
1.1 问题定义
令 \(x=[x_1,x_2]^\top \in \mathbb{R}^2\),考虑线性规划:
\[\begin{eqnarray} \min_{x\in\mathbb{R}^2} \quad &&f(x)=x_1+2x_2 \nonumber\\ \text{s.t.} \quad &&x_1 \ge 0,\quad x_2 \ge 0 \nonumber\\ &&x_1 \le 2,\quad x_2 \le 2 \nonumber\\ &&x_1+x_2 \ge 1 \nonumber\\ &&2x_1+x_2 \le 3 \nonumber\\ &&x_1+2x_2 \le 3.5 \end{eqnarray}\]
为了方便写代码,转成 \[\begin{eqnarray} \min_{x\in\mathbb{R}^2} \quad &&c^\top x \nonumber\\ \text{s.t.} \quad &&Ax\le b. \end{eqnarray}\]
其中,
\[\begin{equation} c=[1,2]^\top,\quad A= \begin{bmatrix} -1&0\\ 0&-1\\ 1&0\\ 0&1\\ -1&-1\\ 2&1\\ 1&2 \end{bmatrix},\quad b= \begin{bmatrix} 0\\ 0\\ 2\\ 2\\ -1\\ 3\\ 3.5 \end{bmatrix}, \end{equation}\]
1.2 MATLAB/CVX 代码(LP)
1 | |
2 二维 QP 示例:CVX 求解凸二次规划
2.1 问题定义
这里给一个凸二次目标(\(Q\succeq 0\)),线性约束与上面类似,方便对比:
\[\begin{equation} \min_{x\in\mathbb{R}^2} \quad \frac{1}{2}x^\top Qx + q^\top x \end{equation}\]
取 \[\begin{equation} Q= \begin{bmatrix} 2&0\\ 0&4 \end{bmatrix}\succ 0,\quad q= \begin{bmatrix} -2\\ -6 \end{bmatrix}. \end{equation}\]
约束仍取一个有界可行域(线性约束): \[\begin{equation} Ax\le b, \end{equation}\] 其中, \[\begin{equation} A =\begin{bmatrix} -1&0\\ 0&-1\\ 1&0\\ 0&1\\ -1&-1\\ 2&1\\ 1&2 \end{bmatrix},\quad b= \begin{bmatrix} 0\\ 0\\ 2\\ 2\\ -1\\ 3\\ 3.5 \end{bmatrix}. \end{equation}\]
2.2 MATLAB/CVX 代码(QP)
1 | |
3 不同求解器命令行输出
用 Matlab 的 CVX 工具箱求解该 LP 问题之后命令行输出的前两行结构类似,均为:
1 | |
这两行:
CVX打印的(不是求解器)为什么会出现
7 variables, 2 equality constraints,而原问题只有 2 个变量?因为
CVX会把你写的形式 \(Ax ≤ b\) 做等价变换后再交给求解器对 LP 来说,最典型的是交给求解器的是对偶问题:
- 原问题(primal):
\[ \min\ c^\top x\quad \text{s.t.}\ Ax\le b \]
- 对偶问题(dual): \[
\max\ -b^\top y\quad \text{s.t.}\ A^\top y + c = 0,\ y\ge 0
\]
- 这里 \(y \in \mathbb{R}^7\):每条不等式约束对应一个对偶变量 \(\to\)
7 variables - 约束 \(A^\top y + c = 0\) 是 2 维向量等式 \(\to\)
2 equality constraints
- 这里 \(y \in \mathbb{R}^7\):每条不等式约束对应一个对偶变量 \(\to\)
下面贴出三种不同求解器(MOSEK / SeDuMi / SDPT3)其余行的输出,再逐块进行解释。
点击展开:MOSEK 求解 LP 的命令行输出
1 | |
Problem 概览块
Type : LO:线性优化(Linear Optimization)Constraints : 2:等式约束数(这里是指对偶问题的等式约束数)Cones : 0:没有锥约束(LP 不需要 SOC/SDP 锥)Scalar variables : 7:变量数(这里是指对偶变量个数)
Presolve 块
Presolve / Linear dependency checker / Eliminator:预处理(去冗余、检查线性相关、尝试消去变量等)。这个问题很小,所以时间基本是 0。
Optimizer / Factor
Optimizer - threads : 16:MOSEK 使用的CPU线程数(并行数)Optimizer - solved problem : the primal:MOSEK 在内部选择以 primal 形式驱动内点法- 这说的是MOSEK 当前拿到的模型的 primal/dual 选择;不等同于原始建模问题的 primal/dual
Optimizer - Constraints / Scalar variables / Cones ...:再次汇总求解阶段实际处理的模型规模Factor - setup time .../order time ...:内点法每次迭代都需要解一组 KKT 线性方程组(一阶最优条件线性化后的方程)- MOSEK 会对该线性系统做稀疏分解,
setup/order time是分解准备和排序(重排以减少填充)的时间统计
- MOSEK 会对该线性系统做稀疏分解,
Factor - nonzeros before factor : 3 after factor : 3:分解前/后的非零元数量统计(体现稀疏结构与“填充”程度)- 该 LP 问题非常小,所以数字也很小,基本没有填充
Factor - dense dim ... flops ...:分解时涉及的致密块维度与浮点运算量(同样因为问题小,所以几乎可忽略)
迭代表
ITE:内点法迭代步PFEAS / DFEAS:原始/对偶可行性残差(越接近 \(0\) 越好)GFEAS:与互补性/间隙相关的残差指标(越接近 \(0\) 越好)PRSTATUS:一个“状态指标”,如果问题有最优解它会趋近于 1;否则可能趋近 -1POBJ / DOBJ:原始/对偶目标值估计(收敛时两者几乎相等)MU:障碍参数/互补性量级(应趋近 0)- 上面这段输出中,迭代到第 5 步:
PFEAS/DFEAS/GFEAS都到 \(10^{-11}\sim10^{-12}\)POBJ=DOBJ= \(1.0\) 说明收敛到最优。
- 上面这段输出中,迭代到第 5 步:
Basis identification
- 这是一段后处理:内点法解出来的解不一定是基解
MOSEK会尝试构造一个最优基(方便 simplex 热启动/敏感性分析等)
Solution summary
Problem status : PRIMAL_AND_DUAL_FEASIBLE:原始和对偶都可行Solution status : OPTIMAL:最优Primal/Dual obj:原始/对偶目标值(两者应一致或非常接近)nrm:解向量的无穷范数(MOSEK 用它作为“规模量级”参考)Viol. con / var:约束/变量违反量(应接近 0)
点击展开:SeDuMi 求解 LP 的命令行输出
1 | |
SeDuMi 也是内点法,但它的打印风格不同。
- 算法配置行
Alg = 2: xz-corrector, Adaptive Step-Differentiation, theta = 0.250, beta = 0.500:Alg=2:使用 内点法 的一种实现(带 预测-校正/纠正器,这里标成 xz-corrector)Adaptive Step-Differentiation:步长/更新策略是自适应的(更稳、更快收敛)theta, beta:算法内部的参数(影响步长与中心化程度),通常用户不需要改
- 问题规模/锥结构摘要
eqs m = 2, order n = 8, dim = 8, blocks = 1:m=2:等式约束数量(求解器标准化后形式里的等式条数)n=8 / dim=8:标准化后变量(或锥变量)维度规模blocks=1:锥/块结构只有 1 个块(例如一个线性锥块或合并成一个块)
- 稀疏性与线性代数工作量提示
nnz(A) = 10 + 0, nnz(ADA) = 4, nnz(L) = 3:nnz(A):约束矩阵 A 的非零元素个数(10+0常见表示主块+附加块/修正块,关键是稀疏度级别)nnz(ADA):形成/使用的 正规方程/Schur 补矩阵(常记作 (A D A^))的非零数,反映求解线性系统的“密度”nnz(L):Cholesky 分解得到的 下三角因子 L 的非零数(越小越省时省内存)
- 迭代表
it : b*y gap delta rate t/tP* t/tD* feas cg cg prec:it:迭代次数b*y:对偶目标值(通常是 \(b^\top y\) 的当前值)gap:原始-对偶 对偶间隙(衡量离最优还有多远,越接近 0 越好)delta:当前迭代的某种残差/扰动指标(很多时候显示为 0 表示无需额外修正或已很小)rate:gap 等量的收敛比率/下降速度指标(越小通常代表下降更快)t/tP*、t/tD*:原始/对偶方向的步长比例(接近 1 通常表示可以走较大步,进展顺利)feas:可行性/残差的量级指标(越接近 1 或越小越好,取决于 SeDuMi 的定义;结合最后的残差行看最可靠)cg cg:是否使用/以及共轭梯度(CG)相关信息(这里基本一直是 1,表示该步的线性系统解法状态正常)prec:预条件/精度相关指标(数值越小通常表示更精)
- 收敛汇总行(迭代耗时与目标)
iter=3:用了 3 次内点迭代digits=Inf:达到非常高的数值精度/收敛判据(可理解为收敛很好)c*x 与 b*y 相等:原始目标 \(c^\top x\) 和对偶目标 \(b^\top y\) 一致(强收敛信号,对偶间隙基本为 0)
- 残差与解的规模检查
|Ax-b| = 2.3e-16, [Ay-c]_+ = 0.0E+00, |x|=1.4e+00, |y|=1.0e+00:|Ax-b|:原始等式残差,越近似 0,越说明原始可行性极好[Ay-c]_+:对偶不等式的正部违反量(常用于衡量对偶可行性),为 0 表示对偶可行|x|, |y|:解向量的范数大小,主要用于观察解是否爆炸/异常大
- 计时明细
Detailed timing (sec):Pre:预处理/建模转换、消元、缩放等耗时IPM:内点法主迭代耗时(解线性系统是大头)Post:后处理/恢复原变量、输出整理耗时 > 这块用于判断时间花在哪:建模预处理 vs 主求解
- 规模与分解信息(数值线性代数提示)
Max-norms: ||b||=2, ||c|| = 2,:- (b, c) 的最大范数规模,用于判断数值尺度是否正常(过大/过小可能导致病态)
Cholesky |add|=0, |skip| = 0, ||L.L|| = 1.:- Cholesky 分解时 没有额外填充(add)、也没有跳过(skip) —— 说明分解过程顺利稳定
||L.L||:分解因子的量级提示(这里为 1,尺度正常)
点击展开:SDPT3 求解 LP 的命令行输出
1 | |
- 规模概览
num. of constraints = 2:SDPT3 接收到的等式约束数量(通常是 CVX 标准化后的形式)dim. of linear var = 7:SDPT3 标准形式中的线性锥/线性部分变量维度(不一定等于原始 LP 的变量个数,是 CVX 转换后的维度)
- 求解器与算法类型提示(横幅)
SDPT3: Infeasible path-following algorithms:- 说明 SDPT3 使用的是一类内点法(路径跟随, path-following)实现
- 这里的 “Infeasible” 是算法名(允许从不可行初始点出发并逐步逼近可行/最优),不表示该问题不可行
- 关键算法开关/参数
version predcorr gam expon scale_data:predcorr = 1:开启预测-校正(predictor-corrector)内点法(通常更快更稳)scale_data = 0:不做(或不启用)数据缩放/归一化处理(有些问题开启缩放会更稳)gam, expon:算法内部参数,一般只需知道这是内点法参数配置
- 迭代
it pstep dstep pinfeas dinfeas gap prim-obj dual-obj cputime ... chol 1 1:it:迭代次数pstep / dstep:本次迭代的原始/对偶步长(越接近 1 通常越顺利)pinfeas / dinfeas:原始/对偶不可行度(残差),越小越好gap:对偶间隙(离最优的差距),越小越好prim-obj / dual-obj:当前原始目标值 / 对偶目标值(收敛时应非常接近)cputime:累计耗时chol 1 1:该迭代主要线性代数步骤采用 Cholesky 分解(后面的数字是内部状态/标记)
- 迭代过程主要看三件事是否单调变好:
pinfeas/dinfeas从大到接近 0(可行性越来越好)gap快速变小到很小(逼近最优)prim-obj 与 dual-obj越来越接近(原始/对偶一致性)
- 停止条件
stop: max(relative gap, infeasibilities) < 1.49e-08:- 停止判据:相对 gap 和 不可行度 的最大值已经小于阈值 \(1.49\times 10^{-8}\),所以认为收敛
- 收敛总结
primal / dual objective value = ...:原始/对偶目标值,最终非常接近(说明解已稳定)gap = trace(XZ):SDP/锥规划语境下的互补性度量(对 LP 的锥形式也适用),越接近 \(0\) 越好relative gap / actual relative gap:gap 的相对指标(越接近 \(0\) 越好)
- 可行性指标(scaled / unscaled)
rel. primal(dual) infeas (scaled problem):若内部做了缩放/等价变换,给出在该尺度下的残差rel. primal(dual) infeas (unscaled problem):对应原尺度(或恢复后)的残差;这为 0 表示恢复到原问题尺度后可行性很好
- 范数信息(量级检查)
norm(·), norm(·), norm(·) = ...:- 给出变量/数据的量级,主要用于诊断数值问题
- 如果范数量级极端(特别大或特别小),可能暗示病态、缩放需求等
- 时间与退出码
Total CPU time ... / per iteration ...:总耗时/每步耗时termination code = 0:正常收敛结束(一般 0 表示成功)
- DIMACS 误差向量
- 一组标准化的误差/残差度量(DIMACS 指标),用于统一评价原始可行、对偶可行、互补性等。
- 数值越小越好
用 Matlab 的 CVX 工具箱求解该 LP 问题之后命令行输出的最后两行均为:
1 | |
Status: Solved:CVX 的最终状态,问题已被成功求解并满足其收敛/可行性判据(不是“尝试过”,而是认为已找到可接受的最优解)。Optimal value (cvx_optval): +1:CVX 记录的最优目标函数值,存放在变量cvx_optval里。 这里等于 +1,即定义的目标函数在最优解处的值为 1(符号 “+” 只是显示格式)。
4 求解 QP(二次规划)时,命令行输出会有什么变化?
通常会有如下变化:
MOSEK
Type往往不再是LO,而会显示为CONIC (conic optimization problem)。- 迭代表依然会有 PFEAS/DFEAS/POBJ/DOBJ/MU 这类列(内点法风格非常像)。
SeDuMi / SDPT3
- 由于它们本质上解的是锥规划,CVX 会把 QP 的二次目标转成等价的锥形式(常见是 SOC/旋转二次锥)。
- 因此日志里经常能看到:锥维度/块结构发生变化(例如 cone blocks、dim/order 等统计改变),迭代列名依旧类似(gap、feas、pinfeas、dinfeas 等)。
5 在使用 MOSEK 求解器时遇到的问题
检测到线性相关,但 Presolve 后约束数仍显示不变
现象
在用 CVX + MOSEK 求解某个 LP 的对偶问题时,命令行输出中会出现类似信息:
Linear dependency checker started/terminatedLin. dep. - number : 1但在
Presolve terminated后,摘要里仍显示:Constraints : 3- 并且
Freed constraints in eliminator : 0
与此同时,从数学形式上看,该对偶模型的三条不等式约束中,第 3 条确实可由前两条线性组合得到,例如:
- (c1) 形如:\[ a^\top y \le 1 \]
- (c2) 形如:\[ b^\top y \le 2 \]
- (c3) 形如:\[ (a+b)^\top y \le 3 \]
显然 (c3) = (c1)+(c2),因此 (c3) 是冗余不等式。
为什么会这样?
MOSEK 日志里的 Constraints : 3 表示模型里线性约束的“行数”,并不等价于“线性无关约束的数量/约束矩阵的秩”。
- 即便存在冗余或线性相关,日志摘要仍可能显示原来的约束条数。
更重要的是: MOSEK 的 linear dependency checker 主要针对的是线性等式(或能导致 KKT 系统奇异的那类依赖)去做秩相关的处理;而这里的相关性发生在 不等式 之间。对于不等式来说:
- 证明“某条不等式被其它不等式蕴含(冗余)”往往更复杂(本质上属于冗余约束检测),求解器通常会做得比较保守;
- 即使检测到“系数行层面”的线性相关,也不一定会把它安全删除(尤其涉及数值容差、边界可行域是否完全等价等问题);
- 因此会出现:检测到依赖(Lin. dep. number = 1),但 presolve 不删除约束(freed constraints = 0),约束数仍显示为 3。
实战建议(对建模/求解速度更友好)
- 能人工识别的明显冗余不等式,建议在建模阶段直接删掉。 冗余约束往往会引入退化、增加内点法 KKT 系统的规模或数值负担,删掉通常不会影响结果,反而更稳更快。
- 不要仅根据日志里
Constraints的数值判断求解器是否做了冗余消除,那只是行数统计,不是独立约束数。
结语
用 CVX 写 LP/QP 的体验是统一的:写数学模型,CVX负责转换,求解器负责迭代。命令行输出里最“承重”的信息其实就三类:
- 模型规模统计(变量 / 约束 / 锥结构)
- 迭代表(feasibility + gap + objective)
- 最终状态(Solved/Optimal + violation)