基于PuLP的排班LP模型:正确定义最小约束并修复连休失效问题
员工排班线性规划问题修正方案(Python PuLP)
问题背景
用Python PuLP构建员工排班LP模型时,原单班次每周总工时约束下程序正常运行,但改为按销售数据分配班次占比的约束后,无法满足员工连续两天休假的要求。问题集中在最小周度员工变量定义与约束2模块,推测是min/max变量计算逻辑错误导致约束冲突,无法兼容“每周最多工作5天、必须连续休2天”的规则。
核心问题分析
原代码的min/max计算逻辑存在本质错误:
max_8_16 = round(eleman_8_16 * 5 * 200) + 10
此处计算的是每周该班次的总人力需求(200个员工×每周最多工作5天×占比),但约束2却将该值作为每日的班次人力要求,导致每日人力需求远超过实际可提供的上限(200个员工每天最多200人,但按此计算可能出现400+的不合理要求),直接触发约束冲突,连休规则自然无法满足。
同时原连休约束(Constraint5)的写法逻辑不严谨,无法确保每个员工至少拥有一组连续两天的休假。
修正方案
1. 重构班次人力约束逻辑
将“每日班次人力硬约束”改为“每周班次总人力占比约束”,既符合销售数据的占比要求,又避免与员工工作/休假规则冲突:
- 计算每周总可用人力:
total_available = 员工总数 × 每周最多工作天数(200×5=1000) - 按销售占比计算各班次每周总需求基准值
- 给总需求设置合理弹性范围(比如±5%,而非固定±10)
2. 修正连休约束逻辑
替换原约束5,确保每个员工在一周内至少有一组连续两天的休假。
3. 优化其他约束细节
- 简化原Constraint4的冗余循环(原循环范围仅触发1次,直接简化逻辑)
- 保留原班次连续规则(Constraint6)的核心逻辑,优化约束命名与可读性
完整修正代码
import json import pulp days = 7 # 一周7天 employees = 200 # 员工总数 max_work_days = 5 # 每周最多工作5天 flexibility_rate = 0.05 # 占比弹性范围±5% # 加载销售数据对应的班次占比 jsonWorkers = json.load(open("shiftData.json")) ratio_8_16 = jsonWorkers["eleman_8_16"] ratio_16_24 = jsonWorkers["eleman_16_24"] ratio_24_8 = jsonWorkers["eleman_24_8"] # 计算每周总可用人力及各班次总需求范围 total_available = employees * max_work_days total_8_16_base = total_available * ratio_8_16 total_16_24_base = total_available * ratio_16_24 total_24_8_base = total_available * ratio_24_8 # 设置弹性范围(避免硬约束导致无解) min_8_16_total = round(total_8_16_base * (1 - flexibility_rate)) max_8_16_total = round(total_8_16_base * (1 + flexibility_rate)) min_16_24_total = round(total_16_24_base * (1 - flexibility_rate)) max_16_24_total = round(total_16_24_base * (1 + flexibility_rate)) min_24_8_total = round(total_24_8_base * (1 - flexibility_rate)) max_24_8_total = round(total_24_8_base * (1 + flexibility_rate)) # 定义变量:(日期, 员工) -> 是否在该班次/休假 var_8_16 = pulp.LpVariable.dicts("8_16", (range(days), range(employees)), 0, 1, "Binary") var_16_24 = pulp.LpVariable.dicts("16_24", (range(days), range(employees)), 0, 1, "Binary") var_24_8 = pulp.LpVariable.dicts("24_8", (range(days), range(employees)), 0, 1, "Binary") var_Tatil = pulp.LpVariable.dicts("Tatil", (range(days), range(employees)), 0, 1, "Binary") # 目标函数:最大化总工作天数(等价于最小化休假天数,可根据需求调整) problem = pulp.LpProblem("Vardiya", pulp.LpMaximize) obj = pulp.lpSum([var_8_16[i][j] + var_16_24[i][j] + var_24_8[i][j] for i in range(days) for j in range(employees)]) problem += obj # 约束1:每天每个员工只能选择一个班次或休假 for i in range(days): for j in range(employees): problem += (var_8_16[i][j] + var_16_24[i][j] + var_24_8[i][j] + var_Tatil[i][j] == 1, f"Daily_Shift_or_Off_{i}_{j}") # 约束2:每周各班次总人力符合占比范围 total_8_16 = pulp.lpSum([var_8_16[i][j] for i in range(days) for j in range(employees)]) problem += (total_8_16 >= min_8_16_total, "Min_Total_8_16") problem += (total_8_16 <= max_8_16_total, "Max_Total_8_16") total_16_24 = pulp.lpSum([var_16_24[i][j] for i in range(days) for j in range(employees)]) problem += (total_16_24 >= min_16_24_total, "Min_Total_16_24") problem += (total_16_24 <= max_16_24_total, "Max_Total_16_24") total_24_8 = pulp.lpSum([var_24_8[i][j] for i in range(days) for j in range(employees)]) problem += (total_24_8 >= min_24_8_total, "Min_Total_24_8") problem += (total_24_8 <= max_24_8_total, "Max_Total_24_8") # 约束3:每个员工每周最多工作5天 for j in range(employees): total_work_days = pulp.lpSum([var_8_16[i][j] + var_16_24[i][j] + var_24_8[i][j] for i in range(days)]) problem += (total_work_days <= max_work_days, f"Max_Work_Days_{j}") # 约束4:每个员工必须有连续两天休假 for j in range(employees): # 确保至少存在一组连续两天的休假 consecutive_off = pulp.lpSum([var_Tatil[i][j] * var_Tatil[i+1][j] for i in range(days-1)]) problem += (consecutive_off >= 1, f"Consecutive_Off_{j}") # 约束5:员工连续工作时班次保持一致(原Constraint6逻辑) for j in range(employees): # 次日班次必须和当日一致 for i in range(days - 1): problem += (var_8_16[i][j] + var_16_24[i+1][j] + var_24_8[i+1][j] <= 1, f"Same_Shift_Next_Day_8_16_{i}_{j}") problem += (var_16_24[i][j] + var_8_16[i+1][j] + var_24_8[i+1][j] <= 1, f"Same_Shift_Next_Day_16_24_{i}_{j}") problem += (var_24_8[i][j] + var_8_16[i+1][j] + var_16_24[i+1][j] <= 1, f"Same_Shift_Next_Day_24_8_{i}_{j}") # 间隔3天的班次也必须一致(原逻辑保留) for i in range(days - 3): problem += (var_8_16[i][j] + var_16_24[i+3][j] + var_24_8[i+3][j] <= 1, f"Same_Shift_3Days_8_16_{i}_{j}") problem += (var_16_24[i][j] + var_8_16[i+3][j] + var_24_8[i+3][j] <= 1, f"Same_Shift_3Days_16_24_{i}_{j}") problem += (var_24_8[i][j] + var_8_16[i+3][j] + var_16_24[i+3][j] <= 1, f"Same_Shift_3Days_24_8_{i}_{j}") # 求解模型 problem.solve(pulp.PULP_CBC_CMD(msg=0)) # 输出结果示例 print(f"求解状态: {pulp.LpStatus[problem.status]}") # 验证部分员工的休假情况 for j in range(5): off_days = [i for i in range(days) if pulp.value(var_Tatil[i][j]) == 1] print(f"员工{j}休假日期: {off_days}")
关键验证要点
- 班次占比验证:输出
total_8_16、total_16_24、total_24_8的实际值,确认是否在设定的弹性范围内 - 连休验证:检查每个员工的休假日期是否包含至少一组连续两天
- 工作天数验证:确认每个员工每周工作天数≤5
- 班次连续验证:检查员工的班次记录是否符合“连续工作班次一致”的规则
内容的提问来源于stack exchange,提问作者Parristaba
相关产品推荐
相关产品推荐

