线性规划跑不通,多半死在矩阵形状上:洹拓科技谈 linprog 的约束写法
一、目标函数是对的,报错却是维度
制造业里的小规模优化问题大多是这个模样:两种产品排几台设备、几种原料配出一批合金、几个仓库往几个销地调货。写业务逻辑时人人都会,把 scipy.optimize.linprog 跑起来却频频卡住,报错信息还很不友好——常见的是矩阵乘法的形状不匹配(matmul: Input operand 1 has a mismatch),或者一句 ValueError: Initial guess is not feasible。
近期技术社区里就有一组这样的问答:用 linprog 时 A_ub 设置不正确导致维度报错。这个问题有个容易被忽略的特征:它几乎从不指向”你的模型建错了”,而是指向”你写的系数表和求解器约定的排布方式不一样”。约束矩阵是二维的,而人脑里的约束是一行一句话,把话语摊成矩阵时行列写反、变量顺序错位、加了松弛变量却没同步扩长度,三种错法都能产生完全不同的报错文本,根子却是同一个。

二、先把契约写清楚
linprog 求解的是标准形式:
min c @ x |
四个数组共用一个隐式约定:n 是决策变量个数,m 是约束个数。于是形状要求是刚性的——A_ub 必须是 (m, n),b_ub 长度必须是 m,c 长度必须是 n,bounds 长度必须是 n。换句话说,n 这个数一旦定了,c、bounds、A_ub 的列数三处必须同时跟着变;任何一处不同步,就是维度报错。
n 从哪来?它是你自己列出的决策变量清单的长度,是整个模型的锚点。所有的错,本质上是”变量清单”没有显式写下来,只存在于作者的记忆里。
三、三步建模:变量清单 → 约束清单 → 形状自检
正确的最小例子:两种产品 A、B,两道工序,A 单件利润 40、B 单件利润 30,工序一可用 48 小时、工序二可用 60 小时,求利润最大的产量。linprog 只做最小化,所以利润取负。
import numpy as np |
三步的含义是:第一步把变量清单写成一行代码或一段 docstring,明确 n 和每个位置代表谁;第二步把每条约束写成”用变量名表述的一句话”,由 row 这样的辅助函数摊平成系数行,人不再手工数列的位置;第三步是求解前的形状自检,三行 assert 把契约钉死。这段自检代码不超过五行,但它把报错从求解器内部(矩阵乘法失败)提前到你自己的代码里(哪一项长度不对,直接说清楚)。
四、跑不通先查这六条
- c 的长度是否等于 n。这是最常见的漏项,尤其是变量拆细之后(把”产量”拆成”各班次产量”)。
- bounds 是否是 n 个二元组。给成 (0, None) 单个元组、或者上界写成字符串都会失败。
- A_ub 的每一行是否对应一个约束。把约束写成列,等于交付了一个转置矩阵。
- 稀疏格式却按稠密索引填。用 lil_matrix 一类稀疏结构时,索引语义与二维列表不同,混用会出现”形状对了但值串位”。
- 不等式方向。linprog 的 A_ub 只接受 <=,遇到 >= 必须整行乘 -1(包括 b 的那一项),否则约束方向反了,解会离谱或问题直接不可行。
- 需要整数解时不该用 linprog。linprog 只解连续 LP,涉及”开/不停机、指派/不指派”要换 milp。
| 报错信息里出现什么 | 大概率是哪一条错了 |
|---|---|
matmul: Input operand 1 has a mismatch |
3(行列写反)或 1(c 与 A_ub 列数不一致) |
Initial guess is not feasible |
2(bounds 与约束冲突)或 5(方向没翻,给定的初解真不满足) |
| 形状检查直接拒绝,提示矩阵与向量长度不符 | 2、1 同时没同步,通常是加了松弛变量 |
| 求解成功但结果明显荒谬(负产量、超能力) | 5(不等式方向)或系数串位(4) |
| 提示问题无界(unbounded) | 2(忘写上界)或目标符号没取负 |
| 解出小数却要求整数件 | 6,应换 milp 而不是继续调 linprog 参数 |
换 milp 的写法差别不大,但约束要打包成对象,且要显式声明哪些变量取整:
from scipy.optimize import Bounds, LinearConstraint, milp |
五、洹拓视角:工业排产的真实形状
上面的例子只有两个变量,真实排产不是。一旦把问题按”每个订单在每个机台上的加工量”展开,变量数就是订单数乘以机台数,几十个订单配十几台设备,轻松上百到几百个变量。这个规模对纯 LP 来说不算压力,method=’highs’ 的求解器处理起来是常规工作量,所以”规模”从来不是第一障碍。
第一个障碍是问题性质。排产要落地,几乎必然混进整数决策:某台设备今天开还是不开、这批货指派给哪个班组、要不要为并线付一次换型成本。这些是 0/1 变量,linprog 解不了,得走混合整数规划。而混合整数带来的不是”多写几行代码”,是求解时间从秒级跳到分钟甚至小时级,以及”无解时说不清到底是哪条约束造成的”。所以选型判据不该是”哪个模型更精确”,而是”哪个模型在数据缺失时还能给出可解释的答案”。
第二障碍,也是最容易被低估的,是系数从哪来。A_ub 里的每个数字,在工厂里对应工艺路线上的一道工序和一条工时定额。定额没维护、工艺路线靠老师傅记忆、设备能力按名义值填报时,模型解得再准也是在给错误的系数求最优。我们的做法是:系数来源先跑通,再做人工可解释的启发式排产(按交期倒排、按设备能力过滤),最后用 LP 做校验——看这个启发式解离最优目标差多少、哪几道能力约束最紧。差距小就说明启发式够用,不值得上 MILP;差距大且集中在少数约束,就先补那几个环节的定额数据,而不是先补算法。
验收标准我们固定四条:一,求解脚本必须显式打印 n、m 以及 c、A_ub、b_ub、bounds 四项的形状,跑一次就把契约留在日志里;二,求解前必须有形状断言,报错来自自己的 assert 而不是求解器;三,解必须能落回人话——每个非零变量能说出对应哪个订单哪台机,说不清就是索引串位;四,模型结论与现场启发式方案的差距要有记录,差距长期很小却坚持上 MILP 的项目,通常是把优化当成了政绩。