Flow Shop Problem
Problem
The Flow Shop Problem is described as follows. A set of jobs has to be processed on every machine in the shop in a predefined order. Each machine can only process one job at a time. They can work in parallel, but they must all process the jobs in the same order. The workflow is as follows. The first job of the sequence goes to the first machine to be processed. When the first machine has processed the first job, the first job goes to the second machine, and the second job of the sequence starts to be processed by the first machine, and so on. In other words, a job starts on a machine when this job has ended on the previous machine, and when the previous job has ended on this machine.
The goal of the problem is to find a sequence of jobs that minimizes the makespan, which is the time when all jobs have been processed. This version of the Flow Shop Problem is also called the “Permutation Flow Shop”.
Principles learned
- Add a list decision variable to model the order of the jobs on the machines
- Define an array using a recursive lambda function to compute the end dates of the activities
Data
The instances provided are from Taillard. The format of the data files is as follows:
- First line: number of jobs, number of machines, seed used to generate the instance, upper and lower bound previously found.
- For each machine: the processing time of each job on this machine.
Program
The only decision variable of the model is a list variable corresponding to the sequence of jobs. Using the ‘count’ operator, we constrain all the jobs to be processed.
To compute the makespan (last end time), we recursively compute the end times of all activities using the ‘array’ operator. On the first machine, each job starts right after the previous one has ended. We use a recursive lambda function to fill the array containing the end times on the first machine: the end time of the job in position i is the sum of the previous job’s end time (prev
) and its processing time (processingTime[0][jobs[i]]
).
We can then compute the end times on the following machines. The start time of a job on one of these machines is the maximum of two quantities: the time when the job ends on the previous machine, and the time when the previous job ends on the current machine. In the same way as for the first machine, we compute the end times on the other machines by filling arrays using recursive lambda functions.
The makespan to minimize is the time when the last job of the sequence has been processed by the last machine: end[nbMachines-1][nbJobs-1]
.
- Execution (Windows)
-
hexaly flowshop.hxm inFileName=instances/tai20_5.txt [hxTimeLimit=] [solFileName=]
use io;
/* Read instance data */
function input() {
local usage = "Usage: hexaly flowshop.hxm "
+ "inFileName=inputFile [hxTimeLimit=timeLimit]";
if (inFileName == nil) throw usage;
local inFile = io.openRead(inFileName);
nbJobs = inFile.readInt();
nbMachines = inFile.readInt();
inFile.readInt();
inFile.readInt();
inFile.readInt();
processingTime[m in 0...nbMachines][j in 0...nbJobs] = inFile.readInt();
}
/* Declare the optimization model */
function model() {
// Permutation of jobs
jobs <- list(nbJobs);
// All jobs have to be assigned
constraint count(jobs) == nbJobs;
// On machine 0, the jth job ends on the time it took to be processed after
// the end of the previous job
jobEnd[0] <- array(0...nbJobs, (i, prev) => prev + processingTime[0][jobs[i]], 0);
// The jth job on machine m starts when it has been processed by machine n-1
// AND when job j-1 has been processed on machine m.
// It ends after it has been processed.
for [m in 1...nbMachines]
jobEnd[m] <- array(0...nbJobs,
(i, prev) => max(prev, jobEnd[m-1][i]) + processingTime[m][jobs[i]], 0);
// Minimize the makespan: end of the last job on the last machine
makespan <- jobEnd[nbMachines-1][nbJobs-1];
minimize makespan;
}
/* Parametrize the solver */
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 5;
}
/* Write the solution in a file */
function output() {
if (solFileName == nil) return;
local solFile = io.openWrite(solFileName);
solFile.println(makespan.value);
for [j in jobs.value]
solFile.print(j + " ");
solFile.println();
}
- Execution (Windows)
-
set PYTHONPATH=%HX_HOME%\bin\pythonpython flowshop.py instances\tai20_5.txt
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_13_0/bin/pythonpython flowshop.py instances/tai20_5.txt
import hexaly.optimizer
import sys
def read_integers(filename):
with open(filename) as f:
return [int(elem) for elem in f.read().split()]
#
# Read instance data
#
def read_instance(instance_file):
file_it = iter(read_integers(instance_file))
nb_jobs = int(next(file_it))
nb_machines = int(next(file_it))
next(file_it)
next(file_it)
next(file_it)
processing_time_data = [[int(next(file_it)) for j in range(nb_jobs)]
for j in range(nb_machines)]
return nb_jobs, nb_machines, processing_time_data
def main(instance_file, output_file, time_limit):
nb_jobs, nb_machines, processing_time_data = read_instance(instance_file)
with hexaly.optimizer.HexalyOptimizer() as optimizer:
#
# Declare the optimization model
#
model = optimizer.model
# Permutation of jobs
jobs = model.list(nb_jobs)
# All jobs have to be assigned
model.constraint(model.eq(model.count(jobs), nb_jobs))
# For each machine create proccessingTime[m] as an array to be able
# to access it with an 'at' operator
processing_time = [model.array(processing_time_data[m])
for m in range(nb_machines)]
# On machine 0, the jth job ends on the time it took to be processed
# after the end of the previous job
job_end = [None] * nb_machines
first_end_lambda = model.lambda_function(lambda i, prev:
prev + processing_time[0][jobs[i]])
job_end[0] = model.array(model.range(0, nb_jobs), first_end_lambda, 0)
# The jth job on machine m starts when it has been processed by machine n-1
# AND when job j-1 has been processed on machine m.
# It ends after it has been processed.
for m in range(1, nb_machines):
mL = m
end_lambda = model.lambda_function(lambda i, prev:
model.max(prev, job_end[mL - 1][i]) + processing_time[mL][jobs[i]])
job_end[m] = model.array(model.range(0, nb_jobs), end_lambda, 0)
# Minimize the makespan: end of the last job on the last machine
makespan = job_end[nb_machines - 1][nb_jobs - 1]
model.minimize(makespan)
model.close()
# Parameterize the optimizer
optimizer.param.time_limit = time_limit
optimizer.solve()
#
# Write the solution in a file
#
if output_file is not None:
with open(output_file, 'w') as f:
f.write("%d\n" % makespan.value)
for j in jobs.value:
f.write("%d " % j)
f.write("\n")
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python flowshop.py instance_file [output_file] [time_limit]")
sys.exit(1)
instance_file = sys.argv[1]
output_file = sys.argv[2] if len(sys.argv) >= 3 else None
time_limit = int(sys.argv[3]) if len(sys.argv) >= 4 else 5
main(instance_file, output_file, time_limit)
- Compilation / Execution (Windows)
-
cl /EHsc flowshop.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.libflowshop instances\tai20_5.txt
- Compilation / Execution (Linux)
-
g++ flowshop.cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o flowshop./flowshop instances/tai20_5.txt
#include "optimizer/hexalyoptimizer.h"
#include <fstream>
#include <iostream>
#include <vector>
using namespace hexaly;
using namespace std;
class Flowshop {
public:
// Number of jobs
int nbJobs;
// Number of machines
int nbMachines;
// Processing time
vector<vector<int>> processingTimeData;
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Decision variable
HxExpression jobs;
// Objective
HxExpression makespan;
/* Read instance data */
void readInstance(const string& fileName) {
ifstream infile;
infile.exceptions(ifstream::failbit | ifstream::badbit);
infile.open(fileName.c_str());
long tmp;
infile >> nbJobs;
infile >> nbMachines;
infile >> tmp;
infile >> tmp;
infile >> tmp;
processingTimeData.resize(nbMachines);
for (int m = 0; m < nbMachines; ++m) {
processingTimeData[m].resize(nbJobs);
for (int j = 0; j < nbJobs; ++j) {
infile >> processingTimeData[m][j];
}
}
}
void solve(int limit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Permutation of jobs
jobs = model.listVar(nbJobs);
// All jobs have to be assigned
model.constraint(model.count(jobs) == nbJobs);
// For each machine create proccessingTime[m] as an array to be able to access it
// with an 'at' operator
vector<HxExpression> processingTime(nbMachines);
for (int m = 0; m < nbMachines; ++m) {
processingTime[m] = model.array(processingTimeData[m].begin(), processingTimeData[m].end());
}
// On machine 0, the jth job ends on the time it took to be processed after
// the end of the previous job
vector<HxExpression> jobEnd(nbMachines);
HxExpression firstEndLambda = model.createLambdaFunction(
[&](HxExpression i, HxExpression prev) { return prev + processingTime[0][jobs[i]]; });
jobEnd[0] = model.array(model.range(0, nbJobs), firstEndLambda, 0);
// The jth job on machine m starts when it has been processed by machine n-1
// AND when job j-1 has been processed on machine m. It ends after it has been processed.
for (int m = 1; m < nbMachines; ++m) {
int mL = m;
HxExpression endLambda = model.createLambdaFunction([&](HxExpression i, HxExpression prev) {
return model.max(prev, jobEnd[mL - 1][i]) + processingTime[mL][jobs[i]];
});
jobEnd[m] = model.array(model.range(0, nbJobs), endLambda, 0);
}
// Minimize the makespan: end of the last job on the last machine
makespan = jobEnd[nbMachines - 1][nbJobs - 1];
model.minimize(makespan);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(limit);
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());
outfile << makespan.getValue() << endl;
HxCollection jobsCollection = jobs.getCollectionValue();
for (int j = 0; j < nbJobs; ++j) {
outfile << jobsCollection[j] << " ";
}
outfile << endl;
}
};
int main(int argc, char** argv) {
if (argc < 2) {
cerr << "Usage: flowshop 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 {
Flowshop 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 Flowshop.cs /reference:Hexaly.NET.dllFlowshop instances\tai20_5.txt
using System;
using System.IO;
using Hexaly.Optimizer;
public class Flowshop : IDisposable
{
// Number of jobs
int nbJobs;
// Number of machines
int nbMachines;
// Processing time
long[][] processingTimeData;
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Decision variable
HxExpression jobs;
// Objective
HxExpression makespan;
public Flowshop()
{
optimizer = new HexalyOptimizer();
}
/* Read instance data */
void ReadInstance(string fileName)
{
using (StreamReader input = new StreamReader(fileName))
{
string[] firstLineSplit = input
.ReadLine()
.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
nbJobs = int.Parse(firstLineSplit[0]);
nbMachines = int.Parse(firstLineSplit[1]);
string[] matrixText = input
.ReadToEnd()
.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
processingTimeData = new long[nbMachines][];
for (int m = 0; m < nbMachines; ++m)
{
processingTimeData[m] = new long[nbJobs];
for (int j = 0; j < nbJobs; ++j)
processingTimeData[m][j] = long.Parse(matrixText[m * nbJobs + j]);
}
}
}
public void Dispose()
{
if (optimizer != null)
optimizer.Dispose();
}
void Solve(int limit)
{
// Declare the optimization model
HxModel model = optimizer.GetModel();
// Permutation of jobs
jobs = model.List(nbJobs);
// All jobs have to be assigned
model.Constraint(model.Count(jobs) == nbJobs);
// For each machine create proccessingTime[m] as an array to be able to access it
// with an 'at' operator
HxExpression[] processingTime = new HxExpression[nbMachines];
for (int m = 0; m < nbMachines; ++m)
processingTime[m] = model.Array(processingTimeData[m]);
// On machine 0, the jth job ends on the time it took to be processed after
// the end of the previous job
HxExpression[] jobEnd = new HxExpression[nbJobs];
HxExpression firstEndLambda = model.LambdaFunction(
(i, prev) => prev + processingTime[0][jobs[i]]
);
jobEnd[0] = model.Array(model.Range(0, nbJobs), firstEndLambda, 0);
// The jth job on machine m starts when it has been processed by machine n-1
// AND when job j-1 has been processed on machine m. It ends after it has been processed.
for (int m = 1; m < nbMachines; ++m)
{
HxExpression endLambda = model.LambdaFunction(
(i, prev) => model.Max(prev, jobEnd[m - 1][i]) + processingTime[m][jobs[i]]
);
jobEnd[m] = model.Array(model.Range(0, nbJobs), endLambda, 0);
}
// Minimize the makespan: end of the last job on the last machine
makespan = jobEnd[nbMachines - 1][nbJobs - 1];
model.Minimize(makespan);
model.Close();
// Parametrize the optimizer
optimizer.GetParam().SetTimeLimit(limit);
optimizer.Solve();
}
/* Write the solution in a file */
void WriteSolution(string fileName)
{
using (StreamWriter output = new StreamWriter(fileName))
{
output.WriteLine(makespan.GetValue());
HxCollection jobsCollection = jobs.GetCollectionValue();
for (int j = 0; j < nbJobs; ++j)
output.Write(jobsCollection[j] + " ");
output.WriteLine();
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: Flowshop inputFile [solFile] [timeLimit]");
Environment.Exit(1);
}
string instanceFile = args[0];
string outputFile = args.Length > 1 ? args[1] : null;
string strTimeLimit = args.Length > 2 ? args[2] : "5";
using (Flowshop model = new Flowshop())
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(outputFile);
}
}
}
- Compilation / Execution (Windows)
-
javac Flowshop.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. Flowshop instances\tai20_5.txt
- Compilation / Execution (Linux)
-
javac Flowshop.java -cp /opt/hexaly_13_0/bin/hexaly.jarjava -cp /opt/hexaly_13_0/bin/hexaly.jar:. Flowshop instances/tai20_5.txt
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;
public class Flowshop {
// Number of jobs
private int nbJobs;
// Number of machines
private int nbMachines;
// Processing time
private long[][] processingTimeData;
// Hexaly Optimizer
private final HexalyOptimizer optimizer;
// Decision variable
private HxExpression jobs;
// Objective
private HxExpression makespan;
private Flowshop(HexalyOptimizer optimizer) {
this.optimizer = optimizer;
}
/* Read instance data */
private void readInstance(String fileName) throws IOException {
try (Scanner input = new Scanner(new File(fileName))) {
nbJobs = input.nextInt();
nbMachines = input.nextInt();
input.nextInt();
input.nextInt();
input.nextInt();
processingTimeData = new long[nbMachines][nbJobs];
for (int m = 0; m < nbMachines; ++m) {
for (int j = 0; j < nbJobs; ++j) {
processingTimeData[m][j] = input.nextInt();
}
}
}
}
private void solve(int limit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Permutation of jobs
jobs = model.listVar(nbJobs);
// All jobs have to be assigned
model.constraint(model.eq(model.count(jobs), nbJobs));
// For each machine create proccessingTime[m] as an array to be able to access it
// with an 'at' operator
HxExpression[] processingTime = new HxExpression[nbMachines];
for (int m = 0; m < nbMachines; ++m) {
processingTime[m] = model.array(processingTimeData[m]);
}
// On machine 0, the jth job ends on the time it took to be processed after
// the end of the previous job
HxExpression[] jobEnd = new HxExpression[nbJobs];
HxExpression firstEndLambda = model
.lambdaFunction((i, prev) -> model.sum(prev, model.at(processingTime[0], model.at(jobs, i))));
jobEnd[0] = model.array(model.range(0, nbJobs), firstEndLambda, 0);
// The jth job on machine m starts when it has been processed by machine n-1
// AND when job j-1 has been processed on machine m. It ends after it has been processed.
for (int m = 1; m < nbMachines; ++m) {
final int mL = m;
HxExpression endLambda = model.lambdaFunction((i, prev) -> model
.sum(model.max(prev, model.at(jobEnd[mL - 1], i)), model.at(processingTime[mL], model.at(jobs, i))));
jobEnd[m] = model.array(model.range(0, nbJobs), endLambda, 0);
}
// Minimize the makespan: end of the last job on the last machine
makespan = model.at(jobEnd[nbMachines - 1], nbJobs - 1);
model.minimize(makespan);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(limit);
optimizer.solve();
}
/* Write the solution in a file */
private void writeSolution(String fileName) throws IOException {
try (PrintWriter output = new PrintWriter(fileName)) {
output.println(makespan.getValue());
HxCollection jobsCollection = jobs.getCollectionValue();
for (int j = 0; j < nbJobs; ++j) {
output.print(jobsCollection.get(j) + " ");
}
output.println();
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: java Flowshop 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()) {
Flowshop model = new Flowshop(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);
}
}
}