Prize-Collecting Vehicle Routing Problem (PCVRP)
Problem
In the Prize-Collecting Vehicle Routing Problem (PCVRP), a fleet of delivery vehicles with uniform capacity must service customers with known demand for a common commodity. The vehicles start and end their routes at a common depot, and the total demand served by each vehicle must not exceed its capacity. Each customer must be visited by at most one vehicle. It is not necessary to visit all customers, but there is a minimum demand to satisfy. Furthermore, each visited customer provides a certain prize. There are three objective functions: minimizing the fleet size, maximizing the collected prize, and minimizing the total traveled distance.
Principles learned
- Add list decision variables to model the trucks’ sequences of customers
- Define lambda functions to compute the traveled distance and the collected prize
- Add multiple objectives
Data
The instances provided come from the Long et al. Set A instances. They added a prize and a minimum demand to satisfy to the Augerat et al. Set A instances. The format of the data files is as follows:
- First line: the number of vehicles, the vehicle capacity, the minimum demand to satisfy
- Second line: the ID and coordinates of the depot
- Next lines, for each customer: the ID, coordinates, demand, and prize of this customer.
Program
The Hexaly model for the Prize-Collecting Vehicle Routing Problem (PCVRP) uses list decision variables. For each truck, we define a list variable representing the sequence of customers it visits. Using a ‘disjoint’ constraint on all the lists, we ensure that each customer is served by at most one truck.
A truck is actually used in the fleet if it visits at least one customer. Thanks to the ‘count’ operator, which returns the number of elements in a list, we can check whether each truck is actually used and then compute the total number of trucks used in the fleet.
The total quantity delivered by each truck is computed with a lambda function to apply the ‘sum’ operator over all visited customers. Note that the number of terms in this sum varies during the search, along with the size of the list. We can then constrain this quantity to be lower than the truck capacity. Similarly, we also compute the total prize each truck collects along its route.
The distance traveled from one customer to the next is again accessed using the ‘at’ operator on the two-dimensional distance matrix. We compute the total distance traveled by each truck using another lambda function to sum the distances along its route.
The three objectives are defined in lexicographic order. We first minimize the number of trucks used, then we maximize the total collected prize, and then we minimize the total distance traveled by all trucks.
- Execution
-
hexaly pcvrp.hxm inFileName=instances/A-n32-k5_1.txt [hxTimeLimit=] [solFileName=]
use io;
/* Read instance data. The input files follow the "longjianyu" format */
function input() {
usage = "Usage: hexaly pcvrp.hxm inFileName=inputFile "
+ "[solFileName=outputFile] [hxTimeLimit=timeLimit]";
if (inFileName == nil) throw usage;
readInputPcvrp();
computeDistanceMatrix();
}
/* Declare the optimization model */
function model() {
// Sequence of customers visited by each truck
customersSequences[k in 0...nbTrucks] <- list(nbCustomers);
// A customer might be visited by only one truck
constraint disjoint[k in 0...nbTrucks](customersSequences[k]);
for [k in 0...nbTrucks] {
local sequence <- customersSequences[k];
local c <- count(sequence);
// A truck is used if it visits at least one customer
trucksUsed[k] <- c > 0;
// The quantity needed in each route must not exceed the truck capacity
routeQuantities[k] <- sum(sequence, j => demands[j]);
constraint routeQuantities[k] <= truckCapacity;
// Distance traveled by truck k
routeDistances[k] <- sum(1...c, i => distanceMatrix[sequence[i - 1]][sequence[i]])
+ (c > 0 ? (distanceDepot[sequence[0]] + distanceDepot[sequence[c - 1]]) : 0);
// Route prize of truck k
routePrizes[k] <- sum(sequence, j => prizes[j]);
}
// Total nb demands satisfied
totalQuantity <- sum[k in 0...nbTrucks](routeQuantities[k]);
// Minimal number of demands to satisfy
constraint totalQuantity >= demandsToSatisfy;
// Total nb trucks used
nbTrucksUsed <- sum[k in 0...nbTrucks](trucksUsed[k]);
// Total distance traveled
totalDistance <- sum[k in 0...nbTrucks](routeDistances[k]);
// Total prize
totalPrize <- sum[k in 0...nbTrucks](routePrizes[k]);
// Objective: minimize the number of trucks used, then maximize the total prize and minimize the distance traveled
minimize nbTrucksUsed;
maximize totalPrize;
minimize totalDistance;
}
/* Parametrize the solver */
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 20;
}
/* Write the solution in a file with the following format:
* - total prize, number of trucks used and total distance
* - for each truck the customers visited (omitting the start/end at the depot)
* - number of unvisited customers, demands satisfied */
function output() {
if (solFileName == nil) return;
local outfile = io.openWrite(solFileName);
outfile.println(totalPrize.value, " ", nbTrucksUsed.value, " ", totalDistance.value);
nbUnvisitedCustomers = nbCustomers;
for [k in 0...nbTrucks] {
if (trucksUsed[k].value != 1) continue;
// Values in sequence are in [0...nbCustomers].
// +1 is to put it back in [1...nbCustomers+1]
// as in the data files (0 being the depot)
for [customer in customersSequences[k].value] {
outfile.print(customer + 1, " ");
nbUnvisitedCustomers -= 1;
}
outfile.println();
}
outfile.println(nbUnvisitedCustomers, " ", totalQuantity.value);
outfile.close();
}
function readInputPcvrp() {
local inFile = io.openRead(inFileName);
nbTrucks = inFile.readInt();
truckCapacity = inFile.readInt();
demandsToSatisfy = inFile.readInt();
n = 0;
while (!inFile.eof()) {
if (n != inFile.readInt()) throw "Unexpected index";
customersX[n] = round(inFile.readDouble());
customersY[n] = round(inFile.readDouble());
if (n == 0) {
depotId = n;
} else {
// -1 because depot has no demand neither prize
demands[n - 1] = inFile.readInt();
prizes[n - 1] = inFile.readInt();
}
n += 1;
}
nbCustomers = n - 1;
inFile.close();
}
/* Compute the distance matrix */
function computeDistanceMatrix() {
for [i in 0...nbCustomers] {
distanceMatrix[i][i] = 0;
for [j in i+1...nbCustomers] {
local localDistance = computeDist(i + 1, j + 1);
distanceMatrix[j][i] = localDistance;
distanceMatrix[i][j] = localDistance;
}
}
for [i in 0...nbCustomers] {
local localDistance = computeDist(0, i + 1);
distanceDepot[i] = localDistance;
}
}
/* Compute the euclidean distance */
function computeDist(i, j) {
local exactDist = sqrt(pow((customersX[i] - customersX[j]), 2)
+ pow((customersY[i] - customersY[j]), 2));
return round(exactDist);
}
- Execution (Windows)
-
set PYTHONPATH=%HX_HOME%\bin\pythonpython pcvrp.py instances\A-n32-k5_1.txt
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_13_0/bin/pythonpython pcvrp.py instances/A-n32-k5_1.txt
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, output_file):
#
# Read instance data
#
nb_customers, nb_trucks, truck_capacity, dist_matrix_data, dist_depot_data, \
demands_data, demands_to_satisfy, prizes_data = read_input_pcvrp(instance_file)
with hexaly.optimizer.HexalyOptimizer() as optimizer:
#
# Declare the optimization model
#
model = optimizer.model
# Sequence of customers visited by each truck
customers_sequences = [model.list(nb_customers) for _ in range(nb_trucks)]
# A customer might be visited by only one truck
model.constraint(model.disjoint(customers_sequences))
# Create Hexaly arrays to be able to access them with an "at" operator
demands = model.array(demands_data)
prizes = model.array(prizes_data)
dist_matrix = model.array(dist_matrix_data)
dist_depot = model.array(dist_depot_data)
# A truck is used if it visits at least one customer
trucks_used = [(model.count(customers_sequences[k]) > 0) for k in range(nb_trucks)]
dist_routes = [None] * nb_trucks
route_prizes = [None] * nb_trucks
route_quantities = [None] * nb_trucks
for k in range(nb_trucks):
sequence = customers_sequences[k]
c = model.count(sequence)
# The quantity needed in each route must not exceed the truck capacity
demand_lambda = model.lambda_function(lambda j: demands[j])
route_quantities[k] = model.sum(sequence, demand_lambda)
model.constraint(route_quantities[k] <= truck_capacity)
# Distance traveled by each truck
dist_lambda = model.lambda_function(lambda i:
model.at(dist_matrix,
sequence[i - 1],
sequence[i]))
dist_routes[k] = model.sum(model.range(1, c), dist_lambda) \
+ model.iif(c > 0,
dist_depot[sequence[0]] + dist_depot[sequence[c - 1]],
0)
# Route prize of truck k
prize_lambda = model.lambda_function(lambda j: prizes[j])
route_prizes[k] = model.sum(sequence, prize_lambda)
# Total nb demands satisfied
total_quantity = model.sum(route_quantities)
# Minimal number of demands to satisfy
model.constraint(total_quantity >= demands_to_satisfy)
# Total nb trucks used
nb_trucks_used = model.sum(trucks_used)
# Total distance traveled
total_distance = model.sum(dist_routes)
# Total prize
total_prize = model.sum(route_prizes)
# Objective: minimize the number of trucks used, then maximize the total prize and minimize the distance traveled
model.minimize(nb_trucks_used)
model.maximize(total_prize)
model.minimize(total_distance)
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 prize, number of trucks used and total distance
# - for each truck the customers visited (omitting the start/end at the depot)
# - number of unvisited customers, demands satisfied
if output_file is not None:
with open(output_file, 'w') as f:
f.write("%d %d %d\n" % (total_prize.value, nb_trucks_used.value, total_distance.value))
nb_unvisited_customers = nb_customers
for k in range(nb_trucks):
if trucks_used[k].value != 1:
continue
# Values in sequence are in 0...nbCustomers. +1 is to put it back in 1...nbCustomers+1
# as in the data files (0 being the depot)
for customer in customers_sequences[k].value:
f.write("%d " % (customer + 1))
nb_unvisited_customers -= 1
f.write("\n")
f.write("%d %d\n" % (nb_unvisited_customers, total_quantity.value))
# The input files follow the "longjianyu" format
def read_input_pcvrp(filename):
file_it = iter(read_elem(filename))
nb_trucks = int(next(file_it))
truck_capacity = int(next(file_it))
demands_to_satisfy = int(next(file_it))
n = 0
customers_x = []
customers_y = []
depot_x = 0
depot_y = 0
demands = []
prizes = []
it = next(file_it, None)
while (it != None):
node_id = int(it)
if node_id != n:
print("Unexpected index")
sys.exit(1)
if n == 0:
depot_x = int(next(file_it))
depot_y = int(next(file_it))
else:
customers_x.append(int(next(file_it)))
customers_y.append(int(next(file_it)))
demands.append(int(next(file_it)))
prizes.append(int(next(file_it)))
it = next(file_it, None)
n += 1
distance_matrix = compute_distance_matrix(customers_x, customers_y)
distance_depots = compute_distance_depots(depot_x, depot_y, customers_x, customers_y)
nb_customers = n - 1
return nb_customers, nb_trucks, truck_capacity, distance_matrix, distance_depots, \
demands, demands_to_satisfy, prizes
# Compute the distance matrix
def compute_distance_matrix(customers_x, customers_y):
nb_customers = len(customers_x)
distance_matrix = [[None for _ in range(nb_customers)] for _ in range(nb_customers)]
for i in range(nb_customers):
distance_matrix[i][i] = 0
for j in range(nb_customers):
dist = compute_dist(customers_x[i], customers_x[j], customers_y[i], customers_y[j])
distance_matrix[i][j] = dist
distance_matrix[j][i] = dist
return distance_matrix
# Compute the distances to depot
def compute_distance_depots(depot_x, depot_y, customers_x, customers_y):
nb_customers = len(customers_x)
distance_depots = [None] * nb_customers
for i in range(nb_customers):
dist = compute_dist(depot_x, customers_x[i], depot_y, customers_y[i])
distance_depots[i] = dist
return distance_depots
def compute_dist(xi, xj, yi, yj):
exact_dist = math.sqrt(math.pow(xi - xj, 2) + math.pow(yi - yj, 2))
return int(math.floor(exact_dist + 0.5))
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python pcvrp.py input_file [output_file] [time_limit]")
sys.exit(1)
instance_file = sys.argv[1]
output_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, output_file)
- Compilation / Execution (Windows)
-
cl /EHsc pcvrp.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.libpcvrp instances\A-n32-k5_1.txt
- Compilation / Execution (Linux)
-
g++ pcvrp.cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o pcvrp./pcvrp instances/A-n32-k5_1.txt
#include "optimizer/hexalyoptimizer.h"
#include <cmath>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>
using namespace hexaly;
using namespace std;
class Pcvrp {
public:
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Number of customers
int nbCustomers;
// Capacity of the trucks
int truckCapacity;
// Minimum number of demands to satisfy
int demandsToSatisfy;
// Demand on each customer
vector<int> demandsData;
// Prize on each customer
vector<int> prizesData;
// Distance matrix between customers
vector<vector<int>> distMatrixData;
// Distances between customers and depot
vector<int> distDepotData;
// Number of trucks
int nbTrucks;
// Decision variables
vector<HxExpression> customersSequences;
// Are the trucks actually used
vector<HxExpression> trucksUsed;
// Number of trucks used in the solution
HxExpression nbTrucksUsed;
// Distance traveled by all the trucks
HxExpression totalDistance;
// Total prize of the solution
HxExpression totalPrize;
// Total nb demands satisfied in the solution
HxExpression totalQuantity;
/* Read instance data */
void readInstance(const string& fileName) {
readInputPcvrp(fileName);
}
void solve(int limit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
trucksUsed.resize(nbTrucks);
customersSequences.resize(nbTrucks);
vector<HxExpression> distRoutes(nbTrucks);
vector<HxExpression> routeQuantities(nbTrucks);
vector<HxExpression> routePrizes(nbTrucks);
// Sequence of customers visited by each truck
for (int k = 0; k < nbTrucks; ++k) {
customersSequences[k] = model.listVar(nbCustomers);
}
// A customer might be visited by only one truck
model.constraint(model.disjoint(customersSequences.begin(), customersSequences.end()));
// Create Hexaly arrays to be able to access them with an "at" operator
HxExpression demands = model.array(demandsData.begin(), demandsData.end());
HxExpression prizes = model.array(prizesData.begin(), prizesData.end());
HxExpression distMatrix = model.array();
for (int n = 0; n < nbCustomers; ++n) {
distMatrix.addOperand(model.array(distMatrixData[n].begin(), distMatrixData[n].end()));
}
HxExpression distDepot = model.array(distDepotData.begin(), distDepotData.end());
for (int k = 0; k < nbTrucks; ++k) {
HxExpression sequence = customersSequences[k];
HxExpression c = model.count(sequence);
// A truck is used if it visits at least one customer
trucksUsed[k] = c > 0;
// The quantity needed in each route must not exceed the truck capacity
HxExpression demandLambda =
model.createLambdaFunction([&](HxExpression j) { return demands[j]; });
routeQuantities[k] = model.sum(sequence, demandLambda);
model.constraint(routeQuantities[k] <= truckCapacity);
// Distance traveled by truck k
HxExpression distLambda = model.createLambdaFunction(
[&](HxExpression i) { return model.at(distMatrix, sequence[i - 1], sequence[i]); });
distRoutes[k] = model.sum(model.range(1, c), distLambda) +
model.iif(c > 0, distDepot[sequence[0]] + distDepot[sequence[c - 1]], 0);
// Route prize of truck k
HxExpression prizeLambda =
model.createLambdaFunction([&](HxExpression j) { return prizes[j]; });
routePrizes[k] = model.sum(sequence, prizeLambda);
}
// Total nb demands satisfied
totalQuantity = model.sum(routeQuantities.begin(), routeQuantities.end());
model.constraint(totalQuantity >= demandsToSatisfy);
// Total nb trucks used
nbTrucksUsed = model.sum(trucksUsed.begin(), trucksUsed.end());
// Total distance traveled
totalDistance = model.sum(distRoutes.begin(), distRoutes.end());
// Total prize
totalPrize = model.sum(routePrizes.begin(), routePrizes.end());
// Objective: minimize the number of trucks used, then maximize the total prize and minimize the distance traveled
model.minimize(nbTrucksUsed);
model.maximize(totalPrize);
model.minimize(totalDistance);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(limit);
optimizer.solve();
}
/* Write the solution in a file with the following format:
* - total prize, number of trucks used and total distance
* - for each truck the customers visited (omitting the start/end at the depot)
* - number of unvisited customers, demands satisfied */
void writeSolution(const string& fileName) {
ofstream outfile;
outfile.exceptions(ofstream::failbit | ofstream::badbit);
outfile.open(fileName.c_str());
outfile << totalPrize.getValue() << " " << nbTrucksUsed.getValue() << " " << totalDistance.getValue() << endl;
int nbUnvisitedCustomers = nbCustomers;
for (int k = 0; k < nbTrucks; ++k) {
if (trucksUsed[k].getValue() != 1)
continue;
// Values in sequence are in 0...nbCustomers. +1 is to put it back in 1...nbCustomers+1
// as in the data files (0 being the depot)
HxCollection customersCollection = customersSequences[k].getCollectionValue();
for (int i = 0; i < customersCollection.count(); ++i) {
outfile << customersCollection[i] + 1 << " ";
--nbUnvisitedCustomers;
}
outfile << endl;
}
outfile << nbUnvisitedCustomers << " " << totalQuantity.getValue();
}
private:
// The input files follow the "longjianyu" format
void readInputPcvrp(const string& fileName) {
ifstream infile(fileName.c_str());
if (!infile.is_open()) {
throw std::runtime_error("File cannot be opened.");
}
infile >> nbTrucks;
infile >> truckCapacity;
infile >> demandsToSatisfy;
int n = 0;
vector<int> customersX, customersY;
int depotX, depotY;
int x, y, demand, prize;
int id;
while (infile >> id) {
if (id != n) {
throw std::runtime_error("Unexpected index");
}
if (n == 0) {
infile >> depotX;
infile >> depotY;
} else {
infile >> x;
infile >> y;
customersX.push_back(x);
customersY.push_back(y);
infile >> demand;
infile >> prize;
demandsData.push_back(demand);
prizesData.push_back(prize);
}
++n;
}
nbCustomers = n - 1;
computeDistanceMatrix(depotX, depotY, customersX, customersY);
infile.close();
}
// Compute the distance matrix
void computeDistanceMatrix(int depotX, int depotY, const vector<int>& customersX, const vector<int>& customersY) {
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(customersX[i], customersX[j], customersY[i], customersY[j]);
distMatrixData[i][j] = distance;
distMatrixData[j][i] = distance;
}
}
distDepotData.resize(nbCustomers);
for (int i = 0; i < nbCustomers; ++i) {
distDepotData[i] = computeDist(depotX, customersX[i], depotY, customersY[i]);
}
}
int computeDist(int xi, int xj, int yi, int yj) {
double exactDist = sqrt(pow((double)xi - xj, 2) + pow((double)yi - yj, 2));
return floor(exactDist + 0.5);
}
};
int main(int argc, char** argv) {
if (argc < 2) {
cerr << "Usage: pcvrp 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 {
Pcvrp 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 Pcvrp.cs /reference:Hexaly.NET.dllPcvrp instances\A-n32-k5_1.txt
using System;
using System.IO;
using Hexaly.Optimizer;
using System.Collections.Generic;
public class Pcvrp : IDisposable
{
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Number of customers
int nbCustomers;
// Capacity of the trucks
int truckCapacity;
// Minimum number of demands to satisfy
long demandsToSatisfy;
// Demand on each customer
long[] demandsData;
// Prize on each customer
long[] prizesData;
// Distance matrix between customers
long[][] distMatrixData;
// Distances between customers and depot
long[] distDepotData;
// Number of trucks
int nbTrucks;
// Decision variables
HxExpression[] customersSequences;
// Are the trucks actually used
HxExpression[] trucksUsed;
// Number of trucks used in the solution
HxExpression nbTrucksUsed;
// Distance traveled by all the trucks
HxExpression totalDistance;
// Total prize of the solution
HxExpression totalPrize;
// Total nb demands satisfied in the solution
HxExpression totalQuantity;
public Pcvrp()
{
optimizer = new HexalyOptimizer();
}
/* Read instance data */
void ReadInstance(string fileName)
{
ReadInputPcvrp(fileName);
}
public void Dispose()
{
if (optimizer != null)
optimizer.Dispose();
}
void Solve(int limit)
{
// Declare the optimization model
HxModel model = optimizer.GetModel();
trucksUsed = new HxExpression[nbTrucks];
customersSequences = new HxExpression[nbTrucks];
HxExpression[] distRoutes = new HxExpression[nbTrucks];
HxExpression[] routeQuantities = new HxExpression[nbTrucks];
HxExpression[] routePrizes = new HxExpression[nbTrucks];
// Sequence of customers visited by each truck
for (int k = 0; k < nbTrucks; ++k)
customersSequences[k] = model.List(nbCustomers);
// A customer might be visited by only one truck
model.Constraint(model.Disjoint(customersSequences));
// Create HexalyOptimizer arrays to be able to access them with an "at" operator
HxExpression demands = model.Array(demandsData);
HxExpression prizes = model.Array(prizesData);
HxExpression distDepot = model.Array(distDepotData);
HxExpression distMatrix = model.Array(distMatrixData);
for (int k = 0; k < nbTrucks; ++k)
{
HxExpression sequence = customersSequences[k];
HxExpression c = model.Count(sequence);
// A truck is used if it visits at least one customer
trucksUsed[k] = c > 0;
// The quantity needed in each route must not exceed the truck capacity
HxExpression demandLambda = model.LambdaFunction(j => demands[j]);
routeQuantities[k] = model.Sum(sequence, demandLambda);
model.Constraint(routeQuantities[k] <= truckCapacity);
// Distance traveled by truck k
HxExpression distLambda = model.LambdaFunction(
i => distMatrix[sequence[i - 1], sequence[i]]
);
distRoutes[k] =
model.Sum(model.Range(1, c), distLambda)
+ model.If(c > 0, distDepot[sequence[0]] + distDepot[sequence[c - 1]], 0);
// Route prize of truck k
HxExpression prizeLambda = model.LambdaFunction(j => prizes[j]);
routePrizes[k] = model.Sum(sequence, prizeLambda);
}
// Minimal number of demands to satisfy
totalQuantity = model.Sum(routeQuantities);
model.Constraint(totalQuantity >= demandsToSatisfy);
totalPrize = model.Sum(routePrizes);
nbTrucksUsed = model.Sum(trucksUsed);
totalDistance = model.Sum(distRoutes);
// Objective: minimize the number of trucks used, then maximize the total prize and minimize the distance traveled
model.Minimize(nbTrucksUsed);
model.Maximize(totalPrize);
model.Minimize(totalDistance);
model.Close();
// Parametrize the optimizer
optimizer.GetParam().SetTimeLimit(limit);
optimizer.Solve();
}
/* Write the solution in a file with the following format:
* - total prize, number of trucks used and total distance
* - for each truck the customers visited (omitting the start/end at the depot)
* - number of unvisited customers, demands satisfied */
void WriteSolution(string fileName)
{
using (StreamWriter output = new StreamWriter(fileName))
{
output.WriteLine(totalPrize.GetValue() + " " + nbTrucksUsed.GetValue() + " " + totalDistance.GetValue());
int nbUnvisitedCustomers = nbCustomers;
for (int k = 0; k < nbTrucks; ++k)
{
if (trucksUsed[k].GetValue() != 1)
continue;
// Values in sequence are in 0...nbCustomers. +1 is to put it back in 1...nbCustomers+1
// as in the data files (0 being the depot)
HxCollection customersCollection = customersSequences[k].GetCollectionValue();
for (int i = 0; i < customersCollection.Count(); ++i)
output.Write((customersCollection[i] + 1) + " ");
--nbUnvisitedCustomers;
output.WriteLine();
}
output.WriteLine(nbUnvisitedCustomers + " " + totalQuantity.GetValue());
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: Pcvrp 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 (Pcvrp model = new Pcvrp())
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(outputFile);
}
}
// The input files follow the "longjianyu" format
private void ReadInputPcvrp(string fileName)
{
using (StreamReader input = new StreamReader(fileName))
{
string[] splitted;
splitted = input
.ReadLine()
.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
nbTrucks = int.Parse(splitted[0]);
truckCapacity = int.Parse(splitted[1]);
demandsToSatisfy = int.Parse(splitted[2]);
int n = 0;
List<int> customersXList = new List<int>(), customersYList = new List<int>();
List<long> demandsDataList = new List<long>(), prizesDataList = new List<long>();
int depotX = 0, depotY = 0;
string line;
while ((line = input.ReadLine()) != null)
{
splitted = line.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
if (int.Parse(splitted[0]) != n)
throw new Exception("Unexpected index");
if (n == 0)
{
depotX = int.Parse(splitted[1]);
depotY = int.Parse(splitted[2]);
}
else
{
customersXList.Add(int.Parse(splitted[1]));
customersYList.Add(int.Parse(splitted[2]));
demandsDataList.Add(long.Parse(splitted[3]));
prizesDataList.Add(long.Parse(splitted[4]));
}
++n;
}
nbCustomers = n - 1;
int[] customersX = customersXList.ToArray();
int[] customersY = customersYList.ToArray();
demandsData = demandsDataList.ToArray();
prizesData = prizesDataList.ToArray();
ComputeDistanceMatrix(depotX, depotY, customersX, customersY);
}
}
// Compute the distance matrix
private void ComputeDistanceMatrix(int depotX, int depotY, int[] customersX, int[] customersY)
{
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(customersX[i], customersX[j], customersY[i], customersY[j]);
distMatrixData[i][j] = dist;
distMatrixData[j][i] = dist;
}
}
distDepotData = new long[nbCustomers];
for (int i = 0; i < nbCustomers; ++i)
distDepotData[i] = ComputeDist(depotX, customersX[i], depotY, customersY[i]);
}
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));
}
}
- Compilation / Execution (Windows)
-
javac Pcvrp.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. Pcvrp instances\A-n32-k5_1.txt
- Compilation / Execution (Linux)
-
javac Pcvrp.java -cp /opt/hexaly_13_0/bin/hexaly.jarjava -cp /opt/hexaly_13_0/bin/hexaly.jar:. Pcvrp instances/A-n32-k5_1.txt
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;
public class Pcvrp {
// Hexaly Optimizer
private final HexalyOptimizer optimizer;
// Number of customers
int nbCustomers;
// Capacity of the trucks
private int truckCapacity;
// Minimum number of demands to satisfy
private long demandsToSatisfy;
// Demand on each customer
private long[] demandsData;
// Prize on each customer
private long[] prizesData;
// Distance matrix between customers
private long[][] distMatrixData;
// Distances between customers and depot
private long[] distDepotData;
// Number of trucks
private int nbTrucks;
// Decision variables
private HxExpression[] customersSequences;
// Are the trucks actually used
private HxExpression[] trucksUsed;
// Number of trucks used in the solution
private HxExpression nbTrucksUsed;
// Distance traveled by all the trucks
private HxExpression totalDistance;
// Total prize of the solution
private HxExpression totalPrize;
// Total nb demands satisfied in the solution
private HxExpression totalQuantity;
private Pcvrp(HexalyOptimizer optimizer) {
this.optimizer = optimizer;
}
private void solve(int limit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
trucksUsed = new HxExpression[nbTrucks];
customersSequences = new HxExpression[nbTrucks];
HxExpression[] distRoutes = new HxExpression[nbTrucks];
HxExpression[] routeQuantities = new HxExpression[nbTrucks];
HxExpression[] routePrizes = new HxExpression[nbTrucks];
// Sequence of customers visited by each truck.
for (int k = 0; k < nbTrucks; ++k)
customersSequences[k] = model.listVar(nbCustomers);
// A customer might be visited by only one truck
model.constraint(model.disjoint(customersSequences));
// Create HexalyOptimizer arrays to be able to access them with an "at" operator
HxExpression demands = model.array(demandsData);
HxExpression prizes = model.array(prizesData);
HxExpression distDepot = model.array(distDepotData);
HxExpression distMatrix = model.array(distMatrixData);
for (int k = 0; k < nbTrucks; ++k) {
HxExpression sequence = customersSequences[k];
HxExpression c = model.count(sequence);
// A truck is used if it visits at least one customer
trucksUsed[k] = model.gt(c, 0);
// The quantity needed in each route must not exceed the truck capacity
HxExpression demandLambda = model.lambdaFunction(j -> model.at(demands, j));
routeQuantities[k] = model.sum(sequence, demandLambda);
model.constraint(model.leq(routeQuantities[k], truckCapacity));
// Distance traveled by truck k
HxExpression distLambda = model
.lambdaFunction(i -> model.at(distMatrix, model.at(sequence, model.sub(i, 1)), model.at(sequence, i)));
distRoutes[k] = model.sum(model.sum(model.range(1, c), distLambda),
model.iif(model.gt(c, 0), model.sum(model.at(distDepot, model.at(sequence, 0)),
model.at(distDepot, model.at(sequence, model.sub(c, 1)))), 0));
// Route prize of truck k
HxExpression prizeLambda = model.lambdaFunction(j -> model.at(prizes, j));
routePrizes[k] = model.sum(sequence, prizeLambda);
}
// Minimal number of demands to satisfy
totalQuantity = model.sum(routeQuantities);
model.constraint(model.geq(totalQuantity, demandsToSatisfy));
totalPrize = model.sum(routePrizes);
nbTrucksUsed = model.sum(trucksUsed);
totalDistance = model.sum(distRoutes);
// Objective: minimize the number of trucks used, then maximize the total prize and minimize the distance traveled
model.minimize(nbTrucksUsed);
model.maximize(totalPrize);
model.minimize(totalDistance);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(limit);
optimizer.solve();
}
/* Write the solution in a file with the following format:
* - total prize, number of trucks used and total distance
* - for each truck the customers visited (omitting the start/end at the depot)
* - number of unvisited customers, demands satisfied */
private void writeSolution(String fileName) throws IOException {
try (PrintWriter output = new PrintWriter(fileName)) {
output.println(totalPrize.getValue() + " " + nbTrucksUsed.getValue() + " " + totalDistance.getValue());
int nbUnvisitedCustomers = nbCustomers;
for (int k = 0; k < nbTrucks; ++k) {
if (trucksUsed[k].getValue() != 1)
continue;
// Values in sequence are in 0...nbCustomers. +1 is to put it back in 1...nbCustomers+1
// as in the data files (0 being the depot)
HxCollection customersCollection = customersSequences[k].getCollectionValue();
for (int i = 0; i < customersCollection.count(); ++i) {
output.print((customersCollection.get(i) + 1) + " ");
--nbUnvisitedCustomers;
}
output.println();
}
output.println(nbUnvisitedCustomers + " " + totalQuantity.getValue());
}
}
// The input files follow the "longjianyu" format
private void readInstance(String fileName) throws IOException {
try (Scanner input = new Scanner(new File(fileName))) {
nbTrucks = input.nextInt();
truckCapacity = input.nextInt();
demandsToSatisfy = input.nextInt();
int n = 0;
ArrayList<Integer> customersXList = new ArrayList<>(), customersYList = new ArrayList<>();
ArrayList<Long> demandsDataList = new ArrayList<>(), prizesDataList = new ArrayList<>();
int depotX = 0, depotY = 0;
while (input.hasNext()) {
int id = input.nextInt();
if (id != n)
throw new IOException("Unexpected index");
if (n == 0) {
depotX = input.nextInt();
depotY = input.nextInt();
} else {
customersXList.add(input.nextInt());
customersYList.add(input.nextInt());
demandsDataList.add(input.nextLong());
prizesDataList.add(input.nextLong());
}
++n;
}
nbCustomers = n - 1;
int[] customersX = customersXList.stream().mapToInt(i -> i).toArray();
int[] customersY = customersYList.stream().mapToInt(i -> i).toArray();
demandsData = demandsDataList.stream().mapToLong(i -> i).toArray();
prizesData = prizesDataList.stream().mapToLong(i -> i).toArray();
computeDistanceMatrix(depotX, depotY, customersX, customersY);
}
}
// Compute the distance matrix
private void computeDistanceMatrix(int depotX, int depotY, int[] customersX, int[] customersY) {
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(customersX[i], customersX[j], customersY[i], customersY[j]);
distMatrixData[i][j] = dist;
distMatrixData[j][i] = dist;
}
}
distDepotData = new long[nbCustomers];
for (int i = 0; i < nbCustomers; ++i) {
distDepotData[i] = computeDist(depotX, customersX[i], depotY, customersY[i]);
}
}
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 Pcvrp 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";
Pcvrp model = new Pcvrp(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);
}
}
}