Resource Availability Cost Problem (RACP)
SchedulingProblem
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
- Add interval decision variables to model the tasks
- Add integer decision variables to model the capacities
- Define lambda functions to model the cumulative resource constraints
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\pythonpython racp.py instances\racp1.rcp
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_14_0/bin/pythonpython 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.libracp 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.dllRacp 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.jarjava -cp %HX_HOME%\bin\hexaly.jar;. Racp instances\racp1.rcp
- Compilation / Execution (Linux)
-
javac Racp.java -cp /opt/hexaly_14_0/bin/hexaly.jarjava -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);
}
}
}