Stochastic Job Shop Scheduling Problem¶
Principles learned¶
Add multiple list decision variables
Constrain the number of elements in a list
Use multidimensional arrays
Use interval decision variables
Order interval decision variables by pairing them up with a list variable
Problem¶
A set of jobs has to be processed on every machine of the shop. Each job consists in an ordered sequence of tasks (called activities), each representing the processing of the job on one of the machines. Each job has one activity per machine, and cannot start an activity while the previous activity of the job is not completed. Each activity has a given processing time (which depends on the scenario) and each machine can only process one activity at a time.
We also consider multiple scenarios, all of which represent the same set of jobs (and subsequently the same set of activites). What changes between each scenario is the time taken to process each activity. For a given sequence of jobs and a given scenario, the makespan (for that scenario) is the time when all jobs have been processed. It should be noted that the same sequence of jobs will have different resulting makespans for each scenario.
The goal is therefore to find a sequence of jobs that minimizes the maximum makespan over all the scenarios -ie the sequence of jobs that minimizes the time after which all jobs have been processed in every scenario.
Download the exampleData¶
The format of the data files is as follows:
First line: number of jobs, number of machines, number of scenarios.
From the fourth line, for each scenario:
For each job: the processing time on each machine (given in processing order).
For each job: the processing order (ordered list of visited machines, same for all scenarios).
Program¶
The model is very similar to the original Job Shop Problem, to which we add different scenarios. The original decision variables remain almost unchanged, with the exception that we now take into account the different scenarios by adding an extra dimension to the array of task intervals, as well as to the array representing their respective processing time. Regardless, we keep interval decision variables to model the time ranges of the activities, and a list decision variable for each machine (representing the order of the activities scheduled on this machine). The chosen order in which each machine will process the jobs is the same between all scenarios.
The precedence and disjunctive resource constraints are modeled in the same way as for the original job shop problem and are now established for each scenario. The makespan to be minimized is the time when all jobs have been processed in each scenario.
- Execution:
- localsolver stochastic_jobshop.lsp inFileName=instances/ft20_10.txt [outFileName=] [lsTimeLimit=]
use io;
/* Read instance data. */
function input() {
local usage = "Usage: localsolver stochastic_jobshop.lsp inFileName=instanceFile "
+ "[outFileName=outputFile] [lsTimeLimit=timeLimit]";
if (inFileName == nil) throw usage;
inFile = io.openRead(inFileName);
inFile.readln();
nbJobs = inFile.readInt();
nbMachines = inFile.readInt();
nbScenarios = inFile.readInt();
inFile.readln();
// Processing times for each job on each machine (given in the processing order)
for [s in 0...nbScenarios][j in 0...nbJobs][m in 0...nbMachines] {
processingTimesInProcessingOrderPerScenario[s][j][m] = inFile.readInt();
}
inFile.readln();
for [j in 0...nbJobs][k in 0...nbMachines] {
local m = inFile.readInt()-1;
// Processing order of machines for each job
machineOrder[j][k] = m;
for [s in 0...nbScenarios] {
// Reorder processing times: processingTime[s][j][m] is the processing time of the
// task of job j that is processed on machine m in the scenario s
processingTimePerScenario[s][j][m] = processingTimesInProcessingOrderPerScenario[s][j][k];
}
}
inFile.close();
// Trivial upper bounds for the start times of the tasks
maxStart = max[s in 0...nbScenarios](sum[j in 0...nbJobs][m in 0...nbMachines](processingTimePerScenario[s][j][m]));
}
/* Declare the optimization model */
function model() {
// Interval decisions: time range of each task
// tasks[s][j][m] is the interval of time of the task of job j which is processed
// on machine m in the scenario s
tasks[s in 0...nbScenarios][j in 0...nbJobs][m in 0...nbMachines] <- interval(0, maxStart);
// Task duration constraints
for [s in 0...nbScenarios][j in 0...nbJobs][m in 0...nbMachines]
constraint length(tasks[s][j][m]) == processingTimePerScenario[s][j][m];
// Precedence constraints between the tasks of a job
for [s in 0...nbScenarios][j in 0...nbJobs][k in 0...nbMachines-1]
constraint tasks[s][j][machineOrder[j][k]] < tasks[s][j][machineOrder[j][k + 1]];
// Sequence of tasks on each machine
jobsOrder[m in 0...nbMachines] <- list(nbJobs);
for [m in 0...nbMachines] {
// Each job has a task scheduled on each machine
constraint count(jobsOrder[m]) == nbJobs;
// Disjunctive resource constraints between the tasks on a machine
for [s in 0...nbScenarios] {
constraint and(0...nbJobs-1,
i => tasks[s][jobsOrder[m][i]][m] < tasks[s][jobsOrder[m][i + 1]][m]);
}
}
// Minimize the maximum makespan: end of the last task of the last job
// over all scenarios
makespans[s in 0...nbScenarios] <- max[j in 0...nbJobs](end(tasks[s][j][machineOrder[j][nbMachines - 1]]));
maxMakespan <- max[s in 0...nbScenarios](makespans[s]);
minimize maxMakespan;
}
/* Parameterize the solver */
function param() {
if (lsTimeLimit == nil) lsTimeLimit = 60;
}
/* Write the solution in a file with the following format:
* - for each machine, the job sequence */
function output() {
if (outFileName != nil) {
outFile = io.openWrite(outFileName);
println("Solution written in file ", outFileName);
for [m in 0...nbMachines]
outFile.println[j in 0...nbJobs](jobsOrder[m].value[j], " ");
}
}
- Execution (Windows)
- set PYTHONPATH=%LS_HOME%\bin\pythonpython stochastic_jobshop.py instances\ft20_10.txt
- Execution (Linux)
- export PYTHONPATH=/opt/localsolver_12_0/bin/pythonpython stochastic_jobshop.py instances/ft20_10.txt
import localsolver
import sys
def read_instance(filename):
with open(filename) as f:
lines = f.readlines()
first_line = lines[1].split()
# Number of jobs
nb_jobs = int(first_line[0])
# Number of machines
nb_machines = int(first_line[1])
# Number of scenarios
nb_scenarios = int(first_line[2])
# Processing times for each job on each machine (given in the processing order)
processing_times_in_processing_order_per_scenario = [[[int(lines[s*(nb_jobs+1)+i].split()[j])
for j in range(nb_machines)]
for i in range(3, 3 + nb_jobs)]
for s in range(nb_scenarios)]
# Processing order of machines for each job
machine_order = [[int(lines[i].split()[j]) - 1 for j in range(nb_machines)]
for i in range(4 + nb_scenarios*(nb_jobs+1), 4 + nb_scenarios*(nb_jobs+1) + nb_jobs)]
# Reorder processing times: processing_time[s][j][m] is the processing time of the
# task of job j that is processed on machine m in the scenario s
processing_time_per_scenario = [[[processing_times_in_processing_order_per_scenario[s][j][machine_order[j].index(m)]
for m in range(nb_machines)]
for j in range(nb_jobs)]
for s in range(nb_scenarios)]
# Trivial upper bound for the start times of the tasks
max_start = max([sum(sum(processing_time_per_scenario[s][j])
for j in range(nb_jobs)) for s in range(nb_scenarios)])
return nb_jobs, nb_machines, nb_scenarios, processing_time_per_scenario, machine_order, max_start
def main(instance_file, output_file, time_limit):
nb_jobs, nb_machines, nb_scenarios, processing_time_per_scenario, machine_order, max_start = read_instance(
instance_file)
with localsolver.LocalSolver() as ls:
#
# Declare the optimization model
#
model = ls.model
# Interval decisions: time range of each task
# tasks[s][j][m] is the interval of time of the task of job j which is processed
# on machine m in the scenario s
tasks = [[[model.interval(0, max_start) for m in range(nb_machines)]
for j in range(nb_jobs)]
for s in range(nb_scenarios)]
# Task duration constraints
for s in range(nb_scenarios):
for j in range(nb_jobs):
for m in range(0, nb_machines):
model.constraint(model.length(tasks[s][j][m]) == processing_time_per_scenario[s][j][m])
# Create a LocalSolver array in order to be able to access it with "at" operators
task_array = model.array(tasks)
# Precedence constraints between the tasks of a job
for s in range(nb_scenarios):
for j in range(nb_jobs):
for k in range(nb_machines - 1):
model.constraint(
tasks[s][j][machine_order[j][k]] < tasks[s][j][machine_order[j][k + 1]])
# Sequence of tasks on each machine
jobs_order = [model.list(nb_jobs) for m in range(nb_machines)]
for m in range(nb_machines):
# Each job has a task scheduled on each machine
sequence = jobs_order[m]
model.constraint(model.eq(model.count(sequence), nb_jobs))
# Disjunctive resource constraints between the tasks on a machine
for s in range(nb_scenarios):
sequence_lambda = model.lambda_function(
lambda i: model.lt(model.at(task_array, s, sequence[i], m),
model.at(task_array, s, sequence[i + 1], m)))
model.constraint(model.and_(model.range(0, nb_jobs - 1), sequence_lambda))
# Minimize the maximum makespan: end of the last task of the last job
# over all scenarios
makespans = [model.max([model.end(tasks[s][j][machine_order[j][nb_machines - 1]]) for j in range(nb_jobs)])
for s in range(nb_scenarios)]
max_makespan = model.max(makespans)
model.minimize(max_makespan)
model.close()
# Parameterize the solver
ls.param.time_limit = time_limit
ls.solve()
#
# Write the solution in a file with the following format:
# - for each machine, the job sequence
#
if output_file != None:
final_jobs_order = [list(jobs_order[m].value) for m in range(nb_machines)]
with open(output_file, "w") as f:
print("Solution written in file ", output_file)
for m in range(nb_machines):
for j in range(nb_jobs):
f.write(str(final_jobs_order[m][j]) + " ")
f.write("\n")
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python stochastic_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 stochastic_jobshop.cpp -I%LS_HOME%\include /link %LS_HOME%\bin\localsolver120.libstochastic_jobshop instances\ft20_10.txt
- Compilation / Execution (Linux)
- g++ stochastic_jobshop.cpp -I/opt/localsolver_12_0/include -llocalsolver120 -lpthread -o stochastic_jobshop./stochastic_jobshop instances/ft20_10.txt
#include "localsolver.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <limits>
#include <numeric>
#include <vector>
using namespace localsolver;
using namespace std;
class StochasticJobshop {
private:
// Number of jobs
int nbJobs;
// Number of machines
int nbMachines;
// Number of scenarios
int nbScenarios;
// Processing order of machines for each job
vector<vector<int>> machineOrder;
// Processing time on each machine for each job (given in the machine order and for a given scenario)
vector<vector<vector<int>>> processingTimePerScenario;
// Trivial upper bound for the start times of the tasks
int maxStart;
// LocalSolver
LocalSolver localsolver;
// Decision variables: time range of each task
vector<vector<vector<LSExpression>>> tasks;
// Decision variables: sequence of tasks on each machine
vector<LSExpression> jobsOrder;
// Objective = minimize the maximum of all makespans
LSExpression maxMakespan;
public:
StochasticJobshop() : localsolver() {}
void readInstance(const string& fileName) {
ifstream infile;
infile.exceptions(ifstream::failbit | ifstream::badbit);
infile.open(fileName.c_str());
infile.ignore(numeric_limits<streamsize>::max(), '\n');
infile >> nbJobs;
infile >> nbMachines;
infile >> nbScenarios;
infile.ignore(numeric_limits<streamsize>::max(), '\n');
// Processing times for each job on each machine (given in the processing order)
infile.ignore(numeric_limits<streamsize>::max(), '\n');
vector<vector<vector<int>>> processingTimeInProcessingOrderPerScenario =
vector<vector<vector<int>>>(nbScenarios, vector<vector<int>>(nbJobs, vector<int>(nbMachines)));
for (int s = 0; s < nbScenarios; ++s) {
for (int j = 0; j < nbJobs; ++j) {
for (int m = 0; m < nbMachines; ++m) {
infile >> processingTimeInProcessingOrderPerScenario[s][j][m];
}
}
infile.ignore(numeric_limits<streamsize>::max(), '\n');
}
// Processing order of machines for each job
infile.ignore(numeric_limits<streamsize>::max(), '\n');
infile.ignore(numeric_limits<streamsize>::max(), '\n');
machineOrder.resize(nbJobs);
for (int j = 0; j < nbJobs; ++j) {
machineOrder[j].resize(nbMachines);
for (int m = 0; m < nbMachines; ++m) {
int x;
infile >> x;
machineOrder[j][m] = x - 1;
}
}
// Reorder processing times: processingTimePerScenario[s][j][m] is the processing time of the
// task of job j that is processed on machine m in scenario s
for (int s = 0; s < nbScenarios; ++s) {
processingTimePerScenario.resize(nbScenarios);
for (int j = 0; j < nbJobs; ++j) {
processingTimePerScenario[s].resize(nbJobs);
for (int m = 0; m < nbMachines; ++m) {
processingTimePerScenario[s][j].resize(nbMachines);
vector<int>::iterator findM = find(machineOrder[j].begin(), machineOrder[j].end(), m);
unsigned int k = distance(machineOrder[j].begin(), findM);
processingTimePerScenario[s][j][m] = processingTimeInProcessingOrderPerScenario[s][j][k];
}
}
}
// Trivial upper bound for the start times of the tasks
vector<int> maxStartPerScenario = vector<int>(nbScenarios);
for (int s = 0; s < nbScenarios; ++s) {
for (int j = 0; j < nbJobs; ++j) {
maxStartPerScenario[s] +=
accumulate(processingTimePerScenario[s][j].begin(), processingTimePerScenario[s][j].end(), 0);
}
}
maxStart = *max_element(maxStartPerScenario.begin(), maxStartPerScenario.end());
infile.close();
}
void solve(int timeLimit) {
// Declare the optimization model
LSModel model = localsolver.getModel();
// Interval decisions: time range of each task
// tasks[s][j][m] is the interval of time of the task of job j which is
// processed on machine m in scenario s
tasks.resize(nbScenarios);
for (unsigned int s = 0; s < nbScenarios; ++s) {
tasks[s].resize(nbJobs);
for (unsigned int j = 0; j < nbJobs; ++j) {
tasks[s][j].resize(nbMachines);
for (unsigned int m = 0; m < nbMachines; ++m) {
tasks[s][j][m] = model.intervalVar(0, maxStart);
// Task duration constraints
model.constraint(model.length(tasks[s][j][m]) == processingTimePerScenario[s][j][m]);
}
}
}
// Create a LocalSolver array in order to be able to access it with "at" operators
vector<LSExpression> taskArray = vector<LSExpression>(nbScenarios);
for (int s = 0; s < nbScenarios; ++s) {
taskArray[s] = model.array();
for (int j = 0; j < nbJobs; ++j) {
taskArray[s].addOperand(model.array(tasks[s][j].begin(), tasks[s][j].end()));
}
}
// Precedence constraints between the tasks of a job
for (int s = 0; s < nbScenarios; ++s) {
for (int j = 0; j < nbJobs; ++j) {
for (int k = 0; k < nbMachines - 1; ++k) {
model.constraint(tasks[s][j][machineOrder[j][k]] < tasks[s][j][machineOrder[j][k + 1]]);
}
}
}
// Sequence of tasks on each machine
jobsOrder.resize(nbMachines);
for (int m = 0; m < nbMachines; ++m) {
jobsOrder[m] = model.listVar(nbJobs);
}
for (int m = 0; m < nbMachines; ++m) {
// Each job has a task scheduled on each machine
LSExpression sequence = jobsOrder[m];
model.constraint(model.eq(model.count(sequence), nbJobs));
// Disjunctive resource constraints between the tasks on a machine
for (int s = 0; s < nbScenarios; ++s) {
LSExpression sequenceLambda = model.createLambdaFunction([&](LSExpression i) {
return model.at(taskArray[s], sequence[i], m) < model.at(taskArray[s], sequence[i + 1], m);
});
model.constraint(model.and_(model.range(0, nbJobs - 1), sequenceLambda));
}
}
// Minimize the maximum makespan: end of the last task of the last job
// over all scenarios
vector<LSExpression> makespans = vector<LSExpression>(nbScenarios);
for (int s = 0; s < nbScenarios; ++s) {
makespans[s] = model.max();
for (int j = 0; j < nbJobs; ++j) {
makespans[s].addOperand(model.end(tasks[s][j][machineOrder[j][nbMachines - 1]]));
}
}
maxMakespan = model.max();
for (int s = 0; s < nbScenarios; ++s)
maxMakespan.addOperand(makespans[s]);
model.minimize(maxMakespan);
model.close();
// Parameterize the solver
localsolver.getParam().setTimeLimit(timeLimit);
localsolver.solve();
}
/* Write the solution in a file with the following format:
* - for each machine, the job sequence */
void writeSolution(const string& fileName) {
ofstream outfile;
outfile.exceptions(ofstream::failbit | ofstream::badbit);
outfile.open(fileName.c_str());
cout << "Solution written in file " << fileName << endl;
for (int m = 0; m < nbMachines; ++m) {
LSCollection finalJobsOrder = jobsOrder[m].getCollectionValue();
for (int j = 0; j < nbJobs; ++j) {
outfile << finalJobsOrder.get(j) << " ";
}
outfile << endl;
}
outfile.close();
}
};
int main(int argc, char** argv) {
if (argc < 2) {
cout << "Usage: stochastic_jobshop instanceFile [outputFile] [timeLimit]" << endl;
exit(1);
}
const char* instanceFile = argv[1];
const char* outputFile = argc > 2 ? argv[2] : NULL;
const char* strTimeLimit = argc > 3 ? argv[3] : "60";
StochasticJobshop model;
try {
model.readInstance(instanceFile);
const int timeLimit = atoi(strTimeLimit);
model.solve(timeLimit);
if (outputFile != NULL)
model.writeSolution(outputFile);
return 0;
} catch (const exception& e) {
cerr << "An error occurred: " << e.what() << endl;
return 1;
}
}
- Compilation / Execution (Windows)
- copy %LS_HOME%\bin\localsolvernet.dll .csc StochasticJobshop.cs /reference:localsolvernet.dllStochasticJobshop instances\ft20_10.txt
using System;
using System.Linq;
using System.IO;
using localsolver;
public class StochasticJobshop : IDisposable
{
// Number of jobs
private int nbJobs;
// Number of machines
private int nbMachines;
// Number of machines
private int nbScenarios;
// Processing order of machines for each job
private int[,] machineOrder;
// Processing time on each machine for each job (given in the machine order)
private long[,,] processingTimePerScenario;
// Trivial upper bound for the start times of the tasks
private long maxStart;
// LocalSolver
private LocalSolver localsolver;
// Decision variables: time range of each task
private LSExpression[,,] tasks;
// Decision variables: sequence of tasks on each machine
private LSExpression[] jobsOrder;
// Objective = minimize the maximum of all makespans
private LSExpression maxMakespan;
public StochasticJobshop()
{
localsolver = new LocalSolver();
}
public void ReadInstance(string fileName)
{
using (StreamReader input = new StreamReader(fileName))
{
input.ReadLine();
string[] splitted = input.ReadLine().Split(' ');
nbJobs = int.Parse(splitted[0]);
nbMachines = int.Parse(splitted[1]);
nbScenarios = int.Parse(splitted[2]);
// Processing times for each job on each machine (given in the processing order)
input.ReadLine();
long[,,] processingTimeInProcessingOrderPerScenario = new long[
nbScenarios,
nbJobs,
nbMachines
];
for (int s = 0; s < nbScenarios; ++s)
{
for (int j = 0; j < nbJobs; ++j)
{
splitted = input.ReadLine().Trim().Split(' ');
for (int m = 0; m < nbMachines; ++m)
processingTimeInProcessingOrderPerScenario[s, j, m] = long.Parse(
splitted[m]
);
}
input.ReadLine();
}
// Processing order of machines for each job
input.ReadLine();
machineOrder = new int[nbJobs, nbMachines];
for (int j = 0; j < nbJobs; ++j)
{
splitted = input.ReadLine().Trim().Split(' ');
for (int m = 0; m < nbMachines; ++m)
machineOrder[j, m] = int.Parse(splitted[m]) - 1;
}
// Reorder processing times: processingTimePerScenario[s, j, m] is the processing time of the
// task of job j that is processed on machine m in scenario s
processingTimePerScenario = new long[nbScenarios, nbJobs, nbMachines];
// Trivial upper bound for the start times of the tasks
long[] maxStartPerScenario = new long[nbScenarios];
for (int s = 0; s < nbScenarios; ++s)
{
for (int j = 0; j < nbJobs; ++j)
{
for (int m = 0; m < nbMachines; ++m)
{
int machineIndex = nbMachines;
for (int k = 0; k < nbMachines; ++k)
{
if (machineOrder[j, k] == m)
{
machineIndex = k;
break;
}
}
processingTimePerScenario[s, j, m] =
processingTimeInProcessingOrderPerScenario[s, j, machineIndex];
maxStartPerScenario[s] += processingTimePerScenario[s, j, m];
}
}
}
maxStart = maxStartPerScenario.Max();
}
}
public void Dispose()
{
localsolver.Dispose();
}
public void Solve(int timeLimit)
{
// Declare the optimization model
LSModel model = localsolver.GetModel();
// Interval decisions: time range of each task
// tasks[s][j][m] is the interval of time of the task of job j which is processed on machine m
// in scenario s
tasks = new LSExpression[nbScenarios, nbJobs, nbMachines];
for (int s = 0; s < nbScenarios; ++s)
{
for (int j = 0; j < nbJobs; ++j)
{
for (int m = 0; m < nbMachines; ++m)
{
tasks[s, j, m] = model.Interval(0, maxStart);
// Task duration constraints
model.Constraint(
model.Length(tasks[s, j, m]) == processingTimePerScenario[s, j, m]
);
}
}
}
// Create a LocalSolver array in order to be able to access it with "at" operators
LSExpression taskArray = model.Array(tasks);
// Precedence constraints between the tasks of a job
for (int s = 0; s < nbScenarios; ++s)
{
for (int j = 0; j < nbJobs; ++j)
{
for (int k = 0; k < nbMachines - 1; ++k)
{
model.Constraint(
tasks[s, j, machineOrder[j, k]] < tasks[s, j, machineOrder[j, k + 1]]
);
}
}
}
// Sequence of tasks on each machine
jobsOrder = new LSExpression[nbMachines];
for (int m = 0; m < nbMachines; ++m)
jobsOrder[m] = model.List(nbJobs);
for (int m = 0; m < nbMachines; ++m)
{
// Each job has a task scheduled on each machine
LSExpression sequence = jobsOrder[m];
model.Constraint(model.Count(sequence) == nbJobs);
// Disjunctive resource constraints between the tasks on a machine
for (int s = 0; s < nbScenarios; ++s)
{
LSExpression sequenceLambda = model.LambdaFunction(
i => taskArray[s][sequence[i], m] < taskArray[s][sequence[i + 1], m]
);
model.Constraint(model.And(model.Range(0, nbJobs - 1), sequenceLambda));
}
}
// Minimize the maximum makespan: end of the last task of the last job
// over all scenarios
LSExpression[] makespans = new LSExpression[nbScenarios];
for (int s = 0; s < nbScenarios; ++s)
{
makespans[s] = model.Max();
for (int j = 0; j < nbJobs; ++j)
{
makespans[s].AddOperand(model.End(tasks[s, j, machineOrder[j, nbMachines - 1]]));
}
}
maxMakespan = model.Max();
for (int s = 0; s < nbScenarios; ++s)
maxMakespan.AddOperand(makespans[s]);
model.Minimize(maxMakespan);
model.Close();
// Parameterize the solver
localsolver.GetParam().SetTimeLimit(timeLimit);
localsolver.Solve();
}
/* Write the solution in a file with the following format:
* - for each machine, the job sequence */
public void WriteSolution(string fileName)
{
using (StreamWriter output = new StreamWriter(fileName))
{
Console.WriteLine("Solution written in file " + fileName);
for (int m = 0; m < nbMachines; ++m)
{
LSCollection finalJobsOrder = jobsOrder[m].GetCollectionValue();
for (int i = 0; i < nbJobs; ++i)
{
int j = (int)finalJobsOrder.Get(i);
output.Write(j + " ");
}
output.WriteLine();
}
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: StochasticJobshop 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 (StochasticJobshop model = new StochasticJobshop())
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(outputFile);
}
}
}
- Compilation / Execution (Windows)
- javac StochasticJobshop.java -cp %LS_HOME%\bin\localsolver.jarjava -cp %LS_HOME%\bin\localsolver.jar;. StochasticJobshop instances\ft20_10.txt
- Compilation / Execution (Linux)
- javac StochasticJobshop.java -cp /opt/localsolver_12_0/bin/localsolver.jarjava -cp /opt/localsolver_12_0/bin/localsolver.jar:. StochasticJobshop instances/ft20_10.txt
import java.util.*;
import java.io.*;
import localsolver.*;
public class StochasticJobshop {
// Number of jobs
private int nbJobs;
// Number of machines
private int nbMachines;
// Number of scenarios
private int nbScenarios;
// Processing time on each machine for each job (given in the machine order)
private long[][][] processingTimePerScenario;
// Processing order of machines for each job
private int[][] machineOrder;
// Trivial upper bound for the start times of the tasks
private long maxStart;
// LocalSolver
final LocalSolver localsolver;
// Decision variables: time range of each task
private LSExpression[][][] tasks;
// Decision variables: sequence of tasks on each machine
private LSExpression[] jobsOrder;
// Objective = minimize the maximum of all makespans
private LSExpression maxMakespan;
public StochasticJobshop(LocalSolver localsolver) throws IOException {
this.localsolver = localsolver;
}
public void readInstance(String fileName) throws IOException {
try (Scanner input = new Scanner(new File(fileName))) {
input.nextLine();
nbJobs = input.nextInt();
nbMachines = input.nextInt();
nbScenarios = input.nextInt();
input.nextLine();
input.nextLine();
// Processing times for each job on each machine (given in the processing order)
long[][][] processingTimesInProcessingOrderPerScenario = new long[nbScenarios][nbJobs][nbMachines];
for (int s = 0; s < nbScenarios; ++s) {
for (int j = 0; j < nbJobs; ++j) {
for (int m = 0; m < nbMachines; ++m) {
processingTimesInProcessingOrderPerScenario[s][j][m] = input.nextInt();
}
}
input.nextLine();
}
// Processing order of machines for each job
input.nextLine();
input.nextLine();
machineOrder = new int[nbJobs][nbMachines];
for (int j = 0; j < nbJobs; ++j) {
for (int m = 0; m < nbMachines; ++m) {
machineOrder[j][m] = input.nextInt() - 1;
}
}
// Reorder processing times: processingTimePerScenario[s][j][m] is the
// processing time of the task of job j that is processed on machine m for a
// given scenario s
processingTimePerScenario = new long[nbScenarios][nbJobs][nbMachines];
// Trivial upper bound for the start times of the tasks
long[] maxStartPerScenario = new long[nbScenarios];
for (int s = 0; s < nbScenarios; ++s) {
maxStartPerScenario[s] = 0;
for (int j = 0; j < nbJobs; ++j) {
for (int m = 0; m < nbMachines; ++m) {
int machineIndex = nbMachines;
for (int k = 0; k < nbMachines; ++k) {
if (machineOrder[j][k] == m) {
machineIndex = k;
break;
}
}
processingTimePerScenario[s][j][m] = processingTimesInProcessingOrderPerScenario[s][j][machineIndex];
maxStartPerScenario[s] += processingTimePerScenario[s][j][m];
}
}
}
maxStart = Arrays.stream(maxStartPerScenario).max().getAsLong();
}
}
public void solve(int timeLimit) {
// Declare the optimization model
LSModel model = localsolver.getModel();
// Interval decisions: time range of each task tasks[s][j][m] is the interval of
// time of the task of job j which is processed on machine m in the scenario s
tasks = new LSExpression[nbScenarios][nbJobs][nbMachines];
for (int j = 0; j < nbJobs; ++j) {
for (int m = 0; m < nbMachines; ++m) {
for (int s = 0; s < nbScenarios; ++s) {
tasks[s][j][m] = model.intervalVar(0, maxStart);
// Task duration constraints
model.constraint(model.eq(model.length(tasks[s][j][m]), processingTimePerScenario[s][j][m]));
}
}
}
// Create a LocalSolver array in order to be able to access it with "at"
// operators
LSExpression taskArray = model.array(tasks);
// Precedence constraints between the tasks of a job
for (int s = 0; s < nbScenarios; ++s) {
for (int j = 0; j < nbJobs; ++j) {
for (int k = 0; k < nbMachines - 1; ++k) {
model.constraint(model.lt(tasks[s][j][machineOrder[j][k]], tasks[s][j][machineOrder[j][k + 1]]));
}
}
}
// Sequence of tasks on each machine
jobsOrder = new LSExpression[nbMachines];
for (int m = 0; m < nbMachines; ++m) {
jobsOrder[m] = model.listVar(nbJobs);
}
for (int m = 0; m < nbMachines; ++m) {
// Each job has a task scheduled on each machine
LSExpression sequence = jobsOrder[m];
model.constraint(model.eq(model.count(sequence), nbJobs));
// Disjunctive resource constraints between the tasks on a machine
for (int s = 0; s < nbScenarios; ++s) {
LSExpression mExpr = model.createConstant(m);
LSExpression sExpr = model.createConstant(s);
LSExpression sequenceLambda = model
.lambdaFunction(i -> model.lt(model.at(taskArray, sExpr, model.at(sequence, i), mExpr),
model.at(taskArray, sExpr, model.at(sequence, model.sum(i, 1)), mExpr)));
model.constraint(model.and(model.range(0, nbJobs - 1), sequenceLambda));
}
}
// Minimize the maximum makespan: end of the last task of the last job
// over all scenarios
LSExpression[] makespans = new LSExpression[nbScenarios];
for (int s = 0; s < nbScenarios; ++s) {
makespans[s] = model.max();
for (int j = 0; j < nbJobs; ++j) {
makespans[s].addOperand(model.end(tasks[s][j][machineOrder[j][nbMachines - 1]]));
}
}
maxMakespan = model.max();
for (int s = 0; s < nbScenarios; ++s) {
maxMakespan.addOperand(makespans[s]);
}
model.minimize(maxMakespan);
model.close();
// Parameterize the solver
localsolver.getParam().setTimeLimit(timeLimit);
localsolver.solve();
}
/*
* Write the solution in a file with the following format:
* - for each machine, the job sequence
*/
public void writeSolution(String fileName) throws IOException {
try (PrintWriter output = new PrintWriter(fileName)) {
System.out.println("Solution written in file " + fileName);
for (int m = 0; m < nbMachines; ++m) {
LSCollection finalJobsOrder = jobsOrder[m].getCollectionValue();
for (int i = 0; i < nbJobs; ++i) {
int j = Math.toIntExact(finalJobsOrder.get(i));
output.write(j + " ");
}
output.write("\n");
}
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("Usage: java StochasticJobshop 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()) {
StochasticJobshop model = new StochasticJobshop(localsolver);
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);
}
}
}