Batch Scheduling Problem¶
Principles learned¶
Use the distinct operator
Enforce precedence of some tasks
Use interval decision variables to schedule batches of tasks
Assign tasks to batches using sets and partitioning
Problem¶
In the batch scheduling problem, a set of tasks must be processed in batches on a set of available resources. The set of tasks must be sorted into batches. Each batch must consist of tasks with the same type and the same task duration. Each batch of tasks must be processed on one resource. For this reason, all tasks in a batch must have the same resource type. Each resource can only process one batch of tasks at a time. The number of tasks in a batch cannot exceed the maximum capacity of the resource it is assigned to. Additionally, some tasks have to be processed before others.
The goal is to find the intervals of time each task should be processed on a resource that minimizes the makespan: the time when all jobs have been processed. This is accomplished by assigned tasks to batches and scheduling batches across the resources.
Download the exampleData¶
The format of the data files is as follows:
First line: number of tasks, number of resources.
Second line: for each resource, the maximum capacity
For each task: task resource assignment, task type, task duration, number of tasks that must succeed this task, task numbers that must succeed.
Program¶
We use set decision variables to determine the tasks assigned to each
batch. For each resource, the largest number of possible batches is equal to the
number of tasks to be processed on that resource. Therefore, we have a resource
specific collection of sets (of this maximum size) to model the batches. Each set
determines the content of the batch (i.e., which tasks are assigned to each
batch). To make sure each task is assigned to exactly one batch, we use the
partition
operator on each resource’s collection of sets.
The tasks in a batch must have the same type, so we use
the distinct
operator coupled with the count
operator. The distinct
operator returns an unordered set of distinct values among the input collection.
We use this within the count
operator to constrain the number of task
types to be the same.
We cannot exceed the maximum capacity of a resource. We again use the
count
operator to constrain the size of a batch associated with a resource
not to exceed the capacity of the resource.
In addition to the set decisions representing the tasks assigned to each batch, we use interval decision variables to model the time ranges of each batch of tasks on a resource. The length of the interval is constrained to match the length of the tasks within the batch (recall that each task in a batch must have the same length).
The disjunctive resource constraint ensures each resource can only process one batch at a time. Given a sequence of batches, the interval corresponding to batch n must start after the interval corresponding to batch (n-1).
We use the find
operator to map a task interval to the resource and batch
interval that contains the task. We use this mapping to enforce precedence
constraints on the tasks that must be processed before other tasks.
The makespan to minimize is the time when all activities have been processed.
- Execution:
- hexaly batch_scheduling.hxm inFileName=instances/5_2-000.nfb [hxTimeLimit=] [solFileName=]
use io;
/* Read instance data. */
function input() {
local usage = "Usage: hexaly batch_scheduling.hxm "
+ "inFileName=inputFile [solFileName=outputFile] [hxTimeLimit=timeLimit]";
if (inFileName == nil) throw usage;
local inFile = io.openRead(inFileName);
// Number of tasks
nbTasks = inFile.readInt();
// Number of resources
nbResources = inFile.readInt();
// Maximum capacity of each resource
capacity[r in 0...nbResources] = inFile.readInt();
// Number of tasks assigned to each resource
nbTasksPerResource[r in 0...nbResources] = 0;
// Types of tasks assigned to each resource
typesInResource[r in 0...nbResources] = {};
// Tasks assigned to each resource
tasksInResource[r in 0...nbResources] = {};
for [i in 0...nbTasks] {
// Type of task i
type[i] = inFile.readInt();
// Resource required for task i
resource[i] = inFile.readInt();
// Index of task i on resource[i]
taskIndexInResource[i] = nbTasksPerResource[resource[i]];
// Map from index of task i on resource[i] to task i type
typesInResource[resource[i]][taskIndexInResource[i]] = type[i];
// Map from index of task i on resource[i] to task i id
tasksInResource[resource[i]][taskIndexInResource[i]] = i;
// Increment number of tasks required by this resource
nbTasksPerResource[resource[i]] = nbTasksPerResource[resource[i]] + 1;
// Duration of task i
duration[i] = inFile.readInt();
// Number of tasks that must succeed task i
nbSuccessors[i] = inFile.readInt();
// Task id that must succeed task i
successors[i][s in 0...nbSuccessors[i]] = inFile.readInt();
}
// Longest possible time horizon
timeHorizon = sum[t in 0...nbTasks](duration[t]);
inFile.close();
}
/* Declare the optimization model */
function model() {
// For each resource, the contents of each batch of tasks performed
batchContent[r in 0...nbResources][b in 0...nbTasksPerResource[r]] <- set(nbTasksPerResource[r]);
// All tasks are assigned to a batch
for [r in 0...nbResources] {
constraint partition(batchContent[r]);
}
// Each batch must consist of tasks with the same type
for [r in 0...nbResources][batch in batchContent[r]] {
constraint count(distinct(batch, i => typesInResource[r][i])) <= 1;
}
// Each batch cannot exceed the maximum capacity of the resource
for [r in 0...nbResources][batch in batchContent[r]] {
constraint count(batch) <= capacity[r];
}
// Interval decisions: time range of each batch of tasks
batchInterval[r in 0...nbResources][n in 0...nbTasksPerResource[r]] <- interval(0, timeHorizon);
// Non-overlap of batch intervals on the same resource
for [r in 0...nbResources][b in 1...nbTasksPerResource[r]] {
constraint batchInterval[r][b-1] < batchInterval[r][b];
}
// Interval decisions: time range of each task
for [t in 0...nbTasks] {
// Retrieve the batch index and resource for this task
local r = resource[t];
local b <- find(batchContent[r], taskIndexInResource[t]);
// Task interval interval associated with task t
taskInterval[t] <- batchInterval[r][b];
}
// Task durations
for [t in 0...nbTasks] {
constraint length(taskInterval[t]) == duration[t];
}
// Precedence constraints between tasks
for [t in 0...nbTasks][s in successors[t]] {
constraint taskInterval[t] < taskInterval[s];
}
// Makespan: end of the last task
makespan <- max[t in 0...nbTasks](end(taskInterval[t]));
minimize makespan;
}
/* Parameterize the solver */
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 15;
}
/* Write the solution in a file with the following format:
* - makespan
- machine number
- preceeding lines are the ordered intervals of the tasks
for the corresponding machine number */
function output() {
if (outFileName != nil) {
outFile = io.openWrite(outFileName);
outFile.println(makespan.value);
for [r in 0...nbResources] {
outFile.println(r);
for [b in 0...nbTasksPerResource[r]] {
local t = tasksInResource[r][b];
outFile.println(t + " " + taskInterval[t].value.start + " " + taskInterval[t].value.end);
}
}
outFile.close();
}
}
- Execution (Windows)
- set PYTHONPATH=%HX_HOME%\bin\pythonpython batch_scheduling.py instances\5_2-000.nfb
- Execution (Linux)
- export PYTHONPATH=/opt/hexaly_13_0/bin/pythonpython batch_scheduling.py instances/5_2-000.nfb
import hexaly.optimizer
import sys
def read_instance(filename):
# The import files follow the "Taillard" format
with open(filename, 'r') as f:
lines = f.readlines()
first_line = lines[0].split()
# Number of tasks
nb_tasks = int(first_line[0])
# Number of resources
nb_resources = int(first_line[1])
second_line = lines[1].split()
# Capacity of each resource
capacity = [int(l) for l in second_line]
# Remaining lines contain task information at each row
nb_tasks_per_resource = [0 for _ in range(nb_resources)]
types_in_resource = [[] for _ in range(nb_resources)]
tasks_in_resource = [[] for _ in range(nb_resources)]
task_index_in_resource = []
types, resources, duration, nb_successors = [], [], [], []
successors = [[] for _ in range(nb_tasks)]
for i in range(nb_tasks):
# Extract dataset line related to this task
task_line = i + 2
task_information = lines[task_line].split()
# Type of task i
types.append(int(task_information[0]))
# Resource required for task i
resources.append(int(task_information[1]))
# Index of task i on resource[i]
task_index_in_resource.append(nb_tasks_per_resource[resources[i]])
# Map from name of task i on resource[i] to task i type
types_in_resource[resources[i]].append(types[i])
# Map from name of task i on resource[i] to task i
tasks_in_resource[resources[i]].append(i)
# Increment number of tasks required by this resource
nb_tasks_per_resource[resources[i]] += 1
# Task duration
duration.append(int(task_information[2]))
# Number of successors of this task
nb_successors.append(int(task_information[3]))
# Tasks that must succeed current task
for succeeding_task in task_information[4:]:
successors[i].append(int(succeeding_task))
# Trivial time horizon
time_horizon = sum(duration[t] for t in range(nb_tasks))
return nb_tasks, nb_resources, capacity, types, resources, duration, \
nb_successors, successors, nb_tasks_per_resource, \
task_index_in_resource, types_in_resource, tasks_in_resource, \
nb_tasks_per_resource, time_horizon
def main(instance_file, output_file, time_limit):
nb_tasks, nb_resources, capacity, types, resources, duration, \
nb_successors, successors, nb_tasks_per_resource, \
task_index_in_resource, types_in_resource, tasks_in_resource, \
nb_tasks_per_resource, time_horizon \
= read_instance(instance_file)
with hexaly.optimizer.HexalyOptimizer() as optimizer:
#
# Declare the optimization model
#
model = optimizer.model
# For each resource, the contents of each batch of tasks performed
batch_content = [[model.set(nb_tasks_per_resource[r])
for b in range(nb_tasks_per_resource[r])]
for r in range(nb_resources)]
# Create HexalyOptimizer arrays in order to be able to access them with "at" operators
batch_content_arrays = [model.array(batch_content[r]) for r in range(nb_resources)]
# All tasks are assigned to a batch
for r in range(nb_resources):
model.constraint(model.partition(batch_content_arrays[r]))
# Each batch must consist of tasks with the same type
types_in_resource_array = model.array(types_in_resource)
for r in range(nb_resources):
resource_type_lambda = model.lambda_function(lambda i: types_in_resource_array[r][i])
for batch in batch_content[r]:
model.constraint(model.count( model.distinct( batch, resource_type_lambda ) ) <= 1)
# Each batch cannot exceed the maximum capacity of the resource
for r in range(nb_resources):
for batch in batch_content[r]:
model.constraint(model.count(batch) <= capacity[r])
# Interval decisions: time range of each batch of tasks
batch_interval = [[model.interval(0, time_horizon)
for _ in range(nb_tasks_per_resource[r])]
for r in range(nb_resources)]
batch_interval_arrays = [model.array(batch_interval[r]) for r in range(nb_resources)]
# Non-overlap of batch intervals on the same resource
for r in range(nb_resources):
for b in range(1, nb_tasks_per_resource[r]):
model.constraint(batch_interval[r][b-1] < batch_interval[r][b])
# Interval decisions: time range of each task
task_interval = [None for _ in range(nb_tasks)]
for t in range(nb_tasks):
# Retrieve the batch index and resource for this task
r = resources[t]
b = model.find( batch_content_arrays[r], task_index_in_resource[t] )
# Task interval interval associated with task t
task_interval[t] = batch_interval_arrays[r][b]
# Task durations
for t in range(nb_tasks):
model.constraint(model.length(task_interval[t]) == duration[t])
# Precedence constraints between tasks
for t in range(nb_tasks):
for s in successors[t]:
model.constraint( task_interval[t] < task_interval[s])
# Makespan: end of the last task
makespan = model.max([model.end(model.at(batch_interval_arrays[r], i))
for i in range(nb_tasks_per_resource[r])
for r in range(nb_resources)])
model.minimize(makespan)
model.close()
# Parametrize the optimizer
optimizer.param.time_limit = time_limit
optimizer.solve()
#
# Write the solution in a file with the following format:
# - makespan
# - machine number
# - preceeding lines are the ordered intervals of the tasks
# for the corresponding machine number
if output_file is not None:
with open(output_file, 'w') as f:
f.write(str(makespan.value) + "\n")
for r in range(nb_resources):
f.write(str(r) + "\n")
for b in range(nb_tasks_per_resource[r]):
t = tasks_in_resource[r][b]
line = str(t) + " " + str(task_interval[t].value.start()) + " " + str(task_interval[t].value.end())
f.write(line + "\n")
print("Solution written in file ", output_file)
if __name__=="__main__":
if len(sys.argv) < 2:
print(
"Usage: python batch_scheduling.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 batch_scheduling.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.libbatch_scheduling instances\tai2020_5.txt
- Compilation / Execution (Linux)
- g++ batch_scheduling.cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o batch_scheduling./batch_scheduling instances/tai2020_5.txt
#include "optimizer/hexalyoptimizer.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <limits>
#include <numeric>
#include <vector>
using namespace hexaly;
using namespace std;
class BatchScheduling {
private:
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Decision variables: collection of sets (representing batches)
vector<vector<HxExpression>> batchContent;
// Decision variables: intervals of batches on a resource
vector<vector<HxExpression>> batchInterval;
// Decision variables: interval of a task
vector<HxExpression> taskInterval;
// Objective = minimize the makespan: end of the last task of the last job
HxExpression makespan;
public:
BatchScheduling() {}
// Number of tasks
int nbTasks;
// Number of resources
int nbResources;
// Capacity of each resource
vector<int> capacity;
// Number of tasks assigned to each resource
vector<int> nbTasksPerResource;
// Types of tasks assigned to each resource
vector<vector<int>> typesInResources;
// Tasks assigned to each resource
vector<vector<int>> tasksInResource;
// Index of task i on resource[i]
vector<int> taskIndexInResource;
// Type of task i
vector<int> type;
// Resource required for task i
vector<int> resource;
// Duration of task i
vector<int> duration;
// Number of tasks that must succeed task i
vector<int> nbSuccessors;
// Task ids that must succeed task i
vector<vector<int>> successors;
// Longest possible time horizon
int timeHorizon{0};
// Read input file
void readInstance(const string& fileName) {
ifstream infile;
infile.exceptions(ifstream::failbit | ifstream::badbit);
infile.open(fileName.c_str());
// first line has number of tasks, number of resources
infile >> nbTasks;
infile >> nbResources;
// second line has the capacity of each resource
capacity.resize(nbResources);
for (int i = 0; i < nbResources; i++) {
infile >> capacity[i];
}
// initalize
nbTasksPerResource.resize(nbResources);
for (int j = 0; j < nbResources; j++) {
typesInResources.push_back(vector<int>());
tasksInResource.push_back(vector<int>());
nbTasksPerResource[j] = 0;
}
type.resize(nbTasks);
resource.resize(nbTasks);
duration.resize(nbTasks);
nbSuccessors.resize(nbTasks);
taskIndexInResource.resize(nbTasks);
// Task information: [type, machine, duration, nbSuccessors, [successors]]
for (int i = 0; i < nbTasks; i++) {
infile >> type[i];
infile >> resource[i];
infile >> duration[i];
infile >> nbSuccessors[i];
// collect which tasks that must succeed task i
successors.push_back(vector<int>());
for (int j = 0; j < nbSuccessors[i]; j++) {
successors[i].push_back(j);
infile >> successors[i][j];
}
// Index of task i on resource[i]
taskIndexInResource[i] = nbTasksPerResource[resource[i]];
// Map from name of task i on resource[i] to task i type
typesInResources[resource[i]].push_back(type[i]);
// Map from name of task i on resource[i] to task i
tasksInResource[resource[i]].push_back(i);
// Incremenet number of tasks required by this resource
nbTasksPerResource[resource[i]] += 1;
// Add task time to the overall trivial time horizon
timeHorizon += duration[i];
}
infile.close();
}
void solve(int timeLimit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// For each resource, the contents of each batch of tasks performed
batchContent.resize(nbResources);
for (unsigned int r = 0; r < nbResources; r++) {
batchContent[r].resize(nbTasksPerResource[r]);
for (unsigned int b = 0; b < nbTasksPerResource[r]; b++) {
batchContent[r][b] = model.setVar(nbTasksPerResource[r]);
}
}
// Create HexalyOptimizer arrays in order to be able to access them with "at" operators
vector<HxExpression> batchContentArray = vector<HxExpression>(nbResources);
for (unsigned int r = 0; r < nbResources; r++) {
batchContentArray[r] = model.array();
for (unsigned int b = 0; b < nbTasksPerResource[r]; b++) {
batchContentArray[r].addOperand(batchContent[r][b]);
}
}
// All tasks are assigned to a batch
for (unsigned int r = 0; r < nbResources; r++) {
model.constraint(model.partition(batchContentArray[r]));
}
// Create HexalyOptimizer arrays in order to be able to access them with "at" operators
vector<HxExpression> typesInResourcesArray = vector<HxExpression>(nbResources);
for (unsigned int r = 0; r < nbResources; r++) {
typesInResourcesArray[r] = model.array();
for (unsigned int i = 0; i < nbTasksPerResource[r]; i++) {
typesInResourcesArray[r].addOperand(typesInResources[r][i]);
}
}
// Each batch must consist of tasks with the same type
for (unsigned int r = 0; r < nbResources; r++) {
HxExpression resourceTypeLambda = model.createLambdaFunction([&](HxExpression i) {
return typesInResourcesArray[r][i];
});
for (unsigned int b = 0; b < nbTasksPerResource[r]; b++) {
HxExpression batch = batchContent[r][b];
model.constraint(model.count(model.distinct(batch, resourceTypeLambda)) <= 1);
}
}
// Each batch cannot exceed the maximum capacity of the resource
for (unsigned int r = 0; r < nbResources; r++) {
for (unsigned int b = 0; b < nbTasksPerResource[r]; b++) {
model.constraint(model.count(batchContent[r][b]) <= capacity[r]);
}
}
// Interval decisions: time range of each batch of tasks
batchInterval.resize(nbResources);
for (unsigned int r = 0; r < nbResources; r++) {
batchInterval[r].resize(nbTasksPerResource[r]);
for (unsigned int b = 0; b < nbTasksPerResource[r]; b++) {
batchInterval[r][b] = model.intervalVar(0, timeHorizon);
}
}
// Create HexalyOptimizer arrays in order to be able to access them with "at" operators
vector<HxExpression> batchIntervalArray = vector<HxExpression>(nbResources);
for (unsigned int r = 0; r < nbResources; r++) {
batchIntervalArray[r] = model.array();
for (unsigned int b = 0; b < nbTasksPerResource[r]; b++) {
batchIntervalArray[r].addOperand(batchInterval[r][b]);
}
}
// Non-overlap of batch intervals on the same resource
for (unsigned int r = 0; r < nbResources; r++) {
for (unsigned int b = 1; b < nbTasksPerResource[r]; b++) {
model.constraint(batchInterval[r][b-1] < batchInterval[r][b]);
}
}
// Interval decisions: time range of each task
taskInterval.resize(nbTasks);
for (unsigned int t = 0; t < nbTasks; t++) {
// Retrieve the batch index and resource for this task
int r = resource[t];
HxExpression b = model.find(batchContentArray[r], taskIndexInResource[t]);
// Task interval associated with task t
taskInterval[t] = batchIntervalArray[r][b];
}
// Task durations
for (unsigned int t = 0; t < nbTasks; t++) {
model.constraint(model.length(taskInterval[t]) == duration[t]);
}
// Precedence constraints between tasks
for (unsigned int t = 0; t < nbTasks; t++) {
for (unsigned int s = 0; s < nbSuccessors[t]; s++) {
model.constraint(taskInterval[t] < taskInterval[successors[t][s]]);
}
}
// Makespan: end of the last task
makespan = model.max();
for (unsigned int t = 0; t < nbTasks; t++) {
makespan.addOperand(model.end(taskInterval[t]));
}
model.minimize(makespan);
model.close();
// Parameterize the optimizer
optimizer.getParam().setTimeLimit(timeLimit);
optimizer.solve();
}
/*
Write the solution in a file with the following format:
- makespan
- machine number
- preceeding lines are the ordered intervals of the tasks
for the corresponding machine number
*/
void writeSolution(const string& fileName) {
ofstream outfile;
outfile.exceptions(ofstream::failbit | ofstream::badbit);
outfile.open(fileName.c_str());
outfile << makespan.getValue() << endl;
for (unsigned int r = 0; r < nbResources; r++) {
outfile << r << endl;
for (unsigned int b = 0; b < nbTasksPerResource[r]; b++) {
int t = tasksInResource[r][b];
int start = taskInterval[t].getIntervalValue().getStart();
int end = taskInterval[t].getIntervalValue().getEnd();
outfile << t << " " << start << " " << end << endl;
}
}
cout << "Solution written in file " << fileName << endl;
outfile.close();
}
};
int main(int argc, char** argv) {
if (argc < 2) {
cout << "Usage: batch_scheduling 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";
BatchScheduling 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 %HX_HOME%\bin\Hexaly.NET.dll .csc BatchScheduling.cs /reference:Hexaly.NET.dllBatchScheduling instances\tai2020_5.txt
using System;
using System.IO;
using Hexaly.Optimizer;
public class BatchScheduling : IDisposable
{
// Number of tasks
private int nbTasks;
// Number of resources
private int nbResources;
// Capacity of each resource
private int[] capacity;
// Number of tasks assigned to each resource
private int[] nbTasksPerResource;
// Types of tasks assigned to each resource
private int[][] typesInResources;
// Tasks assigned to each resource
private int[][] tasksInResource;
// Index of task i on resource[i]
private int[] taskIndexInResource;
// Type of task i
private int[] type;
// Resource required for task i
private int[] resource;
// Duration of task i
private int[] duration;
// Number of tasks that must succeed task i
private int[] nbSuccessors;
// Task names that must succeed task i
private int[][] successors;
// Longest possible time horizon
private int timeHorizon;
// Hexaly Optimizer
private HexalyOptimizer optimizer;
// Decision variables: collection of sets (representing batches)
private HxExpression[][] batchContent;
// Decision variables: intervals of batches on a resource
private HxExpression[][] batchInterval;
// Decision variables: interval of a task
private HxExpression[] taskInterval;
// Objective = minimize the makespan: end of the last task of the last job
private HxExpression makespan;
public BatchScheduling(string fileName)
{
optimizer = new HexalyOptimizer();
}
public static string[] ReadNextLine(StreamReader input) {
return input.ReadLine().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
}
public void ReadInstance(string fileName)
{
using (StreamReader input = new StreamReader(fileName))
{
string[] splitted = ReadNextLine(input);
// first line has number of tasks, number of resources
nbTasks = int.Parse(splitted[0]);
nbResources = int.Parse(splitted[1]);
// second line has the capacity of each resource
capacity = new int[nbResources];
splitted = ReadNextLine(input);
for (int r = 0; r < nbResources; ++r) {
capacity[r] = int.Parse(splitted[r]);
}
// initialize
type = new int[nbTasks];
resource = new int[nbTasks];
duration = new int[nbTasks];
nbSuccessors = new int[nbTasks];
successors = new int[nbTasks][];
taskIndexInResource = new int[nbTasks];
typesInResources = new int[nbResources][];
tasksInResource = new int[nbResources][];
nbTasksPerResource = new int[nbResources];
for (int j = 0; j < nbResources; ++j)
nbTasksPerResource[j] = 0;
// Task information: [type, machine, duration, nbSuccessors, [successors]]
for (int i = 0; i < nbTasks; ++i)
{
splitted = ReadNextLine(input);
type[i] = int.Parse(splitted[0]);
resource[i] = int.Parse(splitted[1]);
duration[i] = int.Parse(splitted[2]);
nbSuccessors[i] = int.Parse(splitted[3]);
// collect which tasks that must succeed task i
successors[i] = new int[nbSuccessors[i]];
for (int j = 0; j < nbSuccessors[i]; j++) {
successors[i][j] = int.Parse(splitted[4+j]);
}
// Index of task i on resource[i]
taskIndexInResource[i] = nbTasksPerResource[resource[i]];
// Incremenet number of tasks required by this resource
nbTasksPerResource[resource[i]] += 1;
// Add task time to the overall trivial time horizon
timeHorizon += duration[i];
}
// Mapping task list to resource task lists
int[] temp_nbTasksPerResource = new int[nbResources];
for (int r = 0; r < nbResources; ++r)
{
typesInResources[r] = new int[nbTasksPerResource[r]];
tasksInResource[r] = new int[nbTasksPerResource[r]];
temp_nbTasksPerResource[r] = 0;
}
for (int i = 0; i < nbTasks; ++i) {
int r = resource[i];
int index = temp_nbTasksPerResource[r];
// Map from name of task i on resource[i] to task i type
typesInResources[r][index] = type[i];
// Map from name of task i on resource[i] to task i
tasksInResource[r][index] = i;
// increment
temp_nbTasksPerResource[r] += 1;
}
}
}
public void Dispose()
{
if (optimizer != null)
optimizer.Dispose();
}
public void Solve(int timeLimit)
{
// Declare the optimization model
HxModel model = optimizer.GetModel();
// For each resource, the contents of each batch of tasks performed
batchContent = new HxExpression[nbResources][];
for (int r = 0; r < nbResources; ++r)
{
batchContent[r] = new HxExpression[nbTasksPerResource[r]];
for (int b = 0; b < nbTasksPerResource[r]; ++b)
batchContent[r][b] = model.Set(nbTasksPerResource[r]);
}
// Create HexalyOptimizer arrays in order to be able to access them with "at" operators
HxExpression[] batchContentArray = new HxExpression[nbResources];
for (int r = 0; r < nbResources; ++r)
batchContentArray[r] = model.Array(batchContent[r]);
// All tasks are assigned to a batch
for (int r = 0; r < nbResources; ++r)
model.Constraint( model.Partition(batchContentArray[r]) );
// Create HexalyOptimizer arrays in order to be able to access them with "at" operators
HxExpression[] typesInResourcesArray = new HxExpression[nbResources];
for (int r = 0; r < nbResources; ++r)
typesInResourcesArray[r] = model.Array(typesInResources[r]);
// Each batch must consist of tasks with the same type
for (int r = 0; r < nbResources; ++r) {
HxExpression resourceTypeLambda = model.LambdaFunction( i =>
{
return typesInResourcesArray[r][i];
});
for (int b = 0; b < nbTasksPerResource[r]; ++b)
model.Constraint(model.Count(
model.Distinct(batchContent[r][b], resourceTypeLambda)) <= 1);
}
// Each batch cannot exceed the maximum capacity of the resource
for (int r = 0; r < nbResources; ++r)
{
for (int b = 0; b < nbTasksPerResource[r]; ++b)
model.Constraint(model.Count(batchContent[r][b]) <= capacity[r]);
}
// Interval decisions: time range of each batch of tasks
batchInterval = new HxExpression[nbResources][];
for (int r = 0; r < nbResources; ++r)
{
batchInterval[r] = new HxExpression[nbTasksPerResource[r]];
for (int b = 0; b < nbTasksPerResource[r]; ++b)
batchInterval[r][b] = model.Interval(0, timeHorizon);
}
// Create HexalyOptimizer arrays in order to be able to access them with "at" operators
HxExpression[] batchIntervalArray = new HxExpression[nbResources];
for (int r = 0; r < nbResources; ++r)
batchIntervalArray[r] = model.Array(batchInterval[r]);
// Non-overlap of batch intervals on the same resource
for (int r = 0; r < nbResources; ++r)
{
for (int b = 1; b < nbTasksPerResource[r]; ++b)
model.Constraint(batchInterval[r][b-1] < batchInterval[r][b]);
}
// Interval decisions: time range of each task
taskInterval = new HxExpression[nbTasks];
for (int t = 0; t < nbTasks; ++t) {
// Retrieve the batch index and resource for this task
int r = resource[t];
HxExpression b = model.Find(batchContentArray[r], taskIndexInResource[t]);
// Task interval associated with task t
taskInterval[t] = batchIntervalArray[r][b];
}
// Task durations
for (int t = 0; t < nbTasks; ++t)
model.Constraint(model.Length(taskInterval[t]) == duration[t]);
// Precedence constraints between tasks
for (int t = 0; t < nbTasks; ++t)
{
for (int s = 0; s < nbSuccessors[t]; ++s)
model.Constraint(taskInterval[t] < taskInterval[successors[t][s]]);
}
// Makespan: end of the last task
makespan = model.Max();
for (int t = 0; t < nbTasks; ++t)
makespan.AddOperand(model.End(taskInterval[t]));
model.Minimize(makespan);
model.Close();
// Parameterize the optimizer
optimizer.GetParam().SetTimeLimit(timeLimit);
optimizer.Solve();
}
/* Write the solution in a file with the following format:
* - makespan
* - machine number
* - preceeding lines are the ordered intervals of the tasks
for the corresponding machine number */
public void WriteSolution(string fileName)
{
using (StreamWriter output = new StreamWriter(fileName))
{
Console.WriteLine("Solution written in file " + fileName);
output.WriteLine(makespan.GetValue());
for (int r = 0; r < nbResources; ++r)
{
output.Write(r);
output.WriteLine();
for (int b = 0; b < nbTasksPerResource[r]; ++b)
{
int t = tasksInResource[r][b];
long start = taskInterval[t].GetIntervalValue().Start();
long end = taskInterval[t].GetIntervalValue().End();
output.Write(t + " " + start + " " + end);
output.WriteLine();
}
}
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: batching_scheduling.cs 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 (BatchScheduling model = new BatchScheduling(instanceFile))
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(outputFile);
}
}
}
- Compilation / Execution (Windows)
- javac BatchScheduling.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. BatchScheduling instances\tai2020_5.txt
- Compilation / Execution (Linux)
- javac BatchScheduling.java -cp /opt/hexaly_13_0/bin/hexaly.jarjava -cp /opt/hexaly_13_0/bin/hexaly.jar:. BatchScheduling instances/tai2020_5.txt
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;
public class BatchScheduling {
// Number of tasks
private int nbTasks;
// Number of resources
private int nbResources;
// Capacity of each resource
private int[] capacity;
// Number of tasks assigned to each resource
private int[] nbTasksPerResource;
// Types of tasks assigned to each resource
private int[][] typesInResources;
// Tasks assigned to each resource
private int[][] tasksInResource;
// Index of task i on resource[i]
private int[] taskIndexInResource;
// Type of task i
private int[] type;
// Resource required for task i
private int[] resource;
// Duration of task i
private int[] duration;
// Number of tasks that must succeed task i
private int[] nbSuccessors;
// Task names that must succeed task i
private int[][] successors;
// Longest possible time horizon
private int timeHorizon;
// Hexaly Optimizer
final HexalyOptimizer optimizer;
// Decision variables: collection of sets (representing batches)
private HxExpression[][] batchContent;
// Decision variables: intervals of batches on a resource
private HxExpression[][] batchInterval;
// Decision variables: interval of a task
private HxExpression[] taskInterval;
// Objective = minimize the makespan: end of the last task of the last job
private HxExpression makespan;
public BatchScheduling(HexalyOptimizer optimizer, String fileName) throws IOException
{
this.optimizer = new HexalyOptimizer();
}
private void readInstance(String fileName) throws IOException {
try (Scanner input = new Scanner(new File(fileName))) {
// first line has number of tasks, number of resources
nbTasks = input.nextInt();
nbResources = input.nextInt();
// second line has the capacity of each resource
capacity = new int[nbResources];
for (int r = 0; r < nbResources; ++r) {
capacity[r] = input.nextInt();
}
// initialize
type = new int[nbTasks];
resource = new int[nbTasks];
duration = new int[nbTasks];
nbSuccessors = new int[nbTasks];
successors = new int[nbTasks][];
taskIndexInResource = new int[nbTasks];
typesInResources = new int[nbResources][];
tasksInResource = new int[nbResources][];
nbTasksPerResource = new int[nbResources];
for (int j = 0; j < nbResources; ++j)
nbTasksPerResource[j] = 0;
// Task information: [type, machine, duration, nbSuccessors, [successors]]
for (int i = 0; i < nbTasks; i++) {
type[i] = input.nextInt();
resource[i] = input.nextInt();
duration[i] = input.nextInt();
nbSuccessors[i] = input.nextInt();
// collect which tasks that must succeed task i
successors[i] = new int[nbSuccessors[i]];
for (int j = 0; j < nbSuccessors[i]; j++) {
successors[i][j] = input.nextInt();
}
// Index of task i on resource[i]
taskIndexInResource[i] = nbTasksPerResource[resource[i]];
// Incremenet number of tasks required by this resource
nbTasksPerResource[resource[i]] += 1;
// Add task time to the overall trivial time horizon
timeHorizon += duration[i];
}
// Mapping task list to resource task lists
int[] temp_nbTasksPerResource = new int[nbResources];
for (int r = 0; r < nbResources; ++r)
{
typesInResources[r] = new int[nbTasksPerResource[r]];
tasksInResource[r] = new int[nbTasksPerResource[r]];
temp_nbTasksPerResource[r] = 0;
}
for (int i = 0; i < nbTasks; ++i) {
int r = resource[i];
int index = temp_nbTasksPerResource[r];
// Map from name of task i on resource[i] to task i type
typesInResources[r][index] = type[i];
// Map from name of task i on resource[i] to task i
tasksInResource[r][index] = i;
// increment
temp_nbTasksPerResource[r] += 1;
}
}
}
public void solve(int timeLimit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// For each resource, the contents of each batch of tasks performed
batchContent = new HxExpression[nbResources][];
for (int r = 0; r < nbResources; ++r)
{
batchContent[r] = new HxExpression[nbTasksPerResource[r]];
for (int b = 0; b < nbTasksPerResource[r]; ++b)
batchContent[r][b] = model.setVar(nbTasksPerResource[r]);
}
// Create HexalyOptimizer arrays in order to be able to access them with "at" operators
HxExpression[] batchContentArray = new HxExpression[nbResources];
for (int r = 0; r < nbResources; ++r)
batchContentArray[r] = model.array(batchContent[r]);
// All tasks are assigned to a batch
for (int r = 0; r < nbResources; ++r)
model.constraint(model.partition(batchContentArray[r]));
// Create HexalyOptimizer arrays in order to be able to access them with "at" operators
HxExpression[] typesInResourcesArray = new HxExpression[nbResources];
for (int r = 0; r < nbResources; ++r)
typesInResourcesArray[r] = model.array(typesInResources[r]);
// Each batch must consist of tasks with the same type
for (int r = 0; r < nbResources; ++r) {
final int rL = r;
HxExpression resourceTypeLambda = model.lambdaFunction( i -> {
return model.at(typesInResourcesArray[rL], i);
});
for (int b = 0; b < nbTasksPerResource[r]; ++b)
model.constraint(model.leq(model.count(model.distinct(batchContent[r][b], resourceTypeLambda)), 1));
}
// Each batch cannot exceed the maximum capacity of the resource
for (int r = 0; r < nbResources; ++r)
{
for (int b = 0; b < nbTasksPerResource[r]; ++b)
model.constraint(model.leq(model.count(batchContent[r][b]), capacity[r]));
}
// Interval decisions: time range of each batch of tasks
batchInterval = new HxExpression[nbResources][];
for (int r = 0; r < nbResources; ++r)
{
batchInterval[r] = new HxExpression[nbTasksPerResource[r]];
for (int b = 0; b < nbTasksPerResource[r]; ++b)
batchInterval[r][b] = model.intervalVar(0, timeHorizon);
}
// Create HexalyOptimizer arrays in order to be able to access them with "at" operators
HxExpression[] batchIntervalArray = new HxExpression[nbResources];
for (int r = 0; r < nbResources; ++r)
batchIntervalArray[r] = model.array(batchInterval[r]);
// Non-overlap of batch intervals on the same resource
for (int r = 0; r < nbResources; ++r)
{
for (int b = 1; b < nbTasksPerResource[r]; ++b)
model.constraint(model.lt(batchInterval[r][b-1], batchInterval[r][b]));
}
// Interval decisions: time range of each task
taskInterval = new HxExpression[nbTasks];
for (int t = 0; t < nbTasks; ++t) {
// Retrieve the batch index and resource for this task
int r = resource[t];
HxExpression b = model.find(batchContentArray[r], taskIndexInResource[t]);
// Task interval associated with task t
taskInterval[t] = model.at(batchIntervalArray[r], b);
}
// Task durations
for (int t = 0; t < nbTasks; ++t)
model.constraint(model.eq(model.length(taskInterval[t]), duration[t]));
// Precedence constraints between tasks
for (int t = 0; t < nbTasks; ++t)
{
for (int s = 0; s < nbSuccessors[t]; ++s)
model.constraint(model.lt(taskInterval[t], taskInterval[successors[t][s]]));
}
// Makespan: end of the last task
makespan = model.max();
for (int t = 0; t < nbTasks; ++t)
makespan.addOperand(model.end(taskInterval[t]));
model.minimize(makespan);
model.close();
// Parameterize the optimizer
optimizer.getParam().setTimeLimit(timeLimit);
optimizer.solve();
}
/*
* Write the solution in a file with the following format:
* - makespan
* - machine number
* - preceeding lines are the ordered intervals of the tasks
for the corresponding machine number
*/
public void writeSolution(String fileName) throws IOException {
try (PrintWriter output = new PrintWriter(fileName)) {
System.out.println("Solution written in file " + fileName);
output.write(makespan.getValue() + "\n");
for (int r = 0; r < nbResources; ++r) {
output.write(r + "\n");
for (int b = 0; b < nbTasksPerResource[r]; ++b) {
int t = tasksInResource[r][b];
long start = taskInterval[t].getIntervalValue().getStart();
long end = taskInterval[t].getIntervalValue().getEnd();
output.write(t + " " + start + " " + end + "\n");
}
}
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("Usage: java BatchScheduling 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()) {
BatchScheduling model = new BatchScheduling(optimizer, instanceFile);
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);
}
}
}