Resource Availability Cost Problem (RACP)

Scheduling

Problem

In the Resource Availability Cost Problem (RACP), a project consists of a set of tasks that need to be scheduled. Each task has a given duration and cannot be interrupted. There are precedence constraints between the tasks: each task must end before any of its successors can start. There is a set of renewable resources. Each task has a given resource requirement or weight (possibly zero) on each resource, representing the amount of that resource it consumes while active. Each resource has a given maximum capacity that needs to be set. A unit of capacity has a given cost, which varies for each resource. The sum of the weights of the processed tasks can never exceed this maximum capacity. The goal is to find a schedule that minimizes the total cost of the required capacity, while ensuring that all tasks are completed before a given deadline.

Principles learned

Data

The Resource Availability Cost Problem (RACP) instances provided come from the RACP instances and follow the Patterson format:

  • First line:
    • Number of tasks (including two additional dummy tasks with duration 0: the source and the sink)
    • Number of renewable resources
  • Second line: cost per unit of capacity of each resource
  • From the third line, for each task:
    • Duration of the task
    • Resource requirement (weight) for each resource
    • Number of successors
    • Task ID for each successor

Model

The Hexaly model for the Resource Availability Cost Problem (RACP) uses interval decision variables to represent the tasks. The length of each interval is equal to the duration of the corresponding task. We also define integer decision variables to represent the capacities assigned to each resource. We then write the precedence constraints: each task must end before any of its successors can start. The makespan is the time when all the tasks have finished. It has to remain smaller than the deadline.

The cumulative resource constraints can be formulated as follows: for each resource and for each time slot t, 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. 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.

Finally, we calculate the total cost as the sum of the unit cost of each resource multiplied by its selected capacity. It is the quantity we want to minimize.

Execution
hexaly racp.hxm inFileName=instances/racp.rcp [hxTimeLimit=] [solFileName=] [deadline=]
use io;

/* Read instance data. The input files follow the "Patterson" format */
function input() {
    local usage = "Usage: hexaly racp.hxm inFileName=instanceFile "
            + "[outFileName=outputFile] [hxTimeLimit=timeLimit] [deadline=deadline]";
    if (inFileName == nil) throw usage;

    inFile = io.openRead(inFileName);

    // Number of operations
    nbTasks = inFile.readInt();

    // Number of resources
    nbResources = inFile.readInt();

    // Cost per unit of capacity of each resource
    for[r in 0...nbResources] {
        resourceCost[r] = inFile.readInt();
    }

    for [i in 0...nbTasks] {
        // Duration of task i
        duration[i] = inFile.readInt();
        
        // Weight of resource r required for task i
        weight[i][0...nbResources] = inFile.readInt();

        // Number of successors of task i
        nbSuccessors[i] = inFile.readInt();

        // Array of successors of task i
        successors[i][0...nbSuccessors[i]] = inFile.readInt()-1;
    }
    
    inFile.close();
    
    // Trivial upper bound for the end times of the tasks
    timeHorizon = sum[i in 0...nbTasks](duration[i]);
    if (deadline == nil) deadline = timeHorizon;
}

/* Declare the optimization model */
function model() {
    // Interval decision variables: time range of each task
    tasks[_ in 0...nbTasks] <- interval(0, timeHorizon);

    // Integer decision variables: capacity of each renewable resource
    capacity[r in 0...nbResources] <- int(0, sum[i in 0...nbTasks](weight[i][r]));
    
    for [i in 0...nbTasks] {
        // Task duration constraints
        constraint length(tasks[i]) == duration[i];
        
        // Precedence constraints between the tasks
        for [s in 0...nbSuccessors[i]] {
            constraint end(tasks[i]) <= start(tasks[successors[i][s]]);
        }
    }
    
    // Deadline constraint on the makespan
    makespan <- max[i in 0...nbTasks](end(tasks[i]));
    constraint makespan <= deadline;
    
    // Resource capacities constraints
    for [r in 0...nbResources] {
        constraint and(0...makespan,
            t => sum[i in 0...nbTasks](weight[i][r] * contains(tasks[i], t)) <= capacity[r]);
    }
    
    // Minimize the total cost
    totalCost <- sum[r in 0...nbResources](resourceCost[r] * capacity[r]);
    minimize totalCost;
}

/* Parameterize the solver */
function param() {
    if (hxTimeLimit == nil) hxTimeLimit = 60;
}

function output() {
/* Write the solution in a file with the following format:
 *  - total cost
 *  - makespan
 *  - the capacity of each resource
 *  - for each task, the task id, the start and end times */
    if (outFileName != nil) {
        outFile = io.openWrite(outFileName);
        println("Solution written in file ", outFileName);
        outFile.println(totalCost.value);
        outFile.println(makespan.value);
        for [r in 0...nbResources-1] {
            outFile.print(capacity[r].value, " ");
        }
        outFile.println(capacity[nbResources-1].value);
        for [i in 0...nbTasks] {
            outFile.println(i + 1, " ", tasks[i].value.start, " ", tasks[i].value.end);
        }
    }
}
Execution (Windows)
set PYTHONPATH=%HX_HOME%\bin\python
python racp.py instances\racp1.rcp
Execution (Linux)
export PYTHONPATH=/opt/hexaly_14_0/bin/python
python racp.py instances/racp1.rcp
import hexaly.optimizer
import sys


# The input files follow the "Patterson" format
def read_instance(filename):
    with open(filename) 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])

    # Cost per unit of capacity of each resource
    resource_cost = [int(lines[1].split()[r]) for r in range(nb_resources)]

    # Duration of each task
    duration = [0 for _ in range(nb_tasks)]

    # Weight of resource r required for task i
    weight = [[] for _ in range(nb_tasks)]

    # Number of successors
    nb_successors = [0 for _ in range(nb_tasks)]

    # Successors of each task
    successors = [[] for _ in range(nb_tasks)]

    for i in range(nb_tasks):
        line = lines[i + 2].split()
        duration[i] = int(line[0])
        weight[i] = [int(line[r + 1]) for r in range(nb_resources)]
        nb_successors[i] = int(line[nb_resources + 1])
        successors[i] = [int(line[nb_resources + 2 + s]) - 1 for s in range(nb_successors[i])]

    # Trivial upper bound for the end times of the tasks
    horizon = sum(duration[i] for i in range(nb_tasks))

    return (nb_tasks, nb_resources, resource_cost, duration, weight, nb_successors, successors, horizon)


def main(instance_file, dline, output_file, time_limit):
    nb_tasks, nb_resources, resource_cost, duration, weight, nb_successors, successors, horizon = read_instance(
        instance_file)
    
    if dline is None:
        deadline = horizon
    else:
        deadline = int(dline)

    with hexaly.optimizer.HexalyOptimizer() as optimizer:
        #
        # Declare the optimization model
        #
        model = optimizer.model

        # Interval decision variables: time range of each task
        tasks = [model.interval(0, horizon) for _ in range(nb_tasks)]

        # Integer decision variables: capacity of each renewable resource
        capacity = [model.int(0, sum(weight[i][r] for i in range(nb_tasks))) for r in range(nb_resources)]

        # Task duration constraints
        for i in range(nb_tasks):
            model.constraint(model.length(tasks[i]) == duration[i])

        # Precedence constraints between the tasks
        for i in range(nb_tasks):
            for s in range(nb_successors[i]):
                model.constraint(model.end(tasks[i]) <= model.start(tasks[successors[i][s]]))

        # Deadline constraint on the makespan
        makespan = model.max([model.end(tasks[i]) for i in range(nb_tasks)])
        model.constraint(makespan <= deadline)

        # Cumulative resource constraints
        for r in range(nb_resources):
            capacity_respected = model.lambda_function(
                lambda t: model.sum(weight[i][r] * model.contains(tasks[i], t)
                                    for i in range(nb_tasks))
                <= capacity[r])
            model.constraint(model.and_(model.range(makespan), capacity_respected))

        # Minimize the total cost
        total_cost = model.sum(resource_cost[r] * capacity[r] for r in range(nb_resources))
        model.minimize(total_cost)
    
        model.close()
        # Parameterize the optimizer
        optimizer.param.time_limit = time_limit

        optimizer.solve()

        #
        # Write the solution in a file with the following format:
        # - total cost
        # - makespan
        # - the capacity of each resource
        # - for each task, the task id, the start and end times
        #
        if output_file != None:
            with open(output_file, "w") as f:
                print("Solution written in file", output_file)
                f.write(str(total_cost.value) + "\n")
                f.write(str(makespan.value) + "\n")
                for r in range(nb_resources):
                    f.write(str(capacity[r].value) + " ")
                f.write("\n")
                for i in range(nb_tasks):
                    f.write(str(i + 1) + " " + str(tasks[i].value.start()) + " " + str(tasks[i].value.end()))
                    f.write("\n")


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python racp.py instance_file [output_file] [time_limit] [deadline]")
        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
    dline = sys.argv[4] if len(sys.argv) >= 5 else None
    main(instance_file, dline, output_file, time_limit)
Compilation / Execution (Windows)
cl /EHsc racp.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly140.lib
racp instances\racp1.rcp
Compilation / Execution (Linux)
g++ racp.cpp -I/opt/hexaly_14_0/include -lhexaly140 -lpthread -o racp
./racp instances/racp1.rcp
#include "optimizer/hexalyoptimizer.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <limits>
#include <numeric>
#include <vector>

using namespace hexaly;

class Racp {
private:
    // Number of tasks
    int nbTasks;

    // Number of resources
    int nbResources;

    // Deadline of the project
    int deadline;

    // Cost per unit of capacity of each resource
    std::vector<int> resourceCost;

    // Duration of each task
    std::vector<int> duration;

    // Weight of resource r required for task i
    std::vector<std::vector<int>> weight; 

    // Number of successors
    std::vector<int> nbSuccessors;

    // Successors for each task i
    std::vector<std::vector<int>> successors;
    
    // Trivial upper bound for the end times of the tasks
    int horizon = 0;

    // Upper bound for the needed capacity of each resource
    std::vector<int> maxCapacity; 

    // Hexaly Optimizer
    HexalyOptimizer optimizer;

    // Decision variables: time range of each task
    std::vector<HxExpression> tasks;

    // Decision variables: capacity of each renewable resource
    std::vector<HxExpression> capacity;

    // Objective to minimize: end of the last task of the last job
    HxExpression makespan;
    
    // Objective to minimize: total cost of all resources
    HxExpression totalCost;

public:
    Racp() : optimizer() {}

    // The input files follow the "Patterson" format
    void readInstance(const std::string& fileName, const char* dline) {
        std::ifstream infile;
        infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        infile.open(fileName.c_str());

        infile >> nbTasks;
        infile >> nbResources;

        resourceCost.resize(nbResources);
        for (int r = 0; r < nbResources; ++r) {
            infile >> resourceCost[r];
        }

        duration.resize(nbTasks);
        weight.resize(nbTasks);
        nbSuccessors.resize(nbTasks);
        successors.resize(nbTasks);

        for (int i = 0; i < nbTasks; ++i) {
            infile >> duration[i];  
            weight[i].resize(nbResources);
            for (int r = 0; r < nbResources; ++r) {
                infile >> weight[i][r];   
            }
            infile >> nbSuccessors[i];
            successors[i].resize(nbSuccessors[i]);
            for (int s = 0; s < nbSuccessors[i]; ++s) {
                int x;
                infile >> x;
                successors[i][s] = x - 1;
            }
            horizon += duration[i];
        }
            
        deadline = horizon;
        if (dline != NULL) {
            deadline = atoi(dline);
        }
        infile.close();
    }

    void solve(int timeLimit) {
        // Declare the optimization model
        HxModel model = optimizer.getModel();

        // Interval decision variables: time range of each task
        tasks.resize(nbTasks);
        for (int i = 0; i < nbTasks; ++i) {
            tasks[i] = model.intervalVar(0, horizon);

            // Task duration constraints
            model.constraint(model.length(tasks[i]) == duration[i]);
        }

        // Integer decision variables: capacity of each renewable resource
        capacity.resize(nbResources);
        maxCapacity.resize(nbResources);
        for (int r = 0; r < nbResources; ++r) {
            maxCapacity[r] = 0;
            for (int i = 0; i < nbTasks; ++i) {
                maxCapacity[r] += weight[i][r];
            }
            capacity[r] = model.intVar(0, maxCapacity[r]);
        }

        // Precedence constraints between the tasks
        for (int i = 0; i < nbTasks; ++i) {
            for (int s = 0; s < nbSuccessors[i]; ++s) {
                model.constraint(model.end(tasks[i]) <= model.start(tasks[successors[i][s]]));
            }
        }

        // Deadline constraint on the makespan
        makespan = model.max();
        for (int i = 0; i < nbTasks; ++i) {
            makespan.addOperand(model.end(tasks[i]));
        }
        model.constraint(makespan <= deadline);
        
        // Cumulative resource constraints
        for (int r = 0; r < nbResources; ++r) {
            HxExpression capacityRespected = model.createLambdaFunction([&](HxExpression t) {
                HxExpression totalWeight = model.sum();
                for (int i = 0; i < nbTasks; ++i) {
                    HxExpression taskWeight = weight[i][r] * model.contains(tasks[i], t);
                    totalWeight.addOperand(taskWeight);
                }
                return model.leq(totalWeight, capacity[r]);
            });
            model.constraint(model.and_(model.range(0, makespan), capacityRespected));
        }

        // Minimize the total cost
        totalCost = model.sum();
        for (int r = 0; r < nbResources; ++r) {
            totalCost.addOperand(resourceCost[r] * capacity[r]);
        }
        model.minimize(totalCost);

        model.close();

        // Parameterize the optimizer
        optimizer.getParam().setTimeLimit(timeLimit);

        optimizer.solve();
    }

    /* Write the solution in a file with the following format:
     *  - total cost
     *  - makespan
     *  - the capacity of each resource
     *  - for each task, the task 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 << totalCost.getValue() << std::endl;
        outfile << makespan.getValue() << std::endl;
        for(int r = 0; r < nbResources; ++r) {
            outfile << capacity[r].getValue() << " ";
        }
        outfile << std::endl;
        for (int i = 0; i < nbTasks; ++i) {
            outfile << i + 1 << " " << tasks[i].getIntervalValue().getStart() << " "
                    << tasks[i].getIntervalValue().getEnd() << std::endl;
        }
        outfile.close();
    }
};

int main(int argc, char** argv) {
    if (argc < 2) {
        std::cout << "Usage: Racp instanceFile [outputFile] [timeLimit] [deadline]" << 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";
    const char* dline = argc > 4 ? argv[4] : NULL;

    Racp model;
    try {
        model.readInstance(instanceFile, dline);
        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 Racp.cs /reference:Hexaly.NET.dll
Racp instances\racp1.rcp
using System;
using System.IO;
using Hexaly.Optimizer;

public class Racp : IDisposable
{
    // Number of tasks
    private int nbTasks;

    // Number of resources
    private int nbResources;

    // Deadline of the project
    int deadline;

    // Cost per unit of capacity of each resource
    private int[] resourceCost;

    // Duration of each task
    private int[] duration;

    // Weight of resource r required for task i
    private int[,] weight;

    // Number of successors
    private int[] nbSuccessors;

    // Successors for each task i
    private int[][] successors;

    // Trivial upper bound for the end times of the tasks
    private int horizon = 0;

    // Upper bound for the needed capacity of each resource
    private int[] maxCapacity;

    // Hexaly Optimizer
    private HexalyOptimizer optimizer;

    // Decision variables: time range of each task
    private HxExpression[] tasks;

    // Decision variables: capacity of each renewable resource
    private HxExpression[] capacity;

    // Objective to minimize: end of the last task of the last job
    private HxExpression makespan;

    // Objective to minimize: total cost of all resources
    private HxExpression totalCost;

    public Racp()
    {
        optimizer = new HexalyOptimizer();
    }

    // The input files follow the "Patterson" format
    private void ReadInstance(string fileName, string? dline)
    {
        using (StreamReader input = new StreamReader(fileName))
        {
            string[] splitted = SplitInput(input);
            if (splitted.Length == 0)
                splitted = SplitInput(input);
            nbTasks = int.Parse(splitted[0]);
            nbResources = int.Parse(splitted[1]);

            // The maximum capacity of each resource
            resourceCost = new int[nbResources];
            splitted = SplitInput(input);
            for (int r = 0; r < nbResources; ++r)
                resourceCost[r] = int.Parse(splitted[r]);

            duration = new int[nbTasks];
            weight = new int[nbTasks, nbResources];
            nbSuccessors = new int[nbTasks];
            successors = new int[nbTasks][];

            for (int i = 0; i < nbTasks; ++i)
            {
                splitted = SplitInput(input);
                if (splitted.Length == 0)
                    splitted = SplitInput(input);
                duration[i] = int.Parse(splitted[0]);
                for (int r = 0; r < nbResources; ++r)
                    weight[i, r] = int.Parse(splitted[r + 1]);
                nbSuccessors[i] = int.Parse(splitted[nbResources + 1]);
                successors[i] = new int[nbSuccessors[i]];
                for (int s = 0; s < nbSuccessors[i]; ++s)
                    successors[i][s] = int.Parse(splitted[s + nbResources + 2]) - 1;
                horizon += duration[i];
            }
            deadline = horizon;
            if (dline != null) {
                deadline = int.Parse(dline);
            }
        }
    }

    string[] SplitInput(StreamReader input)
    {
        string? line = input.ReadLine();
        if (line == null)
            return new string[0];
        return line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
    }

    public void Dispose()
    {
        optimizer.Dispose();
    }

    public void Solve(int timeLimit)
    {
        // Declare the optimization model
        HxModel model = optimizer.GetModel();

        // Interval decisions: time range of each task
        tasks = new HxExpression[nbTasks];
        for (int i = 0; i < nbTasks; ++i)
        {
            tasks[i] = model.Interval(0, horizon);

            // Task duration constraints
            model.Constraint(model.Length(tasks[i]) == duration[i]);
        }

        // Integer decision variables: capacity of each renewable ressource
        capacity = new HxExpression[nbResources];
        maxCapacity = new int[nbResources];
        for (int r = 0; r < nbResources; ++r)
        {
            maxCapacity[r] = 0;
            for (int i = 0; i < nbTasks; ++i) {
                maxCapacity[r] += weight[i, r];
            }
            capacity[r] = model.Int(0, maxCapacity[r]);
        }

        // Precedence constraints between the tasks
        for (int i = 0; i < nbTasks; ++i)
        {
            for (int s = 0; s < nbSuccessors[i]; ++s)
            {
                model.Constraint(model.End(tasks[i]) <= model.Start(tasks[successors[i][s]]));
            }
        }

        // Deadline constraint on the makespan
        makespan = model.Max();
        for (int i = 0; i < nbTasks; ++i)
            makespan.AddOperand(model.End(tasks[i]));
        model.Constraint(makespan <= deadline);

        // Cumulative resource constraints
        for (int r = 0; r < nbResources; ++r)
        {
            HxExpression capacityRespected = model.LambdaFunction(t =>
                {
                    HxExpression totalWeight = model.Sum();
                    for (int i = 0; i < nbTasks; ++i)
                    {
                        totalWeight.AddOperand(weight[i, r] * model.Contains(tasks[i], t));
                    }
                    return totalWeight <= capacity[r];
                }
            );
            model.Constraint(model.And(model.Range(0, makespan), capacityRespected));
        }

        // Minimize the total cost
        totalCost = model.Sum();
        for (int r = 0; r < nbResources; ++r)
            totalCost.AddOperand(resourceCost[r] * capacity[r]);
        model.Minimize(totalCost);

        model.Close();

        // Parameterize the optimizer
        optimizer.GetParam().SetTimeLimit(timeLimit);

        optimizer.Solve();
    }

    /* Write the solution in a file with the following format:
     *  - total cost
     *  - makespan
     *  - the capacity of each resource
     *  - for each task, the task 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(totalCost.GetValue());
            output.WriteLine(makespan.GetValue());
            for (int r = 0; r < nbResources; ++r)
            {
                output.Write(capacity[r].GetValue() + " ");
            }
            output.WriteLine();
            for (int i = 0; i < nbTasks; ++i)
            {
                output.Write((i + 1) + " " + tasks[i].GetIntervalValue().Start() + " " + tasks[i].GetIntervalValue().End());
                output.WriteLine();
            }
        }
    }

    public static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: Racp instanceFile [outputFile] [timeLimit] [deadline]");
            System.Environment.Exit(1);
        }

        string instanceFile = args[0];
        string? outputFile = args.Length > 1 ? args[1] : null;
        string strTimeLimit = args.Length > 2 ? args[2] : "60";
        string? dline = args.Length > 3 ? args[3] : null;

        using (Racp model = new Racp())
        {
            model.ReadInstance(instanceFile, dline);
            model.Solve(int.Parse(strTimeLimit));
            if (outputFile != null)
                model.WriteSolution(outputFile);
        }
    }
}
Compilation / Execution (Windows)
javac Racp.java -cp %HX_HOME%\bin\hexaly.jar
java -cp %HX_HOME%\bin\hexaly.jar;. Racp instances\racp1.rcp
Compilation / Execution (Linux)
javac Racp.java -cp /opt/hexaly_14_0/bin/hexaly.jar
java -cp /opt/hexaly_14_0/bin/hexaly.jar:. Racp instances/racp1.rcp
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;

public class Racp {
    // Number of tasks
    private int nbTasks;

    // Number of resources
    private int nbResources;

    // Deadline of the project
    private int deadline;

    // Cost per unit of capacity of each resource
    private int[] resourceCost;

    // Duration of each task
    private int[] duration;

    // Weight of resource r required for task i
    private int[][] weight;

    // Number of successors
    private int[] nbSuccessors;

    // Successors for each task i
    private int[][] successors;

    // Trivial upper bound for the end times of the tasks
    private int horizon = 0;

    // Upper bound for the needed capacity of each resource
    private int[] maxCapacity;

    // Hexaly Optimizer
    final HexalyOptimizer optimizer;

    // Decision variables: time range of each task
    private HxExpression[] tasks;

    // Decision variables: capacity of each renewable resource
    private HxExpression[] capacity;

    // Objective to minimize: end of the last task of the last job
    private HxExpression makespan;

    // Objective to minimize: total cost of all resources
    private HxExpression totalCost;

    public 
        Racp(HexalyOptimizer optimizer) throws IOException {
        this.optimizer = optimizer;
    }

    // The input files follow the "Patterson" format
    private void readInstance(String fileName, String dline) throws IOException {
        try (Scanner input = new Scanner(new File(fileName))) {
            input.useLocale(Locale.ROOT);
            nbTasks = input.nextInt();
            nbResources = input.nextInt();

            // The maximum capacity of each resource
            resourceCost = new int[nbResources];
            for (int r = 0; r < nbResources; ++r) {
                resourceCost[r] = input.nextInt();
            }

            duration = new int[nbTasks];
            weight = new int[nbTasks][nbResources];
            nbSuccessors = new int[nbTasks];
            successors = new int[nbTasks][];

            for (int i = 0; i < nbTasks; ++i) {
                duration[i] = input.nextInt();
                for (int r = 0; r < nbResources; ++r) {
                    weight[i][r] = input.nextInt();
                }
                nbSuccessors[i] = input.nextInt();
                successors[i] = new int[nbSuccessors[i]];
                for (int s = 0; s < nbSuccessors[i]; ++s) {
                    successors[i][s] = input.nextInt() - 1;
                }
                horizon += duration[i];
            }

            deadline = horizon;
            if (dline != null) {
                deadline = Integer.parseInt(dline);
            }
        }
    }

    public void solve(int timeLimit) {
        // Declare the optimization model
        HxModel model = optimizer.getModel();

        // Interval decision variables: time range of each task
        tasks = new HxExpression[nbTasks];
        for (int i = 0; i < nbTasks; ++i) {
            tasks[i] = model.intervalVar(0, horizon);

            // Task duration constraints
            model.constraint(model.eq(model.length(tasks[i]), duration[i]));
        }

        // Integer decision variables: capacity of each renewable resource
        capacity = new HxExpression[nbResources];
        maxCapacity = new int[nbResources];
        for (int r = 0; r < nbResources; ++r) {
            maxCapacity[r] = 0;
            for (int i = 0; i < nbTasks; ++i) {
                maxCapacity[r] += weight[i][r];
            }
            capacity[r] = model.intVar(0, maxCapacity[r]);
        }

        // Precedence constraints between the tasks
        for (int i = 0; i < nbTasks; ++i) {
            for (int s = 0; s < nbSuccessors[i]; ++s) {
                model.constraint(model.leq(model.end(tasks[i]),
                    model.start(tasks[successors[i][s]])));
            }
        }

        // Deadline constraint on the makespan
        makespan = model.max();
        for (int i = 0; i < nbTasks; ++i) {
            makespan.addOperand(model.end(tasks[i]));
        }
        model.constraint(model.leq(makespan, deadline));

        // Cumulative resource constraints
        for (int r = 0; r < nbResources; ++r) {
            final int rL = r;
            HxExpression capacityRespected = model.lambdaFunction(t -> {
                HxExpression totalWeight = model.sum();
                for (int i = 0; i < nbTasks; ++i) {
                    totalWeight.addOperand(model.prod(
                            weight[i][rL],
                            model.contains(tasks[i], t)));
                }
                return model.leq(totalWeight, capacity[rL]);
            });
            model.constraint(model.and(model.range(0, makespan), capacityRespected));
        }

        // Minimize the total cost
        totalCost = model.sum();
        for (int r = 0; r < nbResources; ++r) {
            totalCost.addOperand(model.prod(resourceCost[r], capacity[r]));
        }
        model.minimize(totalCost);

        model.close();

        // Parameterize the optimizer
        optimizer.getParam().setTimeLimit(timeLimit);

        optimizer.solve();
    }

    /*
     * Write the solution in a file with the following format:
     * - total cost
     * - makespan
     * - the capacity of each resource
     * - for each task, the task 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(totalCost.getValue());
            output.println(makespan.getValue());

            for (int r = 0; r < nbResources-1; ++r) {
                output.print(capacity[r].getValue() + " ");
            }
            output.println(capacity[nbResources-1].getValue());

            for (int i = 0; i < nbTasks; ++i) {
                output.println((i + 1) + " " + tasks[i].getIntervalValue().getStart() + " "
                        + tasks[i].getIntervalValue().getEnd());
            }
        }
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println("Usage: java Racp instanceFile [outputFile] [timeLimit] [deadline]");
            System.exit(1);
        }

        String instanceFile = args[0];
        String outputFile = args.length > 1 ? args[1] : null;
        String strTimeLimit = args.length > 2 ? args[2] : "60";
        String dline = args.length > 3 ? args[3] : null;

        try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
            Racp model = new Racp(optimizer);
            model.readInstance(instanceFile, dline);
            model.solve(Integer.parseInt(strTimeLimit));
            if (outputFile != null) {
                model.writeSolution(outputFile);
            }
        } catch (Exception ex) {
            System.err.println(ex);
            ex.printStackTrace();
            System.exit(1);
        }
    }
}