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

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\python
python flowshop.py instances\tai20_5.txt
Execution (Linux)
export PYTHONPATH=/opt/hexaly_13_0/bin/python
python 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.lib
flowshop 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.dll
Flowshop 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.jar
java -cp %HX_HOME%\bin\hexaly.jar;. Flowshop instances\tai20_5.txt
Compilation / Execution (Linux)
javac Flowshop.java -cp /opt/hexaly_13_0/bin/hexaly.jar
java -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);
        }
    }
}