Pyomo护患调度模型中如何基于分配动态计算护士步行距离
Pyomo护患分配模型中护士床位步行距离计算优化
我正在使用Pyomo编写护患分配算法,目前遇到的问题是无法为每名护士动态计算其分配到的患者床位之间的步行距离。下方给出相关集合、参数及变量的小型示例,其中变量填充值为求解完成后的预期结果:
# 集合定义 model.PatientIDs = {0, 1, 2} model.NurseIDs = {a, b} # 索引参数(索引为(患者1, 患者2)) # 存储每两个患者床位之间的距离 model.Bed_Distances = {(0, 0) : 0, (0, 1) : 6.4, (0, 2) : 7.2, (1, 0) : 6.4, (1, 1) : 0, (1, 2) : 1.9, (2, 1) : 1.9, (2, 0) : 7.2} # 索引变量(索引为(患者, 护士)) model.Assignments = {(0, a): 1, (0, b): 0, (1, a) : 0, (1, b) : 1, (2, a) : 1, (2, b) : 0} # 索引变量(索引为护士) # 记录每名护士分配到的所有患者床位之间的步行距离总和 model.Nurse_Distances = {a : 7.2, b : 0}
护士步行距离的取值取决于分配给该护士的患者集合,需要在模型求解过程中动态计算。已知model.Assignments为0-1二元决策变量,我当前使用如下约束规则计算护士对应的步行距离:
def nurse_distance(self, nurse): return model.NURSE_DISTANCE[nurse] == sum([model.ASSIGNMENTS[p1, nurse] * model.ASSIGNMENTS[p2, nurse] * model.BED_DISTANCES[p1, p2] for p1 in model.PATIENTS for p2 in model.PATIENTS]) model.DISTANCE_CONSTRAINT = pe.Constraint(model.NURSES, rule = nurse_distance)
添加该约束后,模型要么长时间运行无法收敛,仅在患者数为2-3的极小场景下可较快完成求解,同时会抛出如下NEOS解文件解析错误:
ERROR: Error parsing NEOS solution file NEOS log: Job 11992161 dispatched password: iGYfJtMj ---------- Begin Solver Output ----------packages/pyomo/opt/plugins/sol.py", line 87, in _load raise ValueError("no Options line found") ValueError: no Options line found
请问在Pyomo中是否存在更优的方式维护这类依赖其他变量取值的因变量(即护士床位步行距离依赖护患分配结果)?如果没有,是否有计算效率更高的约束编写方式,无需使用if-then条件语句即可完成该距离计算?
问题原因
你当前写法的核心问题是约束中包含两个二元决策变量相乘的二次项,直接把原本的线性混合整数规划(MILP)变成了二次混合整数规划(MIQP)。绝大多数求解器对MIQP的求解效率远低于MILP,患者数量稍大就会出现长时间不收敛的情况。你遇到的NEOS解文件解析错误,本质是求解器运行异常/超时退出时没有输出符合格式要求的解文件,和Pyomo本身的变量维护逻辑没有关系。
线性化改造方案
不需要使用if-then条件判断,直接把非线性二次项替换为线性约束,将模型转回MILP形式,求解效率可以提升几个数量级,具体做法:
- 首先优化求和逻辑:你当前的写法会把每对床位的距离重复计算两次(p1到p2和p2到p1各计一次),还会把患者到自身的0距离纳入求和,完全可以只遍历
p1 < p2的患者对,直接把求和项数量减半。 - 两个二元变量的乘积
Assignments[p1,n] * Assignments[p2,n]本身就是0-1值:只有p1、p2同时分配给护士n时才为1,否则为0。这个逻辑可以用三个线性约束完全等价替代,不需要任何非线性表达式:- 新增辅助二元变量
PairAssigned[p1,p2,n],标记p1和p2是否同时分配给护士n - 约束1:
PairAssigned[p1,p2,n] <= Assignments[p1,n](p1未分配给n时,标记必须为0) - 约束2:
PairAssigned[p1,p2,n] <= Assignments[p2,n](p2未分配给n时,标记必须为0) - 约束3:
PairAssigned[p1,p2,n] >= Assignments[p1,n] + Assignments[p2,n] - 1(两个患者都分配给n时,标记必须为1)
- 新增辅助二元变量
- 最后护士步行距离的约束就可以写成完全线性的形式,代码实现如下:
# 定义辅助变量,仅需为p1<p2的患者对创建 model.PairAssigned = pe.Var(model.PatientIDs, model.PatientIDs, model.NurseIDs, domain=pe.Binary) # 辅助变量上界约束1 def pair_assign_ub1(model, p1, p2, n): if p1 >= p2: return pe.Constraint.Skip return model.PairAssigned[p1,p2,n] <= model.Assignments[p1,n] model.PairUB1 = pe.Constraint(model.PatientIDs, model.PatientIDs, model.NurseIDs, rule=pair_assign_ub1) # 辅助变量上界约束2 def pair_assign_ub2(model, p1, p2, n): if p1 >= p2: return pe.Constraint.Skip return model.PairAssigned[p1,p2,n] <= model.Assignments[p2,n] model.PairUB2 = pe.Constraint(model.PatientIDs, model.PatientIDs, model.NurseIDs, rule=pair_assign_ub2) # 辅助变量下界约束 def pair_assign_lb(model, p1, p2, n): if p1 >= p2: return pe.Constraint.Skip return model.PairAssigned[p1,p2,n] >= model.Assignments[p1,n] + model.Assignments[p2,n] - 1 model.PairLB = pe.Constraint(model.PatientIDs, model.PatientIDs, model.NurseIDs, rule=pair_assign_lb) # 护士步行距离线性约束 def nurse_distance(model, n): return model.Nurse_Distances[n] == sum( model.Bed_Distances[p1,p2] * model.PairAssigned[p1,p2,n] for p1 in model.PatientIDs for p2 in model.PatientIDs if p1 < p2 ) model.DistanceConstraint = pe.Constraint(model.NurseIDs, rule=nurse_distance)
补充说明
- Pyomo没有特殊的“动态维护因变量”机制,所有变量的联动都是通过约束实现的,只要约束逻辑正确,求解完成后直接读取变量值即可,不需要额外写回调或者动态更新逻辑。
- 如果你要计算的是护士遍历所有负责床位的最短路径长度,而不是所有床位对的距离和,也可以用类似的线性化思路处理,全程避免二次项即可。
- 转成MILP模型后,不管是本地求解还是提交到NEOS,求解速度和稳定性都会大幅提升,不会再出现之前的解文件解析错误。
- 你提供的示例代码存在命名不一致问题:集合、变量定义时用的是
PatientIDs/NurseIDs/Assignments/Nurse_Distances,约束里写的是PATIENTS/NURSES/ASSIGNMENTS/NURSE_DISTANCE,实际编写时要统一命名,否则会触发属性不存在的报错。
内容的提问来源于stack exchange,提问作者Nurse_Patient_Optimizer
相关产品推荐
相关产品推荐

