
本文详解如何正确调用 scipy.optimize.milp 求解带整数约束的最小成本包装组合问题,涵盖目标函数、约束构造、变量边界、整数声明等关键配置,并指出常见建模误区。
本文详解如何正确调用 scipy.optimize.milp 求解带整数约束的最小成本包装组合问题,涵盖目标函数、约束构造、变量边界、整数声明等关键配置,并指出常见建模误区。
在工业与供应链优化中,常需从多种离散规格(如不同容积/价格的油漆罐)中选择整数个数,以最低总成本满足最低资源需求(如覆盖指定面积)。虽然 pulp 等高级建模库语法直观,但若需轻量依赖或深度集成 SciPy 生态,直接使用 scipy.optimize.milp 是更优选择——前提是准确理解其接口语义。
✅ 正确建模四要素
milp 要求显式定义四个核心组件:目标系数向量 c、整数声明 integrality、变量边界 bounds 和 线性约束 constraints。错误常源于对 A 矩阵维度、不等式方向或边界默认值的误解。
-
目标函数
c:直接传入各选项单位成本(如[4870, 2910, 632, 1790]),milp默认执行最小化。 -
整数约束
integrality:必须为长度匹配的0/1数组(1表示整数变量),推荐用np.ones(n, dtype=np.uint8)显式声明,避免类型隐式转换失败。 -
变量下界
bounds.lb:所有变量需 ≥ 0(非负整数),不可省略;Bounds(lb=0)会广播至全部变量,lb=np.zeros(n)更明确。 -
覆盖约束
LinearConstraint:设A = [exp_0, exp_1, ..., exp_{n-1}](每罐可覆盖面积),则A @ x >= target_value等价于lb=target_value;上界ub=max_exp用于剪枝,提升求解效率。
? 完整可运行示例
import numpy as np
from scipy.optimize import milp, Bounds, LinearConstraint
# 原始数据:罐体体积(L)、单位面积耗漆量(L/m²)、单价(元)
pdict = {
'Can_9ltr': [9.0, 0.17, 4870],
'Can_4.5ltr': [4.5, 0.17, 2910],
'Can_1ltr': [1.0, 0.17, 632],
'Can_2.25ltr':[2.25, 0.17, 1790]
}
# 构造向量化输入
names = list(pdict.keys())
volumes = np.array([pdict[n][0] for n in names])
consumes = np.array([pdict[n][1] for n in names])
prices = np.array([pdict[n][2] for n in names])
coverage = volumes / consumes # 每罐可涂刷面积 (m²)
target_area = 30.0
n = len(names)
# 计算合理上界(避免无界松弛)
max_cover = coverage.max()
upper_bound = np.ceil(target_area / max_cover) * max_cover
# 调用 milp —— 关键:A 是 1×n 向量,lb/ub 是标量或 1×1 数组
result = milp(
c=prices, # 最小化总成本
integrality=np.ones(n, dtype=np.uint8), # 全部变量为整数
bounds=Bounds(lb=np.zeros(n)), # x_i ≥ 0
constraints=LinearConstraint(
A=coverage.reshape(1, -1), # shape: (1, n)
lb=target_area, # coverage @ x ≥ target_area
ub=upper_bound # coverage @ x ≤ upper_bound
)
)
if not result.success:
raise RuntimeError(f"MILP failed: {result.message}")
# 解析结果
solution = {name: int(result.x[i]) for i, name in enumerate(names)}
total_cost = sum(solution[name] * prices[i] for i, name in enumerate(names))
print("最优采购方案:")
for name, qty in solution.items():
if qty > 0:
print(f" {name}: {qty} 罐(单价 {prices[list(names).index(name)]} 元)")
print(f"总成本: {total_cost:.0f} 元")输出示例:
最优采购方案: Can_4.5ltr: 1 罐(单价 2910 元) Can_1ltr: 1 罐(单价 632 元) 总成本: 3542 元
⚠️ 常见错误与规避指南
-
❌ 错误使用
A_ub/b_ub(linprog风格):milp不接受A_ub参数,必须用LinearConstraint显式指定lb/ub。混淆二者将导致约束失效或维度报错。 -
❌ 整数声明无效:
integrality=[1,1,1,1]在旧版 SciPy 可能因类型推断失败;务必使用np.uint8或np.int8数组。 -
❌ 忘记非负约束:
milp不默认x ≥ 0,缺失bounds将允许负数解(如-1罐),逻辑错误。 -
❌ 上下界方向颠倒:
lb对应≥,ub对应≤;若写成lb=upper_bound, ub=target_area,约束恒不成立。 -
✅ 性能提示:添加紧致上界(如
upper_bound)可显著加速求解,尤其当变量范围天然受限时。
通过严格遵循上述范式,即可在不引入额外依赖的前提下,用 scipy.optimize.milp 稳健替代 pulp,实现生产级整数规划求解。

















