Моя задача - создать ростер со следующим типом смен:
• Утро (m): с 7:30 до 14:45 • Раннее утро (m1): с 6:45 до 14:00
• По вызову утром (im): с 06:30 до 14:45
• После полудня (t): с 14:45 до 22:00
• По вызову после обеда (ит): с 13:45 до 22:00
• Ночь (ночное время): с 22:00 до 7:30 следующего дня.
• Ночь по вызову (ino): с 21:00 до 7:30 следующего дня.
• Офис: с 10 до 14
• День отдыха (l): считается днем отдыха на следующий день после ночи, даже если рабочий закончил в 7 утра того дня.
Применяемые правила: цикл состоит из 6 или менее указанных выше смен (кроме офиса и отдыха), прежде чем потребуется 48 или 54 часа отдыха (см. Ниже).
- Если цикл состоит из 5 рабочих дней (m, m1, im, t, it, n, ino), оставшаяся часть должна быть 48 часов или более.
- Если цикл состоит из 6 рабочих дней (m, m1, im, t, it, n, ino), оставшаяся часть должна быть 54 часа или более.
- Между окончанием смены и началом следующей смены (включая офисную) должно пройти не менее 12 часов. Таким образом, невозможно иметь n, t, например
- Ночи: после одной ночи (n) или ночи по вызову (ino) должен быть 48 часов отдыха, если нет другой ночи (nn) или отдыха и ночи (nln), когда вам нужно 54 часа отдыха. Так что допустимыми примерами являются nllm (предоставляется 48-часовой отдых) или nnllt или nlnllt (предоставляется 54-часовой отдых).
- Допускается не более 5 утренних / ранних утренних / утренних звонков в цикле.
- Офис не считается рабочим днем, но он не считается отдыхом, поэтому он должен соблюдать 12-часовой отдых, как и другие смены, но он не считается рабочим днем в течение 5 или 6 дней за цикл.
- Максимум 2 смены по вызову за цикл.
Состав должен соответствовать определенной конфигурации, определяемой количеством рабочих на каждую смену (m, m1, t, n) каждый день. Дежурство по вызову не является обязательным. Никаких проблем по этой части.
Пока правила с 3 по 7 выполнены. Проблема, которая у меня есть, касается 1 и 2, поскольку я не могу сделать это с обычным ограничением (это становится слишком сложным). Я пробовал подход создания и набора часов отдыха между последовательными сменами, что я и сделал (третья строка изображения). Пример:
Проблема в четвертой строке: просуммируйте последовательный отдых (от конца смены + дни отдыха + до начала следующей смены), т.е. суммируйте нули вместе с оставшимся рабочим днем (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];
float
и вместо этого использовалint
(т. Е. Изменить единицу измерения). AFAIK, естьMiniZinc
решатели, которые плохо работают (или совсем не работают) с типомfloat
, но очень хороши сint
. Расстояние12
часов не должно требовать никаких вычислений, я думаю, что можно предварительно вычислить действительных преемников. Для многих ограничений вариантon call
можно абстрагировать (или наоборот), потому что он на самом деле ничего не меняет. - person Patrick Trentin   schedule 10.11.2019array [Staff, 1..15] of var 1..30: cycle_start; array[Staff, 1..15] of var 0..30: duration;
. Вы можете бесплатно получить индексы массива, необходимые для вычисленияdelta_time
между концом одного цикла и началом следующего цикла. Сегодня я начал пытаться автоматически сгенерировать отношение перехода для всех ограничений ожиданияXX
часов. Это кажется выполнимым, но я не уверен, что решатели с этим справятся. - person Patrick Trentin   schedule 12.11.2019cycle_start[0] = 0, duration[0] = 3
, тогда мы знаем, чтоshift[cycle_start[0] + duration[0] - 1]
- это последний сдвиг в цикле, и мы можем наложить ограничение, сказав, чтоstart_time
изshift[cycle_start[1]]
должен быть не менее48h
после. Более того, мы можем сказать, что для каждогоi
междуcycle_start[0] + duration[0]
иcycle_start[1]
мы должны иметь этоshift[i] = l
. Кроме того, у нас есть способ подсчитать, сколькоm/m1/im/t/it/n/ino
находится междуcycle_start[0]
иcycle_start[0] + duration[0]
, что решит проблему размера скользящего окна. - person Patrick Trentin   schedule 13.11.2019cycle_start
иduration
равен1..15
, потому что в одном месяце может быть не более15
циклов (фактически, меньше). Можно ожидать, что конечные значения в этих двух массивах не имеют смысла (например, вне интервала1..30
), поэтому нужно обратить внимание на то, как кодируются ограничения, чтобы избежать фиктивных результатовunsat
. - person Patrick Trentin   schedule 13.11.2019i
cycle_start[i] = 0
не должно быть действительным из-за требования наличия не менее48h
между одним и следующим циклом. Задание s.t. каждый цикл пуст, также следует сделать недействительным в соответствии с требованием, чтобы смены всех сотрудников, вместе взятых, обеспечивали достаточную оплату труда в течение дня (я полагаю, вы ожидаете, что хотя бы один человек на работе в любой раз, может и больше). - person Patrick Trentin   schedule 13.11.2019