news 2026/9/16 2:16:40

CPLEX从LP到MIP:十案例掌握混合整数规划与工程建模

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CPLEX从LP到MIP:十案例掌握混合整数规划与工程建模

简介:IBM ILOG CPLEX是业界领先的优化求解器,适用于线性规划、整数规划、二次规划及混合整数规划等场景。10个循序渐进的编程案例覆盖从简单线性规划、运输问题到整数规划、二次规划、混合整数规划,再到约束编程、多阶段决策、参数调整、灵敏度分析与API建模应用,帮助运筹优化初学者和工程技术人员理解CPLEX的建模与求解流程。压缩包共361个文件,以实例文件、数据文件和模型工程为主,涵盖dat、mod、oplproject等CPLEX OPL项目格式,另有xml、launch等辅助配置文件,整体仅760KB,轻量易用。目前已有456人学习下载。通过逐个案例的步骤解析、代码示例、结果分析与改进策略,读者能系统掌握变量、约束、目标函数的构建方法,并学会根据不同问题特性调整求解参数,进而灵活应用到实际资源分配与生产计划中。

1. 一套案例拆完 CPLEX:从 LP 到 MIP 再处处是坑的工程建模

一家制造企业同时面对订单、产能、原料三张表:排产吨数是连续变量,设备开不开是 0-1 变量,这就成了混合整数规划。IBM ILOG CPLEX 是这类问题的工业级求解器,能在几十万约束的问题上快速给出最优解与最优性证明。这套 10 个编程案例从线性规划、运输问题、整数规划、二次规划,一路讲到混合整数规划、约束编程、多阶段决策、参数调整、灵敏度分析和 API 建模。适合刚接触运筹优化的 Python 用户建立建模手感,也适合在 MATLAB 里配过 Gurobi、现在要把 CPLEX 接入工程的开发者;前四个案例解决怎么建模型,后六个解决模型为什么慢、解出来怎么解释。

2. 案例一与案例二:线性规划和运输问题的建模底座

先把环境问题解决,再谈建模。CPLEX 的安装分成求解器引擎和编程接口两层,很多教程只提 pip install,结果用户跑起来才发现引擎根本没配好。安装过程的正确顺序,直接影响后面十个案例能不能复现。

2.1 安装、下载与许可证校验

2.1.1 在 Anaconda 里集成 CPLEX

大多数项目我习惯用 Anaconda 管理 Python 环境,把 CPLEX 求解器引擎和 Python API 分开装:求解器引擎是二进制程序,API 只是外壳。从官网下载 CPLEX 安装包时选对平台,Windows 就选 Windows-x86-64,Linux 服务器选 Linux-x86-64;如果只用 Python 建模,直接通过 conda 小宝库装 IBM 维护的包更快:

conda install -c ibmdecisionoptimization cplex pip install docplex

先装 cplex 再装 docplex,两者版本要匹配。conda 源里的 cplex 会把求解器引擎一起带进来,docplex 是高层建模库,代码里同时用到import cplexfrom docplex.mp.model import Model时,实际依赖关系是 docplex 调用 cplex 的 Python API。我在内网环境部署过这套组合,离线安装需要提前把缓存目录准备好,运行时不会联网校验。

逻辑说明:conda 安装的是引擎包,pip 安装的是建模 API。参数方面,如果没有 conda 环境权限,可以去 IBM 官网下完整安装包,但安装路径不要带空格和中文,否则后续动态库加载会报出诡异的依赖错误。

2.1.2 许可证校验

装好之后先验证 license,这一步能排除后面一半的疑难杂症:

import cplex print(cplex.Cplex().get_version())

能打印出版本号说明引擎和 Python 接口都通了。如果报 license 相关错误,检查环境变量ILOG_LICENSE_FILECPLEX_STUDIO_DIR是否指向许可证目录。常见做法是运行安装目录下的许可证配置程序重新设置,或者把 license 文件放到默认的~/cplex下。这一步过不去,后续建模代码写得再漂亮也跑不起来。

注意:CPLEX 安装目录包含空格时,部分版本的动态库加载会失败,建议装到纯英文路径。

2.2 案例一:简单线性规划的目标式写法

线性规划是所有案例的底座:目标函数和约束都是线性的,决策变量连续。第一个案例我通常写成生产能力匹配问题,两种产品共享库存和工时。

from docplex.mp.model import Model m = Model("lp_production") x1 = m.continuous_var(name="P1", lb=0) x2 = m.continuous_var(name="P2", lb=0) m.maximize(2 * x1 + 3 * x2) m.add_constraint(x1 + x2 <= 12, "material") m.add_constraint(3 * x1 + x2 <= 30, "labor") info = m.solve() if info: print(x1.solution_value, x2.solution_value, m.objective_value)

逻辑说明:continuous_var创建非负连续变量,maximize设定目标方向,add_constraint的第二个参数给约束命名。solve()返回值包含求解状态,打印出来的是每个变量的取值和最优目标值。

参数说明:lb=0是默认下界,如果要允许变量为负,要显式设lb=-cplex.infinity。上面例子是最简单的三条约束两条线,CLP 算法直接得到顶点解,无需分支。把第二个约束下界改成 28 再跑,可行域会发生变化,最优解也落在另一个顶点上,这就是单纯形法的几何直觉。

2.3 案例二:运输问题与网络流建模

运输问题是运筹学的经典模型:多个产地、多个需求地,求最小运输成本。网络流模型的核心是节点平衡约束,比线性规划更贴近业务,因为约束结构里自带"流入等于流出"的语义。

2.3.1 成本矩阵与供需约束

假设两个产地 A1、A2,三个需求地 B1、B2、B3,产量和需求如下表:

产地\需求地B1B2B3产量
A18610300
A29127200
需求量200150150500

这里总产量等于总需求量,是平衡运输问题。如果不平衡,需要引入虚拟产地或虚拟需求地,把不等约束变成等式约束,这是网络流建模里最常见的坑。

2.3.2 字典建模与循环约束
from docplex.mp.model import Model supply = {"A1": 300, "A2": 200} demand = {"B1": 200, "B2": 150, "B3": 150} cost = { ("A1", "B1"): 8, ("A1", "B2"): 6, ("A1", "B3"): 10, ("A2", "B1"): 9, ("A2", "B2"): 12, ("A2", "B3"): 7, } m = Model("transport") x = m.continuous_var_dict(cost.keys(), lb=0) for a in supply: m.add_constraint(m.sum(x[a, b] for b in demand) == supply[a], f"supply_{a}") for b in demand: m.add_constraint(m.sum(x[a, b] for a in supply) == demand[b], f"demand_{b}") m.minimize(m.sum(cost[i] * x[i] for i in cost.keys())) m.solve() print(m.objective_value)

逻辑说明:continuous_var_dict给每一对 (产地, 需求地) 创建一个运量变量;第一个循环加供给约束,第二个循环加需求约束。目标函数用sum把运价和运量相乘累加,minimize表明这是成本最小化问题。

参数说明:这里==写成等式约束是因为产销平衡;不平衡时,产量大于需求量的产地用<=,需求量大于产量的需求地用<=,方向不能搞反。这个模型跑完的最优值是 3850,可以手工用最小元素法验算,能看出西北角法给出的初始解和最优解的差距。

3. 案例三与案例五:整数规划与混合整数规划的变量设计

整数规划处理的问题是"决策变量只能取整数值"。第一个误区是把它当线性规划直接求,得到分数解;第二个误区是把分数解四舍五入,结果约束被破坏。这一章用两个案例把整数变量和 0-1 变量的建模差异拆开。

3.1 案例三:先看 LP 松弛再看整数解

假设三个容器,容量分别为 5、8、6,要装下总重量 20,每个容器只能整体装满。先无视整数约束求 LP 松弛:

from docplex.mp.model import Model m = Model("ip_bin") x1 = m.continuous_var(name="x1", lb=0) x2 = m.continuous_var(name="x2", lb=0) x3 = m.continuous_var(name="x3", lb=0) m.minimize(x1 + x2 + x3) m.add_constraint(5 * x1 + 8 * x2 + 6 * x3 == 20) m.solve() print(x1.solution_value, x2.solution_value, x3.solution_value)

逻辑说明:目标是最小化使用的容器个数,LP 松弛会给出分数解,比如 x1 接近 1.2、x2 等于 1、x3 接近 0.666。这时候四舍五入会让等式约束不再成立,必须用整数变量。

把变量声明换成整数变量:

x1 = m.integer_var(name="x1", lb=0) x2 = m.integer_var(name="x2", lb=0) x3 = m.integer_var(name="x3", lb=0)

整数变量声明之后,CPLEX 内部自动切换为分支定界算法。注意观察求解日志,多出NodesBest Integer两列,记录的是分支搜索节点数和当前最优整数解。如果问题规模大,不能直接穷举,要靠 cut 生成去裁剪搜索树。

参数说明:整数变量用integer_var,0-1 变量用binary_var。0-1 变量是整数变量的特例,但约束矩阵裁剪效果差异很大,后面 MIP 案例会看到。

3.2 案例五:混合整数规划中的设备选择问题

生产计划场景里最常见的模型:选择哪些设备开工,一旦开工就有固定成本,连续变量表示产量。设备 A 和设备 B 的产能分别是 40 和 30,固定成本分别是 1000 和 800,单位变动成本分别为 2 和 3,总需求是 50。

from docplex.mp.model import Model m = Model("mip_equipment") A = m.binary_var(name="use_A") B = m.binary_var(name="use_B") a = m.continuous_var(name="ton_A", lb=0) b = m.continuous_var(name="ton_B", lb=0) m.add_constraint(a <= 40 * A, "cap_A") m.add_constraint(b <= 30 * B, "cap_B") m.add_constraint(a + b >= 50, "total_demand") m.minimize(1000 * A + 800 * B + 2 * a + 3 * b) m.solve() print(A.solution_value, B.solution_value, a.solution_value, b.solution_value)

逻辑说明:binary_var表示开关,a <= 40 * A是关键约束,A=0 时产量被压到 0,A=1 时产量上限为 40。这叫大 M 约束,M 取产能上限。固定成本进入目标函数,变动成本乘连续变量,这就是混合整数规划的基本范式。

参数说明:大 M 的取值要尽量紧。取 40 而不是 100000,因为 LP 松弛后的可行域更小,分支定界收敛更快。这个模型解出来是 B 开满 30 吨、A 开 20 吨,总成本 1840。如果不用大 M 约束,可行域里会包含"不开机也生产"的不合理解,求解器不会感知固定成本的存在。

变量类型API适用场景求解算法
连续变量continuous_var产量、流量、仓位单纯形
整数变量integer_var台数、件数分支定界
0-1 变量binary_var开关、选址、分配分支定界 + cut

注意:大 M 约束的 M 值过大会拖慢分支定界收敛,建议取业务上的真实上限,而不是随手写一个 100000。

4. 案例四与案例六:二次规划和约束编程的适用边界

线性模型之外,还有两类问题经常出现:目标里有变量的平方项,约束里有一堆"互斥、排序"逻辑。前者对应二次规划,后者对应约束编程,两条技术路线的选型逻辑完全不同。

4.1 案例四:二次规划的目标式与凸性

二次规划的目标函数包含变量的二次项。风险平价组合优化是最典型的例子:投资组合方差最小化,权重和为 1,单个权重有上下限。

from docplex.mp.model import Model m = Model("qp_portfolio") x1 = m.continuous_var(name="w1", lb=0) x2 = m.continuous_var(name="w2", lb=0) m.add_constraint(x1 + x2 == 1) m.minimize(0.5 * (x1 * x1 + 2 * 0.3 * x1 * x2 + x2 * x2)) m.solve() print(x1.solution_value, x2.solution_value)

逻辑说明:目标里x1*x1等二次项直接写,CPLEX 求解前会做凸性检测。凸二次规划有唯一全局最优解;非凸问题需要额外处理,否则可能会得到局部最优或者直接报错。

参数说明:如果目标写成minimize(x1*x2)这类交叉项,默认求解器可能提示目标非凸。检查方式是把二次项系数矩阵做特征值分解,全为正就是正定凸问题。实际工程中更常见的做法是对协方差矩阵做处理,把特征值钳到正数,这比调求解器参数省事得多。

import numpy as np cov = np.array([[1.0, 0.3], [0.3, 1.0]]) ev = np.linalg.eigvalsh(cov) assert ev.min() > 0, "covariance must be positive definite"

这段代码用于在建模前验证二次项的凸性,避免把问题丢给求解器后才发现模型本身不可解。特征值检查和建模代码是两层关注点:前者验证数据,后者表达优化逻辑。

4.2 案例六:约束编程处理逻辑约束

约束编程(CP)和数学规划(MP)思路完全不同:MP 用不等式和目标函数表达问题,CP 用变量域和全局约束表达问题。排班、路径、装箱类问题,约束关系往往是"谁和谁不能同时做""这些位置必须各不相同",写成 MIP 需要大量 0-1 变量,写成 CP 一个all_different就解决。

from docplex.cp.model import CpoModel m = CpoModel() tasks = m.integer_var_list(4, 1, 10, "task") m.add(m.all_different(tasks)) m.add(m.element(tasks, 0) + 5 <= m.element(tasks, 2))

逻辑说明:integer_var_list创建四个取值 1 到 10 的整数变量;all_different保证两两不同;element按索引取变量当前值,构成长短逻辑约束。CP 求解器用约束传播维护每个变量的值域,遇到矛盾就回溯。

参数说明:CP 的变量可以是不连续的整数域。当问题里大量出现互斥、排序逻辑时,优先考虑 CP;当目标是线性函数且规模巨大时,MP 通常更高效。CPLEX 同时提供两个引擎,分别用docplex.mpdocplex.cp模块导入,不需要额外装求解器。

维度数学规划 MP约束编程 CP
变量表达线性目标 + 线性/二次约束任意变量域与全局约束
强项大规模连续与线性结构排序、互斥、强逻辑约束
求解机制分支定界 / 单纯形约束传播 + 回溯
典型场景生产计划、物流网络排班、装箱、路径规划

有的复杂项目会混合建模:外层 MIP 选设备,内层 CP 排班,两个模型之间用双阶段接口交换解。这种模式在 10 个案例里没有直接展开,但参数调整一章的思路完全可以套用。

5. 案例七、八、九:参数调整、灵敏度分析与多阶段决策

这三个案例偏工程:模型已经建好,下一步是"怎么算得快"和"解出来的东西怎么解读"。对 5 年以上的人来说,这章才是从会用到会用好的分水岭。

5.1 案例八:按问题特性调整求解参数

不同问题适合不同算法:LP 用单纯形,运输网络用网络单纯形或 barrier,MIP 则依赖预处理、cut 生成和启发式。多数情况下默认参数就好,但遇到长时间不收敛时,我一般优先调整下面几个参数:

参数作用常见设置
TimeLimit硬性求解时限60~600 秒
MIPFocus控制搜索策略偏向可行解还是最优性1 偏向可行,2 偏向最优,3 兼顾
EpGap最优间隙阈值0.01 表示找到 1% 以内的解就停
Threads并行线程数4~8,超过核数反而慢
PreInd是否启用预处理默认开,模型病态时关闭

cplex模块中直接设置:

import cplex cpx = cplex.Cplex() cpx.set_log_stream(None) cpx.parameters.timelimit.set(60) cpx.parameters.mip.tolerances.mipgap.set(0.01) cpx.parameters.threads.set(4) cpx.parameters.mip.strategy.search.set(cpx.parameters.mip.strategy.search.values.dynamic)

逻辑说明:set_log_stream(None)关掉日志刷屏,参数逐个设置。mipgap是相对最优间隙,找可行解阶段把它放宽,求解时间能降一个数量级;进入验证阶段再收回到 0.01%。

参数说明:mip.strategy.search.dynamic是较新版本 CPLEX(比如 20.x 系列)推荐的分支策略,对不规则模型比固定探针法更友好。注意mipgapTimeLimit是竞争关系,谁先满足谁停。调参时一次只改一个参数,否则无法定位是哪个设置起的作用。

5.2 案例九:LP 模型看对偶,MIP 模型看场景

灵敏度分析在 LP 中的含义很明确:最优解下,某个约束右端项变化一个单位,目标值的变化量是对偶价格;某个非基变量成本变化到哪个区间,最优基保持稳定。这些通过求解后的对偶值直接读:

# 在 LP 求解成功后访问对偶价格 for cn in m.iter_constraints(): print(cn.name, cn.dual_value) for v in m.iter_variables(): print(v.name, v.reduced_cost)

这里的m是前面任一已求解的 LP 或 QP 模型。逻辑说明:dual_value是每单位 RHS 变化的目标边际影响,reduced_cost是让变量进入最优基需要降低成本的下限。生产计划里看到人工工时约束的对偶价格高,说明瓶颈在这里,扩产应该优先改这组约束。

参数说明:MIP 模型没有传统意义的 dual_value,这时候灵敏度分析退化为场景推演:把某个参数上调 5% 重跑一遍,对比目标值和变量取值。我通常写一个小循环,批量改约束的右端项,记录目标值变化曲线,判断解是否稳健。对多阶段决策问题,这个场景推演思路可以直接扩展成随机规划。

5.3 案例七:多阶段决策与随机规划的确定性等价

多阶段问题呈现决策树的形状,最简单的是两阶段随机规划:今天做一次性决策,明天某个随机事件发生后做调整决策,目标是两者期望成本最小。下面是一个缩微例子,两个等概率场景:

from docplex.mp.model import Model m = Model("two_stage") x = m.integer_var(name="first_stage", lb=0, ub=10) y1 = m.integer_var(name="y_scene1", lb=0, ub=5) y2 = m.integer_var(name="y_scene2", lb=0, ub=8) m.add_constraint(x + y1 >= 8) m.add_constraint(x + y2 >= 11) m.minimize(3 * x + 0.5 * (2 * y1 + 4 * y2)) m.solve() print(x.solution_value, y1.solution_value, y2.solution_value)

逻辑说明:第一阶段变量x在两个场景中取值相同,场景二阶段变量各自取各自的值,这就是确定性等价模型。CPLEX 对这类稀疏大模型优先开 barrier,因为场景之间耦合度低,矩阵接近块对角。

参数说明:场景数量超过 50 个后,直接把全部场景展开往往内存吃紧,常见做法是用 Benders 分解把场景切分到子问题中。CPLEX 支持自动 Benders 切分,通过参数benders.strategy控制:0 关闭,1 自动,2 手动做场景聚合。案例七提供的是多阶段决策的建模骨架,实际项目里场景概率通常来自历史数据或蒙特卡洛抽样。

6. 案例十:模型构建与 API 使用的高效套路

最后一个案例把前面所有建模经验收拢成一个可复用模板。重点不是写一个能跑的 demo,而是让你换数据、换问题结构时不重写代码。

6.1 可复用的构建模板与模型导出

我习惯把求解逻辑抽象成六个步骤:数据、变量、约束、目标、求解、结果解析。下面这个函数复用了十几次,换数据不换代码:

from docplex.mp.model import Model def solve_production(data): m = Model("production_solver") x = m.continuous_var_dict(data["products"], lb=0) for r, rhs in data["resources"].items(): m.add_constraint( m.sum(data["usage"][r][p] * x[p] for p in data["products"]) <= rhs ) m.maximize(m.sum(data["profit"][p] * x[p] for p in data["products"])) if m.solve(): return m.objective_value, {p: x[p].solution_value for p in data["products"]} raise ValueError(f"solve failed: {m.solve_status}") m.export_as_lp("/tmp/model_debug.lp") m.refine_conflict()

逻辑说明:函数只依赖data字典,变量字典、约束循环、目标求和全部抽象成模板。export_as_lp导出模型文件,用文本编辑器肉眼检查约束系数有没有配错;refine_conflict在模型不可行时返回冲突约束的最小集合。

参数说明:模型不可行时,先看冲突集合再动手改数据,比盲目放宽约束高效得多。solve_status里的infeasibleunbounded是两条完全不同的调试路径:前者是约束矛盾,后者是目标方向或变量下界写错。

6.2 多求解器共存时的环境取舍

环境里同时装了 Gurobi 和 CPLEX 并不冲突,两者是独立的动态库和 Python 包,import cplex只会走 CPLEX 的 site-packages。真正会出问题的是 PATH 和 LD_LIBRARY_PATH 被不同安装包的动态库污染。确认当前 Python 实际加载的是哪个包:

import cplex print(cplex.__file__) print(cplex.__version__)

如果打印出的路径不是想用的版本,把 site-packages 里多余的 cplex 目录清掉,或者用PYTHONPATH精确指定目录。MATLAB 里关联了 Gurobi 之后,再装 CPLEX 一样可行,只需在 MATLAB 的startup.m里设置 CPLEX 路径,两者通过不同的 Java 包隔离。关键是不要让两个环境同时修改同一个 Python 的 site-packages。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 2:16:30

STM32直流电机速度闭环PID控制与C#上位机可视化调参实战

简介&#xff1a;面向嵌入式与自动化控制开发者&#xff0c;特别是对电机控制与上位机联调感兴趣的工程师&#xff0c;这套方案结合C#串口上位机与STM32下位机&#xff0c;对直流有刷电机PID速度单闭环&#xff08;位置式PID&#xff09;调参与运行过程进行了深度解析。资源共9…

作者头像 李华
网站建设 2026/9/16 2:16:03

CNN经典架构解析与实战开发指南

1. 卷积神经网络&#xff08;CNN&#xff09;进阶&#xff1a;经典架构解析与实战开发在计算机视觉领域&#xff0c;卷积神经网络&#xff08;CNN&#xff09;已经成为图像识别、分类等任务的标准解决方案。从最早的LeNet到如今的ResNet、EfficientNet等架构&#xff0c;CNN的发…

作者头像 李华
网站建设 2026/9/16 2:16:01

Wazuh部署避坑全指南:从快装脚本到Agent连接验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 2:14:59

Redis入门到实战:数据类型、持久化与缓存架构详解

我记得第一次认认真真把 Redis 用起来&#xff0c;跟 Redis 本身没什么关系&#xff0c;是被一个接口慢查询逼的。表里就几万条数据&#xff0c;MySQL 查询也走了索引&#xff0c;但接口平均响应时间还是到了 800 多毫秒。后来查了半天&#xff0c;发现是每次请求都在重复查同一…

作者头像 李华
网站建设 2026/9/16 2:14:39

IEEE期刊LaTeX模板深度排版指南:参考文献、图表与编译链避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 2:14:19

R语言实现IPDW空间插值:地形与方向感知的地理加权方法

简介&#xff1a;本资源是一份面向地理信息科学、环境统计与R语言空间分析初学者的实战代码包&#xff0c;聚焦反距离加权&#xff08;IDW&#xff09;插值方法在R中的工程化实现&#xff0c;解决空间离散点数据向连续表面建模的核心问题。压缩包为ZIP格式&#xff0c;共含1个R…

作者头像 李华