Vehicle Routing Problem with Transshipment Facilities (VRPTF)
Problem
In the Vehicle Routing Problem with Transshipment Facilities (VRPTF), which is an extension of the Capacitated Vehicle Routing Problem (CVRP), a fleet of delivery vehicles with uniform capacity must service customers with known demand for a single commodity. The vehicles start and end their routes at a common depot. Each customer can only be served by one vehicle, either directly or through a transshipment facility. The total demand served by each vehicle must not exceed its capacity. Assigning a customer to a facility has a cost. While different cost models are possible, we choose to model these costs as the distance between the customer and the facility. The objective is to minimize the cost of serving all the customers, which is the sum of the total travel distance and the customer-to-facility assignment costs.
Principles learned
- Add list decision variables to model the trucks’ sequences of customers
- Define an array using a recursive lambda function to compute the customers’ visiting times
- Add multiple objectives and model the lateness as a soft constraint
Data
The instances we provide come from the C. Prodhon instances for the Location Routing Problem. The format of the data files is as follows:
- The number of customers
- The number of depots
- The x and y coordinates of the depots and the customers
- The capacity of the delivery vehicles
- The capacity of each depot
- The demand for every customer
- The opening cost of each depot
- The opening cost of a route
We use the provided depots as transshipment facilities. Since they are not relevant when solving the Vehicle Routing Problem with Transshipment Facilities (VRPTF), we ignore the following values:
- The capacity of each depot
- The opening cost of each depot
- The opening cost of a route
We compute the coordinates of the common depot as the center of the bounding rectangle that contains all the customers and facilities.
Program
The Hexaly model for the Vehicle Routing Problem with Transshipment Facilities defines a route for each truck r as a list variable (routes[r]
). It corresponds to the sequence of points visited. We consider nbCustomers + nbCustomers * nbFacilities
points:
- One point for each customer
c
. Ifroutes[r]
visits pointc
, it means that customerc
is directly served by truckr
. - One point for each facility
f
, duplicated for each customerc
. Ifroutes[r]
visits pointnbCustomers + c * nbFacilities + f
, it means that customerc
is served by truckr
through transshipment facilityf
.
We constrain all the lists to be pairwise disjoint thanks to the “disjoint” operator, thus ensuring that we don’t visit any point more than once. The number of sequences nbTrucks
is the minimum number of trucks necessary to serve every customer without overloading the vehicles, multiplied by a factor of 1.5 to ensure feasibility.
We must serve each customer c
exactly once, either directly or through a facility. The contains()
operator can be used to determine if a customer or a facility is visited by a truck. Indeed, the boolean expression contains(routes, c)
will evaluate to true
if any truck serves customer c
directly, and to false
otherwise. Likewise, contains(routes, f)
will evaluate to true
if any truck serves customer c
through facility f
. We can then ensure that each customer will be served exactly once by constraining the sum of contains(routes, c)
and all of the contains(routes, f)
for f
between startFacilities
and endFacilities
to be equal to one.
The quantity delivered to a customer c
has to meet the corresponding demand demands[c]
. We compute the quantity served by each truck r
as the sum of demands over all of the points contained in routes[r]
. This quantity must not exceed the truck’s capacity.
We then compute the distance traveled by each truck using the distanceMatrix
and depotDistances
arrays. Using a lambda function and the “at” operator, we sum the distances along each route.
We compute the assignment costs along each route using the assignmentCosts
array. This array is such that assignmentCosts[c] = 0
for each customer c
and assignmentCosts[nbCustomers + c * nbFacilities + f]
is equal to the distance between customer c
and facility f
. Here again, we use a lambda function and the “at” operator to sum the assignment costs along each route.
Finally, the objective is to minimize the sum of the total traveled distance and the total assignment cost.
- Execution
-
hexaly vrptf.hxm inFileName=instances/coord20-5-1.dat [hxTimeLimit=] [solFileName=]
use io;
/* Read instance data */
function input() {
local usage = "Usage: hexaly vrptf.hxm "
+ "inFileName=inputFile [solFileName=outputFile] [hxTimeLimit=timeLimit]";
if (inFileName == nil) throw usage;
readInput();
computeDepotCoordinates();
computeDistances();
computeAssignmentCosts();
demands[c in 0...nbCustomers] = customersDemand[c];
for [c in 0...nbCustomers][f in 0...nbFacilities] {
demands[nbCustomers + c * nbFacilities + f] = customersDemand[c];
}
minNbTrucks = ceil(sum[c in 0...nbCustomers](customersDemand[c]) / capacity);
nbTrucks = ceil(1.5 * minNbTrucks);
}
/* Declare the optimization model */
function model() {
// A point is either a customer or a facility
// Facilities are duplicated for each customer
nbPoints = nbCustomers + nbCustomers * nbFacilities;
// Each route is represented as a list containing the points in the order they are visited
routesSequences[0...nbTrucks] <- list(nbPoints);
// Each point must be visited at most once
constraint disjoint(routesSequences);
for [c in 0...nbCustomers] {
startFacilities = nbCustomers + c * nbFacilities;
endFacilities = startFacilities + nbFacilities;
// Each customer is either contained in a route or assigned to a facility
facilityUsed[f in startFacilities...endFacilities] <- contains(routesSequences, f);
deliveryCount <- contains(routesSequences, c) + sum[f in startFacilities...endFacilities](facilityUsed[f]);
constraint deliveryCount == 1;
}
for [r in 0...nbTrucks] {
local route <- routesSequences[r];
local c <- count(route);
// Each truck cannot carry more than its capacity
quantity_served <- sum(0...c, i => demands[route[i]]);
constraint quantity_served <= capacity;
// Distance travelled by each truck
routeDistances[r] <- sum(0...c-1, i => distanceMatrix[route[i]][route[i + 1]])
+ (c > 0 ? (depotDistances[route[0]] + depotDistances[route[c - 1]]) : 0);
// The cost to assign customers to their facility
routeAssignmentCost[r] <- sum(route, i => assignmentCosts[i]);
}
// The total distance travelled
totalDistance <- sum[r in 0...nbTrucks](routeDistances[r]);
// The total assignement cost
totalAssignmentCost <- sum[r in 0...nbTrucks](routeAssignmentCost[r]);
// Objective: minimize the sum of the total distance travelled and the total assignement cost
totalCost <- totalDistance + totalAssignmentCost;
minimize totalCost;
}
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 20;
}
function output() {
if (solFileName == nil) return;
local solFile = io.openWrite(solFileName);
solFile.println("File name: ", inFileName, "; totalCost = ", totalCost.value,
"; totalDistance = ", totalDistance.value,
"; totalAssignementCost = ", totalAssignmentCost.value);
for [r in 0...nbTrucks] {
local route = routesSequences[r].value;
if (route.count() == 0) continue;
solFile.print("Route ", r, " [");
for [i in 0...route.count()] {
local pointIndex = route[i];
if (pointIndex < nbCustomers) {
solFile.print("Customer ", pointIndex);
} else {
solFile.print("Facility ", pointIndex % nbCustomers, " assigned to Customer ", floor((pointIndex - nbCustomers) / nbFacilities));
}
if (i < route.count() - 1) {
solFile.print(", ");
}
}
solFile.println("]");
}
solFile.close();
}
function readInput() {
local inFile = io.openRead(inFileName);
nbCustomers = inFile.readInt();
nbFacilities = inFile.readInt();
for [f in 0...nbFacilities] {
facilitiesX[f] = inFile.readInt();
facilitiesY[f] = inFile.readInt();
}
for [c in 0...nbCustomers] {
customersX[c] = inFile.readInt();
customersY[c] = inFile.readInt();
}
capacity = inFile.readInt();
// Facility capacities : skip
for [f in 0...nbFacilities] {
inFile.readInt();
}
for [c in 0...nbCustomers] {
customersDemand[c] = inFile.readInt();
}
}
function computeDepotCoordinates() {
// Compute the coordinates of the bounding box containing all of the points
xMinFacilities = min[f in 0...nbFacilities](facilitiesX[f]);
xMaxFacilities = max[f in 0...nbFacilities](facilitiesX[f]);
yMinFacilities = min[f in 0...nbFacilities](facilitiesY[f]);
yMaxFacilities = max[f in 0...nbFacilities](facilitiesY[f]);
xMinCustomers = min[f in 0...nbCustomers](customersX[f]);
xMaxCustomers = max[f in 0...nbCustomers](customersX[f]);
yMinCustomers = min[f in 0...nbCustomers](customersY[f]);
yMaxCustomers = max[f in 0...nbCustomers](customersY[f]);
xMin = min(xMinFacilities, xMinCustomers);
xMax = max(xMaxFacilities, xMaxCustomers);
yMin = min(yMinFacilities, yMinCustomers);
yMax = max(yMaxFacilities, yMaxCustomers);
// We assume that the depot is at the center of the bounding box
depotX = xMin + floor((xMax - xMin) / 2.0);
depotY = yMin + floor((yMax - yMin) / 2.0);
}
function computeDistances() {
// Customer to depot
depotDistances[c in 0...nbCustomers] = computeDistance(customersX[c], depotX, customersY[c], depotY);
// Facility to depot
for [c in 0...nbCustomers][f in 0...nbFacilities] {
depotDistances[nbCustomers + c * nbFacilities + f] = computeDistance(facilitiesX[f], depotX, facilitiesY[f], depotY);
}
// Distances between customers
distanceMatrix[c1 in 0...nbCustomers][c2 in 0...nbCustomers] = computeDistance(customersX[c1], customersX[c2], customersY[c1], customersY[c2]);
// Distances between customers and facilities
for [c1 in 0...nbCustomers][c2 in 0...nbCustomers][f in 0...nbFacilities] {
// Index representing serving c2 through facility f
local facilityIndex = nbCustomers + c2 * nbFacilities + f;
local distance = computeDistance(facilitiesX[f], customersX[c1], facilitiesY[f], customersY[c1]);
distanceMatrix[facilityIndex][c1] = distance;
distanceMatrix[c1][facilityIndex] = distance;
}
// Distances between facilities
for [c1 in 0...nbCustomers][c2 in 0...nbCustomers][f1 in 0...nbFacilities][f2 in 0...nbFacilities] {
distanceMatrix[nbCustomers + c1 * nbFacilities + f1][nbCustomers + c2 * nbFacilities + f2] =
computeDistance(facilitiesX[f1], facilitiesX[f2], facilitiesY[f1], facilitiesY[f2]);
}
}
function computeAssignmentCosts() {
// Compute assignment cost for each point
assignmentCosts[c in 0...nbCustomers] = 0;
for [c in 0...nbCustomers][f in 0...nbFacilities] {
// Cost of serving customer c through facility f
assignmentCosts[nbCustomers + c * nbFacilities + f] = distanceMatrix[c][nbCustomers + c * nbFacilities + f];
}
}
function computeDistance(x1, x2, y1, y2) {
return round(sqrt(pow((x1 - x2), 2) + pow((y1 - y2), 2)));
}
- Execution (Windows)
-
set PYTHONPATH=%HX_HOME%\bin\pythonpython vrptf.py instances\coord20-5-1.dat
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_13_0/bin/pythonpython vrptf.py instances/coord20-5-1.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, nb_facilities, capacity, customers_demands, \
depot_distances_data, distance_matrix_data, assignement_costs_data = read_input(instance_file)
# A point is either a customer or a facility
# Facilities are duplicated for each customer
nb_points = nb_customers + nb_customers * nb_facilities
demands_data = [None] * nb_points
for c in range(nb_customers):
demands_data[c] = customers_demands[c]
for f in range(nb_facilities):
demands_data[nb_customers + c * nb_facilities + f] = customers_demands[c]
min_nb_trucks = int(math.ceil(sum(customers_demands) / capacity))
nb_trucks = int(math.ceil(1.5 * min_nb_trucks))
with hexaly.optimizer.HexalyOptimizer() as optimizer:
#
# Declare the optimization model
#
m = optimizer.model
# Each route is represented as a list containing the points in the order they are visited
routes_sequences = [m.list(nb_points) for _ in range(nb_trucks)]
routes = m.array(routes_sequences)
# Each point must be visited at most once
m.constraint(m.disjoint(routes_sequences))
dist_routes = [None] * nb_trucks
assignement_cost_routes = [None] * nb_trucks
# Create Hexaly arrays to be able to access them with "at" operators
demands = m.array(demands_data)
dist_matrix = m.array()
dist_depots = m.array(depot_distances_data)
assignement_costs = m.array(assignement_costs_data)
for i in range(nb_points):
dist_matrix.add_operand(m.array(distance_matrix_data[i]))
for c in range(nb_customers):
start_facilities = nb_customers + c * nb_facilities
end_facilities = start_facilities + nb_facilities
# Each customer is either contained in a route or assigned to a facility
facility_used = [m.contains(routes, f) for f in range(start_facilities, end_facilities)]
delivery_count = m.contains(routes, c) + m.sum(facility_used)
m.constraint(delivery_count == 1)
for r in range(nb_trucks):
route = routes_sequences[r]
c = m.count(route)
# Each truck cannot carry more than its capacity
demand_lambda = m.lambda_function(lambda j: demands[j])
quantity_served = m.sum(route, demand_lambda)
m.constraint(quantity_served <= capacity)
# Distance traveled by each truck
dist_lambda = m.lambda_function(
lambda i: m.at(dist_matrix, route[i], route[i + 1]))
dist_routes[r] = m.sum(m.range(0, c - 1), dist_lambda) + m.iif(
c > 0,
m.at(dist_depots, route[0])
+ m.at(dist_depots, route[c - 1]),
0)
# Cost to assign customers to their facility
assignment_cost_lambda = m.lambda_function(
lambda i: assignement_costs[i]
)
assignement_cost_routes[r] = m.sum(route, assignment_cost_lambda)
# The total distance travelled
total_distance_cost = m.sum(dist_routes)
# The total assignement cost
total_assignement_cost = m.sum(assignement_cost_routes)
# Objective: minimize the sum of the total distance travelled and the total assignement cost
total_cost = total_distance_cost + total_assignement_cost
m.minimize(total_cost)
m.close()
# Parameterize the optimizer
optimizer.param.time_limit = int(str_time_limit)
optimizer.solve()
if sol_file != None:
with open(sol_file, 'w') as file:
file.write("File name: {}; totalCost = {}; totalDistance = {}; totalAssignementCost = {}\n"
.format(instance_file, total_cost.value, total_distance_cost.value, total_assignement_cost.value))
for r in range(nb_trucks):
route = routes_sequences[r].value
if len(route) == 0:
continue
file.write("Route {} [".format(r))
for i, point in enumerate(route):
if point < nb_customers:
file.write("Customer {}".format(point))
else:
file.write("Facility {} assigned to Customer {}"
.format(point % nb_customers, (point - nb_customers) // nb_facilities))
if i < len(route) - 1:
file.write(", ")
file.write("]\n")
def read_input_dat(filename):
file_it = iter(read_elem(filename))
nb_customers = int(next(file_it))
nb_facilities = int(next(file_it))
facilities_x = [None] * nb_facilities
facilities_y = [None] * nb_facilities
for i in range(nb_facilities):
facilities_x[i] = int(next(file_it))
facilities_y[i] = int(next(file_it))
customers_x = [None] * nb_customers
customers_y = [None] * nb_customers
for i in range(nb_customers):
customers_x[i] = int(next(file_it))
customers_y[i] = int(next(file_it))
truck_capacity = int(next(file_it))
# Facility capacities : skip
for f in range(nb_facilities):
next(file_it)
customer_demands = [None] * nb_customers
for i in range(nb_customers):
customer_demands[i] = int(next(file_it))
depot_x, depot_y = compute_depot_coordinates(customers_x, customers_y,
facilities_x, facilities_y)
depot_distances, distance_matrix = compute_distances(customers_x, customers_y,
facilities_x, facilities_y,
depot_x, depot_y)
assignement_costs = compute_assignment_costs(nb_customers, nb_facilities, distance_matrix)
return nb_customers, nb_facilities, truck_capacity, customer_demands, \
depot_distances, distance_matrix, assignement_costs
def compute_depot_coordinates(customers_x, customers_y, facilities_x, facilities_y):
# Compute the coordinates of the bounding box containing all of the points
x_min = min(min(customers_x), min(facilities_x))
x_max = max(max(customers_x), max(facilities_x))
y_min = min(min(customers_y), min(facilities_y))
y_max = max(max(customers_y), max(facilities_y))
# We assume that the depot is at the center of the bounding box
return x_min + (x_max - x_min) // 2, y_min + (y_max - y_min) // 2
def compute_distances(customers_x, customers_y, facilities_x, facilities_y, depot_x, depot_y):
nb_customers = len(customers_x)
nb_facilities = len(facilities_x)
nb_points = nb_customers + nb_customers * nb_facilities
# Distance to depot
depot_distances = [None] * nb_points
# Customer to depot
for c in range(nb_customers):
depot_distances[c] = compute_dist(customers_x[c], depot_x, customers_y[c], depot_y)
# Facility to depot
for c in range(nb_customers):
for f in range(nb_facilities):
depot_distances[nb_customers + c * nb_facilities + f] = \
compute_dist(facilities_x[f], depot_x, facilities_y[f], depot_y)
# Distance between points
distance_matrix = [[None for _ in range(nb_points)] for _ in range(nb_points)]
# Distances between customers
for c_1 in range(nb_customers):
for c_2 in range(nb_customers):
distance_matrix[c_1][c_2] = \
compute_dist(customers_x[c_1], customers_x[c_2],
customers_y[c_1], customers_y[c_2])
# Distances between customers and facilities
for c_1 in range(nb_customers):
for f in range(nb_facilities):
distance = compute_dist(facilities_x[f], customers_x[c_1],
facilities_y[f], customers_y[c_1])
for c_2 in range(nb_customers):
# Index representing serving c_2 through facility f
facility_index = nb_customers + c_2 * nb_facilities + f
distance_matrix[facility_index][c_1] = distance
distance_matrix[c_1][facility_index] = distance
# Distances between facilities
for f_1 in range(nb_facilities):
for f_2 in range(nb_facilities):
dist = compute_dist(facilities_x[f_1], facilities_x[f_2], facilities_y[f_1], facilities_y[f_2])
for c_1 in range(nb_customers):
for c_2 in range(nb_customers):
distance_matrix[nb_customers + c_1 * nb_facilities + f_1]\
[nb_customers + c_2 * nb_facilities + f_2] = dist
return depot_distances, distance_matrix
def compute_assignment_costs(nb_customers, nb_facilities, distance_matrix):
# Compute assignment cost for each point
nb_points = nb_customers + nb_customers * nb_facilities
assignment_costs = [0] * nb_points
for c in range(nb_customers):
for f in range(nb_facilities):
# Cost of serving customer c through facility f
assignment_costs[nb_customers + c * nb_facilities + f] = \
distance_matrix[c][nb_customers + c * nb_facilities + f]
return assignment_costs
def compute_dist(xi, xj, yi, yj):
exact_dist = math.sqrt(math.pow(xi - xj, 2) + math.pow(yi - yj, 2))
return round(exact_dist)
def read_input(filename):
if filename.endswith(".dat"):
return read_input_dat(filename)
else:
raise Exception("Unknown file format")
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python vrptf.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 vrptf.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.libvrptf instances\coord20-5-1.dat
- Compilation / Execution (Linux)
-
g++ vrptf.cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o vrptf./vrptf instances/coord20-5-1.dat
#include <climits>
#include <cmath>
#include <cstring>
#include <fstream>
#include <iostream>
#include <numeric>
#include <vector>
#include <algorithm>
#include "optimizer/hexalyoptimizer.h"
using namespace hexaly;
using namespace std;
class Vrptf {
public:
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Number of customers
int nbCustomers;
// Customers coordinates
vector<int> xCustomers;
vector<int> yCustomers;
// Customers demands
vector<int> demandsData;
// Number of depots
int nbFacilities;
// Depots coordinates
vector<int> xFacilities;
vector<int> yFacilities;
// Number of points
int nbPoints;
// Depot coordinates
int xDepot;
int yDepot;
// Number of trucks
int nbTrucks;
// Capacity of trucks
int truckCapacity;
// Distance matrix
vector<vector<int>> distMatrixData;
// Distance to depot array
vector<int> distDepotsData;
// Assignement costs
vector<int> assignmentCostsData;
// Decision variables
vector<HxExpression> routesSequences;
// Objective value
HxExpression totalDistanceCost;
HxExpression totalAssignementCost;
HxExpression totalCost;
void readInstance(const char* fileName) {
readInput(fileName);
}
void solve(const char* limit) {
// Declare the optimization model
HxModel m = optimizer.getModel();
double totalDemand = accumulate(demandsData.begin(), demandsData.begin() + nbCustomers, 0);
int minNbTrucks = ceil(totalDemand / truckCapacity);
nbTrucks = ceil(1.5 * minNbTrucks);
// A route is represented as a list containing the points in the order they are visited
routesSequences.resize(nbTrucks);
for (int r = 0; r < nbTrucks; ++r) {
routesSequences[r] = m.listVar(nbPoints);
}
HxExpression routes = m.array(routesSequences.begin(), routesSequences.end());
// Each customer must be visited at most once
m.constraint(m.disjoint(routesSequences.begin(), routesSequences.end()));
// Create Hexaly arrays to be able to access them with "at" operators
HxExpression demands = m.array(demandsData.begin(), demandsData.end());
HxExpression distMatrix = m.array();
for (int i = 0; i < nbPoints; ++i) {
distMatrix.addOperand(m.array(distMatrixData[i].begin(), distMatrixData[i].end()));
}
HxExpression distDepots = m.array(distDepotsData.begin(), distDepotsData.end());
HxExpression assignementCosts = m.array(assignmentCostsData.begin(), assignmentCostsData.end());
vector<HxExpression> distRoutes(nbTrucks);
vector<HxExpression> assignmentCostRoutes(nbTrucks);
for (int c = 0; c < nbCustomers; ++c) {
int startFacilities = nbCustomers + c * nbFacilities;
int endFacilities = startFacilities + nbFacilities;
// Each customer is either contained in a route or assigned to a facility
HxExpression facilityUsedSum = m.sum();
for (int f = startFacilities; f < endFacilities; ++f) {
facilityUsedSum.addOperand(m.contains(routes, f));
}
m.constraint(m.contains(routes, c) + facilityUsedSum == 1);
}
for (int r = 0; r < nbTrucks; ++r) {
HxExpression route = routesSequences[r];
HxExpression c = m.count(route);
// Each truck cannot carry more than its capacity
HxExpression demandLambda = m.lambdaFunction([&](HxExpression j) { return demands[j]; });
HxExpression quantityServed = m.sum(route, demandLambda);
m.constraint(quantityServed <= truckCapacity);
HxExpression distLambda =
m.lambdaFunction([&](HxExpression i) { return m.at(distMatrix, route[i], route[i + 1]); });
// Truck is used if it visits at least one point
HxExpression truckUsed = c > 0;
// Distance traveled by each truck
distRoutes[r] = m.sum(m.range(0, c - 1), distLambda) +
m.iif(truckUsed,
m.at(distDepots, route[0]) +
m.at(distDepots, route[c - 1]),
0);
// The cost to assign customers to their facility
HxExpression assignementCostLambda =
m.lambdaFunction([&](HxExpression i) { return assignementCosts[i]; });
assignmentCostRoutes[r] = m.sum(route, assignementCostLambda);
}
// The total distance travelled
totalDistanceCost = m.sum(distRoutes.begin(), distRoutes.end());
// The total assignement cost
totalAssignementCost = m.sum(assignmentCostRoutes.begin(), assignmentCostRoutes.end());
// Objective: minimize the sum of the total distance travelled and the total assignement cost
totalCost = totalDistanceCost + totalAssignementCost;
m.minimize(totalCost);
m.close();
optimizer.getParam().setTimeLimit(atoi(limit));
optimizer.solve();
}
/* Write the solution in a file */
void writeSolution(const char* inFile, const string& solFile) {
ofstream file;
file.exceptions(ofstream::failbit | ofstream::badbit);
file.open(solFile.c_str());
file << "File name: " << inFile << "; totalCost = " << totalCost.getIntValue()
<< "; totalDistance = " << totalDistanceCost.getIntValue()
<< "; totalAssignementCost = " << totalAssignementCost.getIntValue() << endl;
for (int r = 0; r < nbTrucks; ++r) {
HxCollection route = routesSequences[r].getCollectionValue();
if (route.count() == 0) continue;
file << "Route " << r << " [";
for (int i = 0; i < route.count(); ++i) {
long point = route.get(i);
if (point < nbCustomers) {
file << "Customer " << point;
}
else {
file << "Facility " << point % nbCustomers << " assigned to Customer " << (point - nbCustomers) / nbFacilities;
}
if (i < route.count() - 1) {
file << ", ";
}
}
file << "]" << endl;
}
}
private:
void readInput(const char* fileName) {
string file = fileName;
ifstream infile(file.c_str());
if (!infile.is_open()) {
throw std::runtime_error("File cannot be opened.");
}
infile >> nbCustomers;
xCustomers.resize(nbCustomers);
yCustomers.resize(nbCustomers);
infile >> nbFacilities;
xFacilities.resize(nbFacilities);
yFacilities.resize(nbFacilities);
// A point is either a customer or a facility
// Facilities are duplicated for each customer
nbPoints = nbCustomers + nbCustomers * nbFacilities;
demandsData.resize(nbPoints);
distMatrixData.resize(nbPoints);
for (int i = 0; i < nbPoints; ++i) {
distMatrixData[i].resize(nbPoints);
}
distDepotsData.resize(nbPoints);
for (int f = 0; f < nbFacilities; ++f) {
infile >> xFacilities[f];
infile >> yFacilities[f];
}
for (int c = 0; c < nbCustomers; ++c) {
infile >> xCustomers[c];
infile >> yCustomers[c];
}
infile >> truckCapacity;
// Facility capacities : skip
infile.ignore(1000, '\n');
infile.ignore(1000, '\n');
for (int f = 0; f < nbFacilities; ++f) {
infile.ignore(1000, '\n');
}
for (int c = 0; c < nbCustomers; ++c) {
infile >> demandsData[c];
for (int f = 0; f < nbFacilities; ++f) {
demandsData[nbCustomers + c * nbFacilities + f] = demandsData[c];
}
}
computeDepotCoordinates();
computeDistances();
computeAssignmentCosts();
}
void computeDepotCoordinates() {
// Compute the coordinates of the bounding box containing all of the points
int xMin = INT_MAX;
int xMax = INT_MIN;
int yMin = INT_MAX;
int yMax = INT_MIN;
for (int c = 0; c < nbCustomers; ++c) {
xMin = min(xMin, xCustomers[c]);
}
for (int f = 0; f < nbFacilities; ++f) {
xMin = min(xMin, xFacilities[f]);
}
for (int c = 0; c < nbCustomers; ++c) {
xMax = max(xMax, xCustomers[c]);
}
for (int f = 0; f < nbFacilities; ++f) {
xMax = max(xMax, xFacilities[f]);
}
for (int c = 0; c < nbCustomers; ++c) {
yMin = min(yMin, yCustomers[c]);
}
for (int f = 0; f < nbFacilities; ++f) {
yMin = min(yMin, yFacilities[f]);
}
for (int c = 0; c < nbCustomers; ++c) {
yMax = max(yMax, yCustomers[c]);
}
for (int f = 0; f < nbFacilities; ++f) {
yMax = max(yMax, yFacilities[f]);
}
// We assume that the depot is at the center of the bounding box
xDepot = xMin + (xMax - xMin) / 2;
yDepot = yMin + (yMax - yMin) / 2;
}
void computeDistances() {
// Customer to depot
for (int c = 0; c < nbCustomers; ++c) {
distDepotsData[c] = computeDist(xCustomers[c], xDepot, yCustomers[c], yDepot);
}
// Facility to depot
for (int c = 0; c < nbCustomers; ++c) {
for (int f = 0; f < nbFacilities; ++f) {
distDepotsData[nbCustomers + c * nbFacilities + f] = computeDist(xFacilities[f], xDepot, yFacilities[f],
yDepot);
}
}
// Distances between customers
for (int c1 = 0; c1 < nbCustomers; ++c1) {
for (int c2 = 0; c2 < nbCustomers; ++c2) {
long dist = computeDist(xCustomers[c1], xCustomers[c2], yCustomers[c1], yCustomers[c2]);
distMatrixData[c1][c2] = dist;
distMatrixData[c2][c1] = dist;
}
}
// Distances between customers and facilities
for (int c1 = 0; c1 < nbCustomers; ++c1) {
for (int f = 0; f < nbFacilities; ++f) {
long dist = computeDist(xFacilities[f], xCustomers[c1], yFacilities[f], yCustomers[c1]);
for (int c2 = 0; c2 < nbCustomers; ++c2) {
// Index representing serving c2 through facility f
int facilityIndex = nbCustomers + c2 * nbFacilities + f;
distMatrixData[facilityIndex][c1] = dist;
distMatrixData[c1][facilityIndex] = dist;
}
}
}
// Distances between facilities
for (int f1 = 0; f1 < nbFacilities; ++f1) {
for (int f2 = 0; f2 < nbFacilities; ++f2) {
long dist = computeDist(xFacilities[f1], xFacilities[f2], yFacilities[f1], yFacilities[f2]);
for (int c1 = 0; c1 < nbCustomers; ++c1) {
// Index representing serving c1 through facility f1
int index1 = nbCustomers + c1 * nbFacilities + f1;
for (int c2 = 0; c2 < nbCustomers; ++c2) {
// Index representing serving c2 through facility f2
int index2 = nbCustomers + c2 * nbFacilities + f2;
distMatrixData[index1][index2] = dist;
}
}
}
}
}
void computeAssignmentCosts() {
// Compute assignment cost for each point
assignmentCostsData.resize(nbPoints);
for (int c = 0; c < nbCustomers; ++c) {
assignmentCostsData[c] = 0;
for (int f = 0; f < nbFacilities; ++f) {
// Cost of serving customer c through facility f
assignmentCostsData[nbCustomers + c * nbFacilities + f] =
distMatrixData[c][nbCustomers + c * nbFacilities + f];
}
}
}
int computeDist(int xi, int xj, int yi, int yj) {
double exactDist = sqrt(pow(xi - xj, 2) + pow(yi - yj, 2));
return round(exactDist);
}
};
int main(int argc, char** argv) {
if (argc < 2) {
cerr << "Usage: ./vrptf 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 {
Vrptf model;
model.readInstance(instanceFile);
model.solve(strTimeLimit);
if (solFile != NULL)
model.writeSolution(instanceFile, solFile);
} catch (const std::exception& e) {
std::cerr << "An error occured: " << e.what() << endl;
}
return 0;
}
- Compilation / Execution (Windows)
-
copy %HX_HOME%\bin\Hexaly.NET.dll .csc Vrptf.cs /reference:Hexaly.NET.dllVrptf instances\coord20-5-1.dat
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Hexaly.Optimizer;
public class Vrptf : IDisposable
{
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Number of customers
int nbCustomers;
// Customers coordinates
int[] xCustomers;
int[] yCustomers;
// Customers demands
int[] demandsData;
// Number of facilities
int nbFacilities;
// Depots coordinates
int[] xFacilities;
int[] yFacilities;
// Number of points
int nbPoints;
// Depot coordinates
int xDepot;
int yDepot;
// Number of trucks
int nbTrucks;
// Capacity of a truck
int truckCapacity;
// Distance matrix
long[][] distMatrixData;
// Distance to depot array
long[] distDepotsData;
// Assignement costs
long[] assignmentCostsData;
// Decision variables
HxExpression[] routesSequences;
// Objective value
HxExpression totalDistanceCost;
HxExpression totalAssignementCost;
HxExpression totalCost;
public Vrptf()
{
optimizer = new HexalyOptimizer();
}
public void Dispose()
{
if (optimizer != null)
optimizer.Dispose();
}
void Solve(int limit)
{
// Declare the optimization model
HxModel m = optimizer.GetModel();
double totalDemand = 0;
for (int c = 0; c < nbCustomers; ++c)
{
totalDemand += demandsData[c];
}
int minNbTrucks = (int)Math.Ceiling(totalDemand / truckCapacity);
nbTrucks = (int)Math.Ceiling(1.5 * minNbTrucks);
routesSequences = new HxExpression[nbTrucks];
// A route is represented as a list containing the points in the order they are visited
for (int r = 0; r < nbTrucks; ++r)
{
routesSequences[r] = m.List(nbPoints);
}
HxExpression routes = m.Array(routesSequences);
// Each point must be visited at most once
m.Constraint(m.Disjoint(routesSequences));
HxExpression demands = m.Array(demandsData);
HxExpression distMatrix = m.Array(distMatrixData);
HxExpression distDepots = m.Array(distDepotsData);
HxExpression assignmentCosts = m.Array(assignmentCostsData);
HxExpression[] distRoutes = new HxExpression[nbTrucks];
HxExpression[] assignmentCostRoutes = new HxExpression[nbTrucks];
for (int c = 0; c < nbCustomers; ++c)
{
int startFacilities = nbCustomers + c * nbFacilities;
int endFacilities = startFacilities + nbFacilities;
// Each customer is either contained in a route or assigned to a facility
HxExpression facilityUsedSum = m.Sum();
for (int f = startFacilities; f < endFacilities; ++f)
{
facilityUsedSum.AddOperand(m.Contains(routes, f));
}
m.Constraint(m.Contains(routes, c) + facilityUsedSum == 1);
}
for (int r = 0; r < nbTrucks; ++r)
{
HxExpression route = routesSequences[r];
HxExpression c = m.Count(route);
// Each truck cannot carry more than its capacity
HxExpression demandLambda = m.LambdaFunction(j => m.At(demands, j));
HxExpression quantityServed = m.Sum(route, demandLambda);
m.Constraint(quantityServed <= truckCapacity);
HxExpression distLambda = m.LambdaFunction(i => m.At(distMatrix, m.At(route, i), m.At(route, i + 1)));
// Truck is used if it visits at least one point
HxExpression truckUsed = c > 0;
// Distance traveled by each truck
distRoutes[r] = m.Sum(m.Range(0, c - 1), distLambda) +
m.If(truckUsed,
m.At(distDepots, m.At(route, 0)) +
m.At(distDepots, m.At(route, c - 1)),
0);
// The cost to assign customers to their facility
HxExpression assignmentCostLambda = m.LambdaFunction(i => m.At(assignmentCosts, i));
assignmentCostRoutes[r] = m.Sum(route, assignmentCostLambda);
}
// The total distance travelled
totalDistanceCost = m.Sum(distRoutes);
// The total assignement cost
totalAssignementCost = m.Sum(assignmentCostRoutes);
// Objective: minimize the sum of the total distance travelled and the total assignement cost
totalCost = totalDistanceCost + totalAssignementCost;
m.Minimize(totalCost);
m.Close();
optimizer.GetParam().SetTimeLimit(limit);
optimizer.Solve();
}
/* Read instance data */
void ReadInstance(string fileName)
{
using (StreamReader input = new StreamReader(fileName))
{
string[] line;
line = ReadNextLine(input);
nbCustomers = int.Parse(line[0]);
xCustomers = new int[nbCustomers];
yCustomers = new int[nbCustomers];
line = ReadNextLine(input);
nbFacilities = int.Parse(line[0]);
xFacilities = new int[nbFacilities];
yFacilities = new int[nbFacilities];
// A point is either a customer or a facility
// Facilities are duplicated for each customer
nbPoints = nbCustomers + nbCustomers * nbFacilities;
distMatrixData = new long[nbPoints][];
distDepotsData = new long[nbPoints];
demandsData = new int[nbPoints];
line = ReadNextLine(input);
for (int f = 0; f < nbFacilities; ++f)
{
line = ReadNextLine(input);
xFacilities[f] = int.Parse(line[0]);
yFacilities[f] = int.Parse(line[1]);
}
line = ReadNextLine(input);
for (int c = 0; c < nbCustomers; ++c)
{
line = ReadNextLine(input);
xCustomers[c] = int.Parse(line[0]);
yCustomers[c] = int.Parse(line[1]);
}
line = ReadNextLine(input);
line = ReadNextLine(input);
truckCapacity = int.Parse(line[0]);
line = ReadNextLine(input);
// Facility capacities : skip
for (int f = 0; f < nbFacilities; ++f)
{
ReadNextLine(input);
}
line = ReadNextLine(input);
for (int c = 0; c < nbCustomers; ++c)
{
line = ReadNextLine(input);
demandsData[c] = int.Parse(line[0]);
for (int f = 0; f < nbFacilities; f++)
{
demandsData[nbCustomers + c * nbFacilities + f] = demandsData[c];
}
}
line = ReadNextLine(input);
ComputeDepotCoordinates();
ComputeDistanceArrays();
ComputeAssignmentCosts();
}
}
void ComputeDepotCoordinates()
{
// Compute the coordinates of the bounding box containing all of the points
int xMin = Math.Min(xCustomers.Min(), xFacilities.Min());
int xMax = Math.Max(xCustomers.Max(), xFacilities.Max());
int yMin = Math.Min(yCustomers.Min(), yFacilities.Min());
int yMax = Math.Max(yCustomers.Max(), yFacilities.Max());
// We assume that the depot is at the center of the bounding box
xDepot = xMin + (xMax - xMin) / 2;
yDepot = yMin + (yMax - yMin) / 2;
}
void ComputeDistanceArrays()
{
distDepotsData = new long[nbPoints];
// Customer to depot
for (int c = 0; c < nbCustomers; ++c)
{
distDepotsData[c] = ComputeDist(xCustomers[c], xDepot, yCustomers[c], yDepot);
}
// Facility to depot
for (int c = 0; c < nbCustomers; ++c)
{
for (int f = 0; f < nbFacilities; ++f)
{
distDepotsData[nbCustomers + c * nbFacilities + f] = ComputeDist(xFacilities[f], xDepot, yFacilities[f], yDepot);
}
}
distMatrixData = new long[nbPoints][];
for (int i = 0; i < nbPoints; i++)
{
distMatrixData[i] = new long[nbPoints];
}
// Distances between customers
for (int c1 = 0; c1 < nbCustomers; ++c1)
{
for (int c2 = 0; c2 < nbCustomers; ++c2)
{
long dist = ComputeDist(xCustomers[c1], xCustomers[c2], yCustomers[c1], yCustomers[c2]);
distMatrixData[c1][c2] = dist;
distMatrixData[c2][c1] = dist;
}
}
// Distances between customers and facilities
for (int c1 = 0; c1 < nbCustomers; ++c1)
{
for (int f = 0; f < nbFacilities; ++f)
{
long dist = ComputeDist(xFacilities[f], xCustomers[c1], yFacilities[f], yCustomers[c1]);
for (int c2 = 0; c2 < nbCustomers; ++c2)
{
// Index representing serving c2 through facility f
int facilityIndex = nbCustomers + c2 * nbFacilities + f;
distMatrixData[facilityIndex][c1] = dist;
distMatrixData[c1][facilityIndex] = dist;
}
}
}
// Distances between facilities
for (int f1 = 0; f1 < nbFacilities; ++f1)
{
for (int f2 = 0; f2 < nbFacilities; ++f2)
{
long dist = ComputeDist(xFacilities[f1], xFacilities[f2], yFacilities[f1], yFacilities[f2]);
for (int c1 = 0; c1 < nbCustomers; ++c1)
{
// Index representing serving c1 through facility f1
int index1 = nbCustomers + c1 * nbFacilities + f1;
for (int c2 = 0; c2 < nbCustomers; ++c2)
{
// Index representing serving c2 through facility f2
int index2 = nbCustomers + c2 * nbFacilities + f2;
distMatrixData[index1][index2] = dist;
}
}
}
}
}
private long ComputeDist(int xi, int xj, int yi, int yj)
{
double exactDist = Math.Sqrt(Math.Pow(xi - xj, 2) + Math.Pow(yi - yj, 2));
return Convert.ToInt64(Math.Round(exactDist));
}
private void ComputeAssignmentCosts() {
// Compute assignment cost for each point
assignmentCostsData = new long[nbPoints];
for (int c = 0; c < nbCustomers; ++c)
{
assignmentCostsData[c] = 0;
for (int f = 0; f < nbFacilities; ++f)
{
// Cost of serving customer c through facility f
assignmentCostsData[nbCustomers + c * nbFacilities + f] =
distMatrixData[c][nbCustomers + c * nbFacilities + f];
}
}
}
/* Write the solution in a file */
void WriteSolution(string infile, string outfile)
{
using (StreamWriter output = new StreamWriter(outfile))
{
output.WriteLine(
"File name: " + infile + "; totalCost = " + totalCost.GetIntValue()
+ "; totalDistance = " + totalDistanceCost.GetIntValue()
+ "; totalAssignementCost = " + totalAssignementCost.GetIntValue()
);
for (int r = 0; r < nbTrucks; ++r)
{
HxCollection route = routesSequences[r].GetCollectionValue();
if (route.Count() == 0) continue;
output.Write("Route " + r + " [");
for (int i = 0; i < route.Count(); i++)
{
long point = route.Get(i);
if (point < nbCustomers)
{
output.Write("Customer " + point);
}
else
{
output.Write("Facility " + point % nbCustomers + " assigned to Customer " + (point - nbCustomers) / nbFacilities);
}
if (i < route.Count() - 1)
{
output.Write(", ");
}
}
output.WriteLine("]");
}
}
}
String[] ReadNextLine(StreamReader input)
{
return input.ReadLine().Split(new[] { '\t' }, StringSplitOptions.RemoveEmptyEntries);
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: Vrptf inputFile [outputFile] [timeLimit]");
Environment.Exit(1);
}
string instanceFile = args[0];
string strOutput = args.Length > 1 ? args[1] : null;
string strTimeLimit = args.Length > 2 ? args[2] : "20";
using (Vrptf model = new Vrptf())
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (strOutput != null)
model.WriteSolution(instanceFile, strOutput);
}
}
}
- Compilation / Execution (Windows)
-
javac Vrptf.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. Vrptf instances\coord20-5-1.dat
- Compilation / Execution (Linux)
-
javac Vrptf.java -cp /opt/hexaly_13_0/bin/hexaly.jarjava -cp /opt/hexaly_13_0/bin/hexaly.jar:. Vrptf instances/coord20-5-1.dat
import com.hexaly.optimizer.*;
import java.io.*;
import java.util.*;
public class Vrptf {
// Hexaly Optimizer
private final HexalyOptimizer optimizer;
// Number of customers
private int nbCustomers;
// Customers coordinates
private int[] xCustomers;
private int[] yCustomers;
// Customers demands
private int[] demandsData;
// Number of facilities
private int nbFacilities;
// Facilities coordinates
private int[] xFacilities;
private int[] yFacilities;
// Number of points
private int nbPoints;
// Depot coordinates
private int xDepot;
private int yDepot;
// Number of trucks
private int nbTrucks;
// Capacity of trucks
private int truckCapacity;
// Distance matrix
private long[][] distMatrixData;
// Distance to depot array
private long[] distDepotsData;
// Assignement costs
private long[] assignmentCostsData;
// Decision variables
private HxExpression[] routesSequences;
// Objective value
private HxExpression totalDistanceCost;
private HxExpression totalAssignementCost;
private HxExpression totalCost;
private Vrptf(HexalyOptimizer optimizer) {
this.optimizer = optimizer;
}
private void solve(int limit) {
// Declare the optimization model
HxModel m = optimizer.getModel();
double totalDemand = 0;
for (int c = 0; c < nbCustomers; ++c) {
totalDemand += demandsData[c];
}
int minNbTrucks = (int) Math.ceil(totalDemand / truckCapacity);
nbTrucks = (int) Math.ceil(1.5 * minNbTrucks);
routesSequences = new HxExpression[nbTrucks];
// A route is represented as a list containing the points in the order they are
// visited
for (int i = 0; i < nbTrucks; ++i) {
routesSequences[i] = m.listVar(nbPoints);
}
HxExpression routes = m.array(routesSequences);
// Each point must be visited at most once
m.constraint(m.disjoint(routesSequences));
HxExpression demands = m.array(demandsData);
HxExpression distMatrix = m.array(distMatrixData);
HxExpression distDepots = m.array(distDepotsData);
HxExpression assignmentCosts = m.array(assignmentCostsData);
HxExpression[] distRoutes = new HxExpression[nbTrucks];
HxExpression[] assignmentCostRoutes = new HxExpression[nbTrucks];
for (int c = 0; c < nbCustomers; ++c) {
int startFacilities = nbCustomers + c * nbFacilities;
int endFacilities = startFacilities + nbFacilities;
// Each customer is either contained in a route or assigned to a facility
HxExpression facilityUsedSum = m.sum();
for (int f = startFacilities; f < endFacilities; ++f) {
facilityUsedSum.addOperand(m.contains(routes, f));
}
m.constraint(m.eq(m.sum(m.contains(routes, c), facilityUsedSum), 1));
}
for (int r = 0; r < nbTrucks; ++r) {
HxExpression route = routesSequences[r];
HxExpression c = m.count(route);
// Each truck cannot carry more than its capacity
HxExpression demandLambda = m.lambdaFunction(j -> m.at(demands, j));
HxExpression quantityServed = m.sum(route, demandLambda);
m.constraint(m.leq(quantityServed, truckCapacity));
HxExpression distLambda = m
.lambdaFunction(i -> m.at(distMatrix, m.at(route, i), m.at(route, m.sum(i, 1))));
// Truck is used if it visits at least one point
HxExpression truckUsed = m.gt(c, 0);
// Distance traveled by each truck
distRoutes[r] = m.sum(
m.sum(m.range(0, m.sub(c, 1)), distLambda),
m.iif(truckUsed,
m.sum(m.at(distDepots, m.at(route, 0)),
m.at(distDepots, m.at(route, m.sub(c, 1)))),
0));
// The cost to assign customers to their facility
HxExpression assignmentCostLambda = m.lambdaFunction(i -> m.at(assignmentCosts, i));
assignmentCostRoutes[r] = m.sum(route, assignmentCostLambda);
}
// The total distance travelled
totalDistanceCost = m.sum(distRoutes);
// The total assignement cost
totalAssignementCost = m.sum(assignmentCostRoutes);
// Objective: minimize the sum of the total distance travelled and the total
// assignement cost
totalCost = m.sum(totalDistanceCost, totalAssignementCost);
m.minimize(totalCost);
m.close();
optimizer.getParam().setTimeLimit(limit);
optimizer.solve();
}
/* Write the solution to a file */
private void writeSolution(String infile, String outfile) throws IOException {
try (PrintWriter output = new PrintWriter(outfile)) {
output.println("File name: " + infile + "; totalCost = " + totalCost.getIntValue() + "; totalDistance = "
+ totalDistanceCost.getIntValue() + "; totalAssignementCost = "
+ totalAssignementCost.getIntValue());
for (int r = 0; r < nbTrucks; ++r) {
HxCollection route = routesSequences[r].getCollectionValue();
if (route.count() == 0)
continue;
output.print("Route " + r + " [");
for (int i = 0; i < route.count(); i++) {
long point = route.get(i);
if (point < nbCustomers) {
output.print("Customer " + point);
} else {
output.print("Facility " + point % nbCustomers + " assigned to Customer "
+ (point - nbCustomers) / nbFacilities);
}
if (i < route.count() - 1) {
output.print(", ");
}
}
output.println("]");
}
} catch (FileNotFoundException e) {
System.out.println("An error occurred.");
e.printStackTrace();
}
}
private void readInstance(String fileName) throws IOException {
try (Scanner input = new Scanner(new File(fileName))) {
input.useLocale(Locale.ROOT);
nbCustomers = input.nextInt();
nbFacilities = input.nextInt();
// A point is either a customer or a facility
// Facilities are duplicated for each customer
nbPoints = nbCustomers + nbCustomers * nbFacilities;
xFacilities = new int[nbFacilities];
yFacilities = new int[nbFacilities];
for (int f = 0; f < nbFacilities; ++f) {
xFacilities[f] = input.nextInt();
yFacilities[f] = input.nextInt();
}
xCustomers = new int[nbCustomers];
yCustomers = new int[nbCustomers];
for (int c = 0; c < nbCustomers; ++c) {
xCustomers[c] = input.nextInt();
yCustomers[c] = input.nextInt();
}
truckCapacity = input.nextInt();
// Facility capacities : skip
for (int f = 0; f < nbFacilities; ++f) {
input.next();
}
demandsData = new int[nbPoints];
for (int c = 0; c < nbCustomers; ++c) {
demandsData[c] = input.nextInt();
for (int f = 0; f < nbFacilities; ++f) {
demandsData[nbCustomers + c * nbFacilities + f] = demandsData[c];
}
}
computeDepotCoordinates();
computeDistances();
computeAssignmentCosts();
} catch (FileNotFoundException e) {
System.out.println("An error occurred.");
e.printStackTrace();
}
}
void computeDepotCoordinates() {
// Compute the coordinates of the bounding box containing all of the points
int xMin = Integer.MAX_VALUE;
int xMax = Integer.MIN_VALUE;
int yMin = Integer.MAX_VALUE;
int yMax = Integer.MIN_VALUE;
for (int c = 0; c < nbCustomers; ++c) {
xMin = Math.min(xMin, xCustomers[c]);
}
for (int f = 0; f < nbFacilities; ++f) {
xMin = Math.min(xMin, xFacilities[f]);
}
for (int c = 0; c < nbCustomers; ++c) {
xMax = Math.max(xMax, xCustomers[c]);
}
for (int f = 0; f < nbFacilities; ++f) {
xMax = Math.max(xMax, xFacilities[f]);
}
for (int c = 0; c < nbCustomers; ++c) {
yMin = Math.min(yMin, yCustomers[c]);
}
for (int f = 0; f < nbFacilities; ++f) {
yMin = Math.min(yMin, yFacilities[f]);
}
for (int c = 0; c < nbCustomers; ++c) {
yMax = Math.max(yMax, yCustomers[c]);
}
for (int f = 0; f < nbFacilities; ++f) {
yMax = Math.max(yMax, yFacilities[f]);
}
// We assume that the depot is at the center of the bounding box
xDepot = xMin + (xMax - xMin) / 2;
yDepot = yMin + (yMax - yMin) / 2;
}
void computeDistances() {
distDepotsData = new long[nbPoints];
// Customer to depot
for (int c = 0; c < nbCustomers; ++c) {
distDepotsData[c] = computeDist(xCustomers[c], xDepot, yCustomers[c], yDepot);
}
// Facility to depot
for (int c = 0; c < nbCustomers; ++c) {
for (int f = 0; f < nbFacilities; ++f) {
distDepotsData[nbCustomers + c * nbFacilities + f] = computeDist(xFacilities[f], xDepot, yFacilities[f],
yDepot);
}
}
distMatrixData = new long[nbPoints][];
for (int i = 0; i < nbPoints; i++) {
distMatrixData[i] = new long[nbPoints];
}
// Distances between customers
for (int c1 = 0; c1 < nbCustomers; ++c1) {
for (int c2 = 0; c2 < nbCustomers; ++c2) {
long dist = computeDist(xCustomers[c1], xCustomers[c2], yCustomers[c1], yCustomers[c2]);
distMatrixData[c1][c2] = dist;
distMatrixData[c2][c1] = dist;
}
}
// Distances between customers and facilities
for (int c1 = 0; c1 < nbCustomers; ++c1) {
for (int f = 0; f < nbFacilities; ++f) {
long dist = computeDist(xFacilities[f], xCustomers[c1], yFacilities[f], yCustomers[c1]);
for (int c2 = 0; c2 < nbCustomers; ++c2) {
// Index representing serving c2 through facility f
int facilityIndex = nbCustomers + c2 * nbFacilities + f;
distMatrixData[facilityIndex][c1] = dist;
distMatrixData[c1][facilityIndex] = dist;
}
}
}
// Distances between facilities
for (int f1 = 0; f1 < nbFacilities; ++f1) {
for (int f2 = 0; f2 < nbFacilities; ++f2) {
long dist = computeDist(xFacilities[f1], xFacilities[f2], yFacilities[f1], yFacilities[f2]);
for (int c1 = 0; c1 < nbCustomers; ++c1) {
// Index representing serving c1 through facility f1
int index1 = nbCustomers + c1 * nbFacilities + f1;
for (int c2 = 0; c2 < nbCustomers; ++c2) {
// Index representing serving c2 through facility f2
int index2 = nbCustomers + c2 * nbFacilities + f2;
distMatrixData[index1][index2] = dist;
}
}
}
}
}
void computeAssignmentCosts() {
// Compute assignment cost for each point
assignmentCostsData = new long[nbPoints];
for (int c = 0; c < nbCustomers; ++c) {
assignmentCostsData[c] = 0;
for (int f = 0; f < nbFacilities; ++f) {
// Cost of serving customer c through facility f
assignmentCostsData[nbCustomers + c * nbFacilities + f] =
distMatrixData[c][nbCustomers + c * nbFacilities + f];
}
}
}
private long computeDist(int xi, int xj, int yi, int yj) {
double exactDist = Math.sqrt(Math.pow(xi - xj, 2) + Math.pow(yi - yj, 2));
return Math.round(exactDist);
}
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: java vrptf inputFile [outputFile] [timeLimit]");
System.exit(1);
}
try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
String instanceFile = args[0];
String strOutfile = args.length > 1 ? args[1] : null;
String strTimeLimit = args.length > 2 ? args[2] : "20";
Vrptf model = new Vrptf(optimizer);
model.readInstance(instanceFile);
model.solve(Integer.parseInt(strTimeLimit));
if (strOutfile != null)
model.writeSolution(instanceFile, strOutfile);
} catch (Exception ex) {
System.err.println(ex);
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
}