Multi-Mode Resource-Constrained Project Scheduling (MRCPSP)

Scheduling

Problem

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

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\python
python rcpsp_multi_mode.py instances\j3010_5.mm
Execution (Linux)
export PYTHONPATH=/opt/hexaly_14_0/bin/python
python 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.lib
rcpsp_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.dll
RcpspMultiMode 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.jar
java -cp %HX_HOME%\bin\hexaly.jar;. RcpspMultiMode instances\j3010_5.mm
Compilation / Execution (Linux)
javac RcpspMultiMode.java -cp /opt/hexaly_14_0/bin/hexaly.jar
java -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);
        }
    }
}