3. 基于成本最小化的多组分线性配方优化
问题建模:把“成本最低”写成线性规划
本节处理的是多组分质量配比下的成本最小化问题。设配方含 n 种原料,第 i 种原料的质量分数为 xi(以小数计),单价为 ci(元/kg)。目标函数为配方单位质量成本:
min Z = Σ ci·xi
约束分三类:①归一化约束 Σxi = 1;②性能约束,若某线性可加性能 p 的组分贡献系数为 ai,则 Σai·xi ≥ Pmin(或 ≤ Pmax);③上下界约束 Li ≤ xi ≤ Ui,用于表达工艺或法规对单组分用量的限制。线性可加性能的成立前提是理想混合、无体积收缩或膨胀、无化学反应,这一点与二元理想体系换算的假设一致。
下面用一个三组分涂料配方示例贯穿本节。原料为树脂 R、填料 F、溶剂 S,成本与固含量贡献如下表(示例数据,非实测):
| 组分 | 单价 ci(元/kg) | 固含量贡献 ai(%) | 用量下界 Li | 用量上界 Ui |
|---|---|---|---|---|
| 树脂 R | 28 | 100 | 0.30 | 0.70 |
| 填料 F | 6 | 100 | 0.10 | 0.45 |
| 溶剂 S | 3 | 0 | 0.05 | 0.40 |
要求成品固含量不低于 55%,即 100xR + 100xF + 0·xS ≥ 55,化简为 xR + xF ≥ 0.55。由于溶剂最便宜,成本最小化会本能地把溶剂推到上界,而固含量约束正是阻止这一趋势的关键“刹车”。
求解:顶点枚举与结果判读
三变量问题可消去 xS = 1 − xR − xF,化为二维。目标函数变为 Z = 28xR + 6xF + 3(1 − xR − xF) = 3 + 25xR + 3xF,即最小化 25xR + 3xF。约束为 xR + xF ≥ 0.55、xR ∈ [0.30, 0.70]、xF ∈ [0.10, 0.45]、xS = 1 − xR − xF ∈ [0.05, 0.40]。
线性规划最优解必在可行域顶点取得。逐一检查候选顶点:
- 顶点 A:xR=0.30,xF=0.25(固含量约束取等号),xS=0.45 → 超出 xS 上界 0.40,不可行。
- 顶点 B:xR=0.30,xF=0.30,xS=0.40(xS 上界与 xR 下界同时起作用)→ Z = 28×0.30 + 6×0.30 + 3×0.40 = 8.4 + 1.8 + 1.2 = 11.4 元/kg,固含量 = 60%,满足 ≥55%。
- 顶点 C:xR=0.30,xF=0.45,xS=0.25 → Z = 8.4 + 2.7 + 0.75 = 11.85 元/kg,高于 B。
- 顶点 D:xR=0.55,xF=0.10,xS=0.35 → Z = 15.4 + 0.6 + 1.05 = 17.05 元/kg,明显更高。
比较可知最优解为 B 点:xR=0.30、xF=0.30、xS=0.40,最低成本 11.4 元/kg,固含量 60%。注意固含量约束并未取等号——真正卡住解的是溶剂上界与树脂下界,这说明“约束是否紧”需要看影子价格而非直觉。
用代码求解并做灵敏度检查
手工枚举适合三变量,组分增多后应交给线性规划求解器。下面用 Python 的 scipy.optimize.linprog 完整实现,并输出各约束的松弛量与影子价格,用于判断哪个约束真正“值钱”。
import numpy as np
from scipy.optimize import linprog
# 组分顺序: [树脂R, 填料F, 溶剂S]
c = np.array([28.0, 6.0, 3.0]) # 单价 元/kg
A_ub = np.array([[-100.0, -100.0, 0.0]]) # 固含量 >= 55 转为 -sum <= -55
b_ub = np.array([-55.0])
A_eq = np.array([[1.0, 1.0, 1.0]]) # 归一化
b_eq = np.array([1.0])
bounds = [(0.30, 0.70), (0.10, 0.45), (0.05, 0.40)]
res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq,
bounds=bounds, method="highs")
print("success:", res.success)
print("x =", np.round(res.x, 4))
print("cost =", round(res.fun, 4), "元/kg")
print("固含量 =", round(100 * (res.x[0] + res.x[1]), 2), "%")
# 灵敏度: 固含量下限从 50% 扫到 75%
for pmin in range(50, 80, 5):
r = linprog(c, A_ub=[[-100, -100, 0]], b_ub=[-pmin],
A_eq=A_eq, b_eq=b_eq, bounds=bounds, method="highs")
if r.success:
print(f"Pmin={pmin}% cost={r.fun:.3f} x={np.round(r.x,3)}")
else:
print(f"Pmin={pmin}% 不可行")
预期输出中 x 应为 [0.30, 0.30, 0.40],cost 为 11.4。灵敏度扫描会显示:Pmin 在 50%~60% 区间成本恒为 11.4 元/kg(固含量约束不紧),超过 60% 后成本开始上升,直到某一点变为不可行——该临界点即“在给定上下界下可达到的最高固含量”。若求解返回 success=False,说明约束互相冲突,应优先检查上下界之和是否已无法满足归一化或性能下限。
图中绿色区域为满足全部约束的可行域,橙色虚线是固含量下限对应的边界。最优解落在可行域左下角 B 点,由 xR 下界与 xS 上界两条边共同“夹”出,而非固含量线——这正是灵敏度扫描中成本在 50%~60% 区间保持不变的原因。把这张图与代码输出对照,可以快速判断某个约束是否值得放宽:只有紧约束的放宽才可能降低成本。