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

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\python
python pcvrp.py instances\A-n32-k5_1.txt
 
Execution (Linux)
export PYTHONPATH=/opt/hexaly_13_0/bin/python
python 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.lib
pcvrp 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.dll
Pcvrp 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.jar
java -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.jar
java -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);
        }
    }
}