Ограничения Minizinc Roster - PullRequest
       36

Ограничения Minizinc Roster

1 голос
/ 09 ноября 2019

Моя задача - создать список со следующим типом смен:

• Утро (м): с 7:30 до 14:45 • Рано утром (м1): с 6:45 до 14:00

• Утром звонка (я): с 6:30 до 14: 45

• После полудня (т): с 14:45 до 22: 00

• После обеда(it): с 13:45 до 22: 00

• Ночь (n): с 22:00 до 7:30 следующего дня

• Ночь по вызову (ino): с 21:00 до7:30 следующего дня

• Офис: с 10 до 14

• День отдыха (л): считается днем ​​отдыха днем ​​после ночи, даже если рабочий закончил в 7 часов утра в этот день

Применяемые правила: цикл состоит из 6 или менее из вышеуказанных смен (кроме работы в офисе и отдыха), перед необходимостью отдыха в течение 48 часов или 54 часов (см. Ниже)

  1. Еслицикл имеет 5 рабочих дней (m, m1, im, t, it, n, ino), остаток должен составлять 48 часов или более
  2. Если цикл имеет 6 рабочих дней (m, m1, im,т, это, п, ино) остальное должно быть 54 ч или более
  3. должно быть не менее 12 часов междув конце смены и в начале следующей смены (включая офис). Таким образом, невозможно иметь n, t, например,
  4. Ночей: после одной ночи (n) или ночи вызова (ino) должно быть 48 часов отдыха, если нет другой ночи (nn) или отдыхаи ночь (nln), где вам нужно 54 часа отдыха. Допустимыми примерами являются nllm (48 часов отдыха) или nnllt или nlnllt (54 часа отдыха).
  5. Максимум 5 утренних / ранних утренних / утренних звонков принимаются в цикле.
  6. офис не считается рабочим днем, но он не считается отдыхом, поэтому он должен соответствовать 12-часовому отдыху, как и другие смены, но он не считается рабочим днем ​​для 5 или 6 дней в цикле.
  7. В смену звонков входит не более 2 звонков за цикл.

Состав должен соответствовать определенной конфигурации, определяемой числом рабочих для каждой смены (m, m1, t, n)ежедневно. Смена по вызову не обязательна для соблюдения. Никаких проблем с этой частью.

Пока правила с 3 по 7 выполнены. У меня проблема для 1 и 2, так как я не могу сделать это с обычным ограничением (это становится слишком сложным). Я пробовал подход к созданию и распределению часов отдыха между последовательными сменами, что я и сделал (третья строка изображения). Пример:

Desired behaviour

Проблема в четвертой строке: сумма последовательного отдыха (от конца смены + дней отдыха + до началаследующая смена), то есть сумма нулей вместе с оставшимся рабочим днем ​​(1). Затем посчитайте рабочие дни до> = 48 часов, чтобы проверить, есть ли 5 ​​или меньше;считать рабочие дни до> = 54 часов, чтобы проверить, есть ли 6 или меньше… просто идея. Спасибо за вашу помощь!

На данный момент это код (я включил в код действительный RosterCalculated, который можно изменить, чтобы проверить код, изменив его вручную вместо var RosterCalculated. В этом случае ограничение напроверить Конфигурацию также следует удалить). Я считаю, что лучше проверить, действительно ли работают ограничения ...

include "globals.mzn";


%Definitions
enum TypeOfShift = {l,m1,m,t,n,im,it,ino,o};  %Types of shifts
array[TypeOfShift] of float: StartTypeOfShift=[10, 6.75, 7.5, 14.75, 22, 6.5, 13.75, 21, 10]; %Starting hour. The time for l is just to put something convenient
array[TypeOfShift] of float: DurationTypeOfShift=[0, 7.25, 7.25, 7.25, 9.5, 8.25, 8.25, 10.5, 6]; %Duration of shifts (hours)
enum Staff={AA,BB,CC,DD,EE,FF,GG,HH,II,JJ,KK,LL,MM};
array[int] of int: DaysInRoster=[28, 29, 30, 31, 1, 2, 3, 4, 5,6,7,8,9,10]; %Dias a los que corresponde el turnero


int: NumberWorkers = card(Staff); 
int: NumDaysInRoster=length(DaysInRoster);
array[1..NumDaysInRoster,TypeOfShift] of int: Configuration = array2d(1..NumDaysInRoster,TypeOfShift,[ (if (tu==m1) then 1 else
                                                                                                   if (tu==m) then 2 else
                                                                                                   if (tu==t) then 2 else
                                                                                                   if (tu==o) then 0 else
                                                                                                   if (tu==im) then 1 else
                                                                                                   if (tu==n) then 2 else 0
                                                                                                   endif endif endif endif endif endif)|  d in 1..NumDaysInRoster, tu in TypeOfShift ]); %Easy example of configuration

array[Staff, 1..NumDaysInRoster] of TypeOfShift: RosterCalculated = [|t, n, n, l, l, l, l, m, m1, m, t, l, m1, l|
m, l, l, n, l, l, t, t, n, l, l, l, m, l|
n, l, l, m, m1, m, m, n, l, l, m, m, l, m1|
l, t, l, n, l, l, m, m, t, n, l, l, t, l|
t, l, t, l, l, m, t, l, m, t, n, n, l, l|
m, m, m, l, l, m1, m1, n, l, l, m, l, l, t|
n, l, l, l, t, n, n, l, l, l, m1, t, n, n|
l, m, n, l, n, l, l, l, t, n, l, l, t, l|
l, t, l, m, m, l, l, l, m, m, l, m1, m, m|
l, l, m, m1, t, t, l, m1, n, l, l, n, l, m|
l, l, m1, t, l, l, l, t, l, t, t, t, n, l|
m1, m1, t, t, n, n, l, l, l, m1, l, m, l, n|
l, n, l, l, m, t, n, l, l, l, n, l, l, t|];



% Variables
%array[Staff, 1..NumDaysInRoster] of var TypeOfShift: RosterCalculated;  % To create the roster. Remove this line if what we want is to check if the code is working
var int: NumberWorkersNeeded =  sum (i in Staff) ((sum(d in 1..NumDaysInRoster) (RosterCalculated[i,d] != l)));                                       
array[Staff, 1..NumDaysInRoster-1] of var float: RosterCalculatedRests = array2d(Staff, 1..NumDaysInRoster-1,[(24*(d)+StartTypeOfShift[RosterCalculated[i,d+1]]) - (24*(d-1)+StartTypeOfShift[RosterCalculated[i,d]] + DurationTypeOfShift[RosterCalculated[i,d]]) | i in Staff, d in 1..NumDaysInRoster-1]);


% Satisfy configuration. Remove this constraint if what we want is to check if the code is working
/*
constraint forall(d in 1..NumDaysInRoster) 
              (((sum(i in Staff) (RosterCalculated[i,d] == m)) == Configuration[d,m]) /\ ((sum(i in Staff) (RosterCalculated[i,d] == m1)) == Configuration[d,m1]) /\ 
              ((sum(i in Staff) (RosterCalculated[i,d] == t)) == Configuration[d,t]) /\  ((sum(i in Staff) (RosterCalculated[i,d] == n)) == Configuration[d,n]));
*/

% Satisfy configuration on call not necessary to comply
constraint forall(d in 1..NumDaysInRoster) 
              (((sum(i in Staff) (RosterCalculated[i,d] == im)) <= Configuration[d,im]) /\ ((sum(i in Staff) (RosterCalculated[i,d] == it)) <= Configuration[d,it]) /\ 
              ((sum(i in Staff) (RosterCalculated[i,d] == ino)) <= Configuration[d,ino]) /\ ((sum(i in Staff) (RosterCalculated[i,d] == o)) <= Configuration[d,o]));            

% El tiempo transcurrido entre la salida de un turno y la entrada al siguiente tiene que ser igual o superior a 12h. NO NECESARIOS CON MATRIZ V4 (MAS LENTO)
constraint forall(i in Staff, d in 1..NumDaysInRoster-1)
              ((RosterCalculated[i,d+1] != l ) -> (24*(d-1)+StartTypeOfShift[RosterCalculated[i,d]] + DurationTypeOfShift[RosterCalculated[i,d]] + 12 <= 24*d+StartTypeOfShift[RosterCalculated[i,d+1]]));


% Rest after night or on call night (could be changed by regular constraint) 48h or more 
constraint forall(i in Staff, d in 1..NumDaysInRoster-3)
              (((RosterCalculated[i,d] == n) \/ (RosterCalculated[i,d] == ino)) -> ((RosterCalculated[i,d+1]==l \/ RosterCalculated[i,d+1]==n \/ RosterCalculated[i,d+1]==ino) /\
              (RosterCalculated[i,d+2]==l \/ RosterCalculated[i,d+2]==n \/ RosterCalculated[i,d+2]==ino) /\
              (StartTypeOfShift[RosterCalculated[i,d+3]] >= 7.5 \/ RosterCalculated[i,d+3]==l)));  


% Rest after double night has to be 54h or more (could be changed by regular constraint)
constraint forall(i in Staff, d in 1..NumDaysInRoster-4)
              ((((RosterCalculated[i,d] == n) \/ (RosterCalculated[i,d] == ino)) /\ ((RosterCalculated[i,d+1] == n) \/ (RosterCalculated[i,d+1] == ino))) -> ((RosterCalculated[i,d+2]==l) /\
              (RosterCalculated[i,d+3]==l) /\
              (StartTypeOfShift[RosterCalculated[i,d+4]] >= 13.5 \/ RosterCalculated[i,d+4]==l)));  

% Rest after a night free night has to be 54h or more (could be changed by regular constraint)
constraint forall(i in Staff, d in 1..NumDaysInRoster-5)
              ((((RosterCalculated[i,d] == n) \/ (RosterCalculated[i,d] == ino)) /\ (RosterCalculated[i,d+1] == l) /\ ((RosterCalculated[i,d+2] == n) \/ (RosterCalculated[i,d+2] == ino))) -> ((RosterCalculated[i,d+3]==l) /\
              (RosterCalculated[i,d+4]==l) /\
              (StartTypeOfShift[RosterCalculated[i,d+5]] >= 13.5 \/ RosterCalculated[i,d+5]==l)));


% Transition matrix not coping with all the cases...
predicate Max6WorkingDays(array[int] of var TypeOfShift: shift) =
    let {
        array[1..17, 1..5] of 0..17: transition_relation = % Transition matrix not coping with all the cases...
           [|8,   1,    2,  2,  2
            |9,   2,    3,  3,  3
            |10,    3,  4,  4,  4
            |11,    4,  5,  5,  5
            |12,    5,  6,  6,  6
            |13,    6,  7,  7,  15
            |14,    7,  0,  0,  0
            |1,   1,    2,  2,  2
            |1,   2,    3,  3,  3
            |1,   3,    4,  4,  4
            |1,   4,    5,  5,  5
            |1,   5,    6,  6,  6
            |1,   6,    7,  7,  15
            |1,   7,    0,  0,  0
            |16,    0,  0,  0,  0
            |17,    0,  0,  0,  0
            |1,   0,    0,  2,  2
          |];
    } in
        regular(
            [ if (s == l) then 1 else
              if s ==  o then 2 else
              if ((s == m) \/ (s == m1) \/(s == im)) then 3 else
              if ((s == t) \/ (s == it)) then 4 else
                              5 endif
                                endif
                                endif
                                endif
              | s in shift],                % sequence of input values
            17,                             % number of states
            5,                              % number of different input values of state machine
            transition_relation,            % transition relation
            1,                              % initial state
            1..17,                          % final states
         );                                                                                                                                                                                                                                          
constraint forall(i in Staff)
            (Max6WorkingDays([RosterCalculated[i,j] | j in 1..NumDaysInRoster]));                                                              


% Two on calls per cycle as max
predicate Max2OnCall(array[int] of var TypeOfShift: shift) =
    let {
        array[1..5, 1..4] of 0..5: transition_relation =
            [| 1, 2, 1, 1 % im0 (start)
             | 2, 4, 2, 3 % im1_l0
             | 2, 4, 2, 1 % im1_l1
             | 4, 0, 4, 5 % im2_l0
             | 4, 0, 4, 1 % im2_l1
            |];
    } in
        regular(
            [ if ((s == m1) \/ (s == m) \/ (s == t) \/ (s == n)) then 1 else
              if ((s == im) \/ (s == it) \/ (s == ino)) then 2 else
              if s ==  o then 3 else
                              4 endif
                                endif
                                endif
              | s in shift],                % sequence of input values
            5,                              % number of states
            4,                              % number of different input values of state machine
            transition_relation,            % transition relation
            1,                              % initial state
            1..5,                           % final states
         );

constraint forall(i in Staff)
            (Max2OnCall([RosterCalculated[i,j] | j in 1..NumDaysInRoster]));

% Max of 5 mornings per cycle
predicate MaxMsPerCycle(array[int] of var TypeOfShift: shift) =
    let {
        array[1..13, 1..4] of 0..13: transition_relation =
            [| 
              2,    7,  1,  1|
              3,    7,  8,  2|
              4,    7,  9,  3|
              5,    7,  10, 4|
              6,    7,  11, 5|
              0,    7,  12, 6|
              7,    7,  13, 7|
              3,    7,  1,  2|
              4,    7,  1,  3|
              5,    7,  1,  4|
              6,    7,  1,  5|
              0,    7,  1,  6|
              7,    7,  1,  7
            |];
    } in
        regular(
            [ if ((s == m1) \/ (s == m) \/ (s == im)) then 1 else
              if ((s == t) \/ (s == it) \/ (s == n) \/ (s == ino)) then 2 else
              if ((s ==  l)) then 3 else
                              4 endif
                                endif
                                endif
              | s in shift],                % sequence of input values
            13,                              % number of states
            4,                              % number of different input values of state machine
            transition_relation,            % transition relation
            1,                              % initial state
            1..13,                           % final states
         );

constraint forall(i in Staff)
            (MaxMsPerCycle([RosterCalculated[i,j] | j in 1..NumDaysInRoster]));



solve minimize NumberWorkersNeeded;
output[";;"]++["\(DaysInRoster[d]);" | d in 1..NumDaysInRoster];
output[if (d==1) then "\n"++"O3;\(i) " ++ ";" ++ show(RosterCalculated[i,d]) ++ ";" else show(RosterCalculated[i,d]) ++ ";" endif | i in Staff, d in 1..NumDaysInRoster];
output[if (d==1) then "\n"++"O3;\(i) " ++ ";" ++ show(RosterCalculatedRests[i,d]) ++ ";" else show(RosterCalculatedRests[i,d]) ++ ";" endif | i in Staff, d in 1..NumDaysInRoster-1];
...