基于Pyomo实现含供应商依赖型运费的采购成本最小化优化
解决依赖采购总成本的分段运费建模问题
这个需求很常见,要处理这种和采购总成本挂钩的分段运费,我们得用二进制变量来标记是否触发运费条件,再配合大M法把逻辑转化为Pyomo能求解的线性约束。下面是具体的修改步骤和完整代码:
核心思路
每个供应商对应一个0-1变量:
- 对于供应商A:
y_A=1表示从A采购的总成本≤100(需支付10运费),y_A=0表示总成本>100(免运费) - 对于供应商B:
y_B=1表示从B采购的总成本≤150(需支付8运费),y_B=0表示总成本>150(免运费)
然后用大M约束把“总成本是否超过阈值”的逻辑转化为线性约束,最后把运费加到目标函数里。
修改后的完整代码
from pyomo.environ import ConcreteModel, Var, Objective, Constraint, SolverFactory, Binary model = ConcreteModel(name="(MN_2)") # products N = ['prod1', 'prod2', 'prod3'] # suppliers M = ['A', 'B'] # price p = {('prod1', 'A'): 10, ('prod2', 'A'): 9, ('prod3', 'A'): 50, ('prod1', 'B'): 16, ('prod2', 'B'): 20, ('prod3', 'B'): 35} # user quantity constraint (minimum purchase) q_u = {('prod1', 'A'): 2, ('prod2', 'A'): 1, ('prod3', 'A'): 1, ('prod1', 'B'): 1, ('prod2', 'B'): 1, ('prod3', 'B'): 1} # seller quantity constraint (maximum supply) q_s = {('prod1', 'A'): 20, ('prod2', 'A'): 10, ('prod3', 'A'): 10, ('prod1', 'B'): 10, ('prod2', 'B'): 10, ('prod3', 'B'): 10} # 1. 定义决策变量:采购量x,以及标记运费是否触发的二进制变量y model.x = Var(N, M, bounds=(0,10)) # 二进制变量:y[m]=1表示从供应商m采购的总成本≤阈值,需付运费;y[m]=0表示免运费 model.y = Var(M, domain=Binary) # 2. 定义大M值:每个供应商的最大可能总成本(确保约束生效) # 供应商A的最大总成本:sum(p[n,A] * q_s[n,A]) = 10*20 +9*10 +50*10=790,取800 M_A = 800 # 供应商B的最大总成本:16*10 +20*10 +35*10=710,取750 M_B = 750 # 运费阈值 threshold_A = 100 threshold_B = 150 # 运费金额 freight_A = 10 freight_B = 8 # 3. 修改目标函数:采购成本 + 运费 def obj_rule(model): purchase_cost = sum(p[n,m]*model.x[n,m] for n in N for m in M) freight_cost = freight_A * model.y['A'] + freight_B * model.y['B'] return purchase_cost + freight_cost model.obj = Objective(rule=obj_rule, sense=minimize) # 4. 原有约束:用户最小采购量、供应商最大供应量 def user_quantity(model, n, m): return model.x[n,m] >= q_u[n,m] model.user_quantity = Constraint(N, M, rule=user_quantity) def seller_quantity(model, n, m): return model.x[n,m] <= q_s[n,m] model.seller_quantity = Constraint(N, M, rule=seller_quantity) # 5. 添加运费相关的大M约束 # 供应商A的约束: def freight_constraint_A_upper(model): # 当y[A]=1时,总成本≤threshold_A;y[A]=0时,约束自动满足 return sum(p[n,'A']*model.x[n,'A'] for n in N) <= threshold_A + M_A*(1 - model.y['A']) model.freight_A_upper = Constraint(rule=freight_constraint_A_upper) def freight_constraint_A_lower(model): # 当y[A]=0时,总成本>threshold_A(加1e-6避免浮点精度问题);y[A]=1时约束自动满足 return sum(p[n,'A']*model.x[n,'A'] for n in N) >= threshold_A + 1e-6 - M_A*model.y['A'] model.freight_A_lower = Constraint(rule=freight_constraint_A_lower) # 供应商B的约束: def freight_constraint_B_upper(model): return sum(p[n,'B']*model.x[n,'B'] for n in N) <= threshold_B + M_B*(1 - model.y['B']) model.freight_B_upper = Constraint(rule=freight_constraint_B_upper) def freight_constraint_B_lower(model): return sum(p[n,'B']*model.x[n,'B'] for n in N) >= threshold_B + 1e-6 - M_B*model.y['B'] model.freight_B_lower = Constraint(rule=freight_constraint_B_lower) # 求解并打印结果 solver = SolverFactory('glpk') solver.solve(model) print("采购量:") model.x.pprint() print("\n运费触发标记(1=需付运费,0=免运费):") model.y.pprint() print("\n总成本(采购+运费):", model.obj())
关键部分解释
- 二进制变量y:用来把“是否触发运费”的离散逻辑转化为线性变量,Pyomo的
Binary域确保变量只能取0或1。 - 大M约束:
- 上界约束:当
y[m]=1时,强制总成本≤阈值;y[m]=0时,大M项会让约束变得宽松,不会限制总成本。 - 下界约束:当
y[m]=0时,强制总成本略大于阈值(加1e-6是为了避免浮点精度问题,防止出现总成本刚好等于阈值却触发免运费的情况);y[m]=1时,约束自动满足。
- 上界约束:当
- 目标函数:把运费和二进制变量绑定,只有当触发运费时(
y[m]=1)才会把对应运费加入总成本。
这样修改后,模型就能自动计算出包含最优运费策略的采购方案了。
内容的提问来源于stack exchange,提问作者Malcolm
相关产品推荐
相关产品推荐

