在 Python 中调用 CvxPy 进行凸优化

本文通过在 Python 中调用 CvxPy 进行凸优化时遇到的问题,介绍使用 CvxPy 进行凸优化的基本步骤和注意事项。➡️


❓ 问题引出

💭 问题:什么是 CvxPy

CvxPy 是一个基于 Python凸优化建模库。可以把它理解成:

  • 用类似数学公式的方式,在 Python 里写优化问题;
  • 变量 目标函数 约束条件 写清楚;
  • 然后交给 CvxPy 调用底层求解器 solver,自动算出最优解。

🔧 一个简单的小例子

下面是一个“让 (x) 尽量接近 2,同时要求 (x )”的最小化问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import cvxpy as cp

# 定义变量
x = cp.Variable()

# 定义目标函数:minimize (x - 2)^2
objective = cp.Minimize((x - 2)**2)

# 定义约束:x ≥ 0
constraints = [x >= 0]

# 组合成一个问题并求解
prob = cp.Problem(objective, constraints)
prob.solve()

print("最优目标值:", prob.value)
print("最优解 x:", x.value)

上面提到 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
    10
    prob.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
      3
      prob.solve()
      print(prob.solver_stats.solver_name) # 比如 'OSQP'、'CLARABEL'、'SCS' 等
      print(prob.solver_stats.solve_time) # 求解耗时
    • 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

    • **kwargskeyword arguments,可以传递给求解器的其他参数,例如 max_itersabstolreltol 等,具体参数取决于所使用的求解器。

经过查验,自动选择的求解器确实是“合适的”,但有时也可能不是最优的选择。 而且,如果求解器的参数不进行设置,求解时就会采用默认的配置,导致求解效果不理想(比如收敛慢、精度不够等)。 因此,了解常见求解器的类型和特点,并根据具体问题手动选择和配置求解器是很有必要的。


2. 常见的求解器类型

使用方式统一是:
prob.solve(solver=cp.XXX, 参数名=...)
下面“常用参数”里提到的名字,都是可以直接写在 solve() 里的 keyword 参数。

2.1. 常见开源求解器

  • OSQP(Operator Splitting Quadratic Program,算子分裂二次规划求解器)
    • 问题类型:
      主要用于 二次规划(Quadratic Programming, QP,二次规划):二次目标 + 线性约束的凸问题。
      对大规模、稀疏的 QP 特别合适,比如最小二乘带约束、组合优化中的连续松弛等。
    • 优点:
      • 专门针对 QP,一阶迭代算法,对大型、稀疏问题很高效;
      • 支持 warm start(热启动),适合滚动优化、模型预测控制等需要频繁重复求解的场景;
      • 内存占用低,更适合在嵌入式或实时系统里用;
      • 是 CVXPY 里遇到 QP 时常用的默认选择之一。
    • 常用参数:
      • eps_abs:absolute tolerance,绝对容忍度(原/对偶残差在这个量级内就算收敛);
      • eps_rel:relative tolerance,相对容忍度;
      • max_iter:最大迭代次数,迭代过多时提前停止;
      • polish:是否启用解抛光/精修步骤,提高最终解的精度;
      • warm_start:是否热启动,利用上次求解的结果作为初始点。
  • SCS(Splitting Conic Solver,分裂锥求解器)
    • 问题类型:
      通用 锥规划(conic programming,锥规划) 求解器,支持:
      • LP(线性规划)、QP(二次规划)、SOCP(二阶锥规划)、SDP(半定规划)等;
      • 支持指数锥等特殊锥,因此可以处理很多“奇怪但凸”的问题。
    • 优点:
      • 使用一阶分裂算法,可以处理非常大规模的锥规划问题;
      • 支持稀疏矩阵、间接线性求解器,内存友好;
      • 可以在 CPU 上并行,部分实现还支持 GPU;
      • 精度通常不如内点法那么极致,但在工程上一般够用,适合追求规模和鲁棒性。
    • 常用参数:
      • max_iters:最大迭代次数;
      • epseps_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 的一些精度参数:abstolreltolfeastol 等;
      • 一些整数求解相关参数(不同版本名字不完全一样),例如:
        • 最大节点数或最大迭代次数(控制搜索规模);
        • verbose:是否打印整数求解过程。
      • 整体上更适合作为“免费教学/原型工具”,大规模或高难整数问题建议考虑更强的 MIP 求解器。
  • CLARABEL(Clarabel Conic Interior-Point Solver,通用锥内点法求解器)
    • 问题类型:
      支持广义锥规划:
      • LP(线性规划)、QP(二次规划)、SOCP(二阶锥规划)、SDP(半定规划)、指数锥、幂锥等;
      • 适合结构丰富的通用凸优化问题。
    • 优点:
      • 现代原始-对偶内点法,稳定性和精度都很好;
      • 对包含二阶锥、半定锥的复杂问题表现好;
      • 和 CVXPY 新版本集成紧密,是“通用凸问题”的推荐开源选择之一。
    • 常用参数:
      • max_iter:最大迭代次数;
      • tol_feas:可行性容忍度;
      • tol_gap:原对偶间隙容忍度;
      • tol_kkt:KKT 条件残差容忍度;
      • verbose(或类似日志等级参数):控制日志输出。
  • CVXOPT(CVXOPT Convex Optimization Library,凸优化库)
    • 问题类型:
      通用凸优化库,支持:
      • LP(线性规划)、QP(二次规划);
      • SOCP(二阶锥规划)、SDP(半定规划)等。
        通常用于小中规模问题和教学、科研实验。
    • 优点:
      • 历史较久,文档和示例较多;
      • 本身就是一个 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 间接使用。
    • 常用参数(在 CVXPY 中封装后名字可能简化):
      • time_limit:求解时间上限(防止卡太久);
      • max_iters 或类似迭代上限参数:控制迭代次数;
      • mip_gapallowableGap:最优间隙容忍度(控制整数优化精度与时间);
      • verbose:日志输出开关。
  • CBC(COIN-OR Branch and Cut,COIN-OR 分支割求解器)
    • 问题类型:
      专门做 MILP(混合整数线性规划) 和部分 MIQP(混合整数二次规划)
      适合有整数变量、约束主要为线性的优化问题。
    • 优点:
      • COIN-OR 项目下的开源 MIP 求解器,免费使用;
      • 在 MILP 上表现良好,是开源世界里常用的分支割求解器;
      • 可以通过安装 cvxpy[CBC] 直接和 CVXPY 集成。
    • 常用参数:
      • time_limitmaximumSeconds:求解时间上限;
      • maxNodes:搜索的最大节点数,用来控制分支树规模;
      • mip_gapallowableGap:最优间隙容忍度(控制精度和时间的权衡);
      • 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 直接支持调用。
    • 常用参数(在 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 调用。
    • 常用参数(在 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 limitmax iterationsnode limit 等)。
  • 用户在外部中断了程序(比如手动停止进程)。
  • 有些求解器在内部把“达到最大迭代/时间”映射为 user limit

排查步骤:

  1. 打印并查看 problem.solver_stats(包含迭代次数、实际用时等)。
  2. problem.solve(...) 中开启 verboseproblem.solve(solver=cp.OSQP, verbose=True),观察 solver 的终止消息。
  3. 检查传给 solver 的选项(是否设置了 max_iter, time_limit 等)。
  4. 检查是否有外部中断(脚本被杀、环境限制、IDE 停止等)。
  5. 查看 x.valueproblem.value:有时即使被中断,solver 也返回了“当前最优候选解”,你可以选择使用它而不是返回全零。

应对建议:

  • 如果是迭代/时间限制导致:
    • 增加 solver 的迭代或时间上限(例如 OSQP 的 max_iter,或其他 solvertime_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 参数或改成软约束。

在 Python 中调用 CvxPy 进行凸优化
https://nanana-szz.github.io/CvxPy/
作者
John Doe
发布于
2025年11月19日
许可协议