Project Scheduling with Production and Consumption of Resources

Problem

In the Project Scheduling Problem with Production and Consumption of Resources, a project consists of a set of tasks to schedule. 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.

The problem also involves two sets of resources. On the one hand, renewable resources are occupied by a task and then freed as soon as the task ends. On the other hand, inventory resources are consumed and/or produced by the tasks. Each task has a given resource requirement, or weight, for each renewable resource. Renewable resources have a given capacity: they can process several tasks at once as long as their cumulated weights never exceed the capacity. The tasks also consume a certain amount of each inventory resource when they start, and produce a certain amount when they end. Each inventory resource has an initial level. The levels decrease whenever a task starts and consumes resources, and increases when a task ends and produces resources.

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 come from Koné et al. The format of the data files is as follows:

  • First line: number of tasks, number of renewable resources, number of inventory resources
  • Second line: maximum capacity for each renewable resource, initial level for each inventory resource
  • From the third line, for each task:
    • Duration of the task
    • Renewable resource requirements (weights) for each resource
    • Inventory resource consumption (at the beginning) and production (at the end) for each inventory resource
    • Number of successors
    • Task ID of each successor

Program

The Hexaly Model for the Project Scheduling Problem with Consumption and Production of Resources uses interval decision variables to model the tasks. The length of each interval is equal to the duration of the corresponding task.

We then write the precedence constraints: each task must end before any of its successors can start.

The renewable resource constraints can be formulated as follows: for each renewable 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.

We can then model the inventory resource constraints in a similar way. For each inventory resource and each time slot t, the initial level plus the total amount of resource produced by the tasks ending before t must be greater or equal to the total amount of resource consumed by the tasks starting before t. We use another variadic ‘and’ formulation with a lambda function to ensure the resource level remains positive at any time.

The makespan to minimize is the time when all the tasks have ended.

Execution
hexaly rcpsp_producer_consumer.hxm inFileName=instances/ConsProd_bl2002.rcp [outFileName=] [hxTimeLimit=]
use io;

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

    inFile = io.openRead(inFileName);
    nbTasks = inFile.readInt(); // Number of operations
    nbResources = inFile.readInt(); // Number of renewable resources
    nbInventories = inFile.readInt(); // Number of inventories
    capacity[0...nbResources] = inFile.readInt();
    initLevel[0...nbInventories] = inFile.readInt();

    for [i in 0...nbTasks] {
        duration[i] = inFile.readInt();
        weight[i][0...nbResources] = inFile.readInt();
        for [r in 0...nbInventories] {
            startCons[i][r] = inFile.readInt();
            endProd[i][r] = inFile.readInt(); 
        }
        nbSuccessors[i] = inFile.readInt();
        successors[i][0...nbSuccessors[i]] = inFile.readInt()-1;
    }
    
    inFile.close();
    
    finishLevel[r in 0...nbInventories] = initLevel[r];
    for [r in 0...nbInventories] {
        for [i in 0...nbTasks] {
            finishLevel[r] -= startCons[i][r];
            finishLevel[r] += endProd[i][r];
        }
    }
    
    timeHorizon = sum[i in 0...nbTasks](duration[i]);
}

/* Declare the optimization model */
function model() {
    tasks[i in 0...nbTasks] <- interval(0, timeHorizon);
    
    for [i in 0...nbTasks] {
        constraint length(tasks[i]) == duration[i];
        
        for [s in 0...nbSuccessors[i]] {
            constraint end(tasks[i]) <=  start(tasks[successors[i][s]]);
        }
    }
    
    makespan <- max[i in 0...nbTasks] (end(tasks[i]));
    
    for [r in 0...nbResources] {
        constraint and(0...makespan,
            t => (sum[i in 0...nbTasks](weight[i][r] * (contains(tasks[i],t))) <= capacity[r]));
    }
    
    for [r in 0...nbInventories] {
        constraint and(0...makespan,
            t => (0 <=  initLevel[r] +
                  sum[i in 0...nbTasks]((endProd[i][r] * (end(tasks[i]) <= t)) - 
                                      (startCons[i][r] * (start(tasks[i]) <= t)))));
    }
    
    minimize makespan;
}

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

function output() {
    if (outFileName != nil) {
        outFile = io.openWrite(outFileName);
        println("Solution written in file ", outFileName);
        outFile.println(makespan.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 rcpsp_producer_consumer.py instances\ConsProd_bl2002.rcp
Execution (Linux)
export PYTHONPATH=/opt/hexaly_13_0/bin/python
python rcpsp_producer_consumer.py instances/ConsProd_bl2002.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])

    # Number of inventories
    nb_inventories = int(first_line[2])
    
    second_line = lines[1].split()

    # Maximum capacity of each resource
    capacity = [int(second_line[r]) for r in range(nb_resources)]

    # Initial level of each inventory
    init_level = [int(second_line[r + nb_resources]) for r in range(nb_inventories)]

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

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

    # Inventory consumed at beginning of task i
    start_cons = [[] for i in range(nb_tasks)]

    # Inventory produced at end of task i
    end_prod = [[] for i in range(nb_tasks)]

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

    # Successors of each task i
    successors = [[] for i 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)]
        start_cons[i] = [int(line[2*r + nb_resources + 1]) for r in range(nb_inventories)]
        end_prod[i] = [int(line[2*r + nb_resources + 2]) for r in range(nb_inventories)]
        nb_successors[i] = int(line[2*nb_inventories + nb_resources + 1])
        successors[i] = [int(line[2*nb_inventories + nb_resources + 2 + s]) - 1 for s in range(nb_successors[i])]

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

    return (nb_tasks, nb_resources, nb_inventories, capacity, init_level, duration, weight, start_cons, end_prod, nb_successors, successors, horizon)


def main(instance_file, output_file, time_limit):
    nb_tasks, nb_resources, nb_inventories, capacity, init_level, duration, weight, start_cons, end_prod, nb_successors, successors, horizon = read_instance(
        instance_file)

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

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

        # 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(tasks[i] < tasks[successors[i][s]])

        # Makespan: end of the last task
        makespan = model.max([model.end(tasks[i]) for i in range(nb_tasks)])

        # 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))

        # Non-negative inventory constraints
        for r in range(nb_resources):
            inventory_value = model.lambda_function(
                lambda t: model.sum(end_prod[i][r] * (model.end(tasks[i]) <= t)
                                        - start_cons[i][r] * (model.start(tasks[i]) <= t)
                                    for i in range(nb_tasks)) 
                                    + init_level[r]
                >= 0)
            model.constraint(model.and_(model.range(makespan), inventory_value))

        # 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 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(makespan.value) + "\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 rcpsp_producer_consumer.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_producer_consumer.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.lib
rcpsp_producer_consumer instances\ConsProd_bl2002.rcp
Compilation / Execution (Linux)
g++ rcpsp_producer_consumer.cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o rcpsp_producer_consumer
./rcpsp_producer_consumer instances/ConsProd_bl2002.rcp
#include "optimizer/hexalyoptimizer.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <limits>
#include <numeric>
#include <vector>

using namespace hexaly;

class RcpspProducerConsumer {
private:
    // Number of tasks
    int nbTasks;
    // Number of resources
    int nbResources;
    // Number of inventories
    int nbInventories;
    // Maximum capacity of each resource
    std::vector<int> capacity;
    // Initial level of each inventory
    std::vector<int> initLevel;
    // Duration of each task
    std::vector<int> duration;
    // Resource weight of resource r required for task i
    std::vector<std::vector<int>> weight;
    // Inventory consumed at beginning of task
    std::vector<std::vector<int>> startCons;
    // Inventory produced at end of task
    std::vector<std::vector<int>> endProd;
    // Number of successors
    std::vector<int> nbSuccessors;
    // Successors for each task i
    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 of each task
    std::vector<HxExpression> tasks;
    // Objective = minimize the makespan: end of the last task of the last job
    HxExpression makespan;

public:
    RcpspProducerConsumer(const std::string& fileName) : optimizer() {}

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

        infile >> nbTasks;
        infile >> nbResources;
        infile >> nbInventories;

        // The maximum capacity of each resource
        capacity.resize(nbResources);
        for (int r = 0; r < nbResources; ++r) {
            infile >> capacity[r];
        }
        // The initial level of each inventory
        initLevel.resize(nbInventories);
        for (int r = 0; r < nbInventories; ++r) {
            infile >> initLevel[r];
        }

        duration.resize(nbTasks);
        weight.resize(nbTasks);
        startCons.resize(nbTasks);
        endProd.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];
            }
            startCons[i].resize(nbInventories);
            endProd[i].resize(nbInventories);
            for (int r = 0; r < nbInventories; ++r) {
                infile >> startCons[i][r];
                infile >> endProd[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];
        }

        infile.close();
    }

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

        // Interval decisions: 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]);
        }

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

        // Makespan: end of the last task
        makespan = model.max();
        for (int i = 0; i < nbTasks; ++i) {
            makespan.addOperand(model.end(tasks[i]));
        }

        // 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));
        }

        // Non-negative inventory constraints
        for (int r = 0; r < nbInventories; ++r) {
            HxExpression inventoryValue = model.createLambdaFunction([&](HxExpression t) {
                HxExpression totalValue = model.sum();
                totalValue.addOperand(initLevel[r]);
                for (int i = 0; i < nbTasks; ++i) {
                    HxExpression taskValue = endProd[i][r] * (t >= model.end(tasks[i])) - startCons[i][r] * (t >= model.start(tasks[i]));
                    totalValue.addOperand(taskValue);
                }
                return model.geq(totalValue, 0);
            });
            model.constraint(model.and_(model.range(0, makespan), inventoryValue));
        }

        // Minimize the 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 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;
        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: rcpsp_producer_consumer 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";

    RcpspProducerConsumer 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 RcpspProducerConsumer.cs /reference:Hexaly.NET.dll
RcpspProducerConsumer instances\ConsProd_bl2002.rcp
using System;
using System.IO;
using Hexaly.Optimizer;

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

    // Number of resources
    private int nbResources;

    // Number of inventories
    private int nbInventories;

    // Maximum capacity of each resource
    private int[] capacity;

    // Initial level of each inventory
    private int[] initLevel;

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

    // Resource weight of resource r required for task i
    private int[,] weight;
    
    // Inventory consumed at beginning of task
    private int[,] startCons;

    // Inventory produced at end of task
    private int[,] endProd;

    // Number of successors
    private int[] nbSuccessors;

    // Successors for each task i
    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 of each task
    private HxExpression[] tasks;

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

    public RcpspProducerConsumer(string fileName)
    {
        optimizer = new HexalyOptimizer();
    }

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

    // The input files follow the "Patterson" format
    private void ReadInstance(string fileName)
    {
        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]);
            nbInventories = int.Parse(splitted[2]);

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


            duration = new int[nbTasks];
            weight = new int[nbTasks, nbResources];
            startCons = new int[nbTasks, nbInventories];
            endProd = new int[nbTasks, nbInventories];
            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]);
                for (int r = 0; r < nbInventories; ++r) {
                    startCons[i, r] = int.Parse(splitted[2*r + nbResources + 1]);
                    endProd[i, r] = int.Parse(splitted[2*r + nbResources + 2]);
                }                   
                nbSuccessors[i] = int.Parse(splitted[2*nbInventories + nbResources + 1]);
                successors[i] = new int[nbSuccessors[i]];
                for (int s = 0; s < nbSuccessors[i]; ++s)
                    successors[i][s] = int.Parse(splitted[s + 2*nbInventories + nbResources + 2]) - 1;
                horizon += duration[i];
            }
        }
    }

    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]);
        }

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

        // Makespan: end of the last task
        makespan = model.Max();
        for (int i = 0; i < nbTasks; ++i)
            makespan.AddOperand(model.End(tasks[i]));

        // 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));
        }

        // Non-negative inventories constraints
        for (int r = 0; r < nbInventories; ++r)
        {
            HxExpression inventoryValue = model.LambdaFunction(t =>
                {
                    HxExpression totalValue = model.Sum();
                    totalValue.AddOperand(initLevel[r]);
                    for (int i = 0; i < nbTasks; ++i)
                    {
                        totalValue.AddOperand(endProd[i,r] * (model.End(tasks[i]) <= t) - startCons[i,r] * (model.Start(tasks[i]) <= t));
                    }
                    return totalValue >= 0;
                }
            );
            model.Constraint(model.And(model.Range(0, makespan), inventoryValue));
        }

        // Minimize the 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 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());
            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: RcpspProducerConsumer 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 (RcpspProducerConsumer model = new RcpspProducerConsumer(instanceFile))
        {
            model.ReadInstance(instanceFile);
            model.Solve(int.Parse(strTimeLimit));
            if (outputFile != null)
                model.WriteSolution(outputFile);
        }
    }
}
Compilation / Execution (Windows)
javac RcpspProducerConsumer.java -cp %HX_HOME%\bin\hexaly.jar
java -cp %HX_HOME%\bin\hexaly.jar;. RcpspProducerConsumer instances\ConsProd_bl2002.rcp
Compilation / Execution (Linux)
javac RcpspProducerConsumer.java -cp /opt/hexaly_13_0/bin/hexaly.jar
java -cp /opt/hexaly_13_0/bin/hexaly.jar:. RcpspProducerConsumer instances/ConsProd_bl2002.rcp
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;

public class RcpspProducerConsumer {
    // Number of tasks
    private int nbTasks;
    // Number of resources
    private int nbResources;
    // Number of inventories
    private int nbInventories;
    // Maximum capacity of each resource
    private int[] capacity;
    // Initial level of each inventory
    private int[] initLevel;
    // Duration of each task
    private int[] duration;
    // Resource weight of resource r required for task i
    private int[][] weight;
    // Inventory consumed at beginning of task
    private int[][] startCons;
    // Inventory produced at end of task
    private int[][] endProd;
    // Number of successors
    private int[] nbSuccessors;
    // Successors for each task i
    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 of each task
    private HxExpression[] tasks;
    // Objective = minimize the makespan: end of the last task of the last job
    private HxExpression makespan;

    public RcpspProducerConsumer(HexalyOptimizer optimizer, String fileName) throws IOException {
        this.optimizer = optimizer;
    }

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

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

            duration = new int[nbTasks];
            weight = new int[nbTasks][nbResources];
            startCons = new int[nbTasks][nbInventories];
            endProd = new int[nbTasks][nbInventories];
            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();
                }
                for (int r = 0; r < nbInventories; ++r) {
                    startCons[i][r] = input.nextInt();
                    endProd[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];
            }
        }
    }

    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.intervalVar(0, horizon);

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

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

        // Makespan: end of the last task
        makespan = model.max();
        for (int i = 0; i < nbTasks; ++i) {
            makespan.addOperand(model.end(tasks[i]));
        }

        // 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));
        }

        // Non-negative inventories constraints
        for (int r = 0; r < nbInventories; ++r) {
            final int rL = r;
            HxExpression inventoryValue = model.lambdaFunction(t -> {
                HxExpression totalValue = model.sum();
                totalValue.addOperand(initLevel[rL]);
                for (int i = 0; i < nbTasks; ++i) {
                    totalValue.addOperand(model.sub(model.prod(
                            endProd[i][rL], 
                            model.leq(model.end(tasks[i]), t)),
                        model.prod(startCons[i][rL],
                                model.leq(model.start(tasks[i]), t))));
                }
                return model.geq(totalValue, 0);
            });
            model.constraint(model.and(model.range(0, makespan), inventoryValue));
        }

        // Minimize the 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 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());

            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 RcpspProducerConsumer 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()) {
            RcpspProducerConsumer model = new RcpspProducerConsumer(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);
        }
    }
}