You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Google OR-tools中如何覆盖特定硬约束实现多角色排班

针对Google OR-Tools排班系统的角色切换约束解决方案

我刚好做过类似的排班系统优化,你的需求核心是区分单/多角色场景应用不同的硬约束,既要保证单角色时班次长度的严格性,又要允许多角色切换时的灵活调整。下面给出三种可落地的实现方案,都是基于OR-Tools CP-SAT的硬约束逻辑,完全符合你的要求:


方案3:重新定义"工作班次"(最简洁推荐)

这种思路的核心是把员工的实际工作状态(不管承担哪个角色)作为约束对象,再通过额外约束确保单角色场景下的严格性。

步骤1:定义核心变量

from ortools.sat.python import cp_model

model = cp_model.CpModel()
min_shift = 2  # 你的最小班次长度
employees = ["E1", "E2", "E3"]
roles = ["R1", "R2"]
times = list(range(7))  # 假设一周7个排班时段

# 1. 基础工作变量:员工e在t时段是否做角色r的工作
works = {}
for e in employees:
    for t in times:
        for r in roles:
            works[(e, t, r)] = model.NewBoolVar(f"works_{e}_{t}_{r}")

# 2. 实际工作状态变量:员工e在t时段是否处于工作状态(不管角色)
is_working = {}
for e in employees:
    for t in times:
        is_working[(e, t)] = model.NewBoolVar(f"is_working_{e}_{t}")
        # 关联works和is_working:只要有一个角色工作,就标记为工作状态
        model.AddBoolOr([works[(e, t, r)] for r in roles]).OnlyEnforceIf(is_working[(e, t)])
        model.AddBoolAnd([works[(e, t, r)].Not() for r in roles]).OnlyEnforceIf(is_working[(e, t)].Not())

步骤2:应用跨角色的最小班次硬约束

针对is_working变量,确保所有连续工作段的长度≥min_shift

for e in employees:
    # 定义工作段的开始/结束标记
    start_working = [model.NewBoolVar(f"start_working_{e}_{t}") for t in times]
    end_working = [model.NewBoolVar(f"end_working_{e}_{t}") for t in times]

    for t in times:
        # 标记工作段开始:当前时段工作,上一时段休息(或为第一个时段)
        if t == 0:
            model.Add(start_working[t] == is_working[(e, t)])
        else:
            model.Add(start_working[t] == is_working[(e, t)].And(is_working[(e, t-1)].Not()))
        
        # 标记工作段结束:当前时段工作,下一时段休息(或为最后一个时段)
        if t == times[-1]:
            model.Add(end_working[t] == is_working[(e, t)])
        else:
            model.Add(end_working[t] == is_working[(e, t)].And(is_working[(e, t+1)].Not()))

    # 硬约束:每个工作段开始后,必须至少连续工作min_shift时长
    for t in times:
        earliest_end = min(t + min_shift - 1, times[-1])
        # 只要t时段开始工作,必须在t到earliest_end之间的某个时段结束工作
        model.AddBoolOr([end_working[t_end] for t_end in range(t, earliest_end + 1)]).OnlyEnforceIf(start_working[t])

步骤3:约束单角色场景的严格性

对于仅承担单一角色的员工,强制is_working完全等价于该角色的工作状态,这样跨角色的班次约束就自动转化为单角色的班次约束:

for e in employees:
    # 标记员工是否被分配了某个角色
    assigned_roles = {}
    for r in roles:
        assigned_roles[r] = model.NewBoolVar(f"assigned_role_{e}_{r}")
        # 如果员工在任何时段做该角色工作,则标记为已分配
        model.AddBoolOr([works[(e, t, r)] for t in times]).OnlyEnforceIf(assigned_roles[r])
        model.AddBoolAnd([works[(e, t, r)].Not() for t in times]).OnlyEnforceIf(assigned_roles[r].Not())
    
    # 对每个角色,当员工仅分配该角色时,强制is_working与该角色的工作状态完全一致
    for r in roles:
        single_role_condition = model.And(
            sum(assigned_roles.values()) == 1,
            assigned_roles[r]
        )
        for t in times:
            model.Add(is_working[(e, t)] == works[(e, t, r)]).OnlyEnforceIf(single_role_condition)

方案1:分场景的条件硬约束

如果需要更直观地控制单/多角色的约束逻辑,可以直接分情况添加硬约束:

  1. 先通过辅助变量标记员工是否承担多角色
  2. 对单角色员工,直接约束其对应角色的工作段长度≥min_shift
  3. 对多角色员工,仅约束其跨角色的实际工作段长度≥min_shift

这种方案代码量稍大,但逻辑更直白,适合需要自定义不同场景规则的情况。


方案2:有限状态机(Automaton)建模

OR-Tools的CP-SAT支持自动机约束,可以为单/多角色场景分别定义状态转换规则:

  • 单角色状态机:休息→工作后,必须连续工作≥min_shift时长才能回到休息
  • 多角色状态机:休息→任意角色工作→切换角色→休息(只要连续工作总时长≥min_shift

通过model.AddAutomaton()结合条件变量(是否多角色),可以灵活切换状态机规则。这种方案适合处理更复杂的序列逻辑(比如角色切换的冷却时间等)。


所有方案都是基于硬约束实现的,单角色场景的班次长度要求会被严格执行,多角色场景则允许跨角色的灵活排班,完全满足你的需求。

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

火山引擎 最新活动