问题
I've written a Minizinc-Model that allows a teacher to schedule single lessons of his students. Teacher and students can prioritize their available time slots (prioTeacher
, respectively prio
).
The model works fine for simple and small input sets, but with a realistic set of input data, i.e. 3 days, each having 44 time slots (== 15 minutes) and 11 students, didn't find the optimal solution after more than 24 hours.
Model (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"]
;
Data
example 1 (works fine)
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]);
Example 2 (takes verrry long...)
% 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]);
(You can access the files here: https://gitlab.com/andibachmann/stupla_mzn/tree/master/mzn/t3 )
I did my calculations with mzn-gecode (G12 MiniZinc evaluation driver, version 2.0.2).
Any hints and/or directions for further improvements are welcome!
Regards Andi
回答1:
I did the following changes to the model:
- combined the
DAY
andSLOT
dimensions into a singleTIME
dimension (parameters are converted accordingly), caution is taken so that lessons don't extend over multiple days. - removed the explicit
schedule
representation of the solution - it is now calculated on-the-fly in the output section. - pre-calculated feasible start times for student lessons - thereby only the start times must be constrained in the model.
- changed to use binary variables
start
andactive
that represent if a student lesson starts at a given time and is active at given time, respectively. - made all constraints linear in the
start
andactive
variables.
Using the modified model the OSICBC solver solves the larger instance to optimum within a second.
% 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;
% Make the time axis one-dimensional and convert all data accordingly.
set of int: TIME = 1..maxSlots*num_days;
function int: time(int: d, int: z) = (d-1)*maxSlots + z;
set of TIME: teacher_time = {time(d, z) | d in DAY, z in teacher[d]};
array[STUDENT] of set of TIME: feasible_time = [{time(d, z) | d in DAY, z in feasible[s,d]} | s in STUDENT];
array[STUDENT] of set of TIME: feasible_start_time =
[{time(d,z) | d in DAY, z in 1..maxSlots-lessonDuration[s]+1 where forall(u in time(d,z)..time(d,z)+lessonDuration[s]-1)(u in feasible_time[s] intersect teacher_time)} | s in STUDENT];
array[STUDENT,TIME] of 0..3: prio_time = array2d(STUDENT, TIME, [prio[s,d,z] | s in STUDENT, d in DAY, z in SLOT]);
array[TIME] of 0..3: prioTeacher_time = [prioTeacher[d,z] | d in DAY, z in SLOT];
%
% decision VARIABLES
array[STUDENT,TIME] of var 0..1: start;
array[STUDENT,TIME] of var 0..1: active;
% -----------------------------------------------------------
% CONSTRAINTS
% 1. a lesson can only start at a feasible time
constraint forall(s in STUDENT, t in TIME)
(start[s,t] <= bool2int(t in feasible_start_time[s]));
% 2. each lesson must have a start time
constraint forall(s in STUDENT)
(sum(t in TIME)(start[s,t]) = 1);
% 3. maximum one lesson active at any time
constraint forall(t in TIME)
(sum(s in STUDENT)(active[s,t]) <= 1);
% 4&5. constraints defining if lesson active
constraint forall(s in STUDENT, d in 1..num_days)
(active[s,time(d,1)] = start[s,time(d,1)]);
constraint forall(s in STUDENT, d in 1..num_days, z in 2..maxSlots)
(active[s,time(d,z)] <= active[s,time(d,z-1)] + start[s,time(d,z)]);
% 6. ensure duration of lesson is fulfilled
constraint forall(s in STUDENT)
(sum(t in TIME)(active[s,t]) = lessonDuration[s]);
var int: obj = sum(s in STUDENT, t in TIME)
(active[s,t] * (prio_time[s,t] + k*prioTeacher_time[t]));
solve maximize obj;
output [
" Slot# ||"] ++
[ " \(d) |" | d in DAY ] ++
[
"| obj = " ++ show(obj) ++
" [teacher=\(sum(s in STUDENT, t in TIME)(active[s,t] * k*prioTeacher_time[t])), " ++
"stud=\(sum(s in STUDENT, t in TIME)(active[s,t] * prio_time[s,t])), k=\(k)]" ] ++
[ "\n -------++"] ++
[ "-------+" | d in DAY ] ++
["+\n"] ++
[
if d = 1 then show_int(5,z) ++ " ||" else "" endif ++
show_int(4,let {var int: student = sum(s in STUDENT)(s*active[s,time(d,z)])} in if student > 0 then student else bool2int(z in teacher[d]) - 1 endif) ++ " |" ++
if d = num_days then "|\n" else "" endif | z in SLOT, d in DAY
] ++ [ " -------++"] ++
[ "-------+" | d in DAY ]
++ ["+\n"]
;
Another option (sticking to the original model and easier to read) would be:
...
array[STUDENT] of var TIME: start_time;
include "disjunctive.mzn";
constraint disjunctive(start_time, lessonDuration);
constraint forall(s in STUDENT)
(start_time[s] in feasible_start_time[s]);
var int: obj = sum(s in STUDENT, t in TIME where t >= start_time[s] /\ t <= start_time[s] + lessonDuration[s] - 1)(prio_time[s,t] + k*prioTeacher_time[t]);
solve maximize obj;
...
来源:https://stackoverflow.com/questions/53819378/optimization-problem-of-a-single-lessons-scheduling-model