I have a working Miniznic model to schedule individual lessons of 1 professor having n students (Optimization Problem of a Single Lessons Scheduling Model). The model considers availability (hard constraint) and the time preferences (objective function) of the professor as well as of the students.
Now, I want to extend the model and optimize the schedule such that gaps between lessons are minimized.
Example:
Schedule : p L p p L L . . p p L L p L L p L p p . L p L L . L p L
Real Gaps : . L p p L L . . . . L L p L L p L . . . L p L L . L p L
where
`p` = 0 == Teacher available, but no Lesson
`L` = 1 == Teacher gives lesson (to student)
`.` = -1 == Teacher not available
Obviously, the p
in slot 1 must not be counted as a gap. Similarly,
slots 9 and 10 are no gaps, neither. Eliminating all false gaps, the
Schedule
should finally look like the Real Gaps
array (Note: false gaps are marked with .
; the same as not available).
The result would be a gap array [2, 1, 1, 1, 1]
(for each gap showing the number of slots it lasts). Based on such an array one could then e.g. formulate an objective to minimize the overall gap slots.
In ruby
I was able to formulate an algorithm that does what I want:
def gap_array(schedule_arr)
# initialize variables
start_search = false # flag for break start
break_slots = 0 # counter of break slots
res_arr = [] # resulting array
schedule_arr.each do |slot|
if slot == 1 # start watching for break
start_search = true
end
#
if start_search
if slot == 0 # == break
break_slots += 1
elsif slot == 1 # == consecutive lesson slot
if break_slots > 0 # number of break_slots > 0
res_arr.append(break_slots)
break_slots = 0
end
else # == not available
break_slots = 0 # any break so far is discarded
start_search = false
end
end
end
return res_arr
end
How can I formulate such an algorithm in Minizinc?
Thanks!
One way to this in MiniZinc would be to extend the model at Optimization Problem of a Single Lessons Scheduling Model in the following way:
Initially calculate teacher_free
as the slots where the teacher is not available combined with adjacent slots where no lesson takes place (this is done in two steps, going from the left teacher_free_left
and the right teacher_free_right
, respectively, and then combining the results to form teacher_free
).
In the next step the real_gap
is calculated as slots where the teacher is not free and no lesson takes place.
In the objective a penalty term for real_gap
is then introduced like (k2
being the gap penalty weight):
int: k2 = 1;
var int: obj = sum(s in STUDENT, t in TIME)
(active[s,t] * (prio_time[s,t] + k*prioTeacher_time[t])) - k2*sum(real_gap);
Here all the other extensions to the model (with some further comments):
array[DAY,SLOT] of var 0..1: lesson = array2d(DAY, SLOT, [sum(s in STUDENT)(active[s,time(d,z)]) | d in DAY, z in SLOT]);
array[DAY,SLOT] of var 0..1: teacher_free_left;
array[DAY,SLOT] of var 0..1: teacher_free_right;
array[DAY,SLOT] of var 0..1: teacher_free;
array[DAY,SLOT] of var 0..1: real_gap;
predicate equals_and(var 0..1: z, var 0..1: x, var 0..1: y) =
(z <= x /\ z <= y /\ z >= x + y - 1);
predicate equals_or(var 0..1: z, var 0..1: x, var 0..1: y) =
(z >= x /\ z >= y /\ z <= x + y);
% calculate teacher free left
% first slot -> teacher free = no lesson in the slot
% other slots -> teacher free = teacher out or (left slot teacher free and no lesson in slot)
array[DAY,SLOT] of var 0..1: teacher_free_left_temp;
constraint forall(d in DAY)
(teacher_free_left_temp[d,1]=1-lesson[d,1]);
constraint forall(d in DAY, z in 2..maxSlots)
(equals_and(teacher_free_left_temp[d,z], teacher_free_left[d,z-1], 1-lesson[d,z]));
constraint forall(d in DAY, z in SLOT)
(equals_or(teacher_free_left[d,z], 1 - bool2int(z in teacher[d]), teacher_free_left_temp[d,z]));
% calculate teacher free right
% last slot -> teacher free = no lesson in the slot
% other slots -> teacher free = teacher out or (right slot teacher free and no lesson in slot)
array[DAY,SLOT] of var 0..1: teacher_free_right_temp;
constraint forall(d in DAY)
(teacher_free_right_temp[d,maxSlots]=1-lesson[d,maxSlots]);
constraint forall(d in DAY, z in 1..maxSlots-1)
(equals_and(teacher_free_right_temp[d,z], teacher_free_right[d,z+1], 1-lesson[d,z]));
constraint forall(d in DAY, z in SLOT)
(equals_or(teacher_free_right[d,z], 1 - bool2int(z in teacher[d]), teacher_free_right_temp[d,z]));
% teacher free when teacher free left or teacher free right
constraint forall(d in DAY, z in SLOT)
(equals_or(teacher_free[d,z], teacher_free_left[d,z], teacher_free_right[d,z]));
% real gap when teacher not free and no lesson
constraint forall(d in DAY, z in SLOT)
(equals_and(real_gap[d,z], 1-teacher_free[d,z], 1-lesson[d,z]));