LocalSolver logo
is now
Hexaly logo

We're excited to share that we are moving forward. We're leaving behind the LocalSolver brand and transitioning to our new identity: Hexaly. This represents a leap forward in our mission to enable every organization to make better decisions faster when faced with operational and strategic challenges.

This page is for an old version of Hexaly Optimizer. We recommend that you update your version and read the documentation for the latest stable release.

Flexible Job Shop (FJSP)¶

Principles learned¶

  • Add multiple list decision variables
  • Use the find operator
  • Order numeric decision variables by pairing them up with a list variable

Problem¶

../_images/fjsp.png

A set of jobs has to be processed on the machines in the shop. Each job consists of an ordered sequence of tasks (called operations), and each operation must be performed by one of the machines compatible with that operation. An operation cannot begin until the previous operation in the job is completed. Each operation has a given processing time that depends on the chosen machine, and each machine can only process one operation at a time.

The goal is to find a sequence of jobs that minimizes the makespan: the time when all jobs have been processed.

Download the example

Data¶

The format of the data files is as follows:

  • First line: number of jobs, number of machines (+ average number of machines per operations, not needed)
  • From the second line, for each job:
    • Number of operations in that job
    • For each operation:
      • Number of machines compatible with this operation
      • For each compatible machine: a pair of numbers (machine, processing time)

Program¶

The model is very similar to the original Job Shop Problem, and the decision variables remain unchanged: integer decision variables to model the start times of the operations, and a list decision variable for each machine, representing the order of the tasks scheduled on this machine.

Each operation of each job must be processed, hence the partition operator on the lists, which ensures that each task will belong to one and only one machine. Machines that are not compatible for an operation are filtered out using a contains operator.

The find operator takes as argument an array of lists and an integer value, and returns the position of the list containing the value in the array, if it exists. Here, we use this operator to retrieve the id of the machine used for each task. It then allows to deduce the duration of the operation, since it depends on the selected machine.

The disjunctive resource constraints are modeled in the same way as for the original job shop problem, and the makespan to be minimized is the time when all tasks have been processed.

Execution:
localsolver flexible_jobshop.lsp inFileName=instances/Mk01.fjs [outFileName=] [lsTimeLimit=]
/********** flexible_jobshop.lsp **********/

use io;

function input() {
    local usage = "Usage: localsolver flexible_jobshop.lsp inFileName=instanceFile [outFileName=outputFile] [lsTimeLimit=timeLimit]";
    if (inFileName == nil) throw usage;

    // Constant for uncompatible machines
    INFINITE = 1000000;

    inFile = io.openRead(inFileName);
    // Number of jobs
    nbJobs = inFile.readInt();
    // Number of machines
    nbMachines = inFile.readInt();
    inFile.readln(); // skip last number

    // Number of tasks
    nbTasks = 0;
    processingTime = {};
    // Processing time for each task, for each machine
    taskProcessingTime = {};
    // For each job, for each operation, the corresponding task id
    jobOperationTask = {};
    
    for [j in 0..nbJobs-1] {
        // Number of operations for each job
        nbOperations[j] = inFile.readInt();
        for [o in 0..nbOperations[j]-1] {
            local nbMachinesOperation = inFile.readInt();
            for [i in 0..nbMachinesOperation-1] {
                local machine = inFile.readInt() - 1;
                local time = inFile.readInt();
                processingTime[j][o][machine] = time;
                taskProcessingTime[nbTasks][machine] = time;
            }
            jobOperationTask[j][o] = nbTasks;
            nbTasks += 1;
        }
    }
    inFile.close();

    // Trivial upper bound for the start times of the tasks
    maxStart = 0;
    for [j in 0..nbJobs-1][o in 0..nbOperations[j]-1] {
        local maxProcessingTime = 0;
        for [m in 0..nbMachines - 1] {
            if (processingTime[j][o][m] == nil) {
                local task = jobOperationTask[j][o];
                taskProcessingTime[task][m] = INFINITE;
            } else if (processingTime[j][o][m] >= maxProcessingTime) {
                maxProcessingTime = processingTime[j][o][m];
            }
        }
        maxStart += maxProcessingTime;
    }
}

function model() {
    // Sequence of tasks on each machine
    jobsOrder[m in 0..nbMachines-1] <- list(nbTasks);

    // Each task is scheduled on a machine
    constraint partition[m in 0..nbMachines-1](jobsOrder[m]);

    // Only compatible machines can be selected for a task
    for[t in 0..nbTasks-1][m in 0..nbMachines-1] {
        if (taskProcessingTime[t][m] == INFINITE) {
            constraint contains(jobsOrder[m], t) == false;
        }
    }

    // For each task, the selected machine
    taskMachine[t in 0..nbTasks-1] <- find(jobsOrder, t);

    // Integer decisions: start time of each task
    start[t in 0..nbTasks-1] <- int(0, maxStart);

    // The task duration depends on the selected machine
    duration[t in 0..nbTasks-1] <- taskProcessingTime[t][taskMachine[t]];
    end[t in 0..nbTasks-1] <- start[t] + duration[t];

    // Precedence constraints between the operations of a job
    for [j in 0..nbJobs-1][o in 0..nbOperations[j]-2] {
        local t1 = jobOperationTask[j][o];
        local t2 = jobOperationTask[j][o + 1];
        constraint start[t2] >= end[t1];
    }

    // Disjunctive resource constraints between the tasks on a machine
    for [m in 0..nbMachines-1] {
        local sequence <- jobsOrder[m];
        constraint and(0..count(sequence) - 2, t => start[sequence[t + 1]] >= end[sequence[t]]);
    }

    // Minimize the makespan: end of the last task
    makespan <- max[t in 0..nbTasks-1] (end[t]);
    minimize makespan;
}

// Parameterizes the solver
function param() {
    if (lsTimeLimit == nil) lsTimeLimit = 60;
}

// Writes the solution in a file with the following format:
//  - for each operation of each job, the selected machine, the start and end dates
function output() {
    if (outFileName != nil) {
        outFile = io.openWrite(outFileName);
        println("Solution written in file ", outFileName);
        for [j in 0..nbJobs-1][o in 0..nbOperations[j]-1] {
            local task = jobOperationTask[j][o];
            outFile.println(j + 1, "\t", o + 1, "\t", taskMachine[task].value + 1, "\t", start[task].value, "\t", end[task].value);
        }
    }
}
Execution (Windows)
set PYTHONPATH=%LS_HOME%\bin\python
python flexible_jobshop.py instances\Mk01.fjs
Execution (Linux)
export PYTHONPATH=/opt/localsolver_10_5/bin/python
python flexible_jobshop.py instances/Mk01.fjs
########## flexible_jobshop.py ##########

import localsolver
import sys

# Constant for uncompatible machines
INFINITE = 1000000

def read_instance(filename):
    with open(filename) as f:
        lines = f.readlines()

    first_line = lines[0].split()
    # Number of jobs
    nb_jobs = int(first_line[0])
    # Number of machines
    nb_machines = int(first_line[1])

    # Number of operations for each job
    nb_operations = [int(lines[j + 1].split()[0]) for j in range(nb_jobs)]

    # Number of tasks
    nb_tasks = sum(nb_operations[j] for j in range(nb_jobs))

    # Processing time for each task, for each machine
    task_processing_time = [[INFINITE for m in range(nb_machines)] for t in range(nb_tasks)]

    # For each job, for each operation, the corresponding task id
    job_operation_task = [[0 for o in range(nb_operations[j])] for j in range(nb_jobs)]

    id = 0
    for j in range(nb_jobs):
        line = lines[j + 1].split()
        tmp = 0
        for o in range(nb_operations[j]):
            nb_machines_operation = int(line[tmp + o + 1])
            for i in range(nb_machines_operation):
                machine = int(line[tmp + o + 2 * i + 2]) - 1
                time = int(line[tmp + o + 2 * i + 3])
                task_processing_time[id][machine] = time
            job_operation_task[j][o] = id
            id = id + 1
            tmp = tmp + 2 * nb_machines_operation

    # Trivial upper bound for the start times of the tasks
    max_start = sum(max(task_processing_time[t][m] for m in range(nb_machines) if task_processing_time[t][m] != INFINITE) for t in range(nb_tasks))

    return (nb_jobs, nb_machines, nb_tasks, task_processing_time, job_operation_task, nb_operations, max_start)


def main(instance_file, output_file, time_limit):
    nb_jobs, nb_machines, nb_tasks, task_processing_time, job_operation_task, nb_operations, max_start = read_instance(instance_file)

    with localsolver.LocalSolver() as ls:
        # Declares the optimization model
        model = ls.model

        # Sequence of tasks on each machine
        jobs_order = [model.list(nb_tasks) for m in range(nb_machines)]
        machine_array = model.array(jobs_order)

        # Each task is scheduled on a machine
        model.constraint(model.partition(machine_array))

        # Only compatible machines can be selected for a task
        for t in range(nb_tasks):
            for m in range(nb_machines) :
                if (task_processing_time[t][m] == INFINITE):
                    model.constraint(model.not_(model.contains(jobs_order[m], t)))

        # For each task, the selected machine
        task_machine = [model.find(machine_array, t) for t in range(nb_tasks)]

        task_processing_time_array = model.array(task_processing_time)

        # Integer decisions: start time of each task
        start = [model.int(0, max_start) for t in range(nb_tasks)]

        # The task duration depends on the selected machine
        duration = [model.at(task_processing_time_array, t, task_machine[t]) for t in range(nb_tasks)]
        end = [start[t] + duration[t] for t in range(nb_tasks)]

        start_array = model.array(start)
        end_array = model.array(end)

        # Precedence constraints between the operations of a job
        for j in range(nb_jobs):
            for o in range(nb_operations[j]-1):
                t1 = job_operation_task[j][o]
                t2 = job_operation_task[j][o + 1]
                model.constraint(start[t2] >= end[t1])

        # Disjunctive resource constraints between the tasks on a machine
        for m in range(nb_machines):
            sequence = jobs_order[m]
            sequence_selector = model.lambda_function(lambda t: model.geq(model.at(start_array, sequence[t + 1]), model.at(end_array, sequence[t])))
            model.constraint(model.and_(model.range(0, model.count(sequence)-1), sequence_selector))

        # Minimize the makespan: end of the last task
        makespan = model.max([end[t] for t in range(nb_tasks)])
        model.minimize(makespan)

        model.close()

        # Parameterizes the solver
        ls.param.time_limit = time_limit

        ls.solve()

        # Writes the solution in a file with the following format:
        # - for each operation of each job, the selected machine, the start and end dates
        if output_file != None:
            with open(output_file, "w") as f:
                print("Solution written in file", output_file)
                for j in range(nb_jobs):
                    for o in range(0, nb_operations[j]):
                        task = job_operation_task[j][o]
                        f.write(str(j + 1) + "\t" + str(o + 1) + "\t" + str(task_machine[task].value + 1) + "\t" + str(start[task].value) + "\t" + str(end[task].value) + "\n")


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python flexible_jobshop.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 60
    main(instance_file, output_file, time_limit)
Compilation / Execution (Windows)
cl /EHsc flexible_jobshop.cpp -I%LS_HOME%\include /link %LS_HOME%\bin\localsolver105.lib
flexible_jobshop instances\Mk01.fjs
Compilation / Execution (Linux)
g++ flexible_jobshop.cpp -I/opt/localsolver_10_5/include -llocalsolver105 -lpthread -o flexible_jobshop
./flexible_jobshop instances/Mk01.fjs
/********** flexible_jobshop.cpp **********/

#include <iostream>
#include <fstream>
#include <vector>
#include <limits>
#include <numeric>
#include <algorithm>
#include "localsolver.h"

using namespace localsolver;

class FlexibleJobshop {
private:
    // Number of jobs
    int nbJobs;
    // Number of machines
    int nbMachines;
    // Number of tasks
    int nbTasks;
    // Processing time for each task, for each machine
    std::vector<std::vector<int> > taskProcessingTime;
    // For each job, for each operation, the corresponding task id
    std::vector<std::vector<int> > jobOperationTask;
    // Number of operations for each job
    std::vector<int> nbOperations;
    // Trivial upper bound for the start times of the tasks
    int maxStart;
    // Constant for uncompatible machines
    const int INFINITE = 1000000;

    // LocalSolver
    LocalSolver localsolver;
    // Decision variables: start times of the tasks
    std::vector<LSExpression> start;
    // Decision variables: sequence of tasks on each machine
    std::vector<LSExpression> jobsOrder;
    // For each task, the selected machine
    std::vector<LSExpression> taskMachine;
    // End times of the tasks
    std::vector<LSExpression> end;
    // Objective = minimize the makespan: end of the last task
    LSExpression makespan;


    void readInstance(const std::string& fileName) {
        std::ifstream infile;
        infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        infile.open(fileName.c_str());

        infile >> nbJobs;
        infile >> nbMachines;
        infile.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // skip last number

        nbTasks = 0;
        std::vector<std::vector<std::vector<int> > > processingTime = std::vector<std::vector<std::vector<int> > >(nbJobs);
        jobOperationTask.resize(nbJobs);
        nbOperations.resize(nbJobs);
        for (unsigned int j = 0; j < nbJobs; j++) {
            infile >> nbOperations[j];
            jobOperationTask[j].resize(nbOperations[j]);
            processingTime[j].resize(nbOperations[j]);
            for (unsigned int o = 0; o < nbOperations[j]; o++) {
                int nbMachinesOperation;
                infile >> nbMachinesOperation;
                taskProcessingTime.push_back(std::vector<int>(nbMachines, INFINITE));
                processingTime[j][o].resize(nbMachines, INFINITE);
                for (int m = 0; m < nbMachinesOperation; m++) {
                    int machine;
                    int time;
                    infile >> machine;
                    infile >> time;
                    processingTime[j][o][machine - 1] = time;
                    taskProcessingTime[nbTasks][machine - 1] = time;
                }
                jobOperationTask[j][o] = nbTasks;
                nbTasks += 1;
            }
        }
        infile.close();

        // Trivial upper bound for the start times of the tasks
        maxStart = 0;
        for (unsigned int j = 0; j < nbJobs; j++) {
            for (unsigned int o = 0; o < nbOperations[j]; o++) {
                int maxProcessingTime = 0;
                for (unsigned int m = 0; m < nbMachines; m++) {
                    if (processingTime[j][o][m] != INFINITE && processingTime[j][o][m] > maxProcessingTime)
                        maxProcessingTime = processingTime[j][o][m];
                }
                maxStart += maxProcessingTime;
            }
        }
    }

public:
    FlexibleJobshop(const std::string& fileName) : localsolver() {
        readInstance(fileName);
    }

    void solve(int timeLimit) {
        // Declares the optimization model
        LSModel model = localsolver.getModel();

        // Sequence of tasks on each machine
        jobsOrder.resize(nbMachines);
        LSExpression machineArray = model.array();
        for (unsigned int m = 0; m < nbMachines; m++) {
            jobsOrder[m] = model.listVar(nbTasks);
            machineArray.addOperand(jobsOrder[m]);
        }

        // Each task is scheduled on a machine
        model.constraint(model.partition(machineArray));

        // Only compatible machines can be selected for a task
        for (int t = 0; t < nbTasks; t++) {
            for (unsigned int m = 0; m < nbMachines; m++) {
                if (taskProcessingTime[t][m] == INFINITE) {
                    model.constraint(!model.contains(jobsOrder[m], t));
                }
            }
        }

        taskMachine.resize(nbTasks);
        LSExpression taskProcessingTimeArray = model.array();
        for (int t = 0; t < nbTasks; t++) {
            // For each task, the selected machine
            taskMachine[t] = model.find(machineArray, t);
            taskProcessingTimeArray.addOperand(model.array(taskProcessingTime[t].begin(), taskProcessingTime[t].end()));
        }

        start.resize(nbTasks);
        std::vector<LSExpression> duration(nbTasks);
        end.resize(nbTasks);
        for (int t = 0; t < nbTasks; t++) {
            // Integer decisions: start time of each task
            start[t] = model.intVar(0, maxStart);

            // The task duration depends on the selected machine
            duration[t] = model.at(taskProcessingTimeArray, t, taskMachine[t]);
            end[t] = start[t] + duration[t];
        }
        LSExpression startArray = model.array(start.begin(), start.end());
        LSExpression endArray = model.array(end.begin(), end.end());

        // Precedence constraints between the operations of a job
        for (unsigned int j = 0; j < nbJobs; j++) {
            for (unsigned int o = 0; o < nbOperations[j]-1; o++) {
                int t1 = jobOperationTask[j][o];
                int t2 = jobOperationTask[j][o + 1];
                model.constraint(start[t2] >= end[t1]);
            }
        }

        // Disjunctive resource constraints between the tasks on a machine
        for (int m = 0; m < nbMachines; m++) {
            LSExpression sequence = jobsOrder[m];
            LSExpression sequenceSelector = model.createLambdaFunction([&](LSExpression t) {
                return model.geq(model.at(startArray, sequence[t + 1]),
                        model.at(endArray, sequence[t]));
            });
            model.constraint(model.and_(model.range(0, model.count(sequence) - 1), sequenceSelector));
        }

        // Minimize the makespan: end of the last task
        makespan = model.max();
        for (unsigned int t = 0; t < nbTasks; t++) {
            makespan.addOperand(end[t]);
        }
        model.minimize(makespan);

        model.close();

        // Parameterizes the solver
        localsolver.getParam().setTimeLimit(timeLimit);

        localsolver.solve();
    }

    // Writes the solution in a file with the following format:
    //  - for each operation of each job, the selected machine, the start and end dates
    void writeSolution(const std::string& fileName) {
        std::ofstream outfile(fileName.c_str());
        if (!outfile.is_open()) {
            std::cerr << "File " << fileName << " cannot be opened." << std::endl;
            exit(1);
        }
        std::cout << "Solution written in file " << fileName << std::endl;

        for (unsigned int j = 0; j < nbJobs; j++) {
            for (unsigned int o = 0; o < nbOperations[j]; o++) {
                int task = jobOperationTask[j][o];
                outfile << j + 1 << "\t" << o + 1 << "\t" << taskMachine[task].getValue() + 1 << "\t" << start[task].getValue() << "\t" << end[task].getValue() << std::endl;
            }
        }
        outfile.close();
    }
};

int main(int argc, char** argv) {
    if (argc < 2) {
        std::cout << "Usage: flexible_jobshop instanceFile [outputFile] [timeLimit]" << std::endl;
        exit(1);
    }

    const char* instanceFile = argv[1];
    const char* outputFile = argc > 2 ? argv[2] : NULL;
    const char* strTimeLimit = argc > 3 ? argv[3] : "60";

    FlexibleJobshop model(instanceFile);
    try {
        const int timeLimit = atoi(strTimeLimit);
        model.solve(timeLimit);
        if (outputFile != NULL) model.writeSolution(outputFile);
        return 0;
    } catch (const std::exception& e) {
        std::cerr << "An error occurred: " << e.what() << std::endl;
        return 1;
    }
}
Compilation / Execution (Windows)
copy %LS_HOME%\bin\localsolvernet.dll .
csc FlexibleJobshop.cs /reference:localsolvernet.dll
FlexibleJobshop instances\Mk01.fjs
/********** FlexibleJobshop.cs **********/

using System;
using System.IO;
using System.Linq;
using localsolver;

public class FlexibleJobshop : IDisposable
{
    // Number of jobs
    private int nbJobs;
    // Number of machines
    private int nbMachines;
    // Number of tasks
    private int nbTasks;
    // Processing time for each task, for each machine
    private long[][] taskProcessingTime;
    // For each job, for each operation, the corresponding task id
    private int[][] jobOperationTask;
    // Number of operations for each job;
    private int[] nbOperations;
    // Trivial upper bound for the start times of the tasks
    private long maxStart;
    // Constant for uncompatible machines
    private const long INFINITE = 1000000;

    // LocalSolver
    private LocalSolver localsolver;
    // Decision variables: start times of the tasks
    private LSExpression[] start;
    // Decision variables: sequence of tasks on each machine
    private LSExpression[] jobsOrder;
    // For each task, the selected machine
    private LSExpression[] taskMachine;
    // End times of the tasks
    private LSExpression[] end;
    // Objective = minimize the makespan: end of the last task
    private LSExpression makespan;

    public FlexibleJobshop(string fileName)
    {
        localsolver = new LocalSolver();
        ReadInstance(fileName);
    }

    private void ReadInstance(string fileName)
    {
        using (StreamReader input = new StreamReader(fileName))
        {
            char[] separators = new char[] {'\t', ' '};
            string[] splitted = input.ReadLine().Split(separators, StringSplitOptions.RemoveEmptyEntries);
            nbJobs = int.Parse(splitted[0]);
            nbMachines = int.Parse(splitted[1]);

            nbTasks = 0;
            long[][][] processingTime = new long[nbJobs][][];
            jobOperationTask = new int[nbJobs][];
            nbOperations = new int[nbJobs];
            for (int j = 0; j < nbJobs; j++)
            {
                splitted = input.ReadLine().Split(separators, StringSplitOptions.RemoveEmptyEntries);
                nbOperations[j] = int.Parse(splitted[0]);
                jobOperationTask[j] = new int[nbOperations[j]];
                processingTime[j] = new long[nbOperations[j]][];
                int k = 1;
                for (int o = 0; o < nbOperations[j]; o++)
                {
                    int nbMachinesOperation = int.Parse(splitted[k]);
                    k++;
                    processingTime[j][o] = Enumerable.Repeat((long) INFINITE, nbMachines).ToArray();
                    for (int m = 0; m < nbMachinesOperation; m++)
                    {
                        int machine = int.Parse(splitted[k]) - 1;
                        long time = long.Parse(splitted[k + 1]);
                        processingTime[j][o][machine] = time;
                        k += 2;
                    }
                    jobOperationTask[j][o] = nbTasks;
                    nbTasks++;
                }
            }

            // Trivial upper bound for the start times of the tasks
            maxStart = 0;
            taskProcessingTime = new long[nbTasks][];
            for (int j = 0; j < nbJobs; j++)
            {
                long maxProcessingTime = 0;
                for (int o = 0; o < nbOperations[j]; o++)
                {
                    int task = jobOperationTask[j][o];
                    taskProcessingTime[task] = new long[nbMachines];
                    for (int m = 0; m < nbMachines; m++)
                    {
                        taskProcessingTime[task][m] = processingTime[j][o][m];
                        if (processingTime[j][o][m] != INFINITE && processingTime[j][o][m] > maxProcessingTime)
                        {
                            maxProcessingTime = processingTime[j][o][m];
                        }
                    }
                    maxStart += maxProcessingTime;
                }
            }
        }
    }

    public void Dispose()
    {
        localsolver.Dispose();
    }

    public void Solve(int timeLimit)
    {
        // Declares the optimization model
        LSModel model = localsolver.GetModel();

        // Sequence of tasks on each machine
        jobsOrder = new LSExpression[nbMachines];
        LSExpression machineArray = model.Array();
        for (int m = 0; m < nbMachines; m++)
        {
            jobsOrder[m] = model.List(nbTasks);
            machineArray.AddOperand(jobsOrder[m]);
        }

        // Each task is scheduled on a machine
        model.Constraint(model.Partition(machineArray));

        // Only compatible machines can be selected for a task
        for (int t = 0; t < nbTasks; t++)
        {
            for (int m = 0; m < nbMachines; m++)
            {
                if (taskProcessingTime[t][m] == INFINITE)
                {
                    model.Constraint(!model.Contains(jobsOrder[m], t));
                }
            }
        }

        taskMachine = new LSExpression[nbTasks];
        LSExpression[] taskProcessingTimeArray = new LSExpression[nbTasks];
        for (int t = 0; t < nbTasks; t++)
        {
            // For each task, the selected machine
            taskMachine[t] = model.Find(machineArray, t);
            taskProcessingTimeArray[t] = model.Array(taskProcessingTime[t]);
        }

        start = new LSExpression[nbTasks];
        LSExpression[] duration = new LSExpression[nbTasks];
        end = new LSExpression[nbTasks];
        for (int t = 0; t < nbTasks; t++)
        {
            // Integer decisions: start time of each task
            start[t] = model.Int(0, maxStart);

            // The task duration depends on the selected machine
            duration[t] = model.At(taskProcessingTimeArray[t], taskMachine[t]);
            end[t] = start[t] + duration[t];
        }
        LSExpression startArray = model.Array(start);
        LSExpression endArray = model.Array(end);

        // Precedence constraints between the operations of a job
        for (int j = 0; j < nbJobs; j++)
        {
            for (int o = 0; o < nbOperations[j]-1; o++)
            {
                int t1 = jobOperationTask[j][o];
                int t2 = jobOperationTask[j][o + 1];
                model.Constraint(start[t2] >= end[t1]);
            }
        }

        // Disjunctive resource constraints between the tasks on a machine
        for (int m = 0; m < nbMachines; m++)
        {
            LSExpression sequence = jobsOrder[m];
            LSExpression sequenceSelector = model.LambdaFunction(t => startArray[sequence[t + 1]] >= endArray[sequence[t]]);
            model.Constraint(model.And(model.Range(0, model.Count(sequence) - 1), sequenceSelector));
        }

        // Minimize the makespan: end of the last task
        makespan = model.Max();
        for (int t = 0; t < nbTasks; t++)
        {
            makespan.AddOperand(end[t]);
        }
        model.Minimize(makespan);

        model.Close();

        // Parameterizes the solver
        localsolver.GetParam().SetTimeLimit(timeLimit);

        localsolver.Solve();
    }

    // Writes the solution in a file with the following format:
    //  - for each operation of each job, the selected machine, the start and end dates
    public void WriteSolution(string fileName)
    {
        using (StreamWriter output = new StreamWriter(fileName)) {
            Console.WriteLine("Solution written in file " + fileName);
            for (int j = 1; j <= nbJobs; j++)
            {
                for (int o = 1; o <= nbOperations[j - 1]; o++)
                {
                    int task = jobOperationTask[j - 1][o - 1];
                    output.WriteLine(j + "\t" + o + "\t" + taskMachine[task].GetValue() + "\t" + start[task].GetValue() + "\t" + end[task].GetValue());
                }
            }
        }
    }

    public static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine ("Usage: FlexibleJobshop instanceFile [outputFile] [timeLimit]");
            System.Environment.Exit(1);
        }

        string instanceFile = args [0];
        string outputFile = args.Length > 1 ? args[1] : null;
        string strTimeLimit = args.Length > 2 ? args[2] : "60";

        using (FlexibleJobshop model = new FlexibleJobshop(instanceFile))
        {
            model.Solve (int.Parse(strTimeLimit));
            if (outputFile != null)
                model.WriteSolution(outputFile);
        }
    }
}
Compilation / Execution (Windows)
javac FlexibleJobshop.java -cp %LS_HOME%\bin\localsolver.jar
java -cp %LS_HOME%\bin\localsolver.jar;. FlexibleJobshop instances\Mk01.fjs
Compilation / Execution (Linux)
javac FlexibleJobshop.java -cp /opt/localsolver_10_5/bin/localsolver.jar
java -cp /opt/localsolver_10_5/bin/localsolver.jar:. FlexibleJobshop instances/Mk01.fjs
/********** FlexibleJobshop.java **********/

import java.util.*;
import java.io.*;
import localsolver.*;

public class FlexibleJobshop {
    // Number of jobs
    private int nbJobs;
    // Number of machines
    private int nbMachines;
    // Number of tasks
    private int nbTasks;
    // Processing time for each task, for each machine
    private long[][] taskProcessingTime;
    // For each job, for each operation, the corresponding task id
    private int[][] jobOperationTask;
    // Number of operations for each job;
    private int[] nbOperations;
    // Trivial upper bound for the start times of the tasks
    private long maxStart;
    // Constant for uncompatible machines
    private final int INFINITE = 1000000;

    // LocalSolver
    final LocalSolver localsolver;
    // Decision variables: start times of the tasks
    private LSExpression[] start;
    // Decision variables: sequence of tasks on each machine
    private LSExpression[] jobsOrder;
    // For each task, the selected machine
    private LSExpression[] taskMachine;
    // End times of the tasks
    private LSExpression[] end;
    // Objective = minimize the makespan: end of the last task
    private LSExpression makespan;

    public FlexibleJobshop(LocalSolver localsolver, String fileName) throws IOException {
        this.localsolver = localsolver;
        readInstance(fileName);
    }

    private void readInstance(String fileName) throws IOException {
        try (Scanner input = new Scanner(new File(fileName))) {
            nbJobs = input.nextInt();
            nbMachines = input.nextInt();
            input.next(); // skip last number

            nbTasks = 0;
            long[][][] processingTime = new long[nbJobs][][];
            jobOperationTask = new int[nbJobs][];
            nbOperations = new int[nbJobs];
            for (int j = 0; j < nbJobs; j++) {
                nbOperations[j] = input.nextInt();
                jobOperationTask[j] = new int[nbOperations[j]];
                processingTime[j] = new long[nbOperations[j]][nbMachines];
                for (int o = 0; o < nbOperations[j]; o++) {
                    int nbMachinesOperation = input.nextInt();
                    Arrays.fill(processingTime[j][o], INFINITE);
                    for (int m = 0; m < nbMachinesOperation; m++) {
                        int machine = input.nextInt() - 1;
                        long time = input.nextLong();
                        processingTime[j][o][machine] = time;
                    }
                    jobOperationTask[j][o] = nbTasks;
                    nbTasks++;
                }
            }
            
            // Trivial upper bound for the start times of the tasks
            maxStart = 0;
            taskProcessingTime = new long[nbTasks][];
            for (int j = 0; j < nbJobs; j++) {
                long maxProcessingTime = 0;
                for (int o = 0; o < nbOperations[j]; o++) {
                    int task = jobOperationTask[j][o];
                    taskProcessingTime[task] = new long[nbMachines];
                    for (int m = 0; m < nbMachines; m++) {
                        taskProcessingTime[task][m] = processingTime[j][o][m];
                        if (processingTime[j][o][m] != INFINITE && processingTime[j][o][m] > maxProcessingTime) {
                            maxProcessingTime = processingTime[j][o][m];
                        }
                    }
                    maxStart += maxProcessingTime;
                }
            }
        }
    }

    public void solve(int timeLimit) {
        // Declares the optimization model
        LSModel model = localsolver.getModel();

        // Sequence of tasks on each machine
        jobsOrder = new LSExpression[nbMachines];
        LSExpression machineArray = model.array();
        for (int m = 0; m < nbMachines; m++) {
            jobsOrder[m] = model.listVar(nbTasks);
            machineArray.addOperand(jobsOrder[m]);
        }

        // Each task is scheduled on a machine
        model.constraint(model.partition(machineArray));

        // Only compatible machines can be selected for a task
        for (int t = 0; t < nbTasks; t++) {
            for (int m = 0; m < nbMachines; m++) {
                if (taskProcessingTime[t][m] == INFINITE) {
                    model.constraint(model.not(model.contains(jobsOrder[m], t)));
                }
            }
        }

        // For each task, the selected machine
        taskMachine = new LSExpression[nbTasks];
        for (int t = 0; t < nbTasks; t++) {
            taskMachine[t] = model.find(machineArray, t);
        }

        LSExpression taskProcessingTimeArray = model.array(taskProcessingTime);

        start = new LSExpression[nbTasks];
        LSExpression[] duration = new LSExpression[nbTasks];
        end = new LSExpression[nbTasks];
        for (int t = 0; t < nbTasks; t++) {
            // Integer decisions: start time of each task
            start[t] = model.intVar(0, maxStart);

            // The task duration depends on the selected machine
            LSExpression tExpr = model.createConstant(t);
            duration[t] = model.at(taskProcessingTimeArray, tExpr, taskMachine[t]);
            end[t] = model.sum(start[t], duration[t]);
        }
        LSExpression startArray = model.array(start);
        LSExpression endArray = model.array(end);

        // Precedence constraints between the operations of a job
        for (int j = 0; j < nbJobs; j++) {
            for (int o = 0; o < nbOperations[j]-1; o++) {
                int t1 = jobOperationTask[j][o];
                int t2 = jobOperationTask[j][o + 1];
                model.constraint(model.geq(start[t2], end[t1]));
            }
        }

        // Disjunctive resource constraints between the tasks on a machine
        for (int m = 0; m < nbMachines; m++) {
            LSExpression sequence = jobsOrder[m];
            LSExpression sequenceSelector = model.lambdaFunction(t -> model.geq(
                    model.at(startArray, model.at(sequence, model.sum(t, 1))),
                    model.at(endArray, model.at(sequence, t))));
            model.constraint(model.and(model.range(0, model.sub(model.count(sequence), 1)), sequenceSelector));
        }

        // Minimize the makespan: end of the last task
        makespan = model.max();
        for (int t = 0; t < nbTasks; t++) {
            makespan.addOperand(end[t]);
        }
        model.minimize(makespan);

        model.close();

        // Parameterizes the solver
        localsolver.getParam().setTimeLimit(timeLimit);

        localsolver.solve();
    }

    // Writes the solution in a file with the following format:
    //  - for each operation of each job, the selected machine, the start and end dates
    public void writeSolution(String fileName) throws IOException {
        try (PrintWriter output = new PrintWriter(fileName)) {
            System.out.println("Solution written in file " + fileName);

            for (int j = 1; j <= nbJobs; j++) {
                for (int o = 1; o <= nbOperations[j - 1]; o++) {
                    int task = jobOperationTask[j - 1][o - 1];
                    output.write(j + "\t" + o + "\t" + taskMachine[task].getValue() + "\t" + start[task].getValue() + "\t" + end[task].getValue() + "\n");
                }
            }
        }
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println("Usage: java FlexibleJobshop instanceFile [outputFile] [timeLimit]");
            System.exit(1);
        }

        String instanceFile = args[0];
        String outputFile = args.length > 1 ? args[1] : null;
        String strTimeLimit = args.length > 2 ? args[2] : "60";

        try (LocalSolver localsolver = new LocalSolver()) {
            FlexibleJobshop model = new FlexibleJobshop(localsolver, instanceFile);
            model.solve(Integer.parseInt(strTimeLimit));
            if (outputFile != null) {
                model.writeSolution(outputFile);
            }
        } catch (Exception ex) {
            System.err.println(ex);
            ex.printStackTrace();
            System.exit(1);
        }
    }
}