Flexible Job Shop Problem with Setup Times (FJSP-SDST)
Problem
In the Flexible Job Shop Scheduling Problem with Sequence-Dependent Setup Times (FJSP-SDST), 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): each operation can only start when the previous one has ended. Each operation has a set of compatible machines, and must be processed by one of them. The processing time of an operation depends on the machine processing it. The machines can only process one task at a time and must be set up between two consecutive tasks. These setup times depend both on the tasks and on the chosen machine. The objective is to minimize the makespan, which is the time when all jobs have ended.
Principles learned
- Add interval decision variables to model the tasks
- Add list decision variables to model the assignment of tasks on the machines and their order
- Define lambda functions to link the interval and list variables together
Data
The instances we provide come from the Fattahi [1] dataset for the Flexible Job Shop Problem, with additional randomly generated setup times. Their format is as follows:
- First line: number of jobs, number of machines, average number of machines per operation (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: machine index and processing time on this machine
- For each machine and each operation:
- Setup time between this operation and every other operation on this machine.
Program
The Hexaly model for the Flexible Job Shop Scheduling Problem with Sequence-Dependent Setup Times (FJSP-SDST) uses interval decision variables to model the time ranges of the operations, and list decision variables to represent the order of the tasks scheduled on each machine.
Using the ‘partition‘ operator, we ensure that each task is assigned to exactly one machine. For each operation, we filter out incompatible machines thanks to the ‘contains’ operator. Using the ‘find‘ operator, we can then retrieve the index of the machine that was chosen to process each task. This allows us to deduce the processing time of each operation, which depends on the chosen machine, and to constrain the length of each interval accordingly.
The precedence constraints are easily written: for each job, each operation of this job must start after the end of the previous operation. The disjunctive resource constraints can be formulated as follows: for all i, the task processed in position i+1 must start after the end of the task processed in position i plus the setup time between these two tasks. To model this constraint, we define a lambda function expressing the relationship between two consecutive activities. This function is then used within a variadic ‘and’ operator over all tasks processed processed by each machine. Note that the number of terms inside these ‘and’ expressions varies during the search, along with the size of the lists (the number of tasks assigned to each machine).
The objective consists in minimizing the makespan, which is the time when all the tasks have ended.
- Execution
-
hexaly flexible_jobshop_setup.hxm inFileName=instances/Fattahi_setup_01.fjs [outFileName=] [hxTimeLimit=]
use io;
/* Read instance data */
function input() {
local usage = "Usage: hexaly flexible_jobshop_setup.hxm inFileName=instanceFile"
+ " [outFileName=outputFile] [hxTimeLimit=timeLimit]";
if (inFileName == nil) throw usage;
// Constant for incompatible 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] {
// Number of operations for each job
nbOperations[j] = inFile.readInt();
for [o in 0...nbOperations[j]] {
local nbMachinesOperation = inFile.readInt();
for [i in 0...nbMachinesOperation] {
local machine = inFile.readInt() - 1;
local time = inFile.readInt();
processingTime[j][o][machine] = time;
taskProcessingTime[nbTasks][machine] = time;
}
jobOperationTask[j][o] = nbTasks;
nbTasks += 1;
}
}
// Setup time between every two consecutive tasks, for each machine
taskSetupTime[m in 0...nbMachines][i in 0...nbTasks][j in 0...nbTasks] = inFile.readInt();
maxSetup = 0;
for [m in 0...nbMachines][i in 0...nbTasks][j in 0...nbTasks] {
if (taskSetupTime[m][i][j] != INFINITE && taskSetupTime[m][i][j] > maxSetup) {
maxSetup = taskSetupTime[m][i][j];
}
}
inFile.close();
// Trivial upper bound for the start times of the tasks
maxSumProcessingTimes = 0;
for [j in 0...nbJobs][o in 0...nbOperations[j]] {
local maxProcessingTime = 0;
for [m in 0...nbMachines] {
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];
}
}
maxSumProcessingTimes += maxProcessingTime;
}
maxStart = maxSumProcessingTimes + nbTasks * maxSetup;
}
/* Declare the optimization model */
function model() {
// Sequence of tasks on each machine
jobsOrder[m in 0...nbMachines] <- list(nbTasks);
// Each task is scheduled on a machine
constraint partition[m in 0...nbMachines](jobsOrder[m]);
// Only compatible machines can be selected for a task
for [i in 0...nbTasks][m in 0...nbMachines : taskProcessingTime[i][m] == INFINITE]
constraint !contains(jobsOrder[m], i);
// For each task, the selected machine
taskMachine[i in 0...nbTasks] <- find(jobsOrder, i);
// Interval decisions: time range of each task
tasks[i in 0...nbTasks] <- interval(0, maxStart);
// The task duration depends on the selected machine
duration[i in 0...nbTasks] <- taskProcessingTime[i][taskMachine[i]];
for [i in 0...nbTasks]
constraint length(tasks[i]) == duration[i];
// Precedence constraints between the operations of a job
for [j in 0...nbJobs][o in 0...nbOperations[j]-1] {
local i1 = jobOperationTask[j][o];
local i2 = jobOperationTask[j][o + 1];
constraint tasks[i1] < tasks[i2];
}
// Disjunctive resource constraints between the tasks on a machine
for [m in 0...nbMachines] {
local sequence <- jobsOrder[m];
constraint and(0...count(sequence)-1, i =>
start(tasks[sequence[i + 1]]) >= end(tasks[sequence[i]])
+ taskSetupTime[m][sequence[i]][sequence[i + 1]]);
}
// Minimize the makespan: end of the last task
makespan <- max[i in 0...nbTasks](end(tasks[i]));
minimize makespan;
}
/* Parameterize the solver */
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 60;
}
/* Write 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][o in 0...nbOperations[j]] {
local taskIndex = jobOperationTask[j][o];
outFile.println(j + 1, "\t", o + 1, "\t", taskMachine[taskIndex].value + 1,
"\t", tasks[taskIndex].value.start, "\t", tasks[taskIndex].value.end);
}
}
}
- Execution (Windows)
-
set PYTHONPATH=%HX_HOME%\bin\pythonpython flexible_jobshop_setup.py instances\Fattahi_setup_01.fjs
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_13_0/bin/pythonpython flexible_jobshop_setup.py instances/Fattahi_setup_01.fjs
import hexaly.optimizer
import sys
# Constant for incompatible 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 i 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)]
# Setup time between every two consecutive tasks, for each machine
task_setup_time = [[[-1 for r in range(nb_tasks)] for i in range(nb_tasks)] for m in range(nb_machines)]
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
id_line = nb_jobs + 2
max_setup = 0
for m in range(nb_machines):
for i1 in range(nb_tasks):
task_setup_time[m][i1] = list(map(int, lines[id_line].split()))
max_setup = max(max_setup, max(s if s != INFINITE else 0 for s in task_setup_time[m][i1]))
id_line += 1
# Trivial upper bound for the start times of the tasks
max_sum_processing_times = sum(
max(task_processing_time[i][m] for m in range(nb_machines) if task_processing_time[i][m] != INFINITE)
for i in range(nb_tasks))
max_start = max_sum_processing_times + nb_tasks * max_setup
return nb_jobs, nb_machines, nb_tasks, task_processing_time, job_operation_task, \
nb_operations, task_setup_time, max_start
def main(instance_file, output_file, time_limit):
nb_jobs, nb_machines, nb_tasks, task_processing_time_data, job_operation_task, \
nb_operations, task_setup_time_data, max_start = read_instance(instance_file)
with hexaly.optimizer.HexalyOptimizer() as optimizer:
#
# Declare the optimization model
#
model = optimizer.model
# Sequence of tasks on each machine
jobs_order = [model.list(nb_tasks) for _ in range(nb_machines)]
machines = model.array(jobs_order)
# Each task is scheduled on a machine
model.constraint(model.partition(machines))
# Only compatible machines can be selected for a task
for i in range(nb_tasks):
for m in range(nb_machines):
if task_processing_time_data[i][m] == INFINITE:
model.constraint(model.not_(model.contains(jobs_order[m], i)))
# For each task, the selected machine
task_machine = [model.find(machines, i) for i in range(nb_tasks)]
task_processing_time = model.array(task_processing_time_data)
task_setup_time = model.array(task_setup_time_data)
# Interval decisions: time range of each task
tasks = [model.interval(0, max_start) for _ in range(nb_tasks)]
# The task duration depends on the selected machine
duration = [model.at(task_processing_time, i, task_machine[i]) for i in range(nb_tasks)]
for i in range(nb_tasks):
model.constraint(model.length(tasks[i]) == duration[i])
task_array = model.array(tasks)
# Precedence constraints between the operations of a job
for j in range(nb_jobs):
for o in range(nb_operations[j] - 1):
i1 = job_operation_task[j][o]
i2 = job_operation_task[j][o + 1]
model.constraint(tasks[i1] < tasks[i2])
# Disjunctive resource constraints between the tasks on a machine
for m in range(nb_machines):
sequence = jobs_order[m]
sequence_lambda = model.lambda_function(
lambda i: model.start(task_array[sequence[i + 1]]) >= model.end(task_array[sequence[i]])
+ model.at(task_setup_time, m, sequence[i], sequence[i + 1]))
model.constraint(model.and_(model.range(0, model.count(sequence) - 1), sequence_lambda))
# Minimize the makespan: end of the last task
makespan = model.max([model.end(tasks[i]) for i in range(nb_tasks)])
model.minimize(makespan)
model.close()
# Parameterize the optimizer
optimizer.param.time_limit = time_limit
optimizer.solve()
# Write 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]):
taskIndex = job_operation_task[j][o]
f.write(str(j + 1) + "\t" + str(o + 1) + "\t" + str(task_machine[taskIndex].value + 1)
+ "\t" + str(tasks[taskIndex].value.start())
+ "\t" + str(tasks[taskIndex].value.end()) + "\n")
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python flexible_jobshop_setup.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_setup.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.libflexible_jobshop_setup instances\Fattahi_setup_01.fjs
- Compilation / Execution (Linux)
-
g++ flexible_jobshop_setup.cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o flexible_jobshop_setup./flexible_jobshop_setup instances/Fattahi_setup_01.fjs
#include "optimizer/hexalyoptimizer.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <limits>
#include <numeric>
#include <vector>
using namespace hexaly;
class FlexibleJobshopSetup {
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>> taskProcessingTimeData;
// Setup time between every two consecutive tasks, for each machine
std::vector<std::vector<std::vector<int>>> taskSetupTimeData;
// 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 incompatible machines
const int INFINITE = 1000000;
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Decision variables: time range of each task
std::vector<HxExpression> tasks;
// Decision variables: sequence of tasks on each machine
std::vector<HxExpression> jobsOrder;
// For each task, the selected machine
std::vector<HxExpression> taskMachine;
// Objective = minimize the makespan: end of the last task
HxExpression makespan;
public:
FlexibleJobshopSetup() : optimizer() {}
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;
taskProcessingTimeData.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;
taskProcessingTimeData[nbTasks][machine - 1] = time;
}
jobOperationTask[j][o] = nbTasks;
nbTasks += 1;
}
}
taskSetupTimeData = std::vector<std::vector<std::vector<int>>>(
nbMachines, std::vector<std::vector<int>>(nbTasks, std::vector<int>(nbTasks)));
int maxSetup = 0;
for (unsigned int m = 0; m < nbMachines; m++) {
for (unsigned int i = 0; i < nbTasks; i++) {
for (unsigned int j = 0; j < nbTasks; j++) {
infile >> taskSetupTimeData[m][i][j];
if (taskSetupTimeData[m][i][j] != INFINITE && taskSetupTimeData[m][i][j] > maxSetup)
maxSetup = taskSetupTimeData[m][i][j];
}
}
}
infile.close();
// Trivial upper bound for the start times of the tasks
int maxSumProcessingTimes = 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];
}
maxSumProcessingTimes += maxProcessingTime;
}
}
maxStart = maxSumProcessingTimes + nbTasks * maxSetup;
}
void solve(int timeLimit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Sequence of tasks on each machine
jobsOrder.resize(nbMachines);
HxExpression machines = model.array();
for (unsigned int m = 0; m < nbMachines; ++m) {
jobsOrder[m] = model.listVar(nbTasks);
machines.addOperand(jobsOrder[m]);
}
// Each task is scheduled on a machine
model.constraint(model.partition(machines));
// Only compatible machines can be selected for a task
for (int i = 0; i < nbTasks; ++i) {
for (unsigned int m = 0; m < nbMachines; ++m) {
if (taskProcessingTimeData[i][m] == INFINITE) {
model.constraint(!model.contains(jobsOrder[m], i));
}
}
}
taskMachine.resize(nbTasks);
HxExpression taskProcessingTime = model.array();
for (int i = 0; i < nbTasks; ++i) {
// For each task, the selected machine
taskMachine[i] = model.find(machines, i);
taskProcessingTime.addOperand(
model.array(taskProcessingTimeData[i].begin(), taskProcessingTimeData[i].end()));
}
HxExpression taskSetupTime = model.array();
for (int m = 0; m < nbMachines; ++m) {
HxExpression setupTimeMachine = model.array();
for (int i = 0; i < nbTasks; ++i) {
setupTimeMachine.addOperand(
model.array(taskSetupTimeData[m][i].begin(), taskSetupTimeData[m][i].end()));
}
taskSetupTime.addOperand(setupTimeMachine);
}
tasks.resize(nbTasks);
std::vector<HxExpression> duration(nbTasks);
for (int i = 0; i < nbTasks; ++i) {
// Interval decisions: time range of each task
tasks[i] = model.intervalVar(0, maxStart);
// The task duration depends on the selected machine
duration[i] = model.at(taskProcessingTime, i, taskMachine[i]);
model.constraint(model.length(tasks[i]) == duration[i]);
}
HxExpression taskArray = model.array(tasks.begin(), tasks.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 i1 = jobOperationTask[j][o];
int i2 = jobOperationTask[j][o + 1];
model.constraint(taskArray[i1] < taskArray[i2]);
}
}
// Disjunctive resource constraints between the tasks on a machine
for (int m = 0; m < nbMachines; ++m) {
HxExpression sequence = jobsOrder[m];
HxExpression sequenceLambda = model.createLambdaFunction([&](HxExpression i) {
return model.start(taskArray[sequence[i + 1]]) >=
model.end(taskArray[sequence[i]]) + model.at(taskSetupTime, m, sequence[i], sequence[i + 1]);
});
model.constraint(model.and_(model.range(0, model.count(sequence) - 1), sequenceLambda));
}
// Minimize the makespan: end of the last task
makespan = model.max();
for (int i = 0; i < nbTasks; ++i) {
makespan.addOperand(model.end(tasks[i]));
}
model.minimize(makespan);
model.close();
// Parameterize the optimizer
optimizer.getParam().setTimeLimit(timeLimit);
optimizer.solve();
}
/* Write 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 taskIndex = jobOperationTask[j][o];
outfile << j + 1 << "\t" << o + 1 << "\t" << taskMachine[taskIndex].getValue() + 1 << "\t"
<< tasks[taskIndex].getIntervalValue().getStart() << "\t"
<< tasks[taskIndex].getIntervalValue().getEnd() << std::endl;
}
}
outfile.close();
}
};
int main(int argc, char** argv) {
if (argc < 2) {
std::cout << "Usage: flexible_jobshop_setup 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";
FlexibleJobshopSetup model;
try {
model.readInstance(instanceFile);
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 %HX_HOME%\bin\Hexaly.NET.dll .csc FlexibleJobshopSetup.cs /reference:Hexaly.NET.dllFlexibleJobshopSetup instances\Fattahi_setup_01.fjs
using System;
using System.IO;
using System.Linq;
using Hexaly.Optimizer;
public class FlexibleJobshopSetup : 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[][] taskProcessingTimeData;
// Setup time between every two consecutive tasks, for each machine
private int[][][] taskSetupTimeData;
// 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 incompatible machines
private const long INFINITE = 1000000;
// Hexaly Optimizer
private HexalyOptimizer optimizer;
// Decision variables: time range of each task
private HxExpression[] tasks;
// Decision variables: sequence of tasks on each machine
private HxExpression[] jobsOrder;
// For each task, the selected machine
private HxExpression[] taskMachine;
// Objective = minimize the makespan: end of the last task
private HxExpression makespan;
public FlexibleJobshopSetup()
{
optimizer = new HexalyOptimizer();
}
public 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++;
}
}
input.ReadLine();
taskSetupTimeData = new int[nbMachines][][];
int maxSetup = 0;
for (int m = 0; m < nbMachines; ++m)
{
taskSetupTimeData[m] = new int[nbTasks][];
for (int i = 0; i < nbTasks; ++i)
{
taskSetupTimeData[m][i] = new int[nbTasks];
splitted = input.
ReadLine()
.Split(separators, StringSplitOptions.RemoveEmptyEntries);
for (int j = 0; j < nbTasks; ++j)
{
taskSetupTimeData[m][i][j] = int.Parse(splitted[j]);
if (
taskSetupTimeData[m][i][j] != INFINITE
&& taskSetupTimeData[m][i][j] > maxSetup
)
maxSetup = taskSetupTimeData[m][i][j];
}
}
}
// Trivial upper bound for the start times of the tasks
long maxSumProcessingTimes = 0;
taskProcessingTimeData = 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];
taskProcessingTimeData[task] = new long[nbMachines];
for (int m = 0; m < nbMachines; ++m)
{
taskProcessingTimeData[task][m] = processingTime[j][o][m];
if (
processingTime[j][o][m] != INFINITE
&& processingTime[j][o][m] > maxProcessingTime
)
{
maxProcessingTime = processingTime[j][o][m];
}
}
maxSumProcessingTimes += maxProcessingTime;
}
}
maxStart = maxSumProcessingTimes + nbTasks * maxSetup;
}
}
public void Dispose()
{
optimizer.Dispose();
}
public void Solve(int timeLimit)
{
// Declare the optimization model
HxModel model = optimizer.GetModel();
// Sequence of tasks on each machine
jobsOrder = new HxExpression[nbMachines];
HxExpression machines = model.Array();
for (int m = 0; m < nbMachines; ++m)
{
jobsOrder[m] = model.List(nbTasks);
machines.AddOperand(jobsOrder[m]);
}
// Each task is scheduled on a machine
model.Constraint(model.Partition(machines));
// Only compatible machines can be selected for a task
for (int i = 0; i < nbTasks; ++i)
{
for (int m = 0; m < nbMachines; ++m)
{
if (taskProcessingTimeData[i][m] == INFINITE)
model.Constraint(!model.Contains(jobsOrder[m], i));
}
}
// For each task, the selected machine
taskMachine = new HxExpression[nbTasks];
for (int i = 0; i < nbTasks; ++i)
{
taskMachine[i] = model.Find(machines, i);
}
tasks = new HxExpression[nbTasks];
HxExpression[] duration = new HxExpression[nbTasks];
HxExpression taskProcessingTime = model.Array(taskProcessingTimeData);
for (int i = 0; i < nbTasks; ++i)
{
// Interval decisions: time range of each task
tasks[i] = model.Interval(0, maxStart);
// The task duration depends on the selected machine
HxExpression iExpr = model.CreateConstant(i);
duration[i] = model.At(taskProcessingTime, iExpr, taskMachine[i]);
model.Constraint(model.Length(tasks[i]) == duration[i]);
}
HxExpression taskArray = model.Array(tasks);
// 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 i1 = jobOperationTask[j][o];
int i2 = jobOperationTask[j][o + 1];
model.Constraint(tasks[i1] < tasks[i2]);
}
}
HxExpression taskSetupTime = model.Array(taskSetupTimeData);
// Disjunctive resource constraints between the tasks on a machine
for (int m = 0; m < nbMachines; ++m)
{
HxExpression sequence = jobsOrder[m];
HxExpression mexpr = model.CreateConstant(m);
HxExpression sequenceLambda = model.LambdaFunction(
i =>
model.Start(taskArray[sequence[i + 1]])
>= (model.End(taskArray[sequence[i]])
+ model.At(taskSetupTime, mexpr, sequence[i], sequence[i + 1])));
model.Constraint(model.And(model.Range(0, model.Count(sequence) - 1), sequenceLambda));
}
// Minimize the makespan: end of the last task
makespan = model.Max();
for (int i = 0; i < nbTasks; ++i)
{
makespan.AddOperand(model.End(tasks[i]));
}
model.Minimize(makespan);
model.Close();
// Parameterize the optimizer
optimizer.GetParam().SetTimeLimit(timeLimit);
optimizer.Solve();
}
/* Write 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 taskIndex = jobOperationTask[j - 1][o - 1];
output.WriteLine(
j
+ "\t"
+ o
+ "\t"
+ taskMachine[taskIndex].GetValue()
+ "\t"
+ tasks[taskIndex].GetIntervalValue().Start()
+ "\t"
+ tasks[taskIndex].GetIntervalValue().End()
);
}
}
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: FlexibleJobshopSetup 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 (FlexibleJobshopSetup model = new FlexibleJobshopSetup())
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(outputFile);
}
}
}
- Compilation / Execution (Windows)
-
javac FlexibleJobshopSetup.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. FlexibleJobshopSetup instances\Fattahi_setup_01.fjs
- Compilation / Execution (Linux)
-
javac FlexibleJobshopSetup.java -cp /opt/hexaly_13_0/bin/hexaly.jarjava -cp /opt/hexaly_13_0/bin/hexaly.jar:. FlexibleJobshopSetup instances/Fattahi_setup_01.fjs
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;
public class FlexibleJobshopSetup {
// 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[][] taskProcessingTimeData;
// Setup time between every two consecutive tasks, for each machine
private int[][][] taskSetupTimeData;
// 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 incompatible machines
private final int INFINITE = 1000000;
// Hexaly Optimizer
final HexalyOptimizer optimizer;
// Decision variables: time range of each task
private HxExpression[] tasks;
// Decision variables: sequence of tasks on each machine
private HxExpression[] jobsOrder;
// For each task, the selected machine
private HxExpression[] taskMachine;
// Objective = minimize the makespan: end of the last task
private HxExpression makespan;
public FlexibleJobshopSetup(HexalyOptimizer optimizer) throws IOException {
this.optimizer = optimizer;
}
public 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++;
}
}
taskSetupTimeData = new int[nbMachines][nbTasks][nbTasks];
int maxSetup = 0;
for (int m = 0; m < nbMachines; ++m) {
for (int i = 0; i < nbTasks; ++i) {
for (int j = 0; j < nbTasks; ++j) {
taskSetupTimeData[m][i][j] = input.nextInt();
if (taskSetupTimeData[m][i][j] != INFINITE && taskSetupTimeData[m][i][j] > maxSetup)
maxSetup = taskSetupTimeData[m][i][j];
}
}
}
// Trivial upper bound for the start times of the tasks
long maxSumProcessingTimes = 0;
taskProcessingTimeData = 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];
taskProcessingTimeData[task] = new long[nbMachines];
for (int m = 0; m < nbMachines; ++m) {
taskProcessingTimeData[task][m] = processingTime[j][o][m];
if (processingTime[j][o][m] != INFINITE && processingTime[j][o][m] > maxProcessingTime) {
maxProcessingTime = processingTime[j][o][m];
}
}
maxSumProcessingTimes += maxProcessingTime;
}
}
maxStart = maxSumProcessingTimes + nbTasks * maxSetup;
}
}
public void solve(int timeLimit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Sequence of tasks on each machine
jobsOrder = new HxExpression[nbMachines];
HxExpression machines = model.array();
for (int m = 0; m < nbMachines; ++m) {
jobsOrder[m] = model.listVar(nbTasks);
machines.addOperand(jobsOrder[m]);
}
// Each task is scheduled on a machine
model.constraint(model.partition(machines));
// Only compatible machines can be selected for a task
for (int i = 0; i < nbTasks; ++i) {
for (int m = 0; m < nbMachines; ++m) {
if (taskProcessingTimeData[i][m] == INFINITE) {
model.constraint(model.not(model.contains(jobsOrder[m], i)));
}
}
}
// For each task, the selected machine
taskMachine = new HxExpression[nbTasks];
for (int i = 0; i < nbTasks; ++i) {
taskMachine[i] = model.find(machines, i);
}
HxExpression taskProcessingTime = model.array(taskProcessingTimeData);
tasks = new HxExpression[nbTasks];
HxExpression[] duration = new HxExpression[nbTasks];
for (int i = 0; i < nbTasks; ++i) {
// Interval decisions: time range of each task
tasks[i] = model.intervalVar(0, maxStart);
// The task duration depends on the selected machine
HxExpression iExpr = model.createConstant(i);
duration[i] = model.at(taskProcessingTime, iExpr, taskMachine[i]);
model.constraint(model.eq(model.length(tasks[i]), duration[i]));
}
HxExpression taskArray = model.array(tasks);
// 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 i1 = jobOperationTask[j][o];
int i2 = jobOperationTask[j][o + 1];
model.constraint(model.lt(tasks[i1], tasks[i2]));
}
}
HxExpression taskSetupTime = model.array(taskSetupTimeData);
// Disjunctive resource constraints between the tasks on a machine
for (int m = 0; m < nbMachines; ++m) {
HxExpression sequence = jobsOrder[m];
HxExpression mExpr = model.createConstant(m);
HxExpression sequenceLambda = model
.lambdaFunction(
i -> model.geq(model.start(model.at(taskArray, model.at(sequence, model.sum(i, 1)))),
model.sum(model.end(model.at(taskArray, model.at(sequence, i))),
model.at(taskSetupTime, mExpr,
model.at(sequence, i), model.at(sequence, model.sum(i, 1))))));
model.constraint(model.and(model.range(0, model.sub(model.count(sequence), 1)), sequenceLambda));
}
// Minimize the makespan: end of the last task
makespan = model.max();
for (int i = 0; i < nbTasks; ++i) {
makespan.addOperand(model.end(tasks[i]));
}
model.minimize(makespan);
model.close();
// Parameterize the optimizer
optimizer.getParam().setTimeLimit(timeLimit);
optimizer.solve();
}
/*
* Write 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 taskIndex = jobOperationTask[j - 1][o - 1];
output.write(j + "\t" + o + "\t" + taskMachine[taskIndex].getValue() + "\t"
+ tasks[taskIndex].getIntervalValue().getStart()
+ "\t" + tasks[taskIndex].getIntervalValue().getEnd() + "\n");
}
}
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("Usage: java FlexibleJobshopSetup 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 (HexalyOptimizer optimizer = new HexalyOptimizer()) {
FlexibleJobshopSetup model = new FlexibleJobshopSetup(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);
}
}
}
[1] P. Fattahi, M.S. Mehrabad, F. Jolai. (2007). Mathematical modeling and heuristic approaches to flexible job shop scheduling problems. Journal of Intelligent Manufacturing, 18(3), 331–342.