如何在Python的CPLEX中编写带批量折扣的供应商选择目标函数?
解决带全量批量折扣的供应商采购成本最小化问题(Python CPLEX实现)
针对你遇到的问题——无法用普通函数计算CPLEX变量的区间成本,核心原因是CPLEX的决策变量在模型构建阶段是符号化的,不能直接通过数值判断逻辑计算成本。需要将全量批量折扣的成本规则转化为整数线性规划的约束和线性化目标函数,以下是具体实现方案:
问题分析
你的供应商定价属于全量批量折扣:当订购量落在某个区间时,全部采购量都按该区间的单价计算(比如订购7件全部按4元/件算,而非前5件5元、后2件4元)。这种非连续的成本函数需要通过二进制变量标记区间选择,再线性化处理目标函数。
建模思路
决策变量定义:
- 连续变量
x[s]:从供应商s采购的总数量 - 二进制变量
y[s][k]:标记是否选择供应商s的第k个定价区间(1=选择,0=不选) - 连续变量
z[s][k]:线性化辅助变量,代表供应商s在第k个区间的有效采购量(即x[s] * y[s][k]的线性近似)
- 连续变量
核心约束:
- 需求满足:所有供应商采购量之和≥总需求量
- 区间互斥:每个供应商最多选择一个定价区间
- 辅助变量线性化约束:确保
z[s][k]仅在y[s][k]=1时等于x[s],否则为0 - 采购量分解:
x[s]等于所有区间有效采购量之和
目标函数:最小化所有供应商的采购成本之和,即各区间单价乘以对应有效采购量的总和
完整Python CPLEX代码
import cplex # 你的定价列表 price_list = [ {(0, 5): 5, (5, 10): 4}, {(0, 5): 6, (5, 10): 5, (10, 15): 4, (15, 20): 3}, {(0, 2): 3, (2, 3): 2}, {(0, 2): 4, (2, 3): 3, (3, 4): 2} ] # 总需求量(可根据实际调整) total_demand = 20 # 初始化模型 model = cplex.Cplex() model.objective.set_sense(model.objective.sense.minimize) # 定义变量名和类型 S = len(price_list) # 供应商数量 x = {} # 采购量变量 y = {} # 区间选择二进制变量 z = {} # 线性化辅助变量 max_upper = 0 # 所有供应商的最大订购上限,用于大M约束 # 遍历每个供应商,创建变量 for s in range(S): supp_prices = price_list[s] # 按区间下限排序,确保处理顺序正确 sorted_intervals = sorted(supp_prices.keys(), key=lambda x: x[0]) # 更新最大上限 current_max = max(interval[1] for interval in sorted_intervals) if current_max > max_upper: max_upper = current_max # 创建采购量变量x[s] x_var_name = f"x_{s}" model.variables.add(names=[x_var_name], lb=[0.0], ub=[current_max]) x[s] = x_var_name # 创建区间选择变量和辅助变量 for k, (L, U) in enumerate(sorted_intervals): # 二进制变量y[s][k] y_var_name = f"y_{s}_{k}" model.variables.add(names=[y_var_name], types=["B"]) y[(s, k)] = y_var_name # 辅助变量z[s][k] z_var_name = f"z_{s}_{k}" model.variables.add(names=[z_var_name], lb=[0.0], ub=[U]) z[(s, k)] = z_var_name # 添加约束 # 1. 需求满足约束 demand_expr = cplex.SparsePair(ind=[x[s] for s in range(S)], val=[1.0]*S) model.linear_constraints.add(lin_expr=[demand_expr], senses=["G"], rhs=[total_demand]) # 2. 每个供应商的区间互斥约束(最多选一个区间) for s in range(S): supp_prices = price_list[s] sorted_intervals = sorted(supp_prices.keys(), key=lambda x: x[0]) y_vars = [y[(s, k)] for k in range(len(sorted_intervals))] expr = cplex.SparsePair(ind=y_vars, val=[1.0]*len(y_vars)) model.linear_constraints.add(lin_expr=[expr], senses=["L"], rhs=[1.0]) # 3. 辅助变量z的线性化约束,以及x[s]的分解约束 for s in range(S): supp_prices = price_list[s] sorted_intervals = sorted(supp_prices.keys(), key=lambda x: x[0]) x_var = x[s] # 收集当前供应商的z变量,用于x[s]的分解约束 z_vars = [] z_coeffs = [] for k, (L, U) in enumerate(sorted_intervals): y_var = y[(s, k)] z_var = z[(s, k)] z_vars.append(z_var) z_coeffs.append(1.0) # z <= U * y expr1 = cplex.SparsePair(ind=[z_var, y_var], val=[1.0, -U]) model.linear_constraints.add(lin_expr=[expr1], senses=["L"], rhs=[0.0]) # z >= L * y expr2 = cplex.SparsePair(ind=[z_var, y_var], val=[1.0, -L]) model.linear_constraints.add(lin_expr=[expr2], senses=["G"], rhs=[0.0]) # z <= x expr3 = cplex.SparsePair(ind=[z_var, x_var], val=[1.0, -1.0]) model.linear_constraints.add(lin_expr=[expr3], senses=["L"], rhs=[0.0]) # z >= x - max_upper*(1 - y) → z - x + max_upper*y >= -max_upper expr4 = cplex.SparsePair(ind=[z_var, x_var, y_var], val=[1.0, -1.0, max_upper]) model.linear_constraints.add(lin_expr=[expr4], senses=["G"], rhs=[-max_upper]) # x[s] = sum(z[s][k]) expr_decomp = cplex.SparsePair(ind=[x_var] + z_vars, val=[-1.0] + z_coeffs) model.linear_constraints.add(lin_expr=[expr_decomp], senses=["E"], rhs=[0.0]) # 设置目标函数:最小化总成本 obj_expr = [] obj_coeffs = [] for s in range(S): supp_prices = price_list[s] sorted_intervals = sorted(supp_prices.keys(), key=lambda x: x[0]) for k, (L, U) in enumerate(sorted_intervals): price = supp_prices[(L, U)] z_var = z[(s, k)] obj_expr.append(z_var) obj_coeffs.append(price) model.objective.set_linear(zip(obj_expr, obj_coeffs)) # 求解模型 model.solve() # 输出结果 print("求解状态:", model.solution.get_status_string()) print(f"最小总成本:{model.solution.get_objective_value():.2f}") print("各供应商采购量:") for s in range(S): print(f"供应商{s}:{model.solution.get_values(x[s]):.2f}") print("各区间选择情况(1表示选中):") for s in range(S): supp_prices = price_list[s] sorted_intervals = sorted(supp_prices.keys(), key=lambda x: x[0]) for k, (L, U) in enumerate(sorted_intervals): y_val = model.solution.get_values(y[(s, k)]) if y_val > 0.9: print(f"供应商{s} 区间({L},{U}):选中,单价{supp_prices[(L,U)]}")
代码说明
- 先对每个供应商的定价区间按下限排序,确保处理逻辑正确
- 用大M方法实现辅助变量的线性化,避免非线性的变量乘积
- 所有约束和目标函数均为线性表达式,符合CPLEX的求解要求
内容的提问来源于stack exchange,提问作者M Germanos
相关产品推荐
相关产品推荐

