OR-Tools CP-Sat员工调度:日均在岗人数均衡优化实现问题
员工调度CP-Sat问题优化方案
针对你遇到的每日在岗人数差异无法平衡、无法预设在岗人数范围及筛选日期的问题,以下是适配CP-Sat的具体解决方案:
一、动态确定在岗人数范围
无需预先硬编码人数区间,可通过员工总数和每月工作天数计算理论均值,再基于均值设置合理的上下限约束:
- 先计算总工作天数与理论日均在岗人数:
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)
- 为每日在岗人数变量添加上下限约束:
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这类硬筛选,可通过计数变量控制不同在岗人数的天数分布:
- 定义每个在岗人数对应的天数统计变量:
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")
- 添加天数总和约束:
model.Add(sum(count_vars.values()) == days_in_month)
- 关联每日在岗人数与对应计数变量:
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
相关产品推荐
相关产品推荐

