Multi-Mode Resource-Constrained Project Scheduling (MRCPSP)
SchedulingProblem
In the Multi-Mode Resource-Constrained Project Scheduling Problem (MRCPSP), a project consists of a set of tasks to schedule. For each task, execution cannot be interrupted and can be performed in different modes. There are precedence constraints between the tasks: each task must end before any of its successors can start. There is a set of resources that are either renewable (all task consumption is returned to the resource once task processing is complete) or non-renewable. For each mode, each task execution takes a given duration and consumes a given amount of resources or weight (possibly zero). Each resource has a given maximum capacity: it can process several tasks at once, but the sum of the processed tasks’ weights must never exceed this maximum capacity.
The goal is to find a schedule that minimizes the makespan: the time when all tasks have been processed.
Principles learned
- Add optional interval decision variables to model the tasks and modes
- Use the ‘hull‘ operator to manage the tasks independently of modes
- Define lambda functions to model the cumulative resource constraints
Data
The instances we provide for the Multi-Mode Resource-Constrained Project Scheduling Problem (MRCPSP) come from the PSPLIB instances. The format of the data files is as follows:
- The 6th line contains the number of jobs or tasks.
- The 9th line contains the number of renewable resources.
- The 10th line contains the number of non-renewable resources.
- From the 19th line, we get a line for each task, each one containing the following integer data in this order:
- The task ID
- The number of execution modes for this task
- The number of successors of this task
- The IDs of the successors of this task
- Then, from the 5 lines below, we get a line for each mode of each task. Each line contains the integer data associated with each configuration (task, mode) in this order:
- The task ID
- The mode ID
- The duration in this configuration
- The consumed weight for each resource in this configuration
- Finally, 4 lines below, we get one line containing the maximum capacity of each resource.
Program
The Hexaly model for the Multi-Mode Resource Constrained Project Scheduling Problem (MRCPSP) is an extension of the RCPSP model. It uses optional interval decision variables to represent the tasks in their different execution modes. We use the presence operator to detect each task’s active mode. Finally, we use the ‘hull ‘ operator to manage the tasks independently of their execution modes.
For the active mode, the length of each interval is equal to the duration of the corresponding task performed in this mode.
Then, we write the precedence constraints: each task must end before any of its successors can start.
We ensure that exactly one mode is active for each task. This guarantees that all tasks will be scheduled.
The renewable resource constraints can be formulated as follows: for each renewable resource, and for each time slot, the amount of resource consumed by the tasks that are being processed must not exceed the resource’s capacity. To model these constraints, we sum up, for each resource and each time slot, the weights of all active tasks in all active modes. We use a variadic ‘and’ formulation with a lambda function to ensure the resource capacity is respected at any time. Thanks to this variadic ‘and’, the constraint formulation remains compact and efficient even if the time horizon is very large.
The non-renewable resource constraints are modeled by linear constraints involving the presence of each configuration (task, mode).
Finally, the objective is to minimize the makespan.
- Execution
-
hexaly rcpsp_multi_mode.hxm inFileName=instances/j3010_5.mm [hxTimeLimit=] [solFileName=]
use io;
function input() {
local usage = "Usage: hexaly rcpsp_multi_mode.hxm inFileName=instanceFile "
+ "[outFileName=outputFile] [hxTimeLimit=timeLimit]";
if (inFileName == nil) throw usage;
readInput();
}
function model() {
// Optional interval decisions: time range of each task in each mode
tasksInMode[task in 0...nbTasks][mode in 0...nbModes[task]] <- optionalInterval(0, horizon);
presentMode[task in 0...nbTasks][mode in 0...nbModes[task]] <- presence(tasksInMode[task][mode]);
// Hull
tasks[task in 0...nbTasks] <- hull[mode in 0...nbModes[task]](tasksInMode[task][mode]);
// Constraints: Task duration
for [task in 0...nbTasks][mode in 0...nbModes[task]] {
constraint presentMode[task][mode] ? length(tasksInMode[task][mode]) == durations[task][mode] : 1;
}
// Constraints: Precedence between tasks
for [task in 0...nbTasks][s in 0...nbSuccessors[task]] {
constraint tasks[task] < tasks[successors[task][s]];
}
// Constraints: Exactly one active mode for each task
for [task in 0...nbTasks] {
constraint sum[mode in 0...nbModes[task]](presentMode[task][mode]) == 1;
}
// Makespan: end of the last task
makespan <- max[task in 0...nbTasks] (end(tasks[task]));
// Constraints: Renewable resources
for [r in 0...nbRenewableResources] {
constraint and(0...makespan,
t => sum[task in 0...nbTasks][mode in 0...nbModes[task]](
weight[task][r][mode] * contains(tasksInMode[task][mode], t)) <= capacity[r]);
}
// Constraints: Non-renewable resources
for [r in nbRenewableResources...nbResources] {
constraint sum[task in 0...nbTasks][mode in 0...nbModes[task]](
weight[task][r][mode] * presentMode[task][mode]) <= capacity[r];
}
// Objective: Minimize the makespan
minimize makespan;
}
// Parameterize the solver
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 60;
}
/*
* Write the solution in a file with the following format:
* - total makespan
* - for each task, the task ID, the active mode ID, the start and end times
*/
function output() {
if (outFileName != nil) {
outFile = io.openWrite(outFileName);
println("Solution written in file ", outFileName);
outFile.println(makespan.value);
for [task in 0...nbTasks] {
local activeModeId = -1;
for [mode in 0...nbModes[task]] {
if (presentMode[task][mode].value) {
activeModeId = mode;
break;
}
}
outFile.println(task + 1, " ", activeModeId + 1, " ", tasks[task].value.start, " ", tasks[task].value.end);
}
}
}
function readInput() {
inFile = io.openRead(inFileName);
// Parse number of tasks
for [i in 0...5] {
inFile.readln();
}
line = inFile.readln().split(":");
nbTasks = line[1].toInt();
// Parse number of resources
for [i in 0...2] {
inFile.readln();
}
line = inFile.readln().split(":");
nbRenewableResources = line[1].split()[0].toInt();
line = inFile.readln().split(":");
nbNonRenewableResources = line[1].split()[0].toInt();
nbResources = nbRenewableResources + nbNonRenewableResources;
// Parse successors of each task
for [i in 0...8] {
inFile.readln();
}
local taskId = inFile.readInt() - 1;
nbModes = {};
successors = {};
nbSuccessors = {};
while (true) {
nbModes[taskId] = inFile.readInt();
nbSuccessors[taskId] = inFile.readInt();
for [s in 0...nbSuccessors[taskId]] {
successors[taskId][s] = inFile.readInt() - 1;
}
if (taskId + 1 == nbTasks) {
break;
}
taskId = inFile.readInt() - 1;
}
// Parse tasks durations per mode AND consumed resource weight per mode for each task
for [i in 0...4] {
inFile.readln();
}
durations = {};
taskId = inFile.readInt() - 1;
while (true) {
for [m in 0...nbModes[taskId]] {
modeId = inFile.readInt() - 1;
durations[taskId][modeId] = inFile.readInt();
for [resourceId in 0...nbResources] {
weight[taskId][resourceId][modeId] = inFile.readInt();
}
}
if (taskId + 1 == nbTasks) {
break;
}
taskId = inFile.readInt() - 1;
}
// Parse maximum capacity for each resource
for [i in 0...3] {
inFile.readln();
}
line = inFile.readln().split();
for [resource in 0...nbResources] {
capacity[resource] = line[resource].toInt();
}
// Trivial upper bound for the start times of the tasks
horizon = sum[task in 0...nbTasks](max[mode in 0...nbModes[task]](durations[task][mode]));
inFile.close();
}
- Execution (Windows)
-
set PYTHONPATH=%HX_HOME%\bin\pythonpython rcpsp_multi_mode.py instances\j3010_5.mm
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_14_0/bin/pythonpython rcpsp_multi_mode.py instances/j3010_5.mm
import hexaly.optimizer
import sys
def main(instance_file, output_file, time_limit):
nb_tasks, nb_renewable_resources, nb_resources, nb_modes, capacity, duration, weight, \
nb_successors, successors, horizon = read_input(instance_file)
with hexaly.optimizer.HexalyOptimizer() as optimizer:
# Declare the optimization model
model = optimizer.model
# Optional interval decisions: time range for each task and mode
tasks_in_mode = [[model.optional_interval(0, horizon) for m in range(nb_modes[i])] for i in range(nb_tasks)]
present_mode = [[model.presence(tasks_in_mode[i][m]) for m in range(nb_modes[i])] for i in range(nb_tasks)]
# Hull
tasks = [model.hull(tasks_in_mode[task][mode] for mode in range(nb_modes[task])) for task in range(nb_tasks)]
# Constraints: Task duration
for task in range(nb_tasks):
for mode in range(nb_modes[task]):
model.constraint(model.iif(
present_mode[task][mode],
model.eq(model.length(tasks_in_mode[task][mode]), duration[task][mode]),
1
))
# Constraints: Precedence between tasks
for task in range(nb_tasks):
for s in range(nb_successors[task]):
model.constraint(model.lt(tasks[task], tasks[successors[task][s]]))
# Constraints: Exactly one active mode for each task
for task in range(nb_tasks):
model.constraint(model.eq(sum(present_mode[task]), 1))
# Makespan: end of the last task
makespan = model.max([model.end(tasks[task]) for task in range(nb_tasks)])
# Constraints: Renewable resources
def capacity_respected(resource, time):
total_weight = model.sum()
for task in range(nb_tasks):
for mode in range(nb_modes[task]):
total_weight.add_operand(
weight[task][resource][mode]
* model.contains(tasks_in_mode[task][mode], time)
)
return model.leq(total_weight, capacity[resource])
for resource in range(nb_renewable_resources):
capacity_respected_lambda = model.lambda_function(lambda time: capacity_respected(resource, time))
model.constraint(model.and_(model.range(makespan), capacity_respected_lambda))
# Constraints: Non-renewable resources
for resource in range(nb_renewable_resources, nb_resources):
total_weight = model.sum()
for task in range(nb_tasks):
for mode in range(nb_modes[task]):
total_weight.add_operand(weight[task][resource][mode] * present_mode[task][mode])
model.constraint(model.leq(total_weight, capacity[resource]))
# Objective: Minimize the makespan
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:
# - total makespan
# - for each task, the task ID, the mode ID, the start and end times
#
if output_file != None:
with open(output_file, "w") as file:
print("Solution written in file", output_file)
file.write(str(makespan.value) + "\n")
for task in range(nb_tasks):
activeModeId = -1
for mode in range(nb_modes[task]):
if (present_mode[task][mode].value):
activeModeId = mode
break
file.write(str(task + 1) + " " + str(activeModeId + 1) + " "
+ str(tasks[task].value.start()) + " "
+ str(tasks[task].value.end()))
file.write("\n")
def read_input(filename):
with open(filename) as file:
lines = file.readlines()
## Parse number of tasks
line = lines[5].split(":")
nb_tasks = int(line[1])
## Parse number of resources
line = lines[8].split(":")
nb_renewable_resources = int(line[1].split()[0])
line = lines[9].split(":")
nb_non_renewable_resources = int(line[1].split()[0])
nb_resources = nb_renewable_resources + nb_non_renewable_resources
# Number of available modes for each task
nb_modes = [0 for i in range(nb_tasks)]
# Number of successors of each task
nb_successors = [0 for i in range(nb_tasks)]
# Successors of each task
successors = [[] for i in range(nb_tasks)]
## Parse successors of each task
line_index = 18
line = lines[line_index].split()
task_id = int(line[0]) - 1
while True:
nb_modes[task_id] = int(line[1])
nb_successors[task_id] = int(line[2])
successors[task_id] = [int(line[3 + s]) - 1 for s in range(nb_successors[task_id])]
if task_id + 1 == nb_tasks:
break
line_index = line_index + 1
line = lines[line_index].split()
task_id = int(line[0]) - 1
## Parse tasks durations per mode AND consumed resource weight per mode for each task
line_index = line_index + 5
# Duration of each task and mode
duration = [[] for i in range(nb_tasks)]
# Required resource weight for each task and mode
weight = [[[]] for i in range(nb_tasks)]
for task in range(nb_tasks):
weight[task] = [[0 for m in range(nb_modes[task])] for r in range(nb_resources)]
duration[task] = [0 for m in range(nb_modes[task])]
for mode in range(nb_modes[task]):
line = lines[line_index].split()
base_index = 1 if mode == 0 else 0
duration[task][mode] = int(line[base_index + 1])
for resource in range(nb_resources):
weight[task][resource][mode] = int(line[base_index + 2 + resource])
line_index = line_index + 1
# Maximum capacity of each resource
capacity = [int(lines[line_index + 3].split()[r]) for r in range(nb_resources)]
# Trivial upper bound for the start times of the tasks
horizon = sum(max(duration[task][mode] for mode in range(nb_modes[task])) for task in range(nb_tasks))
return (nb_tasks, nb_renewable_resources, nb_resources, nb_modes, capacity, duration, weight, nb_successors, successors, horizon)
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python rcpsp_multi_mode.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 rcpsp_multi_mode.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly140.librcpsp_multi_mode instances\j3010_5.mm
- Compilation / Execution (Linux)
-
g++ rcpsp_multi_mode.cpp -I/opt/hexaly_14_0/include -lhexaly140 -lpthread -o rcpsp_multi_mode./rcpsp_multi_mode instances/j3010_5.mm
#include "optimizer/hexalyoptimizer.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <limits>
#include <numeric>
#include <vector>
#include <cstring>
using namespace hexaly;
using namespace std;
class RcpspMultiMode {
private:
// Number of tasks
int nbTasks;
// Number of renewable resources
int nbRenewableResources;
// Total number of resources (renewable and non-renewable)
int nbResources;
// Number of available modes for each task
std::vector<int> nbModes;
// Maximum capacity of each resource
std::vector<int> capacity;
// Duration of each task and mode
std::vector<std::vector<int>> duration;
// Required resource weight for each task and mode
std::vector<std::vector<std::vector<int>>> weight;
// Number of successors of each task
std::vector<int> nbSuccessors;
// Successors of each task
std::vector<std::vector<int>> successors;
// Trivial upper bound for the start times of the tasks
int horizon = 0;
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Decision variables: time range for each task and mode
std::vector<std::vector<HxExpression>> tasksInMode;
// Intermediate variables: boolean indicating for each pair (task, mode) whether it is active or not
std::vector<std::vector<HxExpression>> presentMode;
// Intermediate variables: time range of each task (hull on all @RcpspMultiMode#tasksInMode)
std::vector<HxExpression> tasks;
// Intermediate variables: number of active modes per task
std::vector<HxExpression> nbModesPerTask;
// Objective = minimize the makespan: end of the last task of the last job
HxExpression makespan;
public:
RcpspMultiMode(const std::string& fileName) : optimizer() {}
void solve(int TimeLimit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Optional interval decisions: time range for each task and mode
tasksInMode.resize(nbTasks);
presentMode.resize(nbTasks);
// Hull
tasks.resize(nbTasks);
// Declare decisions
for (int task = 0; task < nbTasks; task++) {
tasksInMode[task].resize(nbModes[task]);
presentMode[task].resize(nbModes[task]);
for (int mode = 0; mode < nbModes[task]; mode++) {
tasksInMode[task][mode] = model.optionalIntervalVar(0, horizon);
presentMode[task][mode] = model.presence(tasksInMode[task][mode]);
}
tasks[task] = model.hull(model.array(tasksInMode[task].begin(), tasksInMode[task].end()));
}
// Build makespan
makespan = model.max();
for (int task = 0; task < nbTasks; task++) {
makespan.addOperand(model.end(tasks[task]));
}
// Declare constraints on tasks
nbModesPerTask.resize(nbTasks);
for (int task = 0; task < nbTasks; task++) {
// Constraints: Task duration
for (int mode = 0; mode < nbModes[task]; mode++) {
model.constraint(model.iif(
presentMode[task][mode],
model.eq(model.length(tasksInMode[task][mode]), duration[task][mode]),
1
));
}
// Constraints: Precedence between tasks
for (int successor = 0; successor < nbSuccessors[task]; successor++) {
model.constraint(model.lt(tasks[task], tasks[successors[task][successor]]));
}
// Constraints: Exactly one active mode for each task
nbModesPerTask[task] = model.sum();
for (int mode = 0; mode < nbModes[task]; mode++) {
nbModesPerTask[task].addOperand(presentMode[task][mode]);
}
model.constraint(model.eq(nbModesPerTask[task], 1));
}
// Constraints: Renewable resources
for (int resource = 0; resource < nbRenewableResources; resource++) {
HxExpression capacityRespected = model.createLambdaFunction([&](HxExpression time) {
HxExpression totalWeight = model.sum();
for (int task = 0; task < nbTasks; task++) {
for (int mode = 0; mode < nbModes[task]; mode++) {
totalWeight.addOperand(model.prod(
weight[task][mode][resource],
model.contains(tasksInMode[task][mode], time)));
}
}
return model.leq(totalWeight, capacity[resource]);
});
model.constraint(model.and_(model.range(0, makespan), capacityRespected));
}
// Constraints: Non-renewable resources
for (int resource = nbRenewableResources; resource < nbResources; resource++) {
HxExpression totalWeight = model.sum();
for (int task = 0; task < nbTasks; task++) {
for (int mode = 0; mode < nbModes[task]; mode++) {
totalWeight.addOperand(model.prod(
weight[task][mode][resource],
presentMode[task][mode]));
}
}
model.constraint(model.leq(totalWeight, capacity[resource]));
}
// Objective: Minimize makespan
model.minimize(makespan);
model.close();
// Parameterize the optimizer
optimizer.getParam().setTimeLimit(TimeLimit);
optimizer.solve();
}
/*
* Write the solution in a file with the following format:
* - total makespan
* - for each task, the task ID, the active mode ID, the start and end times
*/
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;
outfile << makespan.getValue() << std::endl;
int activeModeId = -1;
for (int task = 0; task < nbTasks; task++) {
for (int mode = 0; mode < nbModes[task]; mode++) {
if (presentMode[task][mode].getValue() == 1) {
activeModeId = mode;
break;
}
}
outfile << task + 1 << " " << activeModeId + 1 << " "
<< tasks[task].getIntervalValue().getStart() << " "
<< tasks[task].getIntervalValue().getEnd() << std::endl;
}
outfile.close();
}
void readInstance(const std::string& fileName) {
std::ifstream infile;
infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
infile.open(fileName.c_str());
// Parse number of tasks
string str;
char* pch;
char* line;
int nbNodes;
for (int i = 0; i < 6; i++) {
getline(infile, str);
}
line = strdup(str.c_str());
pch = strtok(line, ":");
pch = strtok(NULL, ":");
nbTasks = atoi(pch);
// Parse number of resources
for (int i = 0; i < 3; i++) {
getline(infile, str);
}
line = strdup(str.c_str());
pch = strtok(line, ":");
pch = strtok(NULL, ":");
pch = strtok(pch, " ");
nbRenewableResources = atoi(pch);
getline(infile, str);
line = strdup(str.c_str());
pch = strtok(line, ":");
pch = strtok(NULL, ":");
pch = strtok(pch, " ");
int nbNonRenewableResources = atoi(pch);
nbResources = nbRenewableResources + nbNonRenewableResources;
// Parse successors of each task
for (int i = 0; i < 8; i++) {
getline(infile, str);
}
nbModes.resize(nbTasks);
nbSuccessors.resize(nbTasks);
successors.resize(nbTasks);
int task;
infile >> task;
task--;
while (true) {
infile >> nbModes[task];
infile >> nbSuccessors[task];
successors[task].resize(nbSuccessors[task]);
for (int successor = 0; successor < nbSuccessors[task]; successor++) {
infile >> successors[task][successor];
successors[task][successor]--;
}
if (task + 1 == nbTasks) {
break;
}
infile >> task;
task--;
}
// Parse tasks durations per mode AND consumed resource weight per mode for each task
for (int i = 0; i < 5; i++) {
getline(infile, str);
}
infile >> task;
task--;
duration.resize(nbTasks);
weight.resize(nbTasks);
int modeId;
int maxDuration;
while (true) {
weight[task].resize(nbModes[task]);
duration[task].resize(nbModes[task]);
maxDuration = 0;
for (int mode = 0; mode < nbModes[task]; mode++) {
infile >> modeId;
modeId--;
infile >> duration[task][modeId];
weight[task][modeId].resize(nbResources);
maxDuration = std::max(maxDuration, duration[task][modeId]);
for (int resourceId = 0; resourceId < nbResources; resourceId++) {
infile >> weight[task][modeId][resourceId];
}
}
horizon += maxDuration;
if (task + 1 == nbTasks) {
break;
}
infile >> task;
task--;
}
// Parse maximum capacity for each resource
for (int i = 0; i < 4; i++) {
getline(infile, str);
}
capacity.resize(nbResources);
for (int resource = 0; resource < nbResources; resource++) {
infile >> capacity[resource];
}
infile.close();
}
};
int main(int argc, char** argv) {
if (argc < 2) {
std::cout << "Usage: RcpspMultiMode 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";
RcpspMultiMode model(instanceFile);
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 RcpspMultiMode.cs /reference:Hexaly.NET.dllRcpspMultiMode instances\j3010_5.mm
using System;
using System.IO;
using System.Linq;
using Hexaly.Optimizer;
public class RcpspMultiMode : IDisposable
{
// Number of tasks
private int nbTasks;
// Number of renewable resources
private int nbRenewableResources;
// Total number of resources (renewable and non-renewable)
private int nbResources;
// Number of available modes for each task
private int[] nbModes;
// Maximum capacity of each resource
private int[] capacity;
// Duration of each task and mode
private int[][] duration;
// Required resource weight for each task and mode
private int[][][] weight;
// Number of successors of each task
private int[] nbSuccessors;
// Successors of each task
private int[][] successors;
// Trivial upper bound for the start times of the tasks
private int horizon = 0;
// Hexaly Optimizer
private HexalyOptimizer optimizer;
// Decision variables: time range for each task and mode
private HxExpression[][] tasksInMode;
// Intermediate variables: boolean indicating for each pair (task, mode) whether it is active or not
private HxExpression[][] presentMode;
// Intermediate variables: time range of each task (hull of all RcpspMultiMode#tasksInMode)
private HxExpression[] tasks;
// Intermediate variables: number of active modes per task
private HxExpression[] nbModesPerTask;
// Objective = minimize the makespan: end of the last task of the last job
private HxExpression makespan;
public RcpspMultiMode(string fileName)
{
optimizer = new HexalyOptimizer();
}
public void Dispose()
{
optimizer.Dispose();
}
public void Solve(int timeLimit)
{
// Declare the optimization model
HxModel model = optimizer.GetModel();
// Optional interval decisions: time range for each task and mode
tasksInMode = new HxExpression[nbTasks][];
presentMode = new HxExpression[nbTasks][];
// Hull
tasks = new HxExpression[nbTasks];
// Declare decisions
for (int task = 0; task < nbTasks; task++)
{
tasksInMode[task] = new HxExpression[nbModes[task]];
presentMode[task] = new HxExpression[nbModes[task]];
for (int mode = 0; mode < nbModes[task]; mode++)
{
tasksInMode[task][mode] = model.OptionalInterval(0, horizon);
presentMode[task][mode] = model.Presence(tasksInMode[task][mode]);
}
tasks[task] = model.Hull(tasksInMode[task]);
}
// Build makespan
makespan = model.Max();
for (int task = 0; task < nbTasks; task++)
{
makespan.AddOperand(model.End(tasks[task]));
}
// Declare constraints on tasks
nbModesPerTask = new HxExpression[nbTasks];
for (int task = 0; task < nbTasks; task++)
{
// Constraints: Task duration
for (int mode = 0; mode < nbModes[task]; mode++)
{
model.Constraint(model.If(
presentMode[task][mode],
model.Eq(model.Length(tasksInMode[task][mode]), duration[task][mode]),
1
));
}
// Constraints: Precedence between the tasks
for (int successor = 0; successor < nbSuccessors[task]; successor++)
{
model.Constraint(model.Lt(tasks[task], tasks[successors[task][successor]]));
}
// Constraints: Exactly one active mode for each task
nbModesPerTask[task] = model.Sum();
for (int mode = 0; mode < nbModes[task]; mode++)
{
nbModesPerTask[task].AddOperand(presentMode[task][mode]);
}
model.Constraint(model.Eq(nbModesPerTask[task], 1));
}
// Constraints: Renewable resources
for (int resource = 0; resource < nbRenewableResources; resource++)
{
HxExpression capacityRespected = model.LambdaFunction(time =>
{
HxExpression totalWeight = model.Sum();
for (int task = 0; task < nbTasks; task++)
{
for (int mode = 0; mode < nbModes[task]; mode++)
{
totalWeight.AddOperand(model.Prod(
weight[task][mode][resource],
model.Contains(tasksInMode[task][mode], time)));
}
}
return model.Leq(totalWeight, capacity[resource]);
});
model.Constraint(model.And(model.Range(0, makespan), capacityRespected));
}
// Constraints: Non-renewable resources
for (int resource = nbRenewableResources; resource < nbResources; resource++)
{
HxExpression totalWeight = model.Sum();
for (int task = 0; task < nbTasks; task++)
{
for (int mode = 0; mode < nbModes[task]; mode++)
{
totalWeight.AddOperand(model.Prod(
weight[task][mode][resource],
presentMode[task][mode]));
}
}
model.Constraint(model.Leq(totalWeight, capacity[resource]));
}
// Objective: Minimize makespan
model.Minimize(makespan);
model.Close();
// Parameterize the optimizer
optimizer.GetParam().SetTimeLimit(timeLimit);
optimizer.Solve();
}
/*
* Write the solution in a file with the following format:
* - total makespan
* - for each task, the task ID, the active mode ID, the start and end times
*/
public void WriteSolution(string fileName)
{
using (StreamWriter output = new StreamWriter(fileName))
{
Console.WriteLine("Solution written in file " + fileName);
output.WriteLine(makespan.GetValue());
int activeModeId = -1;
for (int task = 0; task < nbTasks; task++)
{
for (int mode = 0; mode < nbModes[task]; mode++)
{
if (presentMode[task][mode].GetValue() == 1)
{
activeModeId = mode;
break;
}
}
output.Write((task + 1) + " " + (activeModeId + 1) + " "
+ tasks[task].GetIntervalValue().Start() + " "
+ tasks[task].GetIntervalValue().End());
output.WriteLine();
}
}
}
string[] SplitInput(StreamReader input)
{
string line = input.ReadLine();
if (line == null)
return new string[0];
return line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
}
private void ReadInstance(string fileName)
{
using (StreamReader input = new StreamReader(fileName))
{
string[] splitted;
// Parse number of tasks
for (int i = 0; i < 5; ++i)
{
input.ReadLine();
}
splitted = input.ReadLine().Split(':');
nbTasks = int.Parse(splitted[1]);
// Parse number of resources
for (int i = 0; i < 2; ++i)
{
input.ReadLine();
}
splitted = input.ReadLine().Split(':');
nbRenewableResources = int.Parse(new string(splitted[1]
.SkipWhile(c => !char.IsDigit(c))
.TakeWhile(c => char.IsDigit(c))
.ToArray()));
splitted = input.ReadLine().Split(':');
int nbNonRenewableResources = int.Parse(new string(splitted[1]
.SkipWhile(c => !char.IsDigit(c))
.TakeWhile(c => char.IsDigit(c))
.ToArray()));
nbResources = nbRenewableResources + nbNonRenewableResources;
// Parse successors of each task
for (int i = 0; i < 8; ++i)
{
input.ReadLine();
}
splitted = SplitInput(input);
nbModes = new int[nbTasks];
nbSuccessors = new int[nbTasks];
successors = new int[nbTasks][];
int task = int.Parse(splitted[0]) - 1;
while (true)
{
nbModes[task] = int.Parse(splitted[1]);
nbSuccessors[task] = int.Parse(splitted[2]);
successors[task] = new int[nbSuccessors[task]];
for (int successor = 0; successor < nbSuccessors[task]; successor++)
{
successors[task][successor] = int.Parse(splitted[3 + successor]) - 1;
}
if (task + 1 == nbTasks)
{
break;
}
splitted = SplitInput(input);
task = int.Parse(splitted[0]) - 1;
}
// Parse tasks durations per mode AND consumed resource weight per mode for each task
for (int i = 0; i < 4; ++i)
{
input.ReadLine();
}
splitted = SplitInput(input);
task = int.Parse(splitted[0]) - 1;
duration = new int[nbTasks][];
weight = new int[nbTasks][][];
int baseIndex;
int modeId;
int maxDuration;
while (true)
{
weight[task] = new int[nbModes[task]][];
duration[task] = new int[nbModes[task]];
maxDuration = 0;
for (int mode = 0; mode < nbModes[task]; mode++)
{
baseIndex = mode == 0 ? 1 : 0;
modeId = int.Parse(splitted[baseIndex]) - 1;
duration[task][modeId] = int.Parse(splitted[baseIndex + 1]);
weight[task][modeId] = new int[nbResources];
maxDuration = Math.Max(maxDuration, duration[task][modeId]);
for (int resourceId = 0; resourceId < nbResources; resourceId++)
{
weight[task][modeId][resourceId] = int.Parse(splitted[baseIndex + 2 + resourceId]);
}
splitted = SplitInput(input);
}
horizon += maxDuration;
if (task + 1 == nbTasks)
{
break;
}
task = int.Parse(splitted[0]) - 1;
}
// Parse maximum capacity for each resource
for (int i = 0; i < 2; ++i)
{
input.ReadLine();
}
capacity = new int[nbResources];
splitted = SplitInput(input);
for (int resource = 0; resource < nbResources; resource++)
{
capacity[resource] = int.Parse(splitted[resource]);
}
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: RcpspMultiMode 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 (RcpspMultiMode model = new RcpspMultiMode(instanceFile))
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(outputFile);
}
}
}
- Compilation / Execution (Windows)
-
javac RcpspMultiMode.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. RcpspMultiMode instances\j3010_5.mm
- Compilation / Execution (Linux)
-
javac RcpspMultiMode.java -cp /opt/hexaly_14_0/bin/hexaly.jarjava -cp /opt/hexaly_14_0/bin/hexaly.jar:. RcpspMultiMode instances/j3010_5.mm
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.*;
import com.hexaly.optimizer.*;
public class RcpspMultiMode {
// Number of tasks
private int nbTasks;
// Number of renewable resources
private int nbRenewableResources;
// Total number of resources (renewable and non-renewable)
private int nbResources;
// Number of available modes for each task
private int[] nbModes;
// Maximum capacity of each resource
private int[] capacity;
// Duration of each task and mode
private int[][] duration;
// Required resource weight for each task and mode
private int[][][] weight;
// Number of successors of each task
private int[] nbSuccessors;
// Successors of each task
private int[][] successors;
// Trivial upper bound for the start times of the tasks
private int horizon = 0;
// Hexaly Optimizer
final HexalyOptimizer optimizer;
// Decision variables: time range for each task and mode
private HxExpression[][] tasksInMode;
// Intermediate variables: boolean indicating for each pair (task, mode) whether it is active or not
private HxExpression[][] presentMode;
// Intermediate variables: time range of each task (hull of all RcpspMultiMode#tasksInMode)
private HxExpression[] tasks;
// Intermediate variables: number of active modes per task
private HxExpression[] nbModesPerTask;
// Objective = minimize the makespan: end of the last task of the last job
private HxExpression makespan;
public RcpspMultiMode(HexalyOptimizer optimizer, String fileName) throws IOException {
this.optimizer = optimizer;
}
public void solve(int timeLimit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Optional interval decisions: time range for each task and mode
tasksInMode = new HxExpression[nbTasks][];
presentMode = new HxExpression[nbTasks][];
// Hull
tasks = new HxExpression[nbTasks];
// Declare decisions
for (int task = 0; task < nbTasks; task++) {
tasksInMode[task] = new HxExpression[nbModes[task]];
presentMode[task] = new HxExpression[nbModes[task]];
for (int mode = 0; mode < nbModes[task]; mode++) {
tasksInMode[task][mode] = model.optionalIntervalVar(0, horizon);
presentMode[task][mode] = model.presence(tasksInMode[task][mode]);
}
tasks[task] = model.hull(tasksInMode[task]);
}
// Build makespan
makespan = model.max();
for (int task = 0; task < nbTasks; task++) {
makespan.addOperand(model.end(tasks[task]));
}
// Declare constraints on tasks
nbModesPerTask = new HxExpression[nbTasks];
for (int task = 0; task < nbTasks; task++) {
// Constraints: Task duration
for (int mode = 0; mode < nbModes[task]; mode++) {
model.constraint(model.iif(
presentMode[task][mode],
model.eq(model.length(tasksInMode[task][mode]), duration[task][mode]),
1
));
}
// Constraints: Precedence between tasks
for (int successor = 0; successor < nbSuccessors[task]; successor++) {
model.constraint(model.lt(tasks[task], tasks[successors[task][successor]]));
}
// Constraints: Exactly one active mode for each task
nbModesPerTask[task] = model.sum();
for (int mode = 0; mode < nbModes[task]; mode++) {
nbModesPerTask[task].addOperand(presentMode[task][mode]);
}
model.constraint(model.eq(nbModesPerTask[task], 1));
}
// Constraints: Renewable resources
for (int r = 0; r < nbRenewableResources; r++) {
final int resource = r;
HxExpression capacityRespected = model.lambdaFunction(time -> {
HxExpression totalWeight = model.sum();
for (int task = 0; task < nbTasks; task++) {
for (int mode = 0; mode < nbModes[task]; mode++) {
totalWeight.addOperand(model.prod(
weight[task][mode][resource],
model.contains(tasksInMode[task][mode], time)));
}
}
return model.leq(totalWeight, capacity[resource]);
});
model.constraint(model.and(model.range(0, makespan), capacityRespected));
}
// Constraints: Non-renewable resources
for (int resource = nbRenewableResources; resource < nbResources; resource++) {
HxExpression totalWeight = model.sum();
for (int task = 0; task < nbTasks; task++) {
for (int mode = 0; mode < nbModes[task]; mode++) {
totalWeight.addOperand(model.prod(weight[task][mode][resource], presentMode[task][mode]));
}
}
model.constraint(model.leq(totalWeight, capacity[resource]));
}
// Objective: Minimize makespan
model.minimize(makespan);
model.close();
// Parameterize the optimizer
optimizer.getParam().setTimeLimit(timeLimit);
optimizer.solve();
}
/*
* Write the solution in a file with the following format:
* - total makespan
* - for each task, the task ID, the active mode ID, the start and end times
*/
public void writeSolution(String fileName) throws IOException {
try (PrintWriter output = new PrintWriter(fileName)) {
System.out.println("Solution written in file " + fileName);
output.println(makespan.getValue());
int activeModeId = -1;
for (int task = 0; task < nbTasks; ++task) {
for (int mode = 0; mode < nbModes[task]; mode++) {
if (presentMode[task][mode].getValue() == 1) {
activeModeId = mode;
break;
}
}
output.println((task + 1) + " " + (activeModeId + 1) + " "
+ tasks[task].getIntervalValue().getStart() + " "
+ tasks[task].getIntervalValue().getEnd());
}
}
}
private void readInstance(String fileName) throws IOException {
try (Scanner input = new Scanner(new File(fileName))) {
input.useLocale(Locale.ROOT);
// Parse number of tasks
for (int i = 0; i < 5; i++) {
input.nextLine();
}
String str = input.nextLine();
String[] splitted = str.split(":");
nbTasks = Integer.valueOf(splitted[1].trim());
// Parse number of resources
for (int i = 0; i < 2; i++) {
input.nextLine();
}
str = input.nextLine();
splitted = str.split(":");
Matcher matcher = Pattern.compile("\\d+").matcher(splitted[1].trim());
matcher.find();
nbRenewableResources = Integer.valueOf(matcher.group());
str = input.nextLine();
splitted = str.split(":");
matcher = Pattern.compile("\\d+").matcher(splitted[1].trim());
matcher.find();
int nbNonRenewableResources = Integer.valueOf(matcher.group());
nbResources = nbRenewableResources + nbNonRenewableResources;
// Parse successors of each task
for (int i = 0; i < 7; i++) {
input.nextLine();
}
str = input.nextLine();
nbModes = new int[nbTasks];
nbSuccessors = new int[nbTasks];
successors = new int[nbTasks][];
int task = input.nextInt() - 1;
while (true) {
nbModes[task] = input.nextInt();
nbSuccessors[task] = input.nextInt();
successors[task] = new int[nbSuccessors[task]];
for (int successor = 0; successor < nbSuccessors[task]; successor++) {
successors[task][successor] = input.nextInt() - 1;
}
if (task + 1 == nbTasks) {
break;
}
task = input.nextInt() - 1;
}
// Parse tasks durations per mode AND consumed resource weight per mode for each task
for (int i = 0; i < 4; i++) {
input.nextLine();
}
str = input.nextLine();
task = input.nextInt() - 1;
duration = new int[nbTasks][];
weight = new int[nbTasks][][];
int modeId;
int maxDuration;
while (true) {
weight[task] = new int[nbModes[task]][];
duration[task] = new int[nbModes[task]];
maxDuration = 0;
for (int mode = 0; mode < nbModes[task]; mode++) {
modeId = input.nextInt() - 1;
duration[task][modeId] = input.nextInt();
weight[task][modeId] = new int[nbResources];
maxDuration = Math.max(maxDuration, duration[task][modeId]);
for (int resourceId = 0; resourceId < nbResources; resourceId++) {
weight[task][modeId][resourceId] = input.nextInt();
}
}
horizon += maxDuration;
if (task + 1 == nbTasks) {
break;
}
task = input.nextInt() - 1;
}
// Parse maximum capacity for each resource
for (int i = 0; i < 3; i++) {
input.nextLine();
}
str = input.nextLine();
capacity = new int[nbResources];
for (int resource = 0; resource < nbResources; resource++) {
capacity[resource] = input.nextInt();
}
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("Usage: java RcpspMultiMode 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()) {
RcpspMultiMode model = new RcpspMultiMode(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);
}
}
}