请求scipy.optimize.linprog/milp调优提示:实现涂料罐组合成本优化
请求scipy.optimize.linprog/milp调优提示:实现涂料罐组合成本优化
看起来你已经用PuLP完美实现了涂料罐组合的成本最小化问题,现在想转用Scipy的linprog或milp工具复现结果,却遇到了整数约束失效、解不符合预期的问题对吧?我来帮你梳理下现有代码的问题,再给出修正后的实现方案~
先明确问题的核心约束与目标
我们要解决的是非负整数线性规划问题:
- 目标:最小化总成本
sum(单罐价格 × 购买数量) - 约束:所有购买的涂料罐能覆盖的总面积 ≥ 目标面积(30㎡)
- 变量:每种涂料罐的购买数量(必须是非负整数)
其中,每罐能覆盖的面积 = 罐容量 / 单位面积消耗量(也就是你代码里计算的exps数组),这个部分你是对的~
你的Scipy代码核心问题
你在构造约束矩阵的时候犯了关键错误:
- 完全偏离了「总覆盖面积≥目标面积」的核心约束,错误地用了
(1/c[i])*exps[i]这种无关计算,导致约束逻辑完全失效 - 额外添加的上界约束(
math.ceil(square/max(exps))*max(exps))其实是多余的——整数规划会自动选择成本最低的组合,不会无意义地多买涂料
接下来是修正后的代码:
方案1:用scipy.optimize.linprog实现
linprog默认处理A_ub @ x ≤ b_ub形式的约束,所以我们要把「总覆盖面积≥30」转换成-exps @ x ≤ -30的等价形式,同时明确设置变量为非负整数:
from scipy.optimize import linprog pdict = {'Can_9ltr':[9, 0.17, 4870], 'Can_4.5ltr':[4.5, 0.17, 2910], 'Can_1ltr':[1, 0.17, 632], 'Can_2.25ltr':[2.25, 0.17, 1790]} square = 30 # 目标函数系数:各罐的价格(要最小化总成本) c = [v[2] for v in pdict.values()] # 每罐能覆盖的面积 = 罐容量 / 单位面积消耗量 exps = [v[0]/v[1] for v in pdict.values()] # 构造约束:总覆盖面积 ≥ square → 转换为 -exps @ x ≤ -square A_ub = [[-val for val in exps]] b_ub = [-square] # 变量边界:所有购买数量≥0 x_bounds = [(0, None) for _ in range(len(pdict))] # 求解:integrality=1表示所有变量为整数,需Scipy 1.9.0+版本支持 res = linprog( c=c, A_ub=A_ub, b_ub=b_ub, bounds=x_bounds, integrality=[1]*len(pdict), method='highs' # highs方法原生支持整数规划 ) # 整理为你需要的结果格式 can_names = list(pdict.keys()) optimal_set = {} total_cost = 0.0 for name, cnt, price in zip(can_names, res.x, c): cost = cnt * price optimal_set[name] = { 'CPU': price, 'Amount': cnt, 'Cost': cost } total_cost += cost result_dict = { 'Target_value': square, 'Optimal_set': optimal_set, 'Cost_total': total_cost } print(result_dict)
方案2:用scipy.optimize.milp实现
milp支持更直观的约束定义(直接设置下界和上界),不需要转换约束方向,代码逻辑更清晰:
from scipy.optimize import milp, LinearConstraint import numpy as np pdict = {'Can_9ltr':[9, 0.17, 4870], 'Can_4.5ltr':[4.5, 0.17, 2910], 'Can_1ltr':[1, 0.17, 632], 'Can_2.25ltr':[2.25, 0.17, 1790]} square = 30 # 目标函数系数:各罐的价格 c = np.array([v[2] for v in pdict.values()]) # 每罐能覆盖的面积 exps = np.array([v[0]/v[1] for v in pdict.values()]) # 约束:总覆盖面积 ≥ square,无上限 constraints = LinearConstraint( A=exps.reshape(1, -1), # 约束矩阵(1行4列) lb=square, # 下界:总覆盖面积≥30 ub=np.inf # 上界:无限制 ) # 变量边界:所有购买数量≥0 bounds = [(0, None) for _ in range(len(pdict))] # 求解:integrality=1表示所有变量为整数 res = milp( c=c, constraints=constraints, bounds=bounds, integrality=[1]*len(pdict) ) # 整理为你需要的结果格式 can_names = list(pdict.keys()) optimal_set = {} total_cost = 0.0 for name, cnt, price in zip(can_names, res.x, c): cost = cnt * price optimal_set[name] = { 'CPU': price, 'Amount': cnt, 'Cost': cost } total_cost += cost result_dict = { 'Target_value': square, 'Optimal_set': optimal_set, 'Cost_total': total_cost } print(result_dict)
验证结果
修正后的代码会输出和你PuLP完全一致的结果:
{ 'Target_value': 30, 'Optimal_set': { 'Can_9ltr': {'CPU': 4870, 'Amount': 0.0, 'Cost': 0.0}, 'Can_4.5ltr': {'CPU': 2910, 'Amount': 1.0, 'Cost': 2910.0}, 'Can_1ltr': {'CPU': 632, 'Amount': 1.0, 'Cost': 632.0}, 'Can_2.25ltr': {'CPU': 1790, 'Amount': 0.0, 'Cost': 0.0} }, 'Cost_total': 3542.0 }
关键注意点
- 确保你使用的是Scipy 1.9.0及以上版本:
linprog的integrality参数和milp函数都是在这个版本之后正式支持的 - 约束构造一定要贴合实际业务逻辑:核心是总覆盖面积满足需求,不要添加无关的计算或约束
- 变量的整数约束必须明确设置,否则会得到非整数的解(也就是允许买半罐,不符合实际业务场景)
备注:内容来源于stack exchange,提问作者Артур Дутов
相关产品推荐
相关产品推荐

