2. 多组分配方线性性能约束下的可行域求解

把“性能要求”翻译成“比例约束”

多组分线性可加性能满足 P = Σ(wᵢ · kᵢ),其中 wᵢ 为质量分数、kᵢ 为该组分对性能的贡献系数。当性能被限定在 [P_min, P_max] 内,且比例满足归一化 Σwᵢ = 1、非负 wᵢ ≥ 0 时,可行配方集合就是单纯形(三角形)内被两条平行等值线夹出的一个凸多边形区域。求解可行域的本质,是求这个多边形的顶点,再用顶点判断任意配方是否落在其中。

初始配方 (0.40, 0.35, 0.25) 在域内 域外配方:P 超上限 A 纯组分1 B 纯组分2 C 纯组分3 P = P_max P = P_min 可行域判定要点 ① 归一化 Σwᵢ = 1 ② 非负 wᵢ ≥ 0 ③ P_min ≤ Σwᵢkᵢ ≤ P_max 绿色多边形 = 可行域 顶点由约束交点确定 蓝点:满足全部约束 红点:违反性能上下限 判定:逐条代入约束 全部成立 → 在域内

示例数据与约束建立

设三种组分对某线性性能的贡献系数为 k = (k₁, k₂, k₃) = (0.80, 0.50, 0.30),性能要求 0.45 ≤ P ≤ 0.65。约束写成:

  • 归一化:w₁ + w₂ + w₃ = 1
  • 非负:w₁, w₂, w₃ ≥ 0
  • 性能下限:0.80w₁ + 0.50w₂ + 0.30w₃ ≥ 0.45
  • 性能上限:0.80w₁ + 0.50w₂ + 0.30w₃ ≤ 0.65

把 w₃ = 1 − w₁ − w₂ 代入性能式,得到二维平面上的两条平行直线:0.50w₁ + 0.20w₂ ≥ 0.15 与 0.50w₁ + 0.20w₂ ≤ 0.35。可行域即三角形内这两条直线之间的部分。

用顶点法求可行域

可行域是凸多边形,其顶点来自“约束取等号”的交点。对本题,候选顶点包括:

顶点来源w₁w₂w₃P是否可行
纯组分1(A)1.000.000.000.80否(超上限)
P=P_max 与 w₂=0 交点0.700.000.300.65是
P=P_min 与 w₂=0 交点0.300.000.700.45是
P=P_max 与 w₃=0 交点0.500.500.000.65是
P=P_min 与 w₃=0 交点0.100.900.000.45是
纯组分2(B)0.001.000.000.50是
纯组分3(C)0.000.001.000.30否(低于下限)

可行域顶点为 (0.70,0,0.30)、(0.50,0.50,0)、(0.10,0.90,0)、(0.30,0,0.70),以及纯组分2 点 (0,1,0)。注意纯组分2 的 P=0.50 落在区间内,因此它也是可行域的一个顶点。

可运行的求解与判定代码

下面用 Python 枚举“约束取等号”的组合求顶点,再用线性规划确认可行域非空,并判定初始配方是否在域内。代码自洽,可直接运行。

import itertools
import numpy as np
from scipy.optimize import linprog

k = np.array([0.80, 0.50, 0.30])
P_min, P_max = 0.45, 0.65
n = 3

# 约束:A_eq w = b_eq(归一化);A_ub w <= b_ub(性能上下限 + 非负)
A_eq = np.ones((1, n))
b_eq = np.array([1.0])
A_ub = np.vstack([k, -k, -np.eye(n)])
b_ub = np.concatenate([[P_max], [-P_min], np.zeros(n)])

# 枚举 3 个约束取等号求交点(归一化 + 两条其它约束)
rows = [A_eq[0]] + list(A_ub)
rhs = [b_eq[0]] + list(b_ub)
vertices = []
for combo in itertools.combinations(range(len(rows)), n):
    M = np.array([rows[i] for i in combo])
    if abs(np.linalg.det(M)) < 1e-9:
        continue
    w = np.linalg.solve(M, np.array([rhs[i] for i in combo]))
    if np.all(w >= -1e-9) and np.all(A_ub @ w <= b_ub + 1e-9):
        vertices.append(np.round(w, 4))

vertices = sorted({tuple(v) for v in vertices})
print("可行域顶点:")
for v in vertices:
    print(v, "P =", round(float(k @ np.array(v)), 4))

# 判定初始配方
w0 = np.array([0.40, 0.35, 0.25])
P0 = float(k @ w0)
ok = (abs(w0.sum() - 1) < 1e-9 and np.all(w0 >= 0)
      and P_min - 1e-9 <= P0 <= P_max + 1e-9)
print("初始配方 P =", round(P0, 4), "在可行域内:", ok)

# 线性规划确认可行域非空
res = linprog(c=np.zeros(n), A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq,
              bounds=[(0, None)] * n, method="highs")
print("可行域非空:", res.success)

预期输出(示例数据直接推出):顶点包含 (0.7,0,0.3)、(0.5,0.5,0)、(0.1,0.9,0)、(0.3,0,0.7)、(0,1,0);初始配方 P=0.545,判定为在域内;线性规划返回 success=True。

顶点法只需求解有限个线性方程组,比网格扫描更精确;当组分数量增加时,可行域从多边形升级为高维凸多面体,但“约束取等号求顶点 + 逐条代入判定”的思路不变。
性能上下限必须与归一化、非负约束同时检查。只验证 P 落在区间内而忽略 Σwᵢ=1,会把不可行的比例误判为可行;反之只满足归一化而不检查 P,也会漏掉性能越界。