# 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¶ 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.

## 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;

// Number of jobs
// Number of machines

processingTime = {};
// Processing time for each task, for each machine
// For each job, for each operation, the corresponding task id

for [j in 0..nbJobs-1] {
// Number of operations for each job
for [o in 0..nbOperations[j]-1] {
for [i in 0..nbMachinesOperation-1] {
local machine = inFile.readInt() - 1;
processingTime[j][o][machine] = time;
}
}
}
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) {
} else if (processingTime[j][o][m] >= maxProcessingTime) {
maxProcessingTime = processingTime[j][o][m];
}
}
maxStart += maxProcessingTime;
}
}

function model() {
// Sequence of tasks on each machine

// 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] {
constraint contains(jobsOrder[m], t) == false;
}
}

// For each task, the selected machine

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

// The task duration depends on the selected machine
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 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] {
}
}
}

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

with open(filename) as f:

first_line = lines.split()
# Number of jobs
nb_jobs = int(first_line)
# Number of machines
nb_machines = int(first_line)

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

nb_tasks = sum(nb_operations[j] for j in range(nb_jobs))

# Processing time for each task, for each machine

# 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])
id = id + 1
tmp = tmp + 2 * nb_machines_operation

# Trivial upper bound for the start times of the tasks

def main(instance_file, output_file, time_limit):

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 m in range(nb_machines) :
model.constraint(model.not_(model.contains(jobs_order[m], t)))

# For each task, the selected machine

# 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
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):
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]):
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
output_file = sys.argv if len(sys.argv) >= 3 else None
time_limit = int(sys.argv) 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;
// Processing time for each task, for each machine
// For each job, for each operation, the corresponding task id
// 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
// End times of the tasks
std::vector<LSExpression> end;
// Objective = minimize the makespan: end of the last task
LSExpression makespan;

std::ifstream infile;
infile.open(fileName.c_str());

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

std::vector<std::vector<std::vector<int> > > processingTime = std::vector<std::vector<std::vector<int> > >(nbJobs);
nbOperations.resize(nbJobs);
for (unsigned int j = 0; j < nbJobs; j++) {
infile >> nbOperations[j];
processingTime[j].resize(nbOperations[j]);
for (unsigned int o = 0; o < nbOperations[j]; o++) {
int nbMachinesOperation;
infile >> nbMachinesOperation;
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;
}
}
}
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() {
}

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++) {
}

// 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++) {
model.constraint(!model.contains(jobsOrder[m], t));
}
}
}

for (int t = 0; t < nbTasks; t++) {
// For each task, the selected machine
}

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
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 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++) {
}
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++) {
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;
const char* outputFile = argc > 2 ? argv : NULL;
const char* strTimeLimit = argc > 3 ? argv : "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;
// Processing time for each task, for each machine
// For each job, for each operation, the corresponding task id
// 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
// 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();
}

{
{
char[] separators = new char[] {'\t', ' '};
nbJobs = int.Parse(splitted);
nbMachines = int.Parse(splitted);

long[][][] processingTime = new long[nbJobs][][];
nbOperations = new int[nbJobs];
for (int j = 0; j < nbJobs; j++)
{
nbOperations[j] = int.Parse(splitted);
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;
}
}
}

// Trivial upper bound for the start times of the tasks
maxStart = 0;
for (int j = 0; j < nbJobs; j++)
{
long maxProcessingTime = 0;
for (int o = 0; o < nbOperations[j]; o++)
{
for (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 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++)
{
}

// 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++)
{
{
model.Constraint(!model.Contains(jobsOrder[m], t));
}
}
}

for (int t = 0; t < nbTasks; t++)
{
// For each task, the selected machine
}

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
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 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++)
{
}
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++)
{
}
}
}
}

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

string instanceFile = args ;
string outputFile = args.Length > 1 ? args : null;
string strTimeLimit = args.Length > 2 ? args : "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;
// Processing time for each task, for each machine
// For each job, for each operation, the corresponding task id
// 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
// 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;
}

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

long[][][] processingTime = new long[nbJobs][][];
nbOperations = new int[nbJobs];
for (int j = 0; j < nbJobs; j++) {
nbOperations[j] = input.nextInt();
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;
}
}
}

// Trivial upper bound for the start times of the tasks
maxStart = 0;
for (int j = 0; j < nbJobs; j++) {
long maxProcessingTime = 0;
for (int o = 0; o < nbOperations[j]; o++) {
for (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 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++) {
}

// 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++) {
model.constraint(model.not(model.contains(jobsOrder[m], t)));
}
}
}

// For each task, the selected machine
for (int t = 0; t < nbTasks; t++) {
}

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);
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 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++) {
}
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++) {
}
}
}
}

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;
String outputFile = args.length > 1 ? args : null;
String strTimeLimit = args.length > 2 ? args : "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);
}
}
}