Inventory Routing (IRP)¶
Principles learned¶
Use a lambda expression to compute a sum with a variable number of terms
Use ternary conditions
Define a sequence of expressions
Access a multi-dimensional array with an “at” operator
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 the inventory of each customer 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 given capacity.
Download the exampleData¶
The instances provided come from the Archetti et al. instances.
The format of the data files is as follows:
The first line gives the number of customers, the number of discrete time instants of the planning time horizon and the transportation capacity of the vehicle.
The second line contains information concerning the supplier:
The index of the supplier
The x coordinate
The y coordinate
The starting level of the inventory
The quantity of product made available at each discrete time instant of the planning time horizon
The unit inventory cost
The next lines contain, for each customer, the following information:
The index of the customer
The x coordinate
The y coordinate
The starting level of the inventory
The maximum level of the inventory
The minimum level of the inventory
The quantity absorbed at each discrete time instant of the planning time horizon
The unit inventory cost
Program¶
This Hexaly model defines a route for each discrete time instant of the
planning time horizon t as a
list variable (route[t]
).
It corresponds to the sequence of customers visited.
The inventory level of the supplier at time t is given by the 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 guarantee that for each delivery time t, the inventory level at the supplier is sufficient to ship the total quantity delivered to the customers at time t.
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 for each customer, the inventory level at each time t is positive.
The capacity constraints guarantee that the total quantity of the product loaded on the vehicle at time t does not exceed the transportation 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:
- localsolver irp.lsp inFileName=instances/abs3n10.dat [lsTimeLimit=] [solFileName=]
use io;
/* Read instance data. The input files follow the "Archetti" format */
function input() {
usage = "Usage: localsolver irp.lsp " +
"inFileName=inputFile [solFileName=outputFile] [lsTimeLimit=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 (lsTimeLimit == nil) lsTimeLimit = 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=%LS_HOME%\bin\pythonpython irp.py instances\abs3n10.dat
- Execution (Linux)
- export PYTHONPATH=/opt/localsolver_13_0/bin/pythonpython irp.py instances/abs3n10.dat
import localsolver
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 localsolver.LocalSolver() as ls:
#
# Declare the optimization model
#
model = ls.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 LocalSolver 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 solver
ls.param.time_limit = int(str_time_limit)
ls.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%LS_HOME%\include /link %LS_HOME%\bin\localsolver130.libirp instances\abs3n10.dat
- Compilation / Execution (Linux)
- g++ irp.cpp -I/opt/localsolver_13_0/include -llocalsolver130 -lpthread -o irp./irp instances/abs3n10.dat
#include "localsolver.h"
#include <cmath>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>
using namespace localsolver;
using namespace std;
class Irp {
public:
// LocalSolver
LocalSolver localsolver;
// Number of customers
int nbCustomers;
// Horizon length
int horizonLength;
// Capacity
int capacity;
// Start level at the supplier
lsint startLevelSupplier;
// Production rate of the supplier
int productionRateSupplier;
// Holding costs of the supplier
double holdingCostSupplier;
// Start level of the customers
vector<lsint> 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<LSExpression>> delivery;
// Decision variables
vector<LSExpression> route;
// Are the customers receiving products
vector<vector<LSExpression>> isDelivered;
// Total inventory cost at the supplier
LSExpression totalCostInventorySupplier;
// Total inventory cost at customers
LSExpression totalCostInventory;
// Total transportation cost
LSExpression totalCostRoute;
// Objective
LSExpression objective;
Irp() {}
/* Read instance data */
void readInstance(const string& fileName) { readInputIrp(fileName); }
void solve(int limit) {
// Declare the optimization model
LSModel model = localsolver.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
LSExpression distMatrix = model.array();
for (int i = 0; i < nbCustomers; ++i) {
distMatrix.addOperand(model.array(distMatrixData[i].begin(), distMatrixData[i].end()));
}
LSExpression distSupplier = model.array(distSupplierData.begin(), distSupplierData.end());
isDelivered.resize(horizonLength);
vector<LSExpression> distRoutes(horizonLength);
for (int t = 0; t < horizonLength; ++t) {
LSExpression sequence = route[t];
LSExpression 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
LSExpression distLambda = model.createLambdaFunction(
[&](LSExpression 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<LSExpression> 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<LSExpression>> 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<LSExpression> 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 solver
localsolver.getParam().setTimeLimit(limit);
localsolver.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) {
LSCollection 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 %LS_HOME%\bin\localsolvernet.dll .csc Irp.cs /reference:localsolvernet.dllIrp instances\abs3n10.dat
using System;
using System.IO;
using System.Globalization;
using localsolver;
public class Irp : IDisposable
{
// LocalSolver
LocalSolver localsolver;
// 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 LSExpression[][] delivery;
// Decision variables
private LSExpression[] route;
// Are the customers receiving products
private LSExpression[][] isDelivered;
// Distance traveled by each truck
LSExpression[] distRoutes;
// Inventory at the supplier
LSExpression[] inventorySupplier;
// Inventory at the customers
LSExpression[][] inventory;
// Total inventory cost at the supplier
LSExpression totalCostInventorySupplier;
// Inventory cost at a customer
LSExpression[] costInventoryCustomer;
// Total inventory cost at customers
LSExpression totalCostInventory;
// Total transportation cost
LSExpression totalCostRoute;
// Objective
LSExpression objective;
public Irp()
{
localsolver = new LocalSolver();
}
public void Dispose()
{
if (localsolver != null)
localsolver.Dispose();
}
void Solve(int limit)
{
// Declare the optimization model
LSModel model = localsolver.GetModel();
delivery = new LSExpression[horizonLength][];
route = new LSExpression[horizonLength];
isDelivered = new LSExpression[horizonLength][];
distRoutes = new LSExpression[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 LSExpression[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
LSExpression distSupplier = model.Array(distSupplierData);
LSExpression distMatrix = model.Array(distMatrixData);
for (int t = 0; t < horizonLength; ++t)
{
LSExpression sequence = route[t];
LSExpression c = model.Count(sequence);
isDelivered[t] = new LSExpression[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
LSExpression 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 LSExpression[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 LSExpression[nbCustomers][];
for (int i = 0; i < nbCustomers; ++i)
{
inventory[i] = new LSExpression[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 LSExpression[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 solver
localsolver.GetParam().SetTimeLimit(limit);
localsolver.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)
{
LSCollection 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 %LS_HOME%\bin\localsolver.jarjava -cp %LS_HOME%\bin\localsolver.jar;. Irp instances/abs3n10.dat
- Compilation / Execution (Linux)
- javac Irp.java -cp /opt/localsolver_13_0/bin/localsolver.jarjava -cp /opt/localsolver_13_0/bin/localsolver.jar:. Irp instances/abs3n10.dat
import java.util.*;
import java.io.*;
import localsolver.*;
public class Irp {
// LocalSolver
private final LocalSolver localsolver;
// 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 LSExpression[][] delivery;
// Decision variables
private LSExpression[] route;
// Are the customers receiving products
private LSExpression[][] isDelivered;
// Distance traveled by each truck
private LSExpression[] distRoutes;
// Inventory at the supplier
private LSExpression[] inventorySupplier;
// Inventory at the customers
private LSExpression[][] inventory;
// Total inventory cost at the supplier
private LSExpression totalCostInventorySupplier;
// Inventory cost at a customer
private LSExpression[] costInventoryCustomer;
// Total inventory cost at customers
private LSExpression totalCostInventory;
// Total transportation cost
private LSExpression totalCostRoute;
// Objective
private LSExpression objective;
private Irp(LocalSolver localsolver) {
this.localsolver = localsolver;
}
private void solve(int limit) {
// Declare the optimization model
LSModel model = localsolver.getModel();
delivery = new LSExpression[horizonLength][nbCustomers];
route = new LSExpression[horizonLength];
isDelivered = new LSExpression[horizonLength][nbCustomers];
distRoutes = new LSExpression[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
LSExpression distSupplier = model.array(distSupplierData);
LSExpression distMatrix = model.array(distMatrixData);
for (int t = 0; t < horizonLength; ++t) {
LSExpression sequence = route[t];
LSExpression 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
LSExpression 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 LSExpression[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 LSExpression[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 LSExpression[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 solver
localsolver.getParam().setTimeLimit(limit);
localsolver.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) {
LSCollection 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 (LocalSolver localsolver = new LocalSolver()) {
String instanceFile = args[0];
String outputFile = args.length > 1 ? args[1] : null;
String strTimeLimit = args.length > 2 ? args[2] : "20";
Irp model = new Irp(localsolver);
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);
}
}
}