mathoptimizationmathematical-optimizationconstraint-programmingminizinc

MiniZinc Constraint Programming: optimize parallel task scheduling model


I started learning constraint programming in MiniZinc, and would like to write a model which schedules t tasks on m machines for parallel execution (nothing difficult, a task needs to be executed only on one machine, and minimize the end time of the last task). My model solves the problem for the given data set (10 tasks with short duration and 2 machines), but if I add a new task (or increase the durations) the searchtime increases exponentially (with Chuffed solver, 10 task -> 2.7s,11 task -> 14s,... ).

include "cumulative.mzn";

int: num_tasks = 10;
set of int : tasks = 1..num_tasks;
int: num_machines = 2;
set of int : machines = 1..num_machines;

% Define the duration of each task
array[tasks] of int: task_durations = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

% Define the start and end times of each task on each machine
array[tasks] of var 0..sum(task_durations): start_times;
array[tasks] of var 0..sum(task_durations): end_times;

% Define a binary variable for each task and machine combination,
% indicating whether the task is executed on the machine
array[tasks,machines] of var 0..1: task_on_machine;

% Define the makespan as the maximum end time over all tasks
var int: makespan = max(end_times);

% Ensure that each task uses only one machine at a given time
constraint forall(m in machines) (
  cumulative(
  start_times,
  task_durations,
  [task_on_machine[t,m] | t in tasks],
  1)
);

% Ensure that the end time of each task is equal to its start time plus its duration
constraint forall(t in tasks)(
    end_times[t] >= start_times[t] + task_durations[t]
);

% Ensure that each task is executed on exactly one machine
constraint forall(t in tasks)(
    sum([task_on_machine[t,m] | m in machines]) == 1
);


% Minimize the makespan
solve :: int_search(
    start_times ++ machines,
    smallest, indomain_min,
    complete
) minimize makespan;

% Output the start and end times of each task on each machine
output [
    "Machine " ++ show(m) ++ ": " ++ show(start_times[t]) ++ " -> " ++ show(end_times[t]) ++ " (" ++ show(task_durations[t]) ++ ")\n"
    | m in machines, t in tasks where fix(task_on_machine[t,m] == 1)
] ++
[ "Makespan: " ++ show(makespan)
];

Do You have any suggestions, how can I optimize the model?

Thank You in advance!

Tried to optimize the model with variable bounds for start_times and end_times, and int_search(), which improved on the performance, but I would like to use the model for larger data sets e.g. 40 or more tasks.


Solution

  • In general yor problem is difficult. It belongs to complexity class NP-hard that means you can not expect to solve it easily for larger instances (the solving time grows exponentially with the size of the problem, in theory), but...

    you model can be improved in several ways. First, you can use diffn constraint instead of cumulative constraints

    constraint diffn(start_times, [task_on_machine[i,1] | i in tasks], task_durations, [1 | i in tasks]) ;

    This obviously requires one dimensional array for task_on_machine. It keeps the number of machine for task execution. In this model numbers are 0 or 1.

    Second. you can make solve statement more efficient. for example

    % Minimize the makespan solve :: seq_search([ int_search( start_times, first_fail, indomain_min, complete), int_search( machines, input_order, indomain_min, complete) ]) minimize makespan;

    Good luck with your model.