HiGHS 线性规划求解器实战:从 20 行建模代码到生产级性能调优
【免费下载链接】HiGHSLinear optimization software项目地址: https://gitcode.com/GitHub_Trending/hi/HiGHS
HiGHS 线性规划求解器是一个 MIT 协议的开源 C++ 求解器,同时覆盖线性规划(LP)、凸二次规划(QP)和混合整数规划(MIP),内置 presolve(求解前自动削减问题规模的过程)、单纯形法、内点法和分支定界框架,不依赖任何第三方库。本文以 Python 接口为主线,从跑通第一个模型讲起,把建模、求解器选型、性能调优和生产环境中的模型交换逐一过一遍,源码结构见highs/主目录,示例代码集中在examples/目录。
一条产线、两种产品、三台瓶颈设备
先说清楚要解决的问题:你负责产线排产,产品 A 每件利润 30 元、产品 B 每件 25 元;每单位 A 耗 1 单位原料、每单位 B 耗 2 单位,原料每天只有 80 单位;A 的日产能上限是 150 件。排产计划每周重排一次,而 Excel 试凑法每次要花大半天,还不敢保证全局最优。
这就是最典型的 LP 场景,也是 HiGHS 的主场。它的优势不在于"功能全",而在于默认配置下就能给出最优解,且 presolve、单纯形、内点法、MIP 四条技术路线在同一个引擎里切换,不需要换工具。
装完highspy后,你可以直接跳到下一节跑通第一个实例;如果暂时只想了解它内部有哪些求解器,仓库文档docs/src/solvers.md有一份完整清单。
HiGHS Python 安装后,5 分钟跑通第一个模型
HiGHS 的 Python 包是highspy,一条 pip 命令即可:
pip install highspy之后就是建模的三步曲:声明变量、加约束、求解取解。下面这个例子对应开篇的排产问题,注意显式给出下界为 0——HiGHS 变量的默认下界是负无穷,不写的话求解器会认为"产量"可以是负数:
import highspy h = highspy.Highs() inf = highspy.kHighsInf # 默认下界是 -inf,排产量不可能为负,所以要显式钉住下界 h.addVars(2, lb=[0.0, 0.0], ub=[100.0, 150.0]) # 目标系数:A 每件 30 元,B 每件 25 元 h.changeColsCost(2, [0, 1], [30.0, 25.0]) # 原料约束 1*x1 + 2*x2 <= 80,右端写成上界 80 h.addRow(0.0, 80.0, 2, [0, 1], [1.0, 2.0]) h.run() info = h.getInfo() print("状态:", h.modelStatusToString(h.getModelStatus())) print("最优目标值:", info.objective_function_value) sol = h.getSolution() print("产量:", list(sol.col_value))输出大致是:
状态: HighsModelStatus.kOptimal 最优目标值: 2000 产量: [40.0, 20.0]run()背后发生的事:presolve 先尝试削减问题,然后调用默认求解器(LP 默认是单纯形法),最后把 primal 解写回getSolution()。getInfo()返回的objective_function_value是最优目标值,simplex_iteration_count是迭代次数,这两个字段后面调优时都会反复用到。
这个接口对 LP、QP、MIP 是同一套,区别只在建模时用什么变量类型。下面看命名建模语法,以及把 MIP 也跑起来。
从 LP 到 MIP:HiGHS 混合整数规划建模教程
对熟悉PuLP或scipy.optimize的人来说,highspy最省适应成本的地方是它的表达式建模语法——变量可以直接参与+、<=运算。仓库自带的家具厂经典例子(完整版在examples/chip.py)精简后长这样:
import highspy h = highspy.Highs() h.silent() # 关掉求解日志,只要结果 # 命名变量 + 目标系数一次声明,后面可以直接用 x['Tables'] 引用 x = h.addVariables(['Tables', 'SetsOfChairs'], obj=[10.0, 25.0]) h.addConstrs(x['Tables'] + 2*x['SetsOfChairs'] <= 80, # 装配工时 x['Tables'] + 4*x['SetsOfChairs'] <= 120, # 涂装工时) h.setMaximize() h.solve() print([h.variableValue(v) for v in x]) # 最优产量 print([h.variableDual(v) for v in x]) # 变量对偶(简化成本) print([h.constrValue(c) for c in h.getConstrs()]) # 约束取到的值解完 LP 顺手能拿到对偶值(影子价格),这对"原料涨价多少还值不值得外购"这类决策问题直接可用。
如果决策是"选或不选",就换成整数变量。highspy提供了addBinaries快捷方法,0-1 背包问题十几行就能写完:
import highspy h = highspy.Highs() h.silent() # 演示 setSolution 热启动时,presolve 会改变问题规模、使传入的解对不上号, # 所以这个例子里先关掉 h.setOptionValue("presolve", "off") x = h.addBinaries(5) # 5 个 0-1 变量 h.addConstr((x * [4, 3, 1, 5, 4]).sum() <= 11) # 背包重量 h.maximize((x * [8, 5, 3, 11, 7]).sum()) # 物品价值 h.run() print("背包总价值:", h.getInfo().objective_function_value) # 22 # 分支定界支持热启动:喂回一个已有解,让搜索从它出发 h.clearSolver() # 清空求解状态,保留模型 h.setSolution(h.getSolution()) h.run()setSolution在 MIP 求解器里并不常见,它的价值在于:业务系统里已有的启发式解可以作为初始可行解传入,搜索不用从零开始。热启动之外,MIP 子问题用什么求解器,是下一个章节的选型问题。
3 种求解策略怎么选
LP 部分,highspy默认走单纯形法(solver = "choose"时 LP 的默认就是它)。想换路线,直接改solver选项即可:"simplex"、"ipm"(HiPO 内点法)、"ipx"、"hipo"。内点法在小问题上不一定占优,但仓库文档里明确:问题规模一大,HiPO 的优势可以拉开一个数量级以上,所以大规模稀疏 LP 值得试一次切换:
h.setOptionValue("solver", "ipm") # 切到内点法(HiPO) h.setOptionValue("parallel", "on") # 启用并行 h.setOptionValue("threads", 4) # 限制最多 4 个线程MIP 部分有一个专门的旋钮:mip_lp_solver控制分支定界树中 LP 子问题的求解器("simplex"/"ipm"/"hipo"),根节点没有基信息时它尤其有用。
这些选项的完整说明见仓库文档docs/src/options/definitions.md,每一项都带取值范围和默认值。参数怎么落到生产环境的配置文件里,下面接着说。
让求解更快的四个参数旋钮
调优的顺序建议是:默认配置跑一遍拿基线 → 一次只改一个参数 → 对比model_status、目标值和迭代/节点数的变化。乱改一堆参数只会让你分不清是谁起了作用。
最常见的三种场景和对应旋钮:
| 问题特征 | 建议旋钮 | 原因 |
|---|---|---|
| 大规模稀疏 LP,单纯形迭代数很高 | solver = "ipm" | 内点法迭代次数不随规模线性膨胀 |
| 大 MIP,且有时间限制 | presolve = "on"+mip_lp_solver = "ipm" | 根节点和深节点松弛用内点法求解更快 |
| LP 反复报数值病态、容差超标 | 微调kkt_tolerance | 控制可行性/最优性(KKT)判定阈值 |
生产环境里参数通常放在选项文件中,一次性生效。HiGHS 选项文件支持#注释、空行和--命令行风格的行:
# highs_options.txt # HiGHS 选项文件:每行一个选项,# 开头为注释 presolve = on time_limit = 3600 # 最多跑 1 小时,超时返回当前最优解 threads = 4 # 并行时最多用 4 个线程 solver = choose # 让 HiGHS 按问题类型自选(LP 默认单纯形) ranging = off # 需要敏感性报告时再打开加载它只需一行:
h.passOptionsFile("highs_options.txt")参数调不动的时候,多半是模型结构的问题——比如满是不参与任何约束的变量、约束右端项量级相差十亿倍。这种情况改参数没有用,应该回到建模层面,把冗余变量清掉、把量级对齐。
参数和配置文件解决了"怎么跑得快",还有两件生产上绕不开的事:模型怎么在系统之间传递,以及解出来之后怎么利用对偶信息。
模型读写、敏感性分析与多目标
HiGHS 原生读 MPS 和 CPLEX 风格 LP 文件,求解后也能把模型写回磁盘,适合在"建模脚本 → 求解服务 → 结果归档"的链路里做模型快照:
# 读入已有模型并求解 h2 = highspy.Highs() h2.readModel("model.lp") h2.run() # 求解后把模型归档,便于事后复盘"当时到底解的是什么" h2.writeModel("model_archive.mps")对 LP 的决策者来说,比最优解本身更值钱的是"解有多稳健"。打开ranging选项后,HiGHS 会一次算出目标系数、变量边界、约束右端项各自允许变化多少而不改变当前最优基:
# highs_options.txt(在上一节的基础上追加) ranging = on多目标优化 HiGHS 也能接:多个目标按权重加权成单目标求解,权重变化时重新求解即可对比方案。到这里,建模、求解、调优、交换的闭环就完整了。
从哪里继续
仓库里的check/目录有一整套测试用例和实例文件(LP、MPS 混合,含issue-*.mps这类回归用例),examples/里还有网络流、多目标、回调(callback)等更多完整脚本,可以直接拿来当模板改。如果你下一步的计划是处理含整数变量的排产或选址问题,建议从 MIP 建模的完整案例读起;需要嵌入自有 C++ 服务时,C 接口头文件在highs/interfaces/highs_c_api.h,示例程序见examples/call_highs_from_c.c。
【免费下载链接】HiGHSLinear optimization software项目地址: https://gitcode.com/GitHub_Trending/hi/HiGHS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考