Parallel Machine Scheduling Problem (PMS)
SchedulingProblem
The Parallel Machine Scheduling Problem (PMS) is a classic problem in the scheduling literature. It consists in the scheduling of tasks on a set of parallel machines. In its most basic formulation, all the machines are identical (i.e. tasks can be scheduled on any machine) and every task can be processed by exactly one machine.
The goal is to find a schedule that minimizes the makespan: the maximum completion time of all the tasks. This variant of the Parallel Machine Scheduling Problem (PMS) is also referred to as P || Cmax , with the P indicating that there are identical parallel machines, and Cmax indicating that the optimization criterion is the makespan.
Principles learned
- Add set decision variables to model the allocation of tasks to resources
- Define lambda functions to calculate the makespan of each machine
Data
To illustrate this example, we took a set of instances from the literature. The format of the Parallel Machine Scheduling (PMS) instances is as follows:
- First line:
- Number of tasks;
- Number of machines;
- Second line: the lengths associated with each task.
Model
The Hexaly model for the Parallel Machine Scheduling (PMS) uses set decision variables. For each machine, we define a set variable representing the set of tasks assigned to this machine. We constrain the set variables to form a partition, ensuring that each task is scheduled on exactly one machine.
We compute the makespan of a machine using a variadic sum operator on the set and a lambda function returning the length associated with any task index. Note that the number of terms in this sum varies during the search, along with the size of the set.
We use the max operator to extract the global makespan from each machine’s individual makespan. This expression defines the objective function that is minimized during the search.
- Execution
-
hexaly parallel_scheduling.hxm inFileName=instances/p_cmax-n120-m3.txt [hxTimeLimit=] [solFileName=]
use io;
/* Read instance data */
function input() {
local usage = "Usage: hexaly parallel_scheduling.hxm "
+ "inFileName=inputFile [solFileName=outputFile] [hxTimeLimit=timeLimit]";
if (inFileName == nil) throw usage;
local inFile = io.openRead(inFileName);
local line = inFile.readln();
line = line.trim().split();
nbTasks = line[2].toInt();
nbMachines = line[3].toInt();
taskLengths[0...nbTasks] = inFile.readInt();
totalTaskLengths = sum[i in 0...nbTasks](taskLengths[i]);
makespanLB = floor(totalTaskLengths / nbMachines);
}
/* Declare the optimization model */
function model() {
// Set decisions: machineTasks[k] represents the tasks assigned to machine k
machineTasks[0...nbMachines] <- set(nbTasks);
// Each task must be scheduled on exactly one machine
constraint partition[m in 0...nbMachines](machineTasks[m]);
// Minimize the makespan
machineMakespan[m in 0...nbMachines] <- sum(machineTasks[m], i => taskLengths[i]);
makespan <- max[m in 0...nbMachines](machineMakespan[m]);
minimize makespan;
}
/* Parametrize the solver */
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 5;
// Stop the search if the lower threshold is reached
hxObjectiveThreshold = makespanLB;
}
/* Write the solution in a file */
function output() {
if (solFileName == nil) return;
local solFile = io.openWrite(solFileName);
for [k in 0...nbMachines] {
solFile.print("Makespan machine ", k, ": ", machineMakespan[k].value, " | Items: ");
if (machineTasks[k].value.count() == 0) continue;
for [e in machineTasks[k].value]
solFile.print(e + " ");
solFile.println();
}
}
- Execution (Windows)
-
set PYTHONPATH=%HX_HOME%\bin\pythonpython parallel_scheduling.py instances\p_cmax-n120-m3.txt
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_14_0/bin/pythonpython parallel_scheduling.py instances/p_cmax-n120-m3.txt
import hexaly.optimizer
import sys
import math
if len(sys.argv) < 2:
print("Usage: python parallel_scheduling.py inputFile [outputFile] [timeLimit]")
sys.exit(1)
with hexaly.optimizer.HexalyOptimizer() as optimizer:
# Read instance data
filename = sys.argv[1]
with open(filename) as f:
line = f.readline()
_, _, n_tasks, n_machines = [elem for elem in line.split()]
# Number of tasks
n_tasks = int(n_tasks)
# Number of machines
n_machines = int(n_machines)
# Lengths of the tasks
task_lengths = [int(elem) for elem in f.readline().split() if elem != '0']
# Lowerbound on the makespan
makespan_lb = int(sum(task_lengths) / n_machines)
# Declare the optimization model
model = optimizer.model
# Set decisions: machineTasks[k] represents the tasks assigned to machine k
machine_tasks = [model.set(n_tasks) for _ in range(n_machines)]
# Each task must be scheduled on exactly one machine
model.constraint(model.partition(machine_tasks))
# Create an array and a lambda function to retrieve the tasks' lengths
lengths = model.array(task_lengths)
lengths_lambda = model.lambda_function(lambda i: lengths[i])
# Minimize the makespan
machine_makespan = [model.sum(i, lengths_lambda) for i in machine_tasks]
makespan = model.max(machine_makespan)
model.minimize(makespan)
model.close()
# Parametrize the optimizer
if len(sys.argv) >= 4:
optimizer.param.time_limit = int(sys.argv[3])
else:
optimizer.param.time_limit = 5
# Stop the search if the lower threshold is reached
optimizer.param.set_objective_threshold(0, makespan_lb)
optimizer.solve()
# Write the solution in a file
if len(sys.argv) >= 3:
with open(sys.argv[2], 'w') as f:
for k in range(n_machines):
f.write("Makespan machine {}: {} | Items: ".format(k, machine_makespan[k].value))
if len(machine_tasks[k].value) == 0:
continue
for e in machine_tasks[k].value:
f.write("%d " % e)
f.write("\n")
- Compilation / Execution (Windows)
-
cl /EHsc parallel_scheduling.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly140.libparallel_scheduling instances\p_cmax-n120-m3.txt
- Compilation / Execution (Linux)
-
g++ parallel_scheduling.cpp -I/opt/hexaly_14_0/include -lhexaly140 -lpthread -o frcpsps./parallel_scheduling instances/p_cmax-n120-m3.txt
#include "optimizer/hexalyoptimizer.h"
#include <cmath>
#include <fstream>
#include <iostream>
#include <numeric>
#include <vector>
using namespace hexaly;
using namespace std;
class ParallelScheduling {
private:
// Number of tasks
int nbTasks;
// Number of machines
int nbMachines;
// Lengths of the tasks
vector<int> taskLengths;
// Lowerbound on the makespan
int makespanLB;
// Set of tasks assigned to machine
vector<HxExpression> machineTasks;
// Makespan of each machine
vector<HxExpression> machineMakespan;
// Hexaly Optimizer
HexalyOptimizer optimizer;
public:
/* Read instance data */
void readInstance(const string& fileName) {
ifstream infile;
infile.exceptions(ifstream::failbit | ifstream::badbit);
infile.open(fileName.c_str());
string dump;
infile >> dump;
infile >> dump;
infile >> nbTasks;
infile >> nbMachines;
taskLengths.resize(nbTasks);
int totalLength = 0;
for (int i = 0; i < nbTasks; ++i) {
infile >> taskLengths[i];
totalLength += taskLengths[i];
}
makespanLB = totalLength / nbMachines;
}
void solve(int limit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
machineTasks.resize(nbMachines);
machineMakespan.resize(nbMachines);
// Set decisions: machineTasks[k] represents the tasks assigned to machine k
for (int i = 0; i < nbMachines; i++){
machineTasks[i] = model.setVar(nbTasks);
}
// Each task must be scheduled on exactly one machine
model.addConstraint(model.partition(machineTasks.begin(), machineTasks.end()));
// Create an array and a lambda function to retrieve the tasks' lengths
HxExpression lengths = model.array(taskLengths.begin(), taskLengths.end());
HxExpression lengthLambda = model.createLambdaFunction(
[&](HxExpression i){ return lengths[i]; });
for (int i = 0; i < nbMachines; i++){
machineMakespan[i] = model.sum(machineTasks[i], lengthLambda);
}
// Minimize the makespan
HxExpression makespan = model.max(machineMakespan.begin(),
machineMakespan.end());
model.minimize(makespan);
model.close();
// Parametrize the solver
optimizer.getParam().setTimeLimit(limit);
// Stop the search if the lower threshold is reached
optimizer.getParam().setObjectiveThreshold(0, (hxint) makespanLB);
optimizer.solve();
}
/* Write the solution in a file */
void writeSolution(const string& fileName) {
ofstream outfile;
outfile.exceptions(ofstream::failbit | ofstream::badbit);
outfile.open(fileName.c_str());
for (int k = 0; k < nbMachines; ++k) {
outfile << "Makespan machine " << k << ": "
<< machineMakespan[k].getValue() << " | Items: ";
HxCollection machineCollection = machineTasks[k].getCollectionValue();
if (machineCollection.count() == 0)
continue;
for (int i = 0; i < machineCollection.count(); ++i) {
outfile << machineCollection[i] << " ";
}
outfile << endl;
}
}
};
int main(int argc, char** argv) {
if (argc < 2) {
cerr << "Usage: parallel_scheduling inputFile [outputFile] [timeLimit]" << endl;
return 1;
}
const char* instanceFile = argv[1];
const char* solFile = argc > 2 ? argv[2] : NULL;
const char* strTimeLimit = argc > 3 ? argv[3] : "5";
try {
ParallelScheduling model;
model.readInstance(instanceFile);
model.solve(atoi(strTimeLimit));
if (solFile != NULL)
model.writeSolution(solFile);
return 0;
} catch (const exception& e) {
cerr << "An error occurred: " << e.what() << endl;
return 1;
}
}
- Compilation / Execution (Windows)
-
copy %HX_HOME%\bin\Hexaly.NET.dll .csc ParallelScheduling.cs /reference:Hexaly.NET.dllParallelScheduling instances\p_cmax-n120-m3.txt
using System;
using System.IO;
using System.Linq;
using Hexaly.Optimizer;
public class ParallelScheduling : IDisposable
{
// Number of tasks
private int nbTasks;
// Number of machines
private int nbMachines;
// Lengths of the tasks
private long[] taskLengths;
// Lowerbound on the makespan
private long makespanLB;
// Set of tasks assigned to each machine
private HxExpression[] machineTasks;
// Makespan of each machine
private HxExpression[] machineMakespan;
// Hexaly Optimizer
private HexalyOptimizer optimizer;
public ParallelScheduling() {
optimizer = new HexalyOptimizer();
}
/* Read instance data */
public void ReadInstance(string fileName)
{
using (StreamReader input = new StreamReader(fileName)){
string[] line = input.ReadLine().Split(" ");
nbTasks = int.Parse(line[2]);
nbMachines = int.Parse(line[3]);
taskLengths = new long[nbTasks];
line = input.ReadLine().Split(" ");
long totalLength = 0;
for (int i = 0; i < nbTasks; i++){
taskLengths[i] = int.Parse(line[i]);
totalLength += taskLengths[i];
}
makespanLB = totalLength / nbMachines;
}
}
public void Dispose()
{
if (optimizer != null)
optimizer.Dispose();
}
public void Solve(int limit)
{
// Declare the optimization model
HxModel model = optimizer.GetModel();
machineTasks = new HxExpression[nbMachines];
machineMakespan = new HxExpression[nbMachines];
//Set decisions: machineTasks[k] represents the tasks assigned to machine k
for (int i = 0; i < nbMachines; i++)
machineTasks[i] = model.Set(nbTasks);
// Each task must be scheduled on exactly one machine
model.AddConstraint(model.Partition(machineTasks));
// Create an array and a lambda function to retrieve the tasks' lengths
HxExpression lengths = model.Array(taskLengths);
var lengthLambda = model.CreateLambdaFunction(iExpr => lengths[iExpr]);
for (int i = 0; i < nbMachines; i++)
machineMakespan[i] = model.Sum(machineTasks[i], lengthLambda);
// Minimize the makespan
HxExpression makespan = model.Max(machineMakespan);
model.Minimize(makespan);
model.Close();
// Parametrize the optimizer
optimizer.GetParam().SetTimeLimit(limit);
// Stop the search if the lower threshold is reached
optimizer.GetParam().SetObjectiveThreshold(0, makespanLB);
optimizer.Solve();
}
/* Write the solution in a file */
void WriteSolution(string fileName)
{
using (StreamWriter output = new StreamWriter(fileName))
{
for (int k = 0; k < nbMachines; ++k)
{
output.Write("Makespan machine " + k + ": "
+ machineMakespan[k].GetValue() + " | Items: ");
HxCollection machineCollection = machineTasks[k].GetCollectionValue();
if (machineCollection.Count() == 0)
continue;
for (int i = 0; i < machineCollection.Count(); ++i)
output.Write(machineCollection[i] + " ");
output.WriteLine();
}
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.Error.WriteLine("Usage: ParallelScheduling inputFile [outputFile] [timeLimit]");
return;
}
string instanceFile = args[0];
string solFile = args.Length > 1 ? args[1] : null;
string strTimeLimit = args.Length > 2 ? args[2] : "5";
try
{
var model = new ParallelScheduling();
model.ReadInstance(instanceFile);
if (!int.TryParse(strTimeLimit, out int limit))
limit = 5;
model.Solve(limit);
if (solFile != null)
model.WriteSolution(solFile);
}
catch (Exception e)
{
Console.Error.WriteLine("An error occurred: " + e.Message);
}
}
}
- Compilation / Execution (Windows)
-
javac ParallelScheduling.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. ParallelScheduling instances\p_cmax-n120-m3.txt
- Compilation / Execution (Linux)
-
javac ParallelScheduling.java -cp /opt/hexaly_14_0/bin/hexaly.jarjava -cp /opt/hexaly_14_0/bin/hexaly.jar:. ParallelScheduling instances/p_cmax-n120-m3.txt
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;
public class ParallelScheduling {
// Number of tasks
private int nbTasks;
// Number of machines
private int nbMachines;
// Lengths of the tasks
private long[] taskLengths;
// Lowerbound on the makespan
private long makespanLB;
// Set of tasks assigned to machine
private HxExpression[] machineTasks;
// Makespan of each machine
private HxExpression[] machineMakespan;
// Hexaly Optimizer
private final HexalyOptimizer optimizer;
private ParallelScheduling(HexalyOptimizer optimizer) {
this.optimizer = optimizer;
}
/* Read instance data */
private void readInstance(String fileName) throws IOException {
try (Scanner input = new Scanner(new File(fileName))) {
input.useLocale(Locale.ROOT);
String[] line = input.nextLine().split(" ");
nbTasks = Integer.parseInt(line[2]);
nbMachines = Integer.parseInt(line[3]);
taskLengths = new long[nbTasks];
long totalLengths = 0;
for (int i = 0; i < nbTasks; ++i) {
taskLengths[i] = input.nextInt();
totalLengths += taskLengths[i];
}
makespanLB = totalLengths / nbMachines;
}
}
private void solve(int limit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
machineTasks = new HxExpression[nbMachines];
machineMakespan = new HxExpression[nbMachines];
// Set decisions: machineTasks[k] represents the tasks in machine k
for (int k = 0; k < nbMachines; ++k)
machineTasks[k] = model.setVar(nbTasks);
// Each task must be scheduled on exactly one machine
model.constraint(model.partition(machineTasks));
// Create an array and a lambda function to retrieve the tasks' lengths
HxExpression lengths = model.array(taskLengths);
HxExpression lengthLambda = model.lambdaFunction(i -> model.at(lengths, i));
for (int k = 0; k < nbMachines; ++k)
machineMakespan[k] = model.sum(machineTasks[k], lengthLambda);
// Minimize the makespan
HxExpression makespan = model.max(machineMakespan);
model.minimize(makespan);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(limit);
// Stop the search if the lower threshold is reached
optimizer.getParam().setObjectiveThreshold(0, makespanLB);
optimizer.solve();
}
/* Write the solution in a file */
private void writeSolution(String fileName) throws IOException {
try (PrintWriter output = new PrintWriter(fileName)) {
for (int k = 0; k < nbMachines; ++k) {
output.print("Makespan machine " + k + ": "
+ machineMakespan[k].getValue() + " | Items: ");
HxCollection machineCollection = machineTasks[k].getCollectionValue();
if (machineCollection.count() == 0)
continue;
for (int i = 0; i < machineCollection.count(); ++i)
output.print(machineCollection.get(i) + " ");
output.println();
}
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: java ParallelScheduling inputFile [outputFile] [timeLimit]");
System.exit(1);
}
String instanceFile = args[0];
String outputFile = args.length > 1 ? args[1] : null;
String strTimeLimit = args.length > 2 ? args[2] : "5";
try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
ParallelScheduling model = new ParallelScheduling(optimizer);
model.readInstance(instanceFile);
model.solve(Integer.parseInt(strTimeLimit));
if (outputFile != null) {
model.writeSolution(outputFile);
}
} catch (Exception ex) {
System.err.println(ex);
ex.printStackTrace();
System.exit(1);
}
}
}