AMPL/Python仓库货位分配模型性能与内存问题求助
仓库货位分配优化模型求解问题排查与修复方案
问题概述
针对30000货位的仓库货位分配模型,3个商品规模下Gurobi/Cplex可正常求解,但存在三类异常:
- CBC求解器处理3个商品时,运行1分钟仍无结果
- Highs求解器触发
Error -1 for call Highs_passH错误 - 3000商品规模下,Gurobi/Cplex报
Unexpected end of file while reading AMPL output.错误
核心问题分析
从模型和代码中可定位几个关键问题,是导致求解异常的主要原因:
- 冗余约束与变量:
AtLeastOneAssignment_Constraint与Assignment_Constraint重复,前者要求每个商品至少分配一个货位,后者直接强制分配恰好一个货位,冗余约束会增加求解器计算负担。prev_loc变量无任何约束条件,在目标函数中作为系数存在,但最优解中该变量会被设为0(最小化成本),属于完全冗余变量,将变量总数从|Items|×|Locations|翻倍至2×|Items|×|Locations|。
- 数据传递效率低下:
- 生成
valid_allocations参数时采用全量笛卡尔积矩阵,3000商品+30000货位的场景下,参数规模达9000万条,远超必要范围(仅需传递有效分配对),导致内存占用过高、通信异常。
- 生成
- 参数与注释不匹配:
Quantity参数注释标注为“通道成本”,但实际传递的是商品需求量,易造成理解偏差。
分问题解决方案
1. CBC求解器求解缓慢
- 移除冗余内容:删除
AtLeastOneAssignment_Constraint约束和prev_loc变量(及目标函数中对应的项),简化模型结构。 - 设置求解器参数:给CBC添加启发式算法和时间限制,提升求解速度:
ampl.option["solver"] = "cbc" ampl.option["cbc_options"] = "sec=60 heur=1" # 60秒时间限制,开启启发式
2. Highs求解器报错Error -1 for call Highs_passH
- 修复模型结构:先执行上述冗余内容移除操作,减少模型复杂度。
- 校验参数有效性:确保
valid_allocations参数无缺失值或非0/1的无效值:cartesian_locations = cartesian_locations.with_columns( valid=pl.col("CART_STD1") | pl.col("CART_STD2") | pl.col("CART_DEMI-HAUT") | pl.col("CART_VOLUMINEUX") ) cartesian_locations = cartesian_locations.fill_null(0) # 填充缺失值为0 cartesian_locations = cartesian_locations.with_columns(pl.col("valid").cast(pl.Int32)) # 强制转为整数 - 更新求解器版本:使用最新版Highs求解器,避免接口兼容性问题。
3. 3000商品规模下Gurobi/Cplex通信错误
- 优化模型变量数:删除冗余的
prev_loc变量,变量总数直接减半,降低内存压力。 - 优化数据传递:仅传递
valid_allocations为1的有效商品-货位对,避免全量矩阵传递:# 生成有效分配对 cartesian_locations = orders.select("ITEM_ID").unique().join(locations, how='cross') cartesian_locations = cartesian_locations.with_columns( valid=pl.col("CART_STD1") | pl.col("CART_STD2") | pl.col("CART_DEMI-HAUT") | pl.col("CART_VOLUMINEUX") ) valid_pairs = cartesian_locations.filter(pl.col("valid") == 1).select(["ITEM_ID", "LOCATION_ID", "valid"]) # 仅传递有效对的参数值 valid_allocations_param.setValues(dict(valid_pairs.iter_rows())) - 设置求解器资源限制:给Gurobi/Cplex分配足够内存和线程,避免内存溢出:
# Gurobi示例 ampl.option["gurobi_options"] = "MemLimit=16 Threads=4" # Cplex示例 ampl.option["cplex_options"] = "miplimits memory=16384 threads=4" - 内存优化:全程使用Polars处理数据(避免转为Pandas),减少内存占用;启用AMPL内存限制选项:
ampl.option["memory_limit"] = "16G"
修改后的完整代码示例
优化后的AMPL模型
set Items; # 商品集合 set Locations; # 货位集合 param Quantity{Items}; # 商品需求量 param Cost_Location{Locations}; # 货位行走距离成本 param valid_allocations{Items, Locations} binary default 0; # 有效分配矩阵,默认0 var assign{Items, Locations} binary; # 决策变量:1表示商品i分配到货位j minimize Total_Cost: sum{i in Items, j in Locations} assign[i,j] * Cost_Location[j]; subject to Assignment_Constraint{i in Items}: sum{j in Locations} assign[i,j] = 1; # 每个商品必须分配至一个货位 subject to Valid_Allocation_Constraint{i in Items, j in Locations}: assign[i,j] <= valid_allocations[i,j]; # 仅允许有效分配
优化后的Python代码
from amplpy import AMPL import polars as pl ampl = AMPL() ampl.option["solver"] = "gurobi" ampl.option["gurobi_options"] = "MemLimit=16 Threads=4" ampl.read("allocation.model") # 获取AMPL集合与参数 items_set = ampl.getSet("Items") locations_set = ampl.getSet("Locations") quantity_demand_param = ampl.getParameter("Quantity") cost_location_param = ampl.getParameter("Cost_Location") valid_allocations_param = ampl.getParameter("valid_allocations") # 读取数据 orders = pl.read_csv("./data/orders.csv", separator=';') locations = pl.read_csv("./data/locations.csv", separator=';') # 若测试3000商品,注释掉以下过滤行 # orders = orders.filter((pl.col("ITEM_ID") == 34682) | (pl.col("ITEM_ID") == 34657) | (pl.col("ITEM_ID") == 29840)) # 设置集合值 items_set.setValues(orders.select("ITEM_ID").unique().to_dict(as_series=False)["ITEM_ID"]) locations_set.setValues(locations.select("LOCATION_ID").unique().to_dict(as_series=False)["LOCATION_ID"]) # 设置参数值 quantity_demand_param.setValues(dict(orders.unique(subset="ITEM_ID").select(["ITEM_ID","QTY"]).iter_rows())) cost_location_param.setValues(dict(locations.unique(subset="LOCATION_ID").select(["LOCATION_ID","WALKING_DISTANCE"]).iter_rows())) # 生成有效分配对 cartesian_locations = orders.select("ITEM_ID").unique().join(locations, how='cross') cartesian_locations = cartesian_locations.with_columns( valid=pl.col("CART_STD1") | pl.col("CART_STD2") | pl.col("CART_DEMI-HAUT") | pl.col("CART_VOLUMINEUX") ) cartesian_locations = cartesian_locations.fill_null(0) cartesian_locations = cartesian_locations.with_columns(pl.col("valid").cast(pl.Int32)) valid_pairs = cartesian_locations.filter(pl.col("valid") == 1).select(["ITEM_ID", "LOCATION_ID", "valid"]) # 传递有效分配参数 valid_allocations_param.setValues(dict(valid_pairs.iter_rows())) # 求解 ampl.solve()
内容的提问来源于stack exchange,提问作者Developppeur
相关产品推荐
相关产品推荐

