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
树脂 R281000.300.70
填料 F61000.100.45
溶剂 S300.050.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%。注意固含量约束并未取等号——真正卡住解的是溶剂上界与树脂下界,这说明“约束是否紧”需要看影子价格而非直觉。

若把固含量下限从 55% 提高到 65%,则 xR+xF ≥ 0.65 与 xS ≤ 0.40 同时紧,解得 xS=0.40、xR+xF=0.60,在 xR 下界 0.30 处得 xF=0.30,Z 仍为 11.4 元/kg;继续提高至 70% 时 xR+xF=0.70,需 xR≥0.30、xF≥0.10,取 xR=0.30、xF=0.40,Z = 8.4+2.4+0.9 = 11.7 元/kg。可见约束收紧到一定程度后成本才开始抬升。

用代码求解并做灵敏度检查

手工枚举适合三变量,组分增多后应交给线性规划求解器。下面用 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,说明约束互相冲突,应优先检查上下界之和是否已无法满足归一化或性能下限。

线性规划假设性能严格线性可加。若固含量贡献存在交互项(如填料吸油导致有效固含下降),需引入交互系数或改用非线性求解,否则最优解在真实体系中可能不达标。上线前应至少做一次小样验证。
三组分配方可行域(消去 xS 后的 xR–xF 平面) xR(树脂) xF(填料) 0.30 0.40 0.50 0.60 0.10 0.20 0.30 0.40 可行域 xR+xF=0.55 等成本线 B 最优 (0.30, 0.30) Z=11.4 元/kg,固含 60% C (0.30,0.45) Z=11.85 D (0.50,0.10) Z=17.05 约束与作用 归一化:xR+xF+xS=1 固含量:xR+xF ≥ 0.55 xR ∈ [0.30, 0.70] xF ∈ [0.10, 0.45] xS ∈ [0.05, 0.40] 紧约束:xR 下界、xS 上界 固含量约束未取等号 → 提高固含量下限至 60% 成本仍为 11.4 元/kg

图中绿色区域为满足全部约束的可行域,橙色虚线是固含量下限对应的边界。最优解落在可行域左下角 B 点,由 xR 下界与 xS 上界两条边共同“夹”出,而非固含量线——这正是灵敏度扫描中成本在 50%~60% 区间保持不变的原因。把这张图与代码输出对照,可以快速判断某个约束是否值得放宽:只有紧约束的放宽才可能降低成本。