4. 线性规划建模实战

线性规划是运筹学中最成熟、应用最广的模型之一。它的价值不在于公式本身,而在于把一段真实的业务约束翻译成一组线性不等式,再交给求解器算出可执行的最优方案。本节以一家小型家具厂的周生产排程为对象,完整走一遍“业务描述—变量定义—目标与约束—求解—结果解读”的闭环。所有数据均为教学示例假设,不代表任何真实企业的实测结果。

一、业务场景与建模准备

假设某家具厂生产两种产品:书桌和椅子。每张书桌需要 4 小时木工工时和 2 小时油漆工时,每把椅子需要 2 小时木工工时和 3 小时油漆工时。本周可用木工工时上限为 120 小时,油漆工时上限为 90 小时。市场部门反馈:书桌单件利润 300 元,椅子单件利润 200 元,且椅子因需求有限,本周最多销售 25 把。工厂希望决定两种产品各生产多少,使本周总利润最大。

建模前先明确三个约定:第一,决策变量取非负实数,允许出现小数产量(若业务要求整件,则需改用整数规划,本节先按连续线性规划处理);第二,工时约束按“不超过上限”处理,即小于等于约束;第三,利润与产量成正比,不考虑固定成本分摊。这些约定必须在建模时写清楚,否则后续结果解读容易产生歧义。

二、变量、目标函数与约束

设 x1 为书桌产量,x2 为椅子产量,单位均为件。目标函数为最大化总利润:

max Z = 300*x1 + 200*x2

约束条件包括资源上限与市场需求:

木工工时:4*x1 + 2*x2 <= 120
油漆工时:2*x1 + 3*x2 <= 90
椅子需求:x2 <= 25
非负约束:x1 >= 0, x2 >= 0

这组式子已经把业务语言完全转成了线性形式:每条约束对应一种资源的“消耗速率 × 产量 ≤ 可用量”。建模时最容易出错的地方是单位不统一和约束方向写反,建议每写一条约束就回读一遍业务原句,确认“左边是消耗、右边是上限”。

可行域与最优解(x1 书桌,x2 椅子) x1(书桌产量) x2(椅子产量) 0 10 20 30 40 50 0 10 20 30 40 木工:4x1+2x2=120 油漆:2x1+3x2=90 需求:x2=25 最优解 (22.5, 15) Z = 300×22.5 + 200×15 = 9750 等利润线 Z=9750 浅蓝区域:可行域 红点:可行域顶点 虚线:约束边界与等利润线

三、用求解器完成计算

手工画图可以看清结构,但实际业务约束往往有几十条,必须借助求解器。下面用 Python 的 PuLP 库实现上述模型,代码可直接运行:

import pulp

prob = pulp.LpProblem("furniture_plan", pulp.LpMaximize)
x1 = pulp.LpVariable("desk", lowBound=0)
x2 = pulp.LpVariable("chair", lowBound=0)

prob += 300*x1 + 200*x2, "profit"
prob += 4*x1 + 2*x2 <= 120, "carpentry"
prob += 2*x1 + 3*x2 <= 90,  "painting"
prob += x2 <= 25,            "demand"

prob.solve()
print(pulp.LpStatus[prob.status])
for v in (x1, x2):
    print(v.name, v.varValue)
print("Z =", pulp.value(prob.objective))

示例运行结果应为:状态 Optimal,desk = 22.5,chair = 15.0,目标值 9750。这个结果与图中顶点 (22.5, 15) 一致,说明模型与几何直觉相互印证。若求解器返回 Infeasible,通常意味着约束互相矛盾(例如把需求写成 x2 >= 25 同时工时又不够);若返回 Unbounded,则多半是漏写了某条资源上限。

四、结果解读与灵敏度意识

得到 22.5 和 15 之后,不能直接把它当成“生产 22 张半书桌”。连续解的意义在于给出资源分配的方向:书桌产量受油漆工时约束更紧,椅子产量则同时受需求和油漆工时影响。把解代回约束可以验证:木工消耗 4×22.5+2×15=120,正好用满;油漆消耗 2×22.5+3×15=90,也正好用满;椅子需求 15≤25,尚有余量。两条资源约束同时取等号,说明本周木工和油漆都是瓶颈资源。

进一步应关注影子价格(对偶值)。在 PuLP 中可通过 prob.constraints["carpentry"].pi 读取。示例模型下,木工工时影子价格约为 62.5 元/小时,油漆工时约为 12.5 元/小时,含义是:木工工时每增加 1 小时,总利润约增加 62.5 元;油漆工时每增加 1 小时,总利润约增加 12.5 元。这直接回答了“加班应该加在哪个工序”的管理问题。但要注意,影子价格只在当前基不变的范围内有效,工时增加过多会触发基变换,需重新求解确认。

建模实战中建议养成三个习惯:一是给每条约束起可读的名字,便于读取对偶值和排查冲突;二是先用小规模数据手算或画图验证模型方向;三是把求解状态、变量值、目标值、约束松弛量一起输出,形成可复核的结果记录。

五、从示例到真实业务的扩展

真实排程往往比两产品两资源复杂得多,但扩展路径是清晰的:增加产品种类就增加变量;增加工序、设备、班组就增加约束行;出现“要么生产要么不生产”的开关决策时引入 0-1 变量转为混合整数规划;出现多周期库存时增加时间下标,把产量、库存、需求按周连接起来。无论规模如何变化,建模的核心动作始终是同一件事——把业务中的“消耗、上限、收益、要求”逐条写成线性表达式,并保证单位、方向和符号前后一致。掌握这个动作,线性规划才真正成为可复用的业务分析工具,而不是一次性的课堂练习。