Я написал Миницинк-модель, которая позволяет учителю планировать отдельные занятия своих учеников.Учитель и ученики могут расставить приоритеты для своих доступных временных интервалов (prioTeacher
, соответственно prio
).
Модель отлично работает для простых и небольших входных наборов, но с реалистичным набором входных данных, т.е. 3 дня,каждый из них имеет 44 временных интервала (== 15 минут) и 11 студентов, но не нашел оптимального решения после более чем 24 часов.
Модель (stupla-prio.mzn
)
% enum of presence days
enum DAY;
int: num_days = card(DAY);
% maximal duration of a lessons
int: maxDur;
% maximal numbers of slots per Day;
int: maxSlots;
set of int: SLOT = 1..maxSlots;
set of int: SLOTx = 0..maxSlots;
% number of students
int: n;
set of int: STUDENT = 1..n;
%
array[DAY] of set of SLOT: teacher;
array[STUDENT,DAY] of set of SLOT: feasible;
array[STUDENT] of 1..maxDur: lessonDuration;
array[STUDENT,DAY,SLOT] of 0..3: prio;
array[DAY,SLOT] of 0..3: prioTeacher;
% Factor for weighting: obj = obj_stud + k * obj_teacher
int: k;
%
% decision VARIABLES
% array[STUDENT,DAY] of var 0..maxSlots: start_slot;
array[STUDENT,DAY] of var SLOTx: start_slot;
array[STUDENT,DAY] of var SLOTx: end_slot;
% 2d-array that stores for each d (in DAYS) and each SLOT
% the STUDENT or
% 0 if it is not allocated or
% -1 the teacher is available neither
array[SLOT,DAY] of var -1..n: schedule;
% -----------------------------------------------------------
% CONSTRAINTS
% 1. For each student 'start_slot' must be in 'feasible'
constraint forall(s in STUDENT, d in DAY where start_slot[s,d] > 0)(
start_slot[s,d] in feasible[s,d] );
% 2. For each student 'end_slot' = 'start_slot' + lessonDuration - 1
constraint forall(s in STUDENT, d in DAY)(
if start_slot[s,d] > 0 then
end_slot[s,d] = start_slot[s,d] + lessonDuration[s] - 1
else
end_slot[s,d] = 0
endif);
% 3. All slot between 'start_slot' and 'end_slot' must be in 'feasible'
constraint forall(s in STUDENT, d in DAY where start_slot[s,d] > 0)(
forall(j in 1..lessonDuration[s]-1) ( start_slot[s,d] + j in feasible[s,d] )
);
% 4. make sure each student has exactly 1 lesson
constraint forall(s in STUDENT)( sum([start_slot[s,d] > 0| d in DAY]) = 1);
% 5. link 'schedule' to 'start_slot' and 'end_slot'
constraint forall(s in STUDENT, d in DAY, z in SLOT) (
(z in feasible[s,d] /\ z >= start_slot[s,d] /\ z <= end_slot[s,d])
<-> schedule[z,d] = s
);
% 6. mark empty slots for teacher
constraint forall(d in DAY, z in SLOT)(
(z in teacher[d] /\ schedule[z,d] = -1) -> schedule[z,d] = 0 );
% objective function students
var int: obj_stud;
constraint obj_stud = sum([prio[schedule[z,d],d,z]|
d in DAY, z in SLOT where schedule[z,d] > 0]);
% objective function teacher
var int: obj_teacher;
constraint obj_teacher = sum([prioTeacher[d,z]|
d in DAY, z in SLOT where schedule[z,d] > 0]);
%solve satisfy;
solve :: int_search( [start_slot[s,d] |s in STUDENT, d in DAY], first_fail, indomain, complete) maximize (obj_stud + k * obj_teacher);
output [
% "start_slot =\n" ++ show2d(start_slot) ++ "\n" ++
% "end_slot = " ++ show2d(end_slot) ++ "\n" ++
% " teacher = " ++ show(teacher) ++ ";\n" ++
% " feasible = " ++ show2d(feasible) ++ "\n" ++
% "schedule = \n" ++ show2d(schedule) ++ ";\n" ++
% " - "
" Slot# ||"] ++
[ " \(d) |" | d in DAY ] ++
[
"| obj = " ++ show(obj_stud + k * obj_teacher) ++
" [teacher=\(obj_teacher), " ++
"stud=\(obj_stud), k=\(k)]" ] ++
[ "\n -------++"] ++
[ "-------+" | d in DAY ] ++
["+\n"] ++
[
if d = 1 then show_int(5,z) ++ " ||" else "" endif ++
show_int(4,schedule[z,d]) ++ " |" ++
if d = num_days then "|\n" else "" endif | z in SLOT, d in DAY
] ++ [ " -------++"] ++
[ "-------+" | d in DAY ]
++ ["+\n"]
;
Данные
пример 1 (работает нормально)
DAY = {Mon, Wed};
maxSlots = 14; % == 30 minutes slot duration
teacher = [ {1,2,3,4,5,6},
{6,11,12,13,14}];
n = 4;
lessonDuration = [2,1,1,3];
maxDur = 3;
feasible = array2d(1..n, DAY, [
{1,2,3,4,5,6}, {6},
{1,2,3}, {}, % Stud2: Day1, Day2
{1}, {13,14}, % Stud3: Day1, Day2
{3,4,5}, {11,12,13,14}]);
prio = array3d(1..n,DAY,1..maxSlots, [
% Stud1
1,1,1,2,2,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,2,0,0,0,0,0,0,0,0,
% Stud2
1,3,3,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,
% Stud3
3,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,2,2,
% Stud4
0,0,1,2,2,0,0,0,0,0,0,0,0,0 ,
0,0,0,0,0,0,0,0,0,0,3,3,1,1]);
%
k = 10;
prioTeacher = array2d(DAY,1..maxSlots, [
% Example 1:
% morning muffel, and break
% 1,1,1,2,2,2,3,1,1,3,3,3,3,3,
% 1,1,1,2,2,2,3,1,1,3,3,3,3,3,]);
% Example 2:
% early bird
3,3,3,3,3,3,1,1,1,1,1,1,1,1,
3,3,3,3,3,3,1,1,1,1,1,1,1,1]);
Пример 2 (занимает много времени ...)
% Datafile
% Available week days
DAY = {Mon, Tue, Wed};
% Number of maximal slots per day, == 15 minutes slots
maxSlots = 44;
% Number of students
n = 11;
% Weighting factor
k = 1;
lessonDuration = [3,3,2,3,3,3,3,3,6,4,2];
maxDur = 6;
teacher = [ {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44},
{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44},
{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44}];
% feasible time slots (teacher and students intersected)
feasible = array2d(1..n, DAY, [
% IH
{1,2,3,4,5,6,7,8}, {}, {1,2,3,4,37,38,39,40,41,42,43},
% MM
{11,12,13,14,15,16,28,29,30,31}, {7,8,9,10,11}, {},
% NW
{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42}, {}, {1,2,3,4,5,6,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42},
% RD
{7,8,9,10,11,12,40,41,42}, {13,14,15,16,17,18,19,20,21,22,23,34,35,36,37,38}, {},
% MS
{7,8,9,10,11,12,34,35,36,37,38,39,40,41,42}, {35,36,37,38,39,40}, {},
% SB
{}, {1,2,3,4,5,6}, {8,9,10,11,12},
% SO
{}, {}, {6,7,8,9,10,11,12,36,37,38,39,40,41,42},
% CT
{}, {}, {1,2,3,4,5,6,7,8,9,10,11,12},
% AG
{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44}, {9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28}, {},
% SS
{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44}, {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44}, {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44},
% RF
{25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42}, {}, {33,34,35,36,37,38,39,40,41,42}
]);
% Prioririties of Teacher
prioTeacher = array2d(DAY,1..maxSlots, [
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]);
% Priorities of Students
prio = array3d(1..n,DAY,1..maxSlots, [
% 1. IH
2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,0,
% 2. MM
0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
% 3. NW
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,
% 4. RD
0,0,0,0,0,0,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
% 5. MS
0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
% 6. SB
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
% 7. SO
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,0,0,
% 8. CT
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
% 9. AG
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
% 10. SS
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
% 11. RF
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,0,0]);
(Вы можете получить доступ к файлам здесь: https://gitlab.com/andibachmann/stupla_mzn/tree/master/mzn/t3)
Я провел расчеты с помощью mzn-gecode (оценочный драйвер G12 MiniZinc, версия 2.0.2).
Любые советы и / или указания по дальнейшим улучшениям приветствуются!
С уважением, Анди