or-tools Employee Scheduling sequential allocations staff groups 员工组

or-tools Employee Scheduling sequential allocations staff groups

使用standard/simple护理示例: https://developers.google.com/optimization/scheduling/employee_scheduling

我试图强制特定 group/team 的护士连续分配几天。 如下图,护士1-9分成“组”。如果首先分配护士 9,则应将组(4 和 7)中的其他成员分配到后面的日子。

我想我可以通过计算一组成员一天又一天被分配的次数来实现这一点,但我无法计算这种情况发生的时间,即 day_i 和 day_i +1分配给同组的护士。

groups = [[3,7,8],[1,6],[5],[4,9,7]] #nurses 3,7,8 are in same group
...
for g in groups:
    for d1, d2 in zip(all_days[:-1],all_days[1:]):
        d1_alloc = sum(shifts[(n, d1, s)] for n in g)
        d2_alloc = sum(shifts[(n, d2, s)] for n in g)
        # ??? how to say only count when both sums = 1/true ???
        # for a group of 3, i.e. [3,7,8] this should occur twice within period

完整代码:

from ortools.sat.python import cp_model

all_days = range(1,10)
all_nurses = range(1,10)
groups = [[3,7,8],[1,6],[5],[4,9,7]] #nurses 3,7,8 are in same group
s=1 #1 shift only

model = cp_model.CpModel()

shifts = {}
for d in all_days:
    for n in all_nurses:
        shifts[(n, d, s)] = model.NewBoolVar('shift_n%sd%is%i' % (n, d, s))
            
# one nurse per shift
for d in all_days:
    model.Add(sum(shifts[(n, d, s)] for n in all_nurses) == 1)
               
# everyone works a shift
for n in all_nurses:
    model.Add(sum(shifts[(n, d, s)] for d in all_days) == 1)
 
# nurses within group should be allocated days one after another
# order of groups is not important - last group [4,9,7] could be allocated firts
# order within groups is not important - can be 7,4,9
    
for g in groups:
    for d1, d2 in zip(all_days[:-1],all_days[1:]):
        d1_alloc = sum(shifts[(n, d1, s)] for n in g)
        d2_alloc = sum(shifts[(n, d2, s)] for n in g)
        # ??? how to say only count when both sums 1/true ???
        # for a group of 3, i.e. [3,7,8] this should occur twice within period


solver = cp_model.CpSolver()
solver.Solve(model)

for d in all_days:
    for n in all_nurses:
        if solver.Value(shifts[(n, d, s)]) == 1:
            print('Day: '+str(d)+' = Nurse '+str(n))

编辑: 可以使用下面的逻辑来实现这个

for group in groups:     
    for n1, n2 in zip(group[:-1],group[1:]):
        for d in all_days[:-1]:

            model.AddBoolOr([shifts[n1, d, 1],shifts[n2, d+1, 1].Not()])
            model.AddBoolOr([shifts[n1, d, 1].Not(),shifts[n2, d+1, 1]])

此解决方案具有限制性 - 分配必须遵循与列出的组相同的顺序。组 [3,7,8] 将始终是 3,7,8,但不是 7,3,8 或 8,3,7,例如...这也可以。

还有必要确保第一天分配给从组开始的某人。 model.Add(sum(shifts[(n, 1, s)] for n in [3,1,5,4]) ==1)

如果只有最多 2 名成员的群组,以下将允许任意顺序。对于组 [3,7] 例如... 3,7 或 7,3.

for group in groups:     
    for n1, n2 in zip(group[:-1],group[1:]):
        
        #Day1
        model.AddImplication(shifts[n1, 1, 1],shifts[n2, 2, 1])
        model.AddImplication(shifts[n2, 1, 1],shifts[n1, 2, 1])
        
        #Day2 + must check preceding day to avoid circular/repeated allocations
        for d in all_days[1:-1]:
            
          model.AddImplication(shifts[n1, d, 1],shifts[n2, d+1, 1]).OnlyEnforceIf(shifts[n2, d-1, 1].Not())
          model.AddImplication(shifts[n2, d, 1],shifts[n1, d+1, 1]).OnlyEnforceIf(shifts[n1, d-1, 1].Not())

编辑 2: 以下可用于解决任何规模的群体...

for g in groups:
    
    for i in range(0,len(g)):
        #1 cycle for every group order [3,7,2], [7,2,3],[2,3,7]

        for d in all_days[:-(len(g)-1)]:
            
            conditions_met = [shifts[g[0], d, 1]] #n1 allocated today
            if d > 1:
                #ensure group members not allocated previous day
                for n in g:
                    conditions_met.append(shifts[n, d-1, 1].Not())
            
            #apply rules for next x days - depending on size of group
            for day in range(d+1,d+len(g)):
                or_cond = []
                for n in g[1:]:
                    or_cond.append(shifts[n,day,1])
                model.AddBoolOr(or_cond).OnlyEnforceIf(conditions_met)

        x = g.pop(0)
        g.append(x)

对于更复杂的约束,我建议查看 this shift scheduling example

特别是包含最小和最大序列约束。