
本文详解如何正确使用 scipy.optimize.milp 求解带整数约束的最小成本覆盖问题,涵盖目标函数、约束构建、整数变量设置及常见错误规避,提供可直接运行的完整示例。
本文详解如何正确使用 `scipy.optimize.milp` 求解带整数约束的最小成本覆盖问题,涵盖目标函数、约束构建、整数变量设置及常见错误规避,提供可直接运行的完整示例。
在实际工业与工程优化场景中(如涂料采购、物流装箱、资源分配),常需在满足最低覆盖/容量要求的前提下,以最小总成本选择若干离散数量的物品组合——这本质上是一个混合整数线性规划(MILP) 问题。虽然 scipy.optimize.linprog 仅支持连续变量,但自 SciPy 1.9.0 起引入的 milp 函数已原生支持整数变量,是替代 PuLP 等外部库的轻量级选择。
然而,初学者常因三类典型问题导致求解失败或结果异常:
- ❌ 错误构造不等式约束(如混淆
A @ x ≤ b与A @ x ≥ b的符号方向); - ❌ 误设
integrality参数类型或维度(必须为长度匹配的整数数组,非布尔列表); - ❌ 忽略变量下界(未显式设
lb=0导致负数量解,或未设bounds引发默认行为不可控)。
以下是以油漆罐选型为例的规范实现流程:
✅ 正确建模要素解析
给定字典 pdict,每项含 [体积(L), 单位面积耗量(L/m²), 单价(元)],目标为覆盖 target_value 平方米,最小化总成本。
-
决策变量:
x[i]表示第i类罐子的购买数量(非负整数); -
目标函数:
min c @ x,其中c[i] = pdict[i][2](单价); -
核心约束:覆盖面积需满足
target_value ≤ sum(x[i] * volume[i] / consume[i]) ≤ upper_bound; -
整数性:
integrality[i] = 1显式声明所有变量为整数; -
边界:
lb = 0确保数量非负(ub可省略,默认无穷)。
✅ 完整可运行代码(推荐写法)
import numpy as np
from scipy.optimize import milp, Bounds, LinearConstraint
# 输入数据(结构化更清晰)
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]
}
target_value = 30.0
# 构建向量化参数
names = list(pdict.keys())
volumes = np.array([pdict[k][0] for k in names])
consumes = np.array([pdict[k][1] for k in names])
prices = np.array([pdict[k][2] for k in names])
coverage_per_can = volumes / consumes # 每罐可涂面积(m²)
# 计算合理上界(避免过松导致数值不稳定)
max_coverage = coverage_per_can.max()
upper_bound = np.ceil(target_value / max_coverage) * max_coverage
# 关键:正确配置 MILP 参数
result = milp(
c=prices, # 最小化总价格
integrality=np.ones(len(prices), dtype=int), # 全部变量为整数
bounds=Bounds(lb=np.zeros(len(prices))), # x_i ≥ 0
constraints=LinearConstraint(
A=coverage_per_can.reshape(1, -1), # 形状 (1, n)
lb=target_value, # ≥ target_value
ub=upper_bound # ≤ upper_bound
)
)
# 验证并输出结果
if result.success:
solution = {name: int(round(result.x[i])) for i, name in enumerate(names)}
total_cost = np.dot(prices, result.x)
print(f"目标面积: {target_value} m² → 最优解: {solution}")
print(f"总成本: ¥{total_cost:.1f}")
print(f"实际覆盖: {np.dot(coverage_per_can, result.x):.1f} m²")
else:
raise RuntimeError(f"MILP 求解失败: {result.message}")输出示例:
目标面积: 30.0 m² → 最优解: {'Can_9ltr': 0, 'Can_4.5ltr': 1, 'Can_1ltr': 1, 'Can_2.25ltr': 0}
总成本: ¥3542.0
实际覆盖: 32.4 m²⚠️ 关键注意事项
-
integrality必须是int或uint8数组:[1,1,1,1]正确;[True,True,True,True]或[1.,1.,1.,1.]将被忽略! -
约束矩阵
A形状需严格为(m, n):单约束时用reshape(1,-1),勿手动写二维列表引发维度错配; -
避免
linprog误用:linprog不支持integrality参数(该参数仅对milp有效),强行传入将被静默忽略; -
上界
upper_bound建议保守设置:过大易使求解器陷入搜索低效区域,推荐用ceil(target / max_coverage) * max_coverage; -
调试技巧:若结果全零或单变量极大,先检查
coverage_per_can计算是否正确(单位一致性),再验证A @ x是否确实 ≥target_value。
通过以上规范建模,scipy.optimize.milp 即可稳定、高效地替代 PuLP 完成中小规模整数规划任务,在不引入额外依赖的前提下,兼顾代码简洁性与生产可用性。

















