在 Python 中调用 CvxPy 进行凸优化
本文通过在 Python 中调用 CvxPy 进行凸优化时遇到的问题,介绍使用 CvxPy 进行凸优化的基本步骤和注意事项。➡️
❓ 问题引出
💭 问题:什么是
CvxPy?
CvxPy 是一个基于 Python 的 凸优化建模库。可以把它理解成:
- 用类似数学公式的方式,在 Python 里写优化问题;
- 把
变量目标函数约束条件写清楚; - 然后交给
CvxPy调用底层求解器solver,自动算出最优解。
🔧 一个简单的小例子
下面是一个“让 (x) 尽量接近 2,同时要求 (x )”的最小化问题:
1 | |
上面提到
CvxPy需要调用solver,为什么示例代码中没有体现?怎么调用求解器solver?选择求解器时需要注意什么?
下面将详细介绍 solver 的调用和选择。
1. CvxPy 中的求解器调用时的设置
- 在
CvxPy中,使用prob.solve()调用求解器。- 当前调用方法将会自动选择
solver。 CvxPy会根据问题的特性和定义的约束条件,自动选择一个合适的求解器来求解优化问题。
- 当前调用方法将会自动选择
CvxPy 自动选择的所谓的 “合适的求解器”** 真的就合适吗?CvxPy 是从哪些 solver 中选择的? 如果想手动指定求解器,应该怎么做?选择求解器时需要注意什么?**
关于上述问题,下面将展开叙述。
prob.solve()这种写法的默认参数是:1
2
3
4
5
6
7
8
9
10prob.solve(
solver=None,
verbose=False,
gp=False,
qcp=False,
requires_grad=False,
enforce_dpp=False,
ignore_dpp=False,
**kwargs
)其中,
当
solver=None时,CvxPy会自动根据问题类型和机器上安装的求解器选择一个“最合适”的 solver。比如:- QP(二次规划)问题 → 优先用
OSQP
- 二阶锥问题(SOCP) → 会选
CLARABEL这类锥规划求解器
- 更通用的凸问题 → 可以用
SCS兜底(几乎所有凸问题都能解)
也就是说:
- 如果想手动指定调用哪个
solver,可以通过prob.solve( solver = cp.SOLVER_NAME )来实现。 - 如果不写
solver=,CVXPY 一样会帮你选一个 solver 并调用,只是被“藏”在内部了。
想知道它到底选了谁,可以在求解后查看:
1
2
3prob.solve()
print(prob.solver_stats.solver_name) # 比如 'OSQP'、'CLARABEL'、'SCS' 等
print(prob.solver_stats.solve_time) # 求解耗时- QP(二次规划)问题 → 优先用
verbose=False:表示不输出求解器的详细日志信息。如果想查看求解过程中的日志,可以将其设置为True。gp=False:表示问题不是几何规划(Geometric Programming)。如果问题是几何规划,可以将其设置为True。qcp=False:表示问题不是二次锥规划(Quadratically Constrained Programming)。如果问题是二次锥规划,可以将其设置为True。requires_grad=False:表示不需要计算梯度信息。如果需要梯度信息,可以将其设置为True。enforce_dpp=False:表示不强制执行 DPP(Disciplined Parametrized Programming)规则。如果需要强制执行,可以将其设置为True。ignore_dpp=False:表示不忽略 DPP 规则。如果需要忽略,可以将其设置为True。**kwargs:keyword arguments,可以传递给求解器的其他参数,例如max_iters、abstol、reltol等,具体参数取决于所使用的求解器。
经过查验,自动选择的求解器确实是“合适的”,但有时也可能不是最优的选择。 而且,如果求解器的参数不进行设置,求解时就会采用默认的配置,导致求解效果不理想(比如收敛慢、精度不够等)。 因此,了解常见求解器的类型和特点,并根据具体问题手动选择和配置求解器是很有必要的。
2. 常见的求解器类型
使用方式统一是:
prob.solve(solver=cp.XXX, 参数名=...)
下面“常用参数”里提到的名字,都是可以直接写在solve()里的 keyword 参数。
2.1. 常见开源求解器
- OSQP(Operator Splitting Quadratic Program,算子分裂二次规划求解器)
- 问题类型:
主要用于 二次规划(Quadratic Programming, QP,二次规划):二次目标 + 线性约束的凸问题。
对大规模、稀疏的 QP 特别合适,比如最小二乘带约束、组合优化中的连续松弛等。 - 优点:
- 专门针对 QP,一阶迭代算法,对大型、稀疏问题很高效;
- 支持 warm start(热启动),适合滚动优化、模型预测控制等需要频繁重复求解的场景;
- 内存占用低,更适合在嵌入式或实时系统里用;
- 是 CVXPY 里遇到 QP 时常用的默认选择之一。
- 专门针对 QP,一阶迭代算法,对大型、稀疏问题很高效;
- 常用参数:
eps_abs:absolute tolerance,绝对容忍度(原/对偶残差在这个量级内就算收敛);
eps_rel:relative tolerance,相对容忍度;
max_iter:最大迭代次数,迭代过多时提前停止;
polish:是否启用解抛光/精修步骤,提高最终解的精度;
warm_start:是否热启动,利用上次求解的结果作为初始点。
- 问题类型:
- SCS(Splitting Conic Solver,分裂锥求解器)
- 问题类型:
通用 锥规划(conic programming,锥规划) 求解器,支持:- LP(线性规划)、QP(二次规划)、SOCP(二阶锥规划)、SDP(半定规划)等;
- 支持指数锥等特殊锥,因此可以处理很多“奇怪但凸”的问题。
- LP(线性规划)、QP(二次规划)、SOCP(二阶锥规划)、SDP(半定规划)等;
- 优点:
- 使用一阶分裂算法,可以处理非常大规模的锥规划问题;
- 支持稀疏矩阵、间接线性求解器,内存友好;
- 可以在 CPU 上并行,部分实现还支持 GPU;
- 精度通常不如内点法那么极致,但在工程上一般够用,适合追求规模和鲁棒性。
- 使用一阶分裂算法,可以处理非常大规模的锥规划问题;
- 常用参数:
max_iters:最大迭代次数;
eps或eps_abs/eps_rel:收敛容忍度(整体或绝对/相对);
alpha:松弛参数,调整算法收敛行为;
use_indirect:是否使用间接法(迭代线性求解器),适合大稀疏问题;
warm_start:是否热启动;
verbose:是否打印迭代日志。
- 问题类型:
- ECOS(Embedded Conic Solver,嵌入式锥求解器)
- 问题类型:
主要用于 LP(线性规划) 和 SOCP(二阶锥规划),支持一部分指数锥约束。
典型场景:二阶锥约束的凸优化,比如稳健优化、部分控制问题等。 - 优点:
- 原始-对偶内点法,精度高、收敛步数较少;
- 对中小规模 SOCP 和 LP 很稳定,是 CVXPY 的老牌默认求解器之一;
- 轻量级,适合嵌入式环境。
- 原始-对偶内点法,精度高、收敛步数较少;
- 常用参数:
abstol:absolute tolerance,绝对精度容忍度;
reltol:relative tolerance,相对精度容忍度;
feastol:feasibility tolerance,可行性容忍度;
max_iters:最大迭代次数;
verbose:是否输出迭代信息。
- 问题类型:
- ECOS_BB(ECOS Branch-and-Bound,ECOS 分支定界整数扩展)
- 问题类型:
在 ECOS 的基础上,通过 Branch-and-Bound(分支定界) 处理整数变量,
用于带整数变量的 MISOCP(Mixed-Integer SOCP,混合整数二阶锥规划) 和部分 MILP(混合整数线性规划) 问题。 - 优点:
- 完全开源,和 ECOS 一起安装好用,对于小中等规模的混合整数锥规划很方便;
- 和 CVXPY 集成紧密,声明整数变量后,直接选
solver=cp.ECOS_BB即可求解。
- 完全开源,和 ECOS 一起安装好用,对于小中等规模的混合整数锥规划很方便;
- 常用参数:
- 继承了 ECOS 的一些精度参数:
abstol、reltol、feastol等;
- 一些整数求解相关参数(不同版本名字不完全一样),例如:
- 最大节点数或最大迭代次数(控制搜索规模);
verbose:是否打印整数求解过程。
- 最大节点数或最大迭代次数(控制搜索规模);
- 整体上更适合作为“免费教学/原型工具”,大规模或高难整数问题建议考虑更强的 MIP 求解器。
- 继承了 ECOS 的一些精度参数:
- 问题类型:
- CLARABEL(Clarabel Conic Interior-Point Solver,通用锥内点法求解器)
- 问题类型:
支持广义锥规划:- LP(线性规划)、QP(二次规划)、SOCP(二阶锥规划)、SDP(半定规划)、指数锥、幂锥等;
- 适合结构丰富的通用凸优化问题。
- LP(线性规划)、QP(二次规划)、SOCP(二阶锥规划)、SDP(半定规划)、指数锥、幂锥等;
- 优点:
- 现代原始-对偶内点法,稳定性和精度都很好;
- 对包含二阶锥、半定锥的复杂问题表现好;
- 和 CVXPY 新版本集成紧密,是“通用凸问题”的推荐开源选择之一。
- 现代原始-对偶内点法,稳定性和精度都很好;
- 常用参数:
max_iter:最大迭代次数;
tol_feas:可行性容忍度;
tol_gap:原对偶间隙容忍度;
tol_kkt:KKT 条件残差容忍度;
verbose(或类似日志等级参数):控制日志输出。
- 问题类型:
- CVXOPT(CVXOPT Convex Optimization Library,凸优化库)
- 问题类型:
通用凸优化库,支持:- LP(线性规划)、QP(二次规划);
- SOCP(二阶锥规划)、SDP(半定规划)等。
通常用于小中规模问题和教学、科研实验。
- LP(线性规划)、QP(二次规划);
- 优点:
- 历史较久,文档和示例较多;
- 本身就是一个 Python 的凸优化库,用来理解“底层怎么解”的也很合适;
- 可以和 GLPK 等结合,扩展线性规划和整数规划能力。
- 历史较久,文档和示例较多;
- 常用参数(通常通过
solvers.options或在 CVXPY 里传下去):maxiters:最大迭代次数;
abstol:绝对精度;
reltol:相对精度;
feastol:可行性容忍度;
show_progress:是否显示迭代进度。
在 CVXPY 中可以类似这样调用:
prob.solve(solver=cp.CVXOPT, maxiters=100, abstol=1e-8, reltol=1e-8)。
- 问题类型:
- GLPK(GNU Linear Programming Kit,GNU 线性规划工具包)
- 问题类型:
专注于 LP(线性规划) 和 MILP(混合整数线性规划)。
不支持一般的二阶锥或半定约束,更适合纯线性问题。 - 优点:
- 完全开源,GPL 许可证,社区非常成熟;
- 对纯 LP / MILP 的性能和鲁棒性都不错,是免费 MIP 求解器中的常见选择;
- 可以通过 CVXPY 间接使用。
- 完全开源,GPL 许可证,社区非常成熟;
- 常用参数(在 CVXPY 中封装后名字可能简化):
time_limit:求解时间上限(防止卡太久);
max_iters或类似迭代上限参数:控制迭代次数;
mip_gap或allowableGap:最优间隙容忍度(控制整数优化精度与时间);
verbose:日志输出开关。
- 问题类型:
- CBC(COIN-OR Branch and Cut,COIN-OR 分支割求解器)
- 问题类型:
专门做 MILP(混合整数线性规划) 和部分 MIQP(混合整数二次规划),
适合有整数变量、约束主要为线性的优化问题。 - 优点:
- COIN-OR 项目下的开源 MIP 求解器,免费使用;
- 在 MILP 上表现良好,是开源世界里常用的分支割求解器;
- 可以通过安装
cvxpy[CBC]直接和 CVXPY 集成。
- COIN-OR 项目下的开源 MIP 求解器,免费使用;
- 常用参数:
time_limit或maximumSeconds:求解时间上限;
maxNodes:搜索的最大节点数,用来控制分支树规模;
mip_gap或allowableGap:最优间隙容忍度(控制精度和时间的权衡);
verbose:是否输出详细的 MIP 求解过程。
- 问题类型:
2.2. 常见商业/高性能求解器(通常需要许可证)
- MOSEK(MOSEK Optimization Suite,大规模凸优化与混合整数优化求解器)
- 问题类型:
支持大规模的 LP(Linear Programming,线性规划)、
QP(Quadratic Programming,二次规划)、
SOCP(Second-Order Cone Programming,二阶锥规划)、
SDP(Semidefinite Programming,半定规划)、
指数锥/幂锥问题以及 MIP(Mixed-Integer Programming,混合整数规划) 等。
对于连续凸优化(尤其是锥规划和半定规划)以及含少量整数变量的凸模型非常强。
- 优点:
- 专注于 凸优化 + 混合整数凸优化,内点法实现成熟、数值稳定性好;
- 在 SOCP / SDP / 指数锥等复杂锥问题上性能很突出,工业界和学术界都有大量应用;
- 提供丰富的接口(C / C++ / Java / .NET / Python / Julia / Rust / R / MATLAB 等)和建模工具;
- 和 CVXPY 集成良好,可以直接作为高精度锥规划/半定规划的首选 solver。
- 专注于 凸优化 + 混合整数凸优化,内点法实现成熟、数值稳定性好;
- 常用参数(在 CVXPY 中):
mosek_params:一个字典,用来传递 MOSEK 自己的参数,例如"MSK_IPAR_NUM_THREADS":使用的线程数;
"MSK_DPAR_INTPNT_CO_TOL_REL_GAP":内点法的相对间隙容忍度;
"MSK_DPAR_INTPNT_TOL_REL_GAP"等其他精度/收敛相关参数;
save_file:让 MOSEK 在优化前把问题写入一个文件(方便调试)。
- 问题类型:
- GUROBI(Gurobi Optimizer,高性能数学规划求解器)
- 问题类型:
支持 LP(线性规划)、QP(二次规划)、
QCP(Quadratically Constrained Programming,二次约束规划)、
MIP(混合整数规划)、MIQP(混合整数二次规划)、MIQCP(混合整数二次约束规划),
以及 SOCP 等多种凸/非凸问题,特别擅长大规模 混合整数模型。
- 优点:
- 在 MILP(Mixed-Integer Linear Programming,混合整数线性规划)、MIQP、MIQCP 等离散优化问题上业界公认的顶级性能;
- 内置多种算法:单纯形法、障碍法、并行分支定界/分支切平面等,支持多线程并行计算;
- 参数系统非常丰富,可细致控制预处理、切平面、启发式、并行策略等;
- 有良好的 Python 接口,CVXPY 直接支持调用。
- 在 MILP(Mixed-Integer Linear Programming,混合整数线性规划)、MIQP、MIQCP 等离散优化问题上业界公认的顶级性能;
- 常用参数(在 CVXPY 中作为 keyword 传入):
TimeLimit:时间上限(秒),到达该时间即停止求解;
MIPGap:混合整数最优间隙(relative gap),用来控制整数问题的精度/时间权衡;
Threads:求解时使用的线程数;
Presolve:预处理级别(0/1/2);
MIPFocus:MIP 求解策略倾向(更快找可行解 / 更快收窄 gap 等);
OutputFlag:是否输出详细日志(0 关 / 1 开)。
- 问题类型:
- CPLEX(IBM ILOG CPLEX Optimizer,高性能数学规划求解器)
- 问题类型:
支持 LP(线性规划)、QP(二次规划)、
QCP(二次约束规划) 以及上述的 MIP / MIQP / MIQCP 等混合整数扩展。
在传统工业运筹优化领域(排产、选址、网络设计等)应用非常广泛。
- 优点:
- 在 LP / MIP / QP 等经典数学规划问题上的性能和鲁棒性都非常好;
- 提供丰富的建模接口(C / C++ / Java / .NET / Python / OPL 等),与 IBM 的决策优化生态配套完善;
- 支持并行求解、网络流特化算法等许多高级特性;
- CVXPY 可以通过
solver=cp.CPLEX调用。
- 在 LP / MIP / QP 等经典数学规划问题上的性能和鲁棒性都非常好;
- 常用参数(在 CVXPY 中):
cplex_params:一个字典,用于传递 CPLEX 自己的参数,例如"timelimit":求解时间上限(秒);
"mip.tolerances.mipgap":整数规划最优间隙;
"threads":线程数;
"barrier.convergetol":障碍法收敛容忍度等。
- 问题类型:
如何在 cvxpy 中选择
- 若是 QP(你目前的情况:二次目标 + 线性约束):
- 首选 OSQP(稀疏、支持 warm start)。若对精度或数值稳定性有更高要求,尝试 Gurobi 或 MOSEK(商业)。
- 若是大型近似解/第一阶方法可接受:
- SCS(大规模、快速但精度有限)。
- 若问题含二次锥(SOCP):
- ECOS 或 MOSEK 通常更合适。
- 若需混合整数(MIP):
- Gurobi、CPLEX、CBC(开源)等。
3. 求解器状态
求解完成后,可以通过 prob.status 查看求解状态。常见 Problem.status 值包括:
optimal:求解成功,发现了满足 KKT(或可接受精度)的最优解。optimal_inaccurate:找到了可用解但精度/数值不完全满足严格标准(solver 仍返回次优/不精确的最优)。infeasible:问题无可行解(硬约束冲突)。infeasible_inaccurate:认为可能不可行,但结论不完全可靠(数值不稳定)。unbounded:目标可无限改进(问题无界)。unbounded_inaccurate:可能无界,但结论不完全可靠。infeasible_or_unbounded/unbounded_or_infeasible(solver 有时给出这种模糊结论)- solver 无法区分无界和不可行的情况。
user_limit:求解被“用户限制”中断(见下文详细说明)。solver_error:求解器运行出错(内部异常、参数不支持、数值崩溃等)。unknown/other solver-specific状态- 某些 solver 会返回特定状态,cvxpy 会尽量映射或保留原始信息。
我在求解时就遇到返回
user_limit的情况,具体是什么原因?
可能原因如下:
- 达到用户/调用传入的资源上限(如
time limit、max iterations、node limit等)。 - 用户在外部中断了程序(比如手动停止进程)。
- 有些求解器在内部把“达到最大迭代/时间”映射为
user limit。
排查步骤:
- 打印并查看
problem.solver_stats(包含迭代次数、实际用时等)。 - 在
problem.solve(...)中开启verbose:problem.solve(solver=cp.OSQP, verbose=True),观察solver的终止消息。 - 检查传给
solver的选项(是否设置了max_iter,time_limit等)。 - 检查是否有外部中断(脚本被杀、环境限制、IDE 停止等)。
- 查看
x.value与problem.value:有时即使被中断,solver也返回了“当前最优候选解”,你可以选择使用它而不是返回全零。
应对建议:
- 如果是迭代/时间限制导致:
增加
solver的迭代或时间上限(例如 OSQP 的max_iter,或其他solver的time_limit/max_iters),例如:1
problem.solve(solver=cp.OSQP, max_iter=200000, verbose=True)改用更适合该问题类型的
solver。
- 如果问题经常
infeasible或数值不稳定:- 放松硬约束为软约束。
- 对变量/数据做适当缩放或添加小的松弛量。
- 实用处理策略:
- 对
user_limit情况,不要直接返回全零;改为:若x.value可用则返回当前解并记录警告/提高后续求解资源;示例逻辑:- if problem.status == “user_limit” and x.value is not None: use x.value as solution (并把 status 写日志)
- 记录
problem.solver_stats并基于统计调整solver参数或改成软约束。
- 对