Inventory Routing Problem (IRP)
Problem
The Inventory Routing Problem (IRP) is a distribution problem in which a product has to be shipped from a supplier to several customers over a given time horizon. Each customer defines a maximum inventory level. The supplier monitors each customer’s inventory and determines its replenishment policy, guaranteeing that no stockout occurs at the customer (vendor-managed inventory policy). Shipments from the supplier to the customers are performed by a vehicle of a given capacity.
Principles learned
- Add list decision variables to model the trucks’ sequences of customers
- Define lambda functions to compute the traveled distance at each time slot
- Define expressions recursively to compute the inventory levels
Data
The instances we provide come from the Archetti et al. instances. The format of the data files is as follows:
- First line: the number of customers, the number of discrete time slots in the planning time horizon, and the vehicle’s transportation capacity.
- Second line: for the supplier
- The index of the supplier
- The x coordinate
- The y coordinate
- The starting level of the supplier’s inventory
- The quantity of product made available at each time slot
- The unit inventory cost
- Next lines: for each customer
- The index of the customer
- The x coordinate
- The y coordinate
- The starting level of the inventory
- The maximum level of inventory
- The minimum level of inventory
- The quantity absorbed at each time slot
- The unit inventory cost
Program
The Hexaly model for the Inventory Routing Problem (IRP) uses list decision variables. For each discrete time slot, we define a list variable representing the customers visited by the truck during this time slot. The cost of each route depends on the distance traveled by the truck. Using a lambda function, we sum up the distances from one customer to the next along the route.
We compute the supplier’s inventory levels recursively. Indeed, its level at time t is given by its level at time t-1, plus the product quantity made available at time t-1, minus the total quantity shipped to the customers at time t-1. The stockout constraints at the supplier ensure that the supplier’s inventory level is always sufficient to ship the total quantity delivered to the customers during a given time slot.
Similarly, we then compute the customers’ inventory levels. For each customer, the inventory level at time t is given by the level at time t-1, plus the product quantity shipped from the supplier to the customer at time t-1, minus the product quantity consumed at time t-1. The stockout constraints at the customers guarantee that each customer’s inventory level is always positive.
The capacity constraints ensure that the total product quantity loaded on the vehicle never exceeds its capacity. The maximum level constraints guarantee that the product quantity shipped from the supplier to each customer is not greater than the maximum inventory level of each customer.
The objective is to minimize the sum of all costs: the total inventory cost at the supplier, the total inventory cost at customers, and the total transportation cost.
- Execution
-
hexaly irp.hxm inFileName=instances/abs3n10.dat [hxTimeLimit=] [solFileName=]
use io;
/* Read instance data. The input files follow the "Archetti" format */
function input() {
usage = "Usage: hexaly irp.hxm " +
"inFileName=inputFile [solFileName=outputFile] [hxTimeLimit=timeLimit]";
if (inFileName == nil) throw usage;
readInputIrp();
// Compute distance matrix
computeDistanceMatrix();
}
/* Declare the optimization model */
function model() {
// Quantity of product delivered at each discrete time instant of
// the planning time horizon to each customer
delivery[t in 0...horizonLength][i in 0...nbCustomers] <- float(0, capacity);
// Sequence of customers visited at each discrete time instant of
// the planning time horizon
route[t in 0...horizonLength] <- list(nbCustomers);
for [t in 0...horizonLength] {
local sequence <- route[t];
local c <- count(sequence);
// Customers receive products only if they are visited
isDelivered[t][i in 0...nbCustomers] <- contains(sequence, i);
// Distance traveled at instant t
costRoute[t] <- (c > 0) ? distanceSupplier[sequence[0]] +
sum(1...c, i => distance[sequence[i-1]][sequence[i]]) +
distanceSupplier[sequence[c-1]] : 0;
}
// Stockout constraints at the supplier
inventorySupplier[0] <- startLevelSupplier;
for [t in 1...horizonLength+1] {
inventorySupplier[t] <- inventorySupplier[t-1]
- sum[i in 0...nbCustomers](delivery[t-1][i])
+ productionRate_supplier;
if (t != horizonLength)
constraint inventorySupplier[t] >= sum[i in 0...nbCustomers](delivery[t][i]);
}
// Stockout constraints at the customers
inventory[i in 0...nbCustomers][0] <- startLevel[i];
for [i in 0...nbCustomers] {
for [t in 1...horizonLength+1] {
inventory[i][t] <- inventory[i][t-1] + delivery[t-1][i] - demandRate[i];
constraint inventory[i][t] >= 0;
}
}
for [t in 0...horizonLength] {
// Capacity constraints
constraint sum[i in 0...nbCustomers](delivery[t][i]) <= capacity;
// Maximum level constraints
for [i in 0...nbCustomers] {
constraint delivery[t][i] <= maxLevel[i] - inventory[i][t];
constraint delivery[t][i] <= maxLevel[i] * isDelivered[t][i];
}
}
// Total inventory cost at the supplier
costInventorySupplier <-
holdingCostSupplier * sum[t in 0...horizonLength+1](inventorySupplier[t]);
// Total inventory cost at customers
costInventory <- sum[i in 0...nbCustomers][t in 0...horizonLength+1](
holdingCost[i] * inventory[i][t]);
// Total transportation cost
costRoute <- sum[t in 0...horizonLength](costRoute[t]);
// Objective: minimize the sum of all costs
objective <- costInventorySupplier + costInventory + costRoute;
minimize(objective);
}
/* Parametrize the solver */
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 20;
}
/* Write the solution in a file with the following format :
* - total distance run by the vehicle
* - the nodes visited at each time step (omitting the start/end at the supplier) */
function output() {
if (solFileName == nil) return;
local outfile = io.openWrite(solFileName);
outfile.println(costRoute.value);
for [t in 0...horizonLength] {
for [customer in route[t].value] {
outfile.print(customer + 1, " ");
}
outfile.println();
}
}
function readInputIrp() {
inFile = io.openRead(inFileName);
// General data
firstLine = inFile.readln().trim().split();
nbCustomers = firstLine[0].toInt()-1;
horizonLength = firstLine[1].toInt();
capacity = firstLine[2].toInt();
// Supplier data
secondLine = inFile.readln().trim().split();
xCoordSupplier = secondLine[1].toDouble();
yCoordSupplier = secondLine[2].toDouble();
startLevelSupplier = secondLine[3].toInt();
productionRate_supplier = secondLine[4].toInt();
holdingCostSupplier = secondLine[5].toDouble();
// Customers data
for [i in 0...nbCustomers] {
elements = inFile.readln().trim().split();
xCoord[i] = elements[1].toDouble();
yCoord[i] = elements[2].toDouble();
startLevel[i] = elements[3].toInt();
maxLevel[i] = elements[4].toInt();
minLevel[i] = elements[5].toInt();
demandRate[i] = elements[6].toInt();
holdingCost[i] = elements[7].toDouble();
}
}
function computeDistanceMatrix() {
distance[i in 0...nbCustomers][j in 0...nbCustomers] =
round(sqrt((xCoord[i] - xCoord[j]) * (xCoord[i] - xCoord[j])
+ (yCoord[i] - yCoord[j]) * (yCoord[i] - yCoord[j])));
distanceSupplier[i in 0...nbCustomers] =
round(sqrt((xCoord[i] - xCoordSupplier) * (xCoord[i] - xCoordSupplier)
+ (yCoord[i] - yCoordSupplier) * (yCoord[i] - yCoordSupplier)));
}
- Execution (Windows)
-
set PYTHONPATH=%HX_HOME%\bin\pythonpython irp.py instances\abs3n10.dat
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_13_0/bin/pythonpython irp.py instances/abs3n10.dat
import hexaly.optimizer
import sys
import math
def read_elem(filename):
with open(filename) as f:
return [str(elem) for elem in f.read().split()]
def main(instance_file, str_time_limit, sol_file):
#
# Read instance data
#
nb_customers, horizon_length, capacity, start_level_supplier, production_rate_supplier, \
holding_cost_supplier, start_level, max_level, demand_rate, holding_cost, \
dist_matrix_data, dist_supplier_data = read_input_irp(instance_file)
with hexaly.optimizer.HexalyOptimizer() as optimizer:
#
# Declare the optimization model
#
model = optimizer.model
# Quantity of product delivered at each discrete time instant of
# the planning time horizon to each customer
delivery = [[model.float(0, capacity) for i in range(nb_customers)]
for _ in range(horizon_length)]
# Sequence of customers visited at each discrete time instant of
# the planning time horizon
route = [model.list(nb_customers) for t in range(horizon_length)]
# Customers receive products only if they are visited
is_delivered = [[model.contains(route[t], i) for i in range(nb_customers)]
for t in range(horizon_length)]
# Create Hexaly arrays to be able to access them with an "at" operator
dist_matrix = model.array(dist_matrix_data)
dist_supplier = model.array(dist_supplier_data)
dist_routes = [None for _ in range(horizon_length)]
for t in range(horizon_length):
sequence = route[t]
c = model.count(sequence)
# Distance traveled at instant t
dist_lambda = model.lambda_function(
lambda i:
model.at(
dist_matrix,
sequence[i - 1],
sequence[i]))
dist_routes[t] = model.iif(
c > 0,
dist_supplier[sequence[0]]
+ model.sum(model.range(1, c), dist_lambda)
+ dist_supplier[sequence[c - 1]],
0)
# Stockout constraints at the supplier
inventory_supplier = [None for _ in range(horizon_length + 1)]
inventory_supplier[0] = start_level_supplier
for t in range(1, horizon_length + 1):
inventory_supplier[t] = inventory_supplier[t - 1] - model.sum(
delivery[t - 1][i] for i in range(nb_customers)) + production_rate_supplier
if t != horizon_length:
model.constraint(
inventory_supplier[t] >= model.sum(delivery[t][i]
for i in range(nb_customers)))
# Stockout constraints at the customers
inventory = [[None for _ in range(horizon_length + 1)] for _ in range(nb_customers)]
for i in range(nb_customers):
inventory[i][0] = start_level[i]
for t in range(1, horizon_length + 1):
inventory[i][t] = inventory[i][t - 1] + delivery[t - 1][i] - demand_rate[i]
model.constraint(inventory[i][t] >= 0)
for t in range(horizon_length):
# Capacity constraints
model.constraint(
model.sum((delivery[t][i]) for i in range(nb_customers)) <= capacity)
# Maximum level constraints
for i in range(nb_customers):
model.constraint(delivery[t][i] <= max_level[i] - inventory[i][t])
model.constraint(delivery[t][i] <= max_level[i] * is_delivered[t][i])
# Total inventory cost at the supplier
total_cost_inventory_supplier = holding_cost_supplier * model.sum(
inventory_supplier[t] for t in range(horizon_length + 1))
# Total inventory cost at customers
total_cost_inventory = model.sum(model.sum(
holding_cost[i] * inventory[i][t] for t in range(horizon_length + 1))
for i in range(nb_customers))
# Total transportation cost
total_cost_route = model.sum(dist_routes[t] for t in range(horizon_length))
# Objective: minimize the sum of all costs
objective = total_cost_inventory_supplier + total_cost_inventory + total_cost_route
model.minimize(objective)
model.close()
# Parameterize the optimizer
optimizer.param.time_limit = int(str_time_limit)
optimizer.solve()
#
# Write the solution in a file with the following format :
# - total distance run by the vehicle
# - the nodes visited at each time step (omitting the start/end at the supplier)
#
if len(sys.argv) >= 3:
with open(sol_file, 'w') as f:
f.write("%d\n" % (total_cost_route.value))
for t in range(horizon_length):
for customer in route[t].value:
f.write("%d " % (customer + 1))
f.write("\n")
# The input files follow the "Archetti" format
def read_input_irp(filename):
file_it = iter(read_elem(filename))
nb_customers = int(next(file_it)) - 1
horizon_length = int(next(file_it))
capacity = int(next(file_it))
x_coord = [None] * nb_customers
y_coord = [None] * nb_customers
start_level = [None] * nb_customers
max_level = [None] * nb_customers
min_level = [None] * nb_customers
demand_rate = [None] * nb_customers
holding_cost = [None] * nb_customers
next(file_it)
x_coord_supplier = float(next(file_it))
y_coord_supplier = float(next(file_it))
start_level_supplier = int(next(file_it))
production_rate_supplier = int(next(file_it))
holding_cost_supplier = float(next(file_it))
for i in range(nb_customers):
next(file_it)
x_coord[i] = float(next(file_it))
y_coord[i] = float(next(file_it))
start_level[i] = int(next(file_it))
max_level[i] = int(next(file_it))
min_level[i] = int(next(file_it))
demand_rate[i] = int(next(file_it))
holding_cost[i] = float(next(file_it))
distance_matrix = compute_distance_matrix(x_coord, y_coord)
distance_supplier = compute_distance_supplier(x_coord_supplier, y_coord_supplier, x_coord, y_coord)
return nb_customers, horizon_length, capacity, start_level_supplier, \
production_rate_supplier, holding_cost_supplier, start_level, max_level, \
demand_rate, holding_cost, distance_matrix, distance_supplier
# Compute the distance matrix
def compute_distance_matrix(x_coord, y_coord):
nb_customers = len(x_coord)
distance_matrix = [[None for i in range(nb_customers)] for j in range(nb_customers)]
for i in range(nb_customers):
distance_matrix[i][i] = 0
for j in range(nb_customers):
dist = compute_dist(x_coord[i], x_coord[j], y_coord[i], y_coord[j])
distance_matrix[i][j] = dist
distance_matrix[j][i] = dist
return distance_matrix
# Compute the distances to the supplier
def compute_distance_supplier(x_coord_supplier, y_coord_supplier, x_coord, y_coord):
nb_customers = len(x_coord)
distance_supplier = [None] * nb_customers
for i in range(nb_customers):
dist = compute_dist(x_coord_supplier, x_coord[i], y_coord_supplier, y_coord[i])
distance_supplier[i] = dist
return distance_supplier
def compute_dist(xi, xj, yi, yj):
return round(math.sqrt(math.pow(xi - xj, 2) + math.pow(yi - yj, 2)))
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python irp.py input_file [output_file] [time_limit]")
sys.exit(1)
instance_file = sys.argv[1]
sol_file = sys.argv[2] if len(sys.argv) > 2 else None
str_time_limit = sys.argv[3] if len(sys.argv) > 3 else "20"
main(instance_file, str_time_limit, sol_file)
- Compilation / Execution (Windows)
-
cl /EHsc irp.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.libirp instances\abs3n10.dat
- Compilation / Execution (Linux)
-
g++ irp.cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o irp./irp instances/abs3n10.dat
#include "optimizer/hexalyoptimizer.h"
#include <cmath>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>
using namespace hexaly;
using namespace std;
class Irp {
public:
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Number of customers
int nbCustomers;
// Horizon length
int horizonLength;
// Capacity
int capacity;
// Start level at the supplier
hxint startLevelSupplier;
// Production rate of the supplier
int productionRateSupplier;
// Holding costs of the supplier
double holdingCostSupplier;
// Start level of the customers
vector<hxint> startLevel;
// Max level of the customers
vector<int> maxLevel;
// Demand rate of the customers
vector<int> demandRate;
// Holding costs of the customers
vector<double> holdingCost;
// Distance matrix
vector<vector<int>> distMatrixData;
// Distance to depot
vector<int> distSupplierData;
// Decision variables
vector<vector<HxExpression>> delivery;
// Decision variables
vector<HxExpression> route;
// Are the customers receiving products
vector<vector<HxExpression>> isDelivered;
// Total inventory cost at the supplier
HxExpression totalCostInventorySupplier;
// Total inventory cost at customers
HxExpression totalCostInventory;
// Total transportation cost
HxExpression totalCostRoute;
// Objective
HxExpression objective;
Irp() {}
/* Read instance data */
void readInstance(const string& fileName) { readInputIrp(fileName); }
void solve(int limit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Quantity of product delivered at each discrete time instant of
// the planning time horizon to each customer
delivery.resize(horizonLength);
for (int t = 0; t < horizonLength; ++t) {
delivery[t].resize(nbCustomers);
for (int i = 0; i < nbCustomers; ++i) {
delivery[t][i] = model.floatVar(0, capacity);
}
}
// Sequence of customers visited at each discrete time instant of
// the planning time horizon
route.resize(horizonLength);
for (int t = 0; t < horizonLength; ++t) {
route[t] = model.listVar(nbCustomers);
}
// Create distances as arrays to be able to access them with an "at" operator
HxExpression distMatrix = model.array();
for (int i = 0; i < nbCustomers; ++i) {
distMatrix.addOperand(model.array(distMatrixData[i].begin(), distMatrixData[i].end()));
}
HxExpression distSupplier = model.array(distSupplierData.begin(), distSupplierData.end());
isDelivered.resize(horizonLength);
vector<HxExpression> distRoutes(horizonLength);
for (int t = 0; t < horizonLength; ++t) {
HxExpression sequence = route[t];
HxExpression c = model.count(sequence);
isDelivered[t].resize(nbCustomers);
// Customers receive products only if they are visited
for (int i = 0; i < nbCustomers; ++i) {
isDelivered[t][i] = model.contains(sequence, i);
}
// Distance traveled at instant t
HxExpression distLambda = model.createLambdaFunction(
[&](HxExpression i) { return model.at(distMatrix, sequence[i - 1], sequence[i]); });
distRoutes[t] = model.iif(c > 0,
distSupplier[sequence[0]] + model.sum(model.range(1, c), distLambda) +
distSupplier[sequence[c - 1]],
0);
}
// Stockout constraints at the supplier
vector<HxExpression> inventorySupplier(horizonLength + 1);
inventorySupplier[0] = model.createConstant(startLevelSupplier);
for (int t = 1; t < horizonLength + 1; ++t) {
inventorySupplier[t] = inventorySupplier[t - 1] -
model.sum(delivery[t - 1].begin(), delivery[t - 1].end()) + productionRateSupplier;
if (t != horizonLength) {
model.constraint(inventorySupplier[t] >= model.sum(delivery[t].begin(), delivery[t].end()));
}
}
// Stockout constraints at the customers
vector<vector<HxExpression>> inventory(nbCustomers);
for (int i = 0; i < nbCustomers; ++i) {
inventory[i].resize(horizonLength + 1);
inventory[i][0] = model.createConstant(startLevel[i]);
for (int t = 1; t < horizonLength + 1; ++t) {
inventory[i][t] = inventory[i][t - 1] + delivery[t - 1][i] - demandRate[i];
model.constraint(inventory[i][t] >= 0);
}
}
for (int t = 0; t < horizonLength; ++t) {
// Capacity constraints
model.constraint(model.sum(delivery[t].begin(), delivery[t].end()) <= capacity);
// Maximum level constraints
for (int i = 0; i < nbCustomers; ++i) {
model.constraint(delivery[t][i] <= maxLevel[i] - inventory[i][t]);
model.constraint(delivery[t][i] <= maxLevel[i] * isDelivered[t][i]);
}
}
// Total inventory cost at the supplier
totalCostInventorySupplier =
holdingCostSupplier * model.sum(inventorySupplier.begin(), inventorySupplier.end());
// Total inventory cost at customers
vector<HxExpression> costInventoryCustomer(nbCustomers);
for (int i = 0; i < nbCustomers; ++i) {
costInventoryCustomer[i] = holdingCost[i] * model.sum(inventory[i].begin(), inventory[i].end());
}
totalCostInventory = model.sum(costInventoryCustomer.begin(), costInventoryCustomer.end());
// Total transportation cost
totalCostRoute = model.sum(distRoutes.begin(), distRoutes.end());
// Objective: minimize the sum of all costs
objective = totalCostInventorySupplier + totalCostInventory + totalCostRoute;
model.minimize(objective);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(limit);
optimizer.solve();
}
/* Write the solution in a file with the following format:
* - total distance run by the vehicle
* - the nodes visited at each time step (omitting the start/end at the supplier) */
void writeSolution(const string& fileName) {
ofstream outfile;
outfile.exceptions(ofstream::failbit | ofstream::badbit);
outfile.open(fileName.c_str());
outfile << totalCostRoute.getValue() << endl;
for (int t = 0; t < horizonLength; ++t) {
HxCollection routeCollection = route[t].getCollectionValue();
for (int i = 0; i < routeCollection.count(); ++i) {
outfile << routeCollection[i] + 1 << " ";
}
outfile << endl;
}
}
private:
// The input files follow the "Archetti" format
void readInputIrp(const string& fileName) {
ifstream infile(fileName.c_str());
if (!infile.is_open()) {
throw std::runtime_error("File cannot be opened.");
}
infile >> nbCustomers;
nbCustomers -= 1;
infile >> horizonLength;
infile >> capacity;
int idSupplier;
double xCoordSupplier, yCoordSupplier;
vector<int> id;
vector<double> xCoord, yCoord;
infile >> idSupplier;
infile >> xCoordSupplier;
infile >> yCoordSupplier;
infile >> startLevelSupplier;
infile >> productionRateSupplier;
infile >> holdingCostSupplier;
vector<int> minLevel;
id.resize(nbCustomers);
xCoord.resize(nbCustomers);
yCoord.resize(nbCustomers);
startLevel.resize(nbCustomers);
maxLevel.resize(nbCustomers);
minLevel.resize(nbCustomers);
demandRate.resize(nbCustomers);
holdingCost.resize(nbCustomers);
for (int i = 0; i < nbCustomers; ++i) {
infile >> id[i];
infile >> xCoord[i];
infile >> yCoord[i];
infile >> startLevel[i];
infile >> maxLevel[i];
infile >> minLevel[i];
infile >> demandRate[i];
infile >> holdingCost[i];
}
printf("%lf", holdingCost[3]);
computeDistanceMatrix(xCoordSupplier, yCoordSupplier, xCoord, yCoord);
infile.close();
}
// Compute the distance matrix
void computeDistanceMatrix(double xCoordSupplier, double yCoordSupplier, const vector<double>& xCoord,
const vector<double>& yCoord) {
distMatrixData.resize(nbCustomers);
for (int i = 0; i < nbCustomers; ++i) {
distMatrixData[i].resize(nbCustomers);
}
for (int i = 0; i < nbCustomers; ++i) {
distMatrixData[i][i] = 0;
for (int j = i + 1; j < nbCustomers; ++j) {
int distance = computeDist(xCoord[i], xCoord[j], yCoord[i], yCoord[j]);
distMatrixData[i][j] = distance;
distMatrixData[j][i] = distance;
}
}
distSupplierData.resize(nbCustomers);
for (int i = 0; i < nbCustomers; ++i) {
distSupplierData[i] = computeDist(xCoordSupplier, xCoord[i], yCoordSupplier, yCoord[i]);
}
}
int computeDist(double xi, double xj, double yi, double yj) {
return round(sqrt(pow(xi - xj, 2) + pow(yi - yj, 2)));
}
};
int main(int argc, char** argv) {
if (argc < 2) {
cerr << "Usage: irp 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] : "20";
try {
Irp 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 Irp.cs /reference:Hexaly.NET.dllIrp instances\abs3n10.dat
using System;
using System.IO;
using System.Globalization;
using Hexaly.Optimizer;
public class Irp : IDisposable
{
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Number of customers
int nbCustomers;
// Horizon length
int horizonLength;
// Capacity
int capacity;
// Start level at the supplier
int startLevelSupplier;
// Production rate of the supplier
int productionRateSupplier;
// Holding costs of the supplier
double holdingCostSupplier;
// Start level of the customers
int[] startLevel;
// Max level of the customers
int[] maxLevel;
// Demand rate of the customers
int[] demandRate;
// Holding costs of the customers
double[] holdingCost;
// Distance matrix between customers
long[][] distMatrixData;
// Distances between customers and supplier
long[] distSupplierData;
// Decision variables
private HxExpression[][] delivery;
// Decision variables
private HxExpression[] route;
// Are the customers receiving products
private HxExpression[][] isDelivered;
// Distance traveled by each truck
HxExpression[] distRoutes;
// Inventory at the supplier
HxExpression[] inventorySupplier;
// Inventory at the customers
HxExpression[][] inventory;
// Total inventory cost at the supplier
HxExpression totalCostInventorySupplier;
// Inventory cost at a customer
HxExpression[] costInventoryCustomer;
// Total inventory cost at customers
HxExpression totalCostInventory;
// Total transportation cost
HxExpression totalCostRoute;
// Objective
HxExpression objective;
public Irp()
{
optimizer = new HexalyOptimizer();
}
public void Dispose()
{
if (optimizer != null)
optimizer.Dispose();
}
void Solve(int limit)
{
// Declare the optimization model
HxModel model = optimizer.GetModel();
delivery = new HxExpression[horizonLength][];
route = new HxExpression[horizonLength];
isDelivered = new HxExpression[horizonLength][];
distRoutes = new HxExpression[horizonLength];
// Quantity of product delivered at each discrete time instant of
// the planning time horizon to each customer
for (int t = 0; t < horizonLength; ++t)
{
delivery[t] = new HxExpression[nbCustomers];
for (int i = 0; i < nbCustomers; ++i)
delivery[t][i] = model.Float(0, capacity);
}
// Sequence of customers visited at each discrete time instant of
// the planning time horizon
for (int t = 0; t < horizonLength; ++t)
route[t] = model.List(nbCustomers);
// Create distances as arrays to be able to access them with an "at" operator
HxExpression distSupplier = model.Array(distSupplierData);
HxExpression distMatrix = model.Array(distMatrixData);
for (int t = 0; t < horizonLength; ++t)
{
HxExpression sequence = route[t];
HxExpression c = model.Count(sequence);
isDelivered[t] = new HxExpression[nbCustomers];
// Customers receive products only if they are visited
for (int i = 0; i < nbCustomers; ++i)
isDelivered[t][i] = model.Contains(sequence, i);
// Distance traveled at instant t
HxExpression distLambda = model.LambdaFunction(
i => distMatrix[sequence[i - 1], sequence[i]]
);
distRoutes[t] = model.If(
c > 0,
distSupplier[sequence[0]]
+ model.Sum(model.Range(1, c), distLambda)
+ distSupplier[sequence[c - 1]],
0
);
}
// Stockout constraints at the supplier
inventorySupplier = new HxExpression[horizonLength + 1];
inventorySupplier[0] = model.CreateConstant(startLevelSupplier);
for (int t = 1; t < horizonLength + 1; ++t)
{
inventorySupplier[t] =
inventorySupplier[t - 1] - model.Sum(delivery[t - 1]) + productionRateSupplier;
if (t != horizonLength)
model.Constraint(inventorySupplier[t] >= model.Sum(delivery[t]));
}
// Stockout constraints at the customers
inventory = new HxExpression[nbCustomers][];
for (int i = 0; i < nbCustomers; ++i)
{
inventory[i] = new HxExpression[horizonLength + 1];
inventory[i][0] = model.CreateConstant(startLevel[i]);
for (int t = 1; t < horizonLength + 1; ++t)
{
inventory[i][t] = inventory[i][t - 1] + delivery[t - 1][i] - demandRate[i];
model.Constraint(inventory[i][t] >= 0);
}
}
for (int t = 0; t < horizonLength; ++t)
{
// Capacity constraints
model.Constraint(model.Sum(delivery[t]) <= capacity);
// Maximum level constraints
for (int i = 0; i < nbCustomers; ++i)
{
model.Constraint(delivery[t][i] <= maxLevel[i] - inventory[i][t]);
model.Constraint(delivery[t][i] <= maxLevel[i] * isDelivered[t][i]);
}
}
// Total inventory cost at the supplier
totalCostInventorySupplier = holdingCostSupplier * model.Sum(inventorySupplier);
// Total inventory cost at customers
costInventoryCustomer = new HxExpression[nbCustomers];
for (int i = 0; i < nbCustomers; ++i)
costInventoryCustomer[i] = holdingCost[i] * model.Sum(inventory[i]);
totalCostInventory = model.Sum(costInventoryCustomer);
// Total transportation cost
totalCostRoute = model.Sum(distRoutes);
// Objective: minimize the sum of all costs
objective = model.Sum(
model.Sum(totalCostInventorySupplier, totalCostInventory),
totalCostRoute
);
model.Minimize(objective);
model.Close();
// Parametrize the optimizer
optimizer.GetParam().SetTimeLimit(limit);
optimizer.Solve();
}
/* Write the solution in a file with the following format:
* - total distance run by the vehicle
* - the nodes visited at each time step (omitting the start/end at the supplier) */
void WriteSolution(string fileName)
{
using (StreamWriter output = new StreamWriter(fileName))
{
output.WriteLine(totalCostRoute.GetValue());
for (int t = 0; t < horizonLength; ++t)
{
HxCollection routeCollection = route[t].GetCollectionValue();
for (int i = 0; i < routeCollection.Count(); ++i)
output.Write((routeCollection[i] + 1) + " ");
output.WriteLine();
}
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: Irp 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] : "20";
using (Irp model = new Irp())
{
model.ReadInputIrp(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(outputFile);
}
}
String[] ReadNextLine(StreamReader input)
{
return input.ReadLine().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
}
// The input files follow the "Archetti" format
private void ReadInputIrp(string fileName)
{
using (StreamReader input = new StreamReader(fileName))
{
string[] splitted;
splitted = ReadNextLine(input);
nbCustomers = int.Parse(splitted[0]) - 1;
horizonLength = int.Parse(splitted[1]);
capacity = int.Parse(splitted[2]);
splitted = ReadNextLine(input);
Console.WriteLine(1);
Console.WriteLine(splitted[1]);
double xCoordSupplier = double.Parse(splitted[1], CultureInfo.InvariantCulture);
Console.WriteLine(2);
double yCoordSupplier = double.Parse(splitted[2], CultureInfo.InvariantCulture);
Console.WriteLine(3);
startLevelSupplier = int.Parse(splitted[3]);
Console.WriteLine(4);
productionRateSupplier = int.Parse(splitted[4]);
holdingCostSupplier = double.Parse(splitted[5], CultureInfo.InvariantCulture);
double[] xCoord = new double[nbCustomers];
double[] yCoord = new double[nbCustomers];
startLevel = new int[nbCustomers];
maxLevel = new int[nbCustomers];
demandRate = new int[nbCustomers];
holdingCost = new double[nbCustomers];
for (int i = 0; i < nbCustomers; ++i)
{
splitted = ReadNextLine(input);
xCoord[i] = double.Parse(splitted[1], CultureInfo.InvariantCulture);
yCoord[i] = double.Parse(splitted[2], CultureInfo.InvariantCulture);
startLevel[i] = int.Parse(splitted[3]);
maxLevel[i] = int.Parse(splitted[4]);
demandRate[i] = int.Parse(splitted[6]);
holdingCost[i] = double.Parse(splitted[7], CultureInfo.InvariantCulture);
}
ComputeDistanceMatrix(xCoordSupplier, yCoordSupplier, xCoord, yCoord);
}
}
// Compute the distance matrix
private void ComputeDistanceMatrix(
double xCoordSupplier,
double yCoordSupplier,
double[] xCoord,
double[] yCoord
)
{
distMatrixData = new long[nbCustomers][];
for (int i = 0; i < nbCustomers; ++i)
distMatrixData[i] = new long[nbCustomers];
for (int i = 0; i < nbCustomers; ++i)
{
distMatrixData[i][i] = 0;
for (int j = i + 1; j < nbCustomers; ++j)
{
long dist = ComputeDist(xCoord[i], xCoord[j], yCoord[i], yCoord[j]);
distMatrixData[i][j] = dist;
distMatrixData[j][i] = dist;
}
}
distSupplierData = new long[nbCustomers];
for (int i = 0; i < nbCustomers; ++i)
distSupplierData[i] = ComputeDist(xCoordSupplier, xCoord[i], yCoordSupplier, yCoord[i]);
}
private long ComputeDist(double xi, double xj, double yi, double yj)
{
return Convert.ToInt64(Math.Round(Math.Sqrt(Math.Pow(xi - xj, 2) + Math.Pow(yi - yj, 2))));
}
}
- Compilation / Execution (Windows)
-
javac Irp.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. Irp instances\abs3n10.dat
- Compilation / Execution (Linux)
-
javac Irp.java -cp /opt/hexaly_13_0/bin/hexaly.jarjava -cp /opt/hexaly_13_0/bin/hexaly.jar:. Irp instances/abs3n10.dat
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;
public class Irp {
// Hexaly Optimizer
private final HexalyOptimizer optimizer;
// Number of customers
private int nbCustomers;
// Horizon length
private int horizonLength;
// Capacity
private int capacity;
// Start level at the supplier
private int startLevelSupplier;
// Production rate of the supplier
private int productionRateSupplier;
// Holding costs of the supplier
private double holdingCostSupplier;
// Start level of the customers
private int[] startLevel;
// Max level of the customers
private int[] maxLevel;
// Demand rate of the customers
private int[] demandRate;
// Holding costs of the customers
private double[] holdingCost;
// Distance matrix
private long[][] distMatrixData;
// Distances between customers and supplier
private long[] distSupplierData;
// Decision variables
private HxExpression[][] delivery;
// Decision variables
private HxExpression[] route;
// Are the customers receiving products
private HxExpression[][] isDelivered;
// Distance traveled by each truck
private HxExpression[] distRoutes;
// Inventory at the supplier
private HxExpression[] inventorySupplier;
// Inventory at the customers
private HxExpression[][] inventory;
// Total inventory cost at the supplier
private HxExpression totalCostInventorySupplier;
// Inventory cost at a customer
private HxExpression[] costInventoryCustomer;
// Total inventory cost at customers
private HxExpression totalCostInventory;
// Total transportation cost
private HxExpression totalCostRoute;
// Objective
private HxExpression objective;
private Irp(HexalyOptimizer optimizer) {
this.optimizer = optimizer;
}
private void solve(int limit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
delivery = new HxExpression[horizonLength][nbCustomers];
route = new HxExpression[horizonLength];
isDelivered = new HxExpression[horizonLength][nbCustomers];
distRoutes = new HxExpression[horizonLength];
// Quantity of product delivered at each discrete time instant of
// the planning time horizon to each customer
for (int t = 0; t < horizonLength; ++t) {
for (int i = 0; i < nbCustomers; ++i) {
delivery[t][i] = model.floatVar(0, capacity);
}
}
// Sequence of customers visited at each discrete time instant of
// the planning time horizon
for (int t = 0; t < horizonLength; ++t) {
route[t] = model.listVar(nbCustomers);
}
// Create distances as arrays to be able to access them with an "at" operator
HxExpression distSupplier = model.array(distSupplierData);
HxExpression distMatrix = model.array(distMatrixData);
for (int t = 0; t < horizonLength; ++t) {
HxExpression sequence = route[t];
HxExpression c = model.count(sequence);
// Customers receive products only if they are visited
for (int i = 0; i < nbCustomers; ++i) {
isDelivered[t][i] = model.contains(sequence, i);
}
// Distance traveled at instant t
HxExpression distLambda = model
.lambdaFunction(i -> model.at(distMatrix, model.at(sequence, model.sub(i, 1)), model.at(sequence, i)));
distRoutes[t] = model.iif(model.gt(c, 0),
model.sum(
model.sum(model.at(distSupplier, model.at(sequence, 0)), model.sum(model.range(1, c), distLambda)),
model.at(distSupplier, model.at(sequence, model.sub(c, 1)))),
0);
}
// Stockout constraints at the supplier
inventorySupplier = new HxExpression[horizonLength + 1];
inventorySupplier[0] = model.createConstant(startLevelSupplier);
for (int t = 1; t < horizonLength + 1; ++t) {
inventorySupplier[t] = model.sum(model.sub(inventorySupplier[t - 1], model.sum(delivery[t - 1])),
productionRateSupplier);
if (t != horizonLength) {
model.constraint(model.geq(inventorySupplier[t], model.sum(delivery[t])));
}
}
// Stockout constraints at the customers
inventory = new HxExpression[nbCustomers][horizonLength + 1];
for (int i = 0; i < nbCustomers; ++i) {
inventory[i][0] = model.createConstant(startLevel[i]);
for (int t = 1; t < horizonLength + 1; ++t) {
inventory[i][t] = model.sub(model.sum(inventory[i][t - 1], delivery[t - 1][i]), demandRate[i]);
model.constraint(model.geq(inventory[i][t], 0));
}
}
for (int t = 0; t < horizonLength; ++t) {
// Capacity constraints
model.constraint(model.leq(model.sum(delivery[t]), capacity));
// Maximum level constraints
for (int i = 0; i < nbCustomers; ++i) {
model.constraint(model.leq(delivery[t][i], model.sub(maxLevel[i], inventory[i][t])));
model.constraint(model.leq(delivery[t][i], model.prod(maxLevel[i], isDelivered[t][i])));
}
}
// Total inventory cost at the supplier
totalCostInventorySupplier = model.prod(holdingCostSupplier, model.sum(inventorySupplier));
// Total inventory cost at customers
costInventoryCustomer = new HxExpression[nbCustomers];
for (int i = 0; i < nbCustomers; ++i) {
costInventoryCustomer[i] = model.prod(holdingCost[i], model.sum(inventory[i]));
}
totalCostInventory = model.sum(costInventoryCustomer);
// Total transportation cost
totalCostRoute = model.sum(distRoutes);
// Objective: minimize the sum of all costs
objective = model.sum(model.sum(totalCostInventorySupplier, totalCostInventory), totalCostRoute);
model.minimize(objective);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(limit);
optimizer.solve();
}
/* Write the solution in a file with the following format:
* - total distance run by the vehicle
* - the nodes visited at each time step (omitting the start/end at the supplier) */
private void writeSolution(String fileName) throws IOException {
try (PrintWriter output = new PrintWriter(fileName)) {
output.println(totalCostRoute.getValue());
for (int t = 0; t < horizonLength; ++t) {
HxCollection routeCollection = route[t].getCollectionValue();
for (int i = 0; i < routeCollection.count(); ++i) {
output.print((routeCollection.get(i) + 1) + " ");
}
output.println();
}
}
}
// The input files follow the "Archetti" format
private void readInstance(String fileName) throws IOException {
try (Scanner input = new Scanner(new File(fileName))) {
String[] splitted;
splitted = input.nextLine().split("\\s+");
nbCustomers = Integer.parseInt(splitted[0]) - 1;
horizonLength = Integer.parseInt(splitted[1]);
capacity = Integer.parseInt(splitted[2]);
splitted = input.nextLine().split("\\s+");
double xCoordSupplier = Double.parseDouble(splitted[2]);
double yCoordSupplier = Double.parseDouble(splitted[3]);
startLevelSupplier = Integer.parseInt(splitted[4]);
productionRateSupplier = Integer.parseInt(splitted[5]);
holdingCostSupplier = Double.parseDouble(splitted[6]);
double[] xCoord = new double[nbCustomers];
double[] yCoord = new double[nbCustomers];
startLevel = new int[nbCustomers];
maxLevel = new int[nbCustomers];
demandRate = new int[nbCustomers];
holdingCost = new double[nbCustomers];
for (int i = 0; i < nbCustomers; ++i) {
splitted = input.nextLine().split("\\s+");
xCoord[i] = Double.parseDouble(splitted[2]);
yCoord[i] = Double.parseDouble(splitted[3]);
startLevel[i] = Integer.parseInt(splitted[4]);
maxLevel[i] = Integer.parseInt(splitted[5]);
demandRate[i] = Integer.parseInt(splitted[7]);
holdingCost[i] = Double.parseDouble(splitted[8]);
}
computeDistanceMatrix(xCoordSupplier, yCoordSupplier, xCoord, yCoord);
}
}
// Compute the distance matrix
private void computeDistanceMatrix(double xCoordSupplier, double yCoordSupplier, double[] xCoord, double[] yCoord) {
distMatrixData = new long[nbCustomers][nbCustomers];
for (int i = 0; i < nbCustomers; ++i) {
distMatrixData[i][i] = 0;
for (int j = i + 1; j < nbCustomers; ++j) {
long dist = computeDist(xCoord[i], xCoord[j], yCoord[i], yCoord[j]);
distMatrixData[i][j] = dist;
distMatrixData[j][i] = dist;
}
}
distSupplierData = new long[nbCustomers];
for (int i = 0; i < nbCustomers; ++i) {
distSupplierData[i] = computeDist(xCoordSupplier, xCoord[i], yCoordSupplier, yCoord[i]);
}
}
private long computeDist(double xi, double xj, double yi, double yj) {
return Math.round(Math.sqrt(Math.pow(xi - xj, 2) + Math.pow(yi - yj, 2)));
}
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: java Irp inputFile [outputFile] [timeLimit]");
System.exit(1);
}
try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
String instanceFile = args[0];
String outputFile = args.length > 1 ? args[1] : null;
String strTimeLimit = args.length > 2 ? args[2] : "20";
Irp model = new Irp(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);
}
}
}
Results
Hexaly Optimizer improves the literature’s best known solutions on 36% of IRP instances from the 2016 ROADEF/EURO Challenge. Our Inventory Routing Problem (IRP) benchmark page illustrates Hexaly Optimizer’s performance on this challenging problem.