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

OR-Tools CP-Sat员工调度:日均在岗人数均衡优化实现问题

员工调度CP-Sat问题优化方案

针对你遇到的每日在岗人数差异无法平衡、无法预设在岗人数范围及筛选日期的问题,以下是适配CP-Sat的具体解决方案:

一、动态确定在岗人数范围

无需预先硬编码人数区间,可通过员工总数和每月工作天数计算理论均值,再基于均值设置合理的上下限约束:

  1. 先计算总工作天数与理论日均在岗人数:
import math

total_working_days = len(employees) * 8
mean_daily = total_working_days / days_in_month
# 基于均值推导上下限,可根据实际情况放宽范围
min_daily = math.floor(mean_daily) - 1
max_daily = math.ceil(mean_daily) + 1
# 避免极端值,确保范围在1到员工总数之间
min_daily = max(1, min_daily)
max_daily = min(len(employees), max_daily)
  1. 为每日在岗人数变量添加上下限约束:
working = []
for day in range(1, days_in_month + 1):
    day_workers = sum([employee.shifts[day] for employee in employees])
    working.append(day_workers)
    model.Add(day_workers >= min_daily)
    model.Add(day_workers <= max_daily)

二、替代固定值筛选日期的约束方法

不用day["count"] == 6这类硬筛选,可通过计数变量控制不同在岗人数的天数分布:

  1. 定义每个在岗人数对应的天数统计变量:
count_vars = {}
for num in range(min_daily, max_daily + 1):
    count_vars[num] = model.NewIntVar(0, days_in_month, f"days_with_{num}_workers")
  1. 添加天数总和约束:
model.Add(sum(count_vars.values()) == days_in_month)
  1. 关联每日在岗人数与对应计数变量:
for num in count_vars:
    day_indicators = []
    for day_idx in range(days_in_month):
        # 布尔变量标记当天在岗人数是否等于num
        is_num = model.NewBoolVar(f"day_{day_idx+1}_has_{num}_workers")
        model.Add(working[day_idx] == num).OnlyEnforceIf(is_num)
        model.Add(working[day_idx] != num).OnlyEnforceIf(is_num.Not())
        day_indicators.append(is_num)
    # 统计所有符合条件的天数
    model.Add(sum(day_indicators) == count_vars[num])

三、最小化在岗人数差异的目标函数

核心是通过CP-Sat的目标优化来缩小每日在岗人数波动,两种常用方式:

方式1:最小化最大与最小在岗人数的差值

max_workers = model.NewIntVar(min_daily, max_daily, "max_daily_workers")
min_workers = model.NewIntVar(min_daily, max_daily, "min_daily_workers")
model.AddMaxEquality(max_workers, working)
model.AddMinEquality(min_workers, working)
model.Minimize(max_workers - min_workers)

方式2:最小化每日在岗人数与均值的绝对值差之和

total_abs_diff = []
target_mean = round(mean_daily)
for day_workers in working:
    abs_diff = model.NewIntVar(0, max_daily - min_daily, f"diff_day_{...}")
    model.AddAbsEquality(abs_diff, day_workers - target_mean)
    total_abs_diff.append(abs_diff)
model.Minimize(sum(total_abs_diff))

完整代码整合示例

from ortools.sat.python import cp_model
import math

# 初始化模型
model = cp_model.CpModel()

# 1. 定义员工每日排班布尔变量
for employee in employees:
    employee.shifts = {}
    for day in range(1, days_in_month + 1):
        employee.shifts[day] = model.NewBoolVar(f"emp_{employee.name}_day_{day}")

# 2. 约束每位员工每月工作8天
for employee in employees:
    model.Add(sum(employee.shifts.values()) == 8)

# 3. 计算每日在岗人数变量
working = []
for day in range(1, days_in_month + 1):
    day_workers = sum([employee.shifts[day] for employee in employees])
    working.append(day_workers)

# 4. 确定在岗人数范围并添加约束
total_working_days = len(employees) * 8
mean_daily = total_working_days / days_in_month
min_daily = max(1, math.floor(mean_daily) - 1)
max_daily = min(len(employees), math.ceil(mean_daily) + 1)
for day_workers in working:
    model.Add(day_workers >= min_daily)
    model.Add(day_workers <= max_daily)

# 5. 设置目标:最小化在岗人数最大差值
max_workers = model.NewIntVar(min_daily, max_daily, "max_daily_workers")
min_workers = model.NewIntVar(min_daily, max_daily, "min_daily_workers")
model.AddMaxEquality(max_workers, working)
model.AddMinEquality(min_workers, working)
model.Minimize(max_workers - min_workers)

# 6. 求解并输出结果
solver = cp_model.CpSolver()
status = solver.Solve(model)

if status in [cp_model.OPTIMAL, cp_model.FEASIBLE]:
    for day_idx in range(days_in_month):
        print(f"Day {day_idx+1}: {solver.Value(working[day_idx])} 人在岗")
    for employee in employees:
        assigned_days = [d for d in range(1, days_in_month+1) if solver.Value(employee.shifts[d])]
        print(f"{employee.name} 排班日期:{assigned_days}")
else:
    print("未找到可行解")

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 00:15:03