Search code examples
optimizationmathematical-optimizationsolverconstraint-programmingminizinc

Optimizing working scheduling MiniZinc code - constraint programming


Please can you help optimize this working MiniZinc code:

Task: There is a conference which has 6x time slots. There are 3 speakers attending the conference who are each available at certain slots. Each speaker will present for a predetermined number of slots.

Objective: Produce the schedule that has the earliest finish of speakers.

Example: Speakers A, B & C. Talk durations = [1, 2, 1]

Speaker availability:

+---+------+------+------+
|   | Sp.A | Sp.B | Sp.C |
+---+------+------+------+
| 1 |      | Busy |      |
| 2 | Busy | Busy | Busy |
| 3 | Busy | Busy |      |
| 4 |      |      |      |
| 5 |      |      | Busy |
| 6 | Busy | Busy |      |
+---+------+------+------+

Link to working MiniZinc code: http://pastebin.com/raw.php?i=jUTaEDv0

What I'm hoping to optimize:

% ensure allocated slots don't overlap and the allocated slot is free for the speaker
constraint 
    forall(i in 1..num_speakers) (
        ending_slot[i] = starting_slot[i] + app_durations[i] - 1
    ) /\
    forall(i,j in 1..num_speakers where i < j) (
        no_overlap(starting_slot[i], app_durations[i], starting_slot[j], app_durations[j])
    ) /\
    forall(i in 1..num_speakers) (
        forall(j in 1..app_durations[i]) (
            starting_slot[i]+j-1 in speaker_availability[i]
        )
    ) 
;

Expected solution:

+---+----------+----------+----------+
|   |   Sp.A   |   Sp.B   |   Sp.C   |
+---+----------+----------+----------+
| 1 | SELECTED | Busy     |          |
| 2 | Busy     | Busy     | Busy     |
| 3 | Busy     | Busy     | SELECTED |
| 4 |          | SELECTED |          |
| 5 |          | SELECTED | Busy     |
| 6 | Busy     | Busy     |          |
+---+----------+----------+----------+

Solution

  • I'm hakank (author of the original model). If I understand it correctly, your question now is how to present the table for the optimal solution, not really about finding the solution itself (all FlatZinc solvers I tested solved it in no time).

    One way of creating the table is to have a help matrix ("m") which contain information if a speaker is selected (1), busy (-1) or not available (0):

    array[1..num_slots, 1..num_speakers] of var -1..1: m;
    

    Then one must connect info in this the matrix and the other decision variables ("starting_slot" and "ending_slot"):

    % connect to matrix m
    constraint
       forall(t in 1..num_slots) (
          forall(s in 1..num_speakers) (
             (not(t in speaker_availability[s]) <-> m[t,s] = -1) 
              /\
              ((t >= starting_slot[s] /\ t <= ending_slot[s]) <-> m[t,s] = 1)
         )
     )
    

    ;

    Then the matrix "m" can be printed like this:

    % ...
    ++
    [ 
       if s = 1 then "\n" else " " endif ++
       if fix(m[t,s]) = -1 then 
          "Busy    " 
       elseif fix(m[t,s]) =  1 then 
          "SELECTED" 
       else
          "        "
       endif
     | t in 1..num_slots, s in 1..num_speakers
    

    ] ;

    As always, there are more than one way of doing this, but I settled with this since it's quite direct.

    Here's the complete model: http://www.hakank.org/minizinc/scheduling_speakers_optimize.mzn

    Update: Adding the output of the model:

    Starting:  [1, 4, 3]
    Durations: [1, 2, 1]
    Ends:      [1, 5, 3]
    z:         5
    
    SELECTED Busy             
    Busy     Busy     Busy    
    Busy     Busy     SELECTED
             SELECTED         
             SELECTED Busy    
    Busy     Busy             
    ----------
    ==========
    

    Update 2: Another way is to use cumulative/4 instead of no_overlap/4 which should be more effective, i.e.

    constraint 
        forall(i in 1..num_speakers) (
        ending_slot[i] = starting_slot[i] + app_durations[i] - 1
        ) 
        % /\ % use cumulative instead (see below)
        % forall(i,j in 1..num_speakers where i < j) (
        %   no_overlap(starting_slot[i], app_durations[i], starting_slot[j], app_durations[j])
        % ) 
        /\
        forall(i in 1..num_speakers) (
        forall(j in 1..app_durations[i]) (
            starting_slot[i]+j-1 in speaker_availability[i]
               )
        ) 
    
        /\ cumulative(starting_slot, app_durations, [1 | i in 1..num_speakers], 1)
    ;
    

    Here's the altered version (which give the same result) http://www.hakank.org/minizinc/scheduling_speakers_optimize2.mzn (I've also skipped the presentation matrix "m" and do all presentation in the output section.)

    For this simple problem instance, there is no discernible difference, but for larger instances this should be faster. (And for larger instances, one might want to test different search heuristics instead of "solve minimize z".)