我的问题是创建一个具有以下轮班类型的花名册:
• 早上 (m):7:30 至 14:45 • 清晨 (m1):6:45 至 14:00
• 早上待命 (im):6:30 至 14:45
• 下午(t):14:45 至 22:00
• 下午随叫随到 (it):13:45 至 22:00
• 夜间(n):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 小时,除非您需要休息 54 小时的另一个夜晚 (nn) 或休息和夜晚 (nln)。所以有效的例子是 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];