You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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}")

关键验证要点

  1. 班次占比验证:输出total_8_16、total_16_24、total_24_8的实际值,确认是否在设定的弹性范围内
  2. 连休验证:检查每个员工的休假日期是否包含至少一组连续两天
  3. 工作天数验证:确认每个员工每周工作天数≤5
  4. 班次连续验证:检查员工的班次记录是否符合“连续工作班次一致”的规则

内容的提问来源于stack exchange,提问作者Parristaba

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 08:14:55