Constrained Pit Limit Problem (CPIT)
Packing SchedulingProblem
In the Constrained Pit Limit Problem (CPIT), we consider a set of blocks that can be extracted from a mining pit during several time periods. Extracting a block requires a certain amount of resources. For each type of resource, the sum of resources consumed over a period cannot exceed a certain limit. Each block must be extracted during the same period or during a later period than all its predecessors. The profit of a block’s extraction depends on the block and the period of extraction. Finally, the objective function is to maximize profits made from block extractions.
Principles learned
- Add set decision variables to model the extraction made during each period
- Retrieve the index of each block’s extraction period thanks to the ‘find‘ operator
- Define a lambda function to sum the profit over each period
Data
The instances provided come from Espinoza et al. and are formatted as follows:
- A .cpit file stores data associated with the Constrained Pit Limit Problem (CPIT):
- The number of blocks
- The number of periods
- The number of resources
- The discount rate (used to compute the profit of a block’s extraction)
- For each resource and each period, bounds for the consumption of the resource over the period
- For each block, its profit
- For each block and each resource, the consumption of the resource by the extraction of the block (only written in the file if not null)
- A .prec file stores each block’s predecessors.
Program
The Hexaly model for the Constrained Pit Limit problem (CPIT) uses set decision variables. Each set represents the blocks extracted during a period. We add a dummy period to model blocks that remain unextracted. Thanks to the ‘partition‘ operator, we ensure that each block is extracted at most once.
For a given type of resource, a lambda function applies the ‘sum’ operator over all extracted blocks to compute the total amount of resources consumed over a period. Note that the number of terms in this sum varies during the search, along with the size of the set. We can then constrain this quantity depending on the bounds defined for the problem.
Using the ‘find‘ operator, we can then retrieve the index of the period chosen to extract each block. This allows us to write the precedence constraints between blocks: each block must be extracted during the same period or during a later period than its predecessors.
The profit of a period is computed with a lambda function to apply the ‘sum’ operator over blocks extracted during that period. Once again, the number of terms in this sum varies during the search, along with the size of the set. We maximize profits over all periods.
- Execution
-
hexaly constrained_pit_limit.hxm inFileName=instances/newman1.cpit [solFileName=] [hxTimeLimit=]
use io;
use math;
// Get the name of the precedence file
function getPrecFileName(){
if (inFileName == nil) inFileName = "instances/newman1.cpit";
beginIndex = inFileName.indexOf("instances/") + 10; // "instances/" is 10 characters
endIndex = inFileName.indexOf(".cpit");
return inFileName.substring(0,beginIndex)+"prec/"
+ inFileName.substring(beginIndex,endIndex-beginIndex) + ".prec";
}
// Read instance data
function input() {
if (inFileName == nil) inFileName = "instances/newman1.cpit";
// getting information about the problem
inFile = io.openRead(inFileName);
inFile.readln(); // Name of the instance
inFile.readln(); // Type of problem
local line = inFile.readln(); // NBlocks
nbBlocks = line.trim().split(" ")[1].toInt();
line = inFile.readln(); // NPeriods
nbPeriods = line.trim().split(" ")[1].toInt();
line = inFile.readln(); // NResources
nbResources = line.trim().split(" ")[1].toInt();
line = inFile.readln(); // Discount Rate
discountRate = line.trim().split(" ")[1].toDouble();
// Resource Constraints Limits
inFile.readln();
// Resource Constraints can be <=, >= or both
// We suppose all constraints have the same form
resourceLB = false;
resourceUB = false;
for [r in 0...nbResources][t in 0...nbPeriods]{
line = inFile.readln();
local lineSplit = line.trim().split(" ");
if (lineSplit.count()==5){
resourceLowerBound[lineSplit[0].toInt()][lineSplit[1].toInt()]
= lineSplit[3].toInt();
resourceUpperBound[lineSplit[0].toInt()][lineSplit[1].toInt()]
= lineSplit[4].toInt();
resourceLB = true;
resourceUB = true;
}
else{
if (lineSplit[2] == "G"){
resourceLowerBound[lineSplit[0].toInt()][lineSplit[1].toInt()]
= lineSplit[3].toInt();
resourceLB = true;
}
else{
resourceUpperBound[lineSplit[0].toInt()][lineSplit[1].toInt()]
= lineSplit[3].toInt();
resourceUB = true;
}
}
}
// Objective Function
line = inFile.readln();
for [b in 0...nbBlocks]{
line = inFile.readln();
local lineSplit = line.trim().split(" ");
profit[lineSplit[0].toInt()] = lineSplit[1].toDouble();
}
discountedProfit[t in 0...nbPeriods][b in 0...nbBlocks] = profit[b]/pow(1+discountRate,t);
// Resource Constraints coefficients
line = inFile.readln();
// Some values are not defined and therefore equal 0
// Here we index by resource r first, then the block index b
for [r in 0...nbResources][b in 0...nbBlocks] resourceUse[r][b] = 0.0;
line = inFile.readln();
while (line != "EOF"){
local lineSplit = line.trim().split(" ");
resourceUse[lineSplit[1].toInt()][lineSplit[0].toInt()] = lineSplit[2].toDouble();
line = inFile.readln();
}
inFile.close();
// Getting information about precedence
inFile = io.openRead(getPrecFileName());
predecessors = {};
for [b in 0...nbBlocks]{
line = inFile.readln();
lineSplit = line.trim().split(" ");
predecessors.add({});
if (lineSplit[1].toInt()>0){
for [p in 0...lineSplit[1].toInt()]{
predecessors[lineSplit[0].toInt()].add(lineSplit[2+p].toInt());
}
}
}
inFile.close();
}
function model() {
// Decision variable: periods[t] is the set of blocks extracted during period t
// There is a dummy period for unextracted blocks
periods[t in 0...nbPeriods+1] <- set(nbBlocks);
// Each block must be assigned to a period (or the dummy period)
constraint partition(periods);
// Resources used to extract at period t must be within defined bounds
sumResourceConsumption[r in 0...nbResources][t in 0...nbPeriods]
<- sum(periods[t], b => resourceUse[r][b]);
for [r in 0...nbResources][t in 0...nbPeriods]{
if (resourceLB){
constraint sumResourceConsumption[r][t] >= resourceLowerBound[r][t];
}
if (resourceUB){
constraint sumResourceConsumption[r][t] <= resourceUpperBound[r][t];
}
}
// A block can only be extracted if all its predecessors have been extracted
blockExtractionPeriod[b in 0...nbBlocks] <- find(periods, b);
for [b in 0...nbBlocks]{
for [pred in predecessors[b]]{
constraint blockExtractionPeriod[b] >= blockExtractionPeriod[pred];
}
}
// Objective function: maximizing profit over extracted blocks
objective <- sum[t in 0...nbPeriods](sum(periods[t], b => discountedProfit[t][b]));
maximize objective;
}
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 60;
}
function output() {
if (outFileName != nil) {
outFile = io.openWrite(outFileName);
outFile.println(objective.value);
for [b in 0...nbBlocks]{
if (blockExtractionPeriod[b].value < nbPeriods){
outFile.println(b + " "+ blockExtractionPeriod[b].value);
}
}
outFile.close();
}
}
- Execution (Windows)
-
set PYTHONPATH=%HX_HOME%\bin\pythonpython constrained_pit_limit.py instances\newman1.cpit
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_14_0/bin/pythonpython constrained_pit_limit.py instances/newman1.cpit
import hexaly.optimizer
import sys
def get_prec_file_name(instance_file):
begin_index = instance_file.index("instances") + 10 # the size of "instances/" is 10
end_index = instance_file.index(".cpit")
return instance_file[0:begin_index] + "prec/" + \
instance_file[begin_index:end_index] + ".prec"
def read_instance(instance_file):
# Reading the first file containing constants of the problem
with open(instance_file) as f:
line = f.readline() # Name of the instance
line = f.readline() # Type of problem
line = f.readline().split(" ") # Number of blocks
nb_blocks = int(line[1])
line = f.readline().split(" ") # Number of periods
nb_periods = int(line[1])
line = f.readline().split(" ") # Number of resources
nb_resources = int(line[1])
line = f.readline().split(" ") # Discount rate
discount_rate = float(line[1])
# Resource Constraints Limits
line = f.readline()
resource_LB,resource_UB = False, False
resource_lower_bound = []
resource_upper_bound = []
for r in range(nb_resources):
resource_lower_bound.append([])
resource_upper_bound.append([])
for t in range(nb_periods):
line = f.readline().split(" ")
if len(line) == 5:
resource_LB,resource_UB = True, True
resource_lower_bound[r].append(int(line[3]))
resource_upper_bound[r].append(int(line[4]))
else:
if line[2] == "G":
resource_lower_bound[r].append(int(line[3]))
resource_LB = True
else: # line[2] == "L"
resource_upper_bound[r].append(int(line[3]))
resource_UB = True
# Objective function
line = f.readline()
profit = [0 for b in range(nb_blocks)]
for b in range(nb_blocks):
line = f.readline().split(" ")
profit[int(line[0])] = float(line[1])
discounted_profit = [[profit[b]/pow(1+discount_rate,t) for b in range(nb_blocks)] \
for t in range(nb_periods)]
# Resource constraints coefficients
# Some values are not defined and therefore equal 0
# Here we index by resource r first, then the block index b
line = f.readline()
resource_use = [[0 for b in range(nb_blocks)] for r in range(nb_resources)]
line = f.readline().split(" ")
while line[0] != "EOF\n":
resource_use[int(line[1])][int(line[0])] = float(line[2])
line = f.readline().split(" ")
# Reading the second file containing precedence relations
with open(get_prec_file_name(instance_file)) as f:
precedence = {}
for b in range(nb_blocks):
line = f.readline()[:-1].split(" ")
if int(line[1]) > 0:
precedence[int(line[0])] = [int(line[i]) for i in range(2,len(line))]
return (nb_blocks, nb_periods, nb_resources, discount_rate, resource_LB, resource_UB, \
resource_lower_bound, resource_upper_bound, discounted_profit, resource_use, precedence)
def main(instance_name, output_file, time_limit):
nb_blocks, nb_periods, nb_resources, discount_rate, resource_LB, resource_UB, \
resource_lower_bound, resource_upper_bound, discounted_profit, resource_use_data, \
precedence = read_instance(instance_name)
with hexaly.optimizer.HexalyOptimizer() as optimizer:
model = optimizer.model # Declare the optimization model
# Decision variables: period_vars[t] is the set of blocks extracted during period t
# There is a dummy period for unextracted blocks
period_vars = [model.set(nb_blocks) for t in range(nb_periods+1)]
periods = model.array(period_vars)
model.constraint(model.partition(periods))
# Resources used to extract at period t must be within defined bounds
resource_use = []
for r in range(nb_resources):
resource_use.append(model.array(resource_use_data[r]))
consumption_lambda = [model.lambda_function(lambda b: resource_use[r][b]) \
for r in range(nb_resources)]
consumption_per_period = [[model.sum(period_vars[t],consumption_lambda[r]) \
for t in range(nb_periods)] for r in range(nb_resources)]
for r in range(nb_resources):
for t in range(nb_periods):
if resource_LB:
model.constraint(consumption_per_period[r][t] >= resource_lower_bound[r][t])
if resource_UB:
model.constraint(consumption_per_period[r][t] <= resource_upper_bound[r][t])
# A block can only be extracted if all its predecessors have been extracted
block_extraction_period = [model.find(periods, b) for b in range(nb_blocks)]
for b in range(nb_blocks):
if b in precedence.keys():
for pred in precedence[b]:
model.constraint(block_extraction_period[b] >= block_extraction_period[pred])
# Objective function: profit over extracted blocks
profits = [model.array(discounted_profit[t]) for t in range(nb_periods)]
profit_lambdas = [model.lambda_function(lambda b: profits[t][b]) for t in range(nb_periods)]
profit_per_period = [model.sum(period_vars[t],profit_lambdas[t]) for t in range(nb_periods)]
objective = model.sum(profit_per_period)
# Maximizing the objective
model.maximize(objective)
model.close()
# Parameterize
optimizer.param.time_limit = time_limit
optimizer.solve()
# Write the solution in a file following the format:
# - 1st line: value of the objective (as an integer)
# - following lines: (if the block is extracted) block's number, period's number
# - "EOF" to signal the end of the file
if output_file is not None:
with open(output_file, 'w') as f:
f.write("%d\n" % int(objective.value))
for b in range(nb_blocks):
if block_extraction_period[b].value < nb_periods:
f.write("{} {}\n".format(b, block_extraction_period[b].value))
f.write("EOF")
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python constrained_pit_limit.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 constrained_pit_limit.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly140.libconstrained_pit_limit instances\newman1.cpit
- Compilation / Execution (Linux)
-
g++ constrained_pit_limit.cpp -I/opt/hexaly_14_0/include -lhexaly140 -lpthread -o constrained_pit_limit
./constrained_pit_limit instances/newman1.cpit
#include "optimizer/hexalyoptimizer.h"
#include <fstream>
#include <iostream>
#include <vector>
using namespace hexaly;
using namespace std;
string getPrecFileName(const string& instanceFile){
size_t posBegin = instanceFile.find("instances/") + 10; // "instances/" is 10 characters
size_t posEnd = instanceFile.find(".cpit");
return instanceFile.substr(0,posBegin)+ "prec/" +
instanceFile.substr(posBegin,posEnd-posBegin) + ".prec";
}
class ConstrainedPitLimit {
private:
// Data from the problem
int nbBlocks;
int nbPeriods;
int nbResources;
double discountRate;
bool resourceLB;
bool resourceUB;
vector<vector<int>> resourceLowerBound;
vector<vector<int>> resourceUpperBound;
vector<double> profitData;
vector<vector<double>> discountedProfitData;
vector<vector<double>> resourceUseData;
vector<vector<int>> precedence;
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Decision variables
vector<HxExpression> periodVars;
// Intermediate expressions
vector<vector<HxExpression>> sumResourceConsumption;
vector<HxExpression> blockExtractionPeriod;
// Objective
HxExpression objective;
public :
// Read instance data
void readInstance(const string& instanceFile){
ifstream infile;
infile.exceptions(ifstream::failbit | ifstream::badbit);
string tmp;
// Reading the first file
infile.open(instanceFile);
infile >> tmp; // NAME:
infile >> tmp; // Name of the problem
infile >> tmp; // TYPE:
infile >> tmp; // CPIT
infile >> tmp; // NBLOCKS:
infile >> nbBlocks;
infile >> tmp; // NPERIODS:
infile >> nbPeriods;
infile >> tmp; // NRESOURCE_SIDE_CONSTRAINTS:
infile >> nbResources;
infile >> tmp; // DISCOUNT_RATE:
infile >> discountRate;
infile >> tmp; // RESOURCE_CONSTRAINT_LIMITS:
resourceLowerBound.resize(nbResources);
resourceUpperBound.resize(nbResources);
// Resource Constraints can be <=, >= or both
// We suppose all constraints have the same form
resourceLB = false;
resourceUB = false;
for (int it=0; it<nbResources*nbPeriods; it++){
int r,t;
infile >> r; // resource index
infile >> t; // period index
infile >> tmp; // type of constraint
if (tmp == "L"){
if (resourceUpperBound[r].size() == 0){
resourceUpperBound[r].resize(nbPeriods);
resourceUB = true;
}
infile >> resourceUpperBound[r][t];
}else{
if (tmp == "I"){
if (resourceLowerBound[r].size() == 0){
resourceLowerBound[r].resize(nbPeriods);
resourceUpperBound[r].resize(nbPeriods);
resourceLB = true;
resourceUB = true;
}
infile >> resourceLowerBound[r][t];
infile >> resourceUpperBound[r][t];
}else{ // tmp == "G"
if (resourceLowerBound[r].size() == 0){
resourceLowerBound[r].resize(nbPeriods);
resourceLB = true;
}
infile >> resourceLowerBound[r][t];
}
}
}
infile >> tmp; // Objective Function
profitData.resize(nbBlocks);
for (int b=0; b<nbBlocks; b++){
infile >> tmp; // index
infile >> profitData[stoi(tmp)];
}
discountedProfitData.resize(nbPeriods);
for (int t=0; t<nbPeriods; t++){
discountedProfitData[t].resize(nbBlocks);
double discountFactor = pow(1+discountRate,t);
for (int b=0; b<nbBlocks; b++){
discountedProfitData[t][b] = profitData[b]/discountFactor;
}
}
infile >> tmp; // Resource Constraints coefficients
resourceUseData.resize(nbResources);
// Some values are not defined and therefore equal 0
// Here we index by resource r first, then the block index b
for (int r=0; r<nbResources; r++){
for (int b=0; b<nbBlocks; b++){
resourceUseData[r].push_back(0.0);
}
}
infile >> tmp; // if not "EOF" then it is the block index
while(tmp != "EOF"){
int r;
infile >> r;
infile >> resourceUseData[r][stoi(tmp)];
infile >> tmp;
}
infile.close();
// Reading the second file
infile.open(getPrecFileName(instanceFile));
precedence.resize(nbBlocks);
for (int it=0; it<nbBlocks; it++){
int b, nbOfPredecessors;
infile >> b;
infile >> nbOfPredecessors;
for (int pred=0; pred<nbOfPredecessors; pred++){
int newPredecessor;
infile >> newPredecessor;
precedence[b].push_back(newPredecessor);
}
}
infile.close();
}
void solve(int limit){
// Declare the optimization model
HxModel model = optimizer.getModel();
// Decision variable: periods[t] is the set of blocks extracted during period t
// There is a dummy period for unextracted blocks
periodVars.resize(nbPeriods+1);
HxExpression periods = model.array();
for (int t=0; t<nbPeriods+1; t++){
periodVars[t] = model.setVar(nbBlocks);
periods.addOperand(periodVars[t]);
}
model.constraint(model.partition(periods));
// Create Hexaly array to be able to access them with "at" operators
vector<HxExpression> resourceUse = vector<HxExpression>(nbResources);
for (int r=0; r<nbResources; r++){
resourceUse[r] = model.array(resourceUseData[r].begin(),resourceUseData[r].end());
}
// Resources used to extract at period t must be within defined bounds
vector<HxExpression> consumptionLambda = {};
sumResourceConsumption.resize(nbResources);
for (int r=0; r<nbResources; r++){
consumptionLambda.push_back(model.lambdaFunction([&](HxExpression b)
{ return resourceUse[r][b]; }));
sumResourceConsumption[r].resize(nbPeriods);
for (int t=0; t<nbPeriods; t++){
sumResourceConsumption[r][t] = model.sum(periodVars[t], consumptionLambda[r]);
if (resourceLB){
model.constraint(sumResourceConsumption[r][t] >= resourceLowerBound[r][t]);
}
if (resourceUB){
model.constraint(sumResourceConsumption[r][t] <= resourceUpperBound[r][t]);
}
}
}
// A block can only be extracted if all its predecessors have been extracted
blockExtractionPeriod.resize(nbBlocks);
for (int b=0; b<nbBlocks; b++){
blockExtractionPeriod[b] = model.find(periods,b);
}
for (int b=0; b<nbBlocks; b++){
for(int pred: precedence[b]){
model.constraint(blockExtractionPeriod[b] >= blockExtractionPeriod[pred]);
}
}
// Objective function: profit over extracted blocks
vector<HxExpression> profits;
vector<HxExpression> profitLambdas;
objective = model.sum();
for (int t=0; t<nbPeriods; t++){
profits.push_back(model.array(discountedProfitData[t].begin(),discountedProfitData[t].end()));
profitLambdas.push_back(model.lambdaFunction([&](HxExpression b){ return profits[t][b]; }));
objective.addOperand(model.sum(periodVars[t],profitLambdas[t]));
}
model.maximize(objective);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(limit);
optimizer.solve();
}
// Write the solution in a file following the format:
// * - 1st line: value of the objective (as an integer)
// * - following lines: (if the block is extracted) block's number, period's number
// * - "EOF" to signal the end of the file
void writeSolution(const string& fileName) {
ofstream outfile;
outfile.exceptions(ofstream::failbit | ofstream::badbit);
outfile.open(fileName.c_str());
outfile << int(objective.getDoubleValue()) << endl;
for (int b = 0; b < nbBlocks; b++){
if (blockExtractionPeriod[b].getIntValue() < nbPeriods){
outfile << b << " " << blockExtractionPeriod[b].getIntValue() << endl;
}
}
outfile << "EOF";
outfile.close();
}
};
int main(int argc, char** argv) {
if (argc < 2) {
cerr << "Usage: constrained_pit_limit inputFile [outputFile] [timeLimit]" << endl;
return 1;
}
const char* instanceFile = argv[1];
const char* solFile = argc > 2 ? argv[2] : NULL;
const char* strTimeLimit = argc > 3 ? argv[3] : "60";
try{
ConstrainedPitLimit model;
model.readInstance(instanceFile);
model.solve(atoi(strTimeLimit));
if (solFile != NULL)
model.writeSolution(solFile);
return 0;
} catch (const exception& e) {
cerr << "An error occurred: " << e.what() << endl;
return 1;
}
}
- Compilation / Execution (Windows)
-
copy %HX_HOME%\bin\Hexaly.NET.dll .csc ConstrainedPitLimit.cs /reference:Hexaly.NET.dllConstrainedPitLimit instances\newman1.cpit
using System;
using System.IO;
using System.Collections.Generic;
using System.Globalization;
using Hexaly.Optimizer;
public class ConstrainedPitLimit : IDisposable
{
// Data from the problem
public int nbBlocks;
public int nbPeriods;
public int nbResources;
public float discountRate;
public bool resourceLB;
public bool resourceUB;
public int[][] resourceLowerBound;
public int[][] resourceUpperBound;
public float[] profitData;
public double[][] discountedProfitData;
public float[][] resourceUseData;
public List<int>[] precedence;
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Decision variables
HxExpression[] periodVars;
// Intermediate expressions
HxExpression[][] sumResourceConsumption;
HxExpression[] blockExtractionPeriod;
// Objective
HxExpression objective;
public ConstrainedPitLimit()
{
optimizer = new HexalyOptimizer();
}
public void Dispose()
{
if (optimizer != null)
optimizer.Dispose();
}
// Get the name of the precedence file
string getPrecFileName(string instanceFile) {
int beginIndex = instanceFile.IndexOf("instances/") + 10; // "instances/" is 10 characters
int endIndex = instanceFile.IndexOf(".cpit");
return instanceFile.Substring(0, beginIndex) + "prec/" +
instanceFile.Substring(beginIndex, endIndex - beginIndex) + ".prec";
}
// Read instance data
void ReadInstance(string instanceFile)
{
// Reading the first file
using (StreamReader input = new StreamReader(instanceFile))
{
string[] line;
input.ReadLine(); // Name
input.ReadLine(); // Type of problem
line = input.ReadLine().Split(new[] { ' ' }); // NBlocks
nbBlocks = int.Parse(line[1]);
line = input.ReadLine().Split(new[] { ' ' }); // NPeriods
nbPeriods = int.Parse(line[1]);
line = input.ReadLine().Split(new[] { ' ' }); // NResources
nbResources = int.Parse(line[1]);
line = input.ReadLine().Split(new[] { ' ' }); // Discount Rate
discountRate = float.Parse(line[1], CultureInfo.InvariantCulture);
// Resource Constraints Limits
input.ReadLine();
// Resource Constraints can be <=, >= or both
// we suppose all constraints have the same form
resourceLB = false;
resourceUB = false;
resourceLowerBound = new int[nbResources][];
resourceUpperBound = new int[nbResources][];
for (int r = 0; r < nbResources; r++)
{
resourceLowerBound[r] = new int[nbPeriods];
resourceUpperBound[r] = new int[nbPeriods];
for (int t = 0; t < nbPeriods; t++)
{
line = input.ReadLine().Split(new[] { ' ' });
if (line[2] == "I")
{
resourceLB = true;
resourceUB = true;
resourceLowerBound[int.Parse(line[0])][int.Parse(line[1])]
= int.Parse(line[3]);
resourceUpperBound[int.Parse(line[0])][int.Parse(line[1])]
= int.Parse(line[4]);
}
else
{
if (line[2] == "G")
{
resourceLB = true;
resourceLowerBound[r][t] = int.Parse(line[3]);
}
else // line[2] == "L"
{
resourceUB = true;
resourceUpperBound[r][t] = int.Parse(line[3]);
}
}
}
}
// Objective Function
input.ReadLine();
profitData = new float[nbBlocks];
for (int b = 0; b < nbBlocks; b++)
{
line = input.ReadLine().Split(new[] { ' ' });
profitData[b] = float.Parse(line[1], CultureInfo.InvariantCulture);
}
discountedProfitData = new double[nbPeriods][];
for (int t = 0; t < nbPeriods; t++)
{
discountedProfitData[t] = new double[nbBlocks];
double discountFactor = Math.Pow(1 + discountRate, t);
for (int b = 0; b < nbBlocks; b++)
{
discountedProfitData[t][b] = profitData[b] / discountFactor;
}
}
// Resource Constraints coefficients
input.ReadLine();
// Some values are not defined and therefore equal 0
// Here we index by resource r first, then the block index b
resourceUseData = new float[nbResources][];
for (int r = 0; r < nbResources; r++)
{
resourceUseData[r] = new float[nbBlocks];
for (int b = 0; b < nbBlocks; b++)
{
resourceUseData[r][b] = 0;
}
}
line = input.ReadLine().Split(new[] { ' ' });
while (line[0] != "EOF")
{
int b = int.Parse(line[0]);
int r = int.Parse(line[1]);
resourceUseData[r][b] = float.Parse(line[2], CultureInfo.InvariantCulture);
line = input.ReadLine().Split(new[] { ' ' });
}
}
// Reading the second file
using (StreamReader input = new StreamReader(getPrecFileName(instanceFile)))
{
string[] line;
precedence = new List<int>[nbBlocks];
for (int b = 0; b < nbBlocks; b++)
{
line = input.ReadLine().Split(new[] { ' ' });
if (int.Parse(line[1]) > 0)
{
precedence[b] = new List<int>();
for (int pred = 0; pred < int.Parse(line[1]); pred++)
{
precedence[b].Add(int.Parse(line[pred + 2]));
}
}
}
}
}
void Solve(int limit)
{
// Declare the optimization model
HxModel model = optimizer.GetModel();
// Decision variable: periods[t] is the set of blocks extracted during period t
// There is a dummy period for unextracted blocks
periodVars = new HxExpression[nbPeriods + 1];
HxExpression periods = model.Array();
for (int t = 0; t < nbPeriods + 1; t++)
{
periodVars[t] = model.Set(nbBlocks);
periods.AddOperand(periodVars[t]);
}
model.Constraint(model.Partition(periods));
// Create Hexaly array to be able to access them with "at" operators
HxExpression[] resourceUse = new HxExpression[nbResources];
for (int r = 0; r < nbResources; r++)
{
resourceUse[r] = model.Array(resourceUseData[r]);
}
// Resources used to extract at period t must be within defined bounds
HxExpression[] consumptionLambda = new HxExpression[nbResources];
sumResourceConsumption = new HxExpression[nbResources][];
for (int r = 0; r < nbResources; r++)
{
consumptionLambda[r] = model.LambdaFunction(b =>
{
return resourceUse[r][b];
});
sumResourceConsumption[r] = new HxExpression[nbPeriods];
for (int t = 0; t < nbPeriods; t++)
{
sumResourceConsumption[r][t] = model.Sum(periodVars[t], consumptionLambda[r]);
if (resourceLB)
{
model.Constraint(sumResourceConsumption[r][t] >= resourceLowerBound[r][t]);
}
if (resourceUB)
{
model.Constraint(sumResourceConsumption[r][t] <= resourceUpperBound[r][t]);
}
}
}
// A block can only be extracted if all its predecessors have been extracted
blockExtractionPeriod = new HxExpression[nbBlocks];
for (int b = 0; b < nbBlocks; b++)
{
blockExtractionPeriod[b] = model.Find(periods, b);
}
for (int b = 0; b < nbBlocks; b++)
{
if (precedence[b] != null)
{
foreach (int pred in precedence[b])
{
model.Constraint(blockExtractionPeriod[b] >= blockExtractionPeriod[pred]);
}
}
}
// Objective function: profit over extracted blocks
HxExpression[] profits = new HxExpression[nbPeriods];
HxExpression[] profitLambdas = new HxExpression[nbPeriods];
objective = model.Sum();
for (int t = 0; t < nbPeriods; t++)
{
profits[t] = model.Array(discountedProfitData[t]);
profitLambdas[t] = model.LambdaFunction(b =>
{
return profits[t][b];
});
objective.AddOperand(model.Sum(periodVars[t], profitLambdas[t]));
}
model.Maximize(objective);
model.Close();
// Parametrize the optimizer
optimizer.GetParam().SetTimeLimit(limit);
optimizer.Solve();
}
// Write the solution in a file following the format:
// * - 1st line: value of the objective (as an integer)
// * - following lines: (if the block is extracted) block's number, period's number
// * - "EOF" to signal the end of the file
void WriteSolution(string fileName)
{
using (StreamWriter output = new StreamWriter(fileName))
{
output.WriteLine(Math.Round(objective.GetDoubleValue()));
for (int b = 0; b < nbBlocks; b++)
{
if (blockExtractionPeriod[b].GetIntValue() < nbPeriods)
{
output.Write(b);
output.Write(' ');
output.WriteLine(blockExtractionPeriod[b].GetIntValue());
}
}
output.WriteLine("EOF");
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: ConstrainedPitLimit inputFile [solFile] [timeLimit]");
Environment.Exit(1);
}
string instanceFile = args[0];
string outputFile = args.Length > 1 ? args[1] : null;
string strTimeLimit = args.Length > 2 ? args[2] : "60";
using (ConstrainedPitLimit model = new ConstrainedPitLimit())
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(outputFile);
}
}
}
- Compilation / Execution (Windows)
-
javac ConstrainedPitLimit.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. ConstrainedPitLimit instances\newman1.cpit
- Compilation / Execution (Linux)
-
javac ConstrainedPitLimit.java -cp /opt/hexaly_14_0/bin/hexaly.jarjava -cp /opt/hexaly_14_0/bin/hexaly.jar:. ConstrainedPitLimit instances/newman1.cpit
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;
public class ConstrainedPitLimit {
// Data from the problem
int nbBlocks;
int nbPeriods;
int nbResources;
double discountRate;
boolean resourceLB;
boolean resourceUB;
int[][] resourceLowerBound;
int[][] resourceUpperBound;
double[] profitData;
double[][] discountedProfitData;
double[][] resourceUseData;
ArrayList<ArrayList<Integer>> precedence;
// Hexaly Optimizer
private final HexalyOptimizer optimizer;
// Decision variables
private HxExpression[] periodVars;
// Intermediate expressions
private HxExpression[][] sumResourceConsumption;
private HxExpression[] blockExtractionPeriod;
// Objective
private HxExpression objective;
private ConstrainedPitLimit(HexalyOptimizer optimizer) {
this.optimizer = optimizer;
}
// Get the name of the precedence file
private String getPrecFileName(String instanceFile){
int beginIndex = instanceFile.indexOf("instances/") + 10; // "instances/" is 10 characters
int endIndex = instanceFile.indexOf(".cpit");
return instanceFile.substring(0, beginIndex) + "prec/" +
instanceFile.substring(beginIndex, endIndex) + ".prec";
}
// Read instance data
private void readInstance(String instanceFile) throws IOException {
// Reading the first file
try (Scanner input = new Scanner(new File(instanceFile))) {
input.useLocale(Locale.ROOT);
String line;
String[] lineSplit;
input.nextLine(); // Name
input.nextLine(); // Type of problem
line = input.nextLine(); // NBlocks
nbBlocks = Integer.parseInt(line.split(" ")[1]);
line = input.nextLine(); // NPeriods
nbPeriods = Integer.parseInt(line.split(" ")[1]);
line = input.nextLine(); // NResources
nbResources = Integer.parseInt(line.split(" ")[1]);
line = input.nextLine(); // Discount Rate
discountRate = Double.parseDouble(line.split(" ")[1]);
// Resource Constraints Limits
input.nextLine();
// Resource Constraints can be <=, >= or both
// we suppose all constraints have the same form
resourceLB = false;
resourceUB = false;
resourceLowerBound = new int[nbResources][nbPeriods];
resourceUpperBound = new int[nbResources][nbPeriods];
for (int it = 0; it < nbResources*nbPeriods; it++){
line = input.nextLine();
lineSplit = line.split(" ");
int r = Integer.parseInt(lineSplit[0]);
int t = Integer.parseInt(lineSplit[1]);
if (lineSplit.length == 5){
resourceLB = true;
resourceUB = true;
resourceLowerBound[r][t] = Integer.parseInt(lineSplit[3]);
resourceUpperBound[r][t] = Integer.parseInt(lineSplit[4]);
}else{
if (lineSplit[2] == "G"){
resourceLB = true;
resourceLowerBound[r][t] = Integer.parseInt(lineSplit[3]);
}else{ // lineSplit[2] == "L"
resourceUB = true;
resourceUpperBound[r][t] = Integer.parseInt(lineSplit[3]);
}
}
}
// Objective Function
input.nextLine();
profitData = new double[nbBlocks];
for (int b = 0; b < nbBlocks; b++){
line = input.nextLine();
profitData[b] = Double.parseDouble(line.split(" ")[1]);
}
discountedProfitData = new double[nbPeriods][nbBlocks];
for (int t = 0; t < nbPeriods; t++){
double discountFactor = Math.pow(1+discountRate,t);
for (int b = 0; b < nbBlocks; b++){
discountedProfitData[t][b] = profitData[b]/discountFactor;
}
}
// Resource Constraints coefficients
line = input.nextLine();
// Some values are not defined and therefore equal 0
// Here we index by resource r first, then the block index b
resourceUseData = new double[nbResources][nbBlocks];
for (int r = 0; r < nbResources; r++){
for (int b = 0; b < nbBlocks; b++){
resourceUseData[r][b] = 0;
}
}
line = input.nextLine();
lineSplit = line.split(" ");
while(!lineSplit[0].equals("EOF")){
resourceUseData[Integer.parseInt(lineSplit[1])][Integer.parseInt(lineSplit[0])]
= Double.parseDouble(lineSplit[2]);
line = input.nextLine();
lineSplit = line.split(" ");
}
}
// Reading the second file
try (Scanner input = new Scanner(new File(getPrecFileName(instanceFile)))) {
input.useLocale(Locale.ROOT);
String line;
String[] lineSplit;
precedence = new ArrayList<ArrayList<Integer>>(nbBlocks);
for (int b = 0; b < nbBlocks; b++){
precedence.add(b, new ArrayList<Integer>());
line = input.nextLine();
lineSplit = line.split(" ");
for (int pred = 0; pred < Integer.parseInt(lineSplit[1]); pred++){
precedence.get(Integer.parseInt(lineSplit[0])).add(
Integer.parseInt(lineSplit[2+pred]));
}
}
}
}
private void solve(int limit){
// Declare the optimization model
HxModel model = optimizer.getModel();
// Decision variable: periods[t] is the set of blocks extracted during period t
// There is a dummy period for unextracted blocks
periodVars = new HxExpression[nbPeriods+1];
HxExpression periods = model.array();
for (int t = 0; t < nbPeriods + 1; t++){
periodVars[t] = model.setVar(nbBlocks);
periods.addOperand(periodVars[t]);
}
model.constraint(model.partition(periods));
// Create Hexaly array to be able to access them with "at" operators
HxExpression[] resourceUse = new HxExpression[nbResources];
for (int r = 0; r < nbResources; r++){
resourceUse[r] = model.array(resourceUseData[r]);
}
// Resources used to extract at period t must be within defined bounds
sumResourceConsumption = new HxExpression[nbResources][nbPeriods];
HxExpression[] consumptionLambda = new HxExpression[nbResources];
for (int r = 0; r < nbResources; r++){
HxExpression resourceUseR = resourceUse[r];
consumptionLambda[r] = model.lambdaFunction(b -> model.at(resourceUseR,b));
for (int t = 0; t < nbPeriods; t++){
sumResourceConsumption[r][t] = model.sum(periodVars[t], consumptionLambda[r]);
if (resourceLB){
model.constraint(
model.geq(sumResourceConsumption[r][t], resourceLowerBound[r][t]));
}
if (resourceUB){
model.constraint(
model.leq(sumResourceConsumption[r][t], resourceUpperBound[r][t]));
}
}
}
// A block can only be extracted if all its predecessors have been extracted
blockExtractionPeriod = new HxExpression[nbBlocks];
for (int b = 0; b < nbBlocks; b++){
blockExtractionPeriod[b] = model.find(periods, b);
}
for (int b = 0; b < nbBlocks; b++){
ArrayList<Integer> predecessors_b = precedence.get(b);
for (int pred : predecessors_b){
model.constraint(model.leq(blockExtractionPeriod[pred],blockExtractionPeriod[b]));
}
}
// Objective function: profit over extracted blocks
HxExpression[] profits = new HxExpression[nbPeriods];
HxExpression[] profitLambdas = new HxExpression[nbPeriods];
objective = model.sum();
for (int t = 0; t < nbPeriods; t++){
profits[t] = model.array(discountedProfitData[t]);
HxExpression profitT = profits[t];
profitLambdas[t] = model.lambdaFunction(b -> model.at(profitT,b));
objective.addOperand(model.sum(periodVars[t], profitLambdas[t]));
}
model.maximize(objective);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(limit);
optimizer.solve();
}
// Write the solution in a file following the format:
// * - 1st line: value of the objective (as an integer)
// * - following lines: (if the block is extracted) block's number, period's number
// * - "EOF" to signal the end of the file
void writeSolution(String fileName) throws IOException {
try (PrintWriter output = new PrintWriter(new FileWriter(fileName))) {
output.println((int)objective.getDoubleValue());
for (int b = 0; b < nbBlocks; b++){
if (blockExtractionPeriod[b].getIntValue() < nbPeriods){
output.print(b);
output.print(" ");
output.println(blockExtractionPeriod[b].getIntValue());
}
}
output.println("EOF");
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: ConstrainedPitLimit inputFile [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()) {
ConstrainedPitLimit model = new ConstrainedPitLimit(optimizer);
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);
}
}
}