Prize-Collecting Vehicle Routing (PCVRP)

Principles learned

  • Add multiple list decision variables

  • Add a disjoint constraint

  • Use a lambda expression to compute a sum with a variable number of terms

  • Define a sequence of expressions

  • Access a multi-dimensional array with an “at” operator

  • Add multiple objectives

Problem

../_images/pccvrp.svg

In the prize-collecting vehicle routing problem (PCVRP), a fleet of vehicles with uniform capacity can deliver customers with known demand and prize for a single commodity. The vehicles start and end their routes at a common depot. Each customer might be served by at most one vehicle. The objectives are to minimize the fleet size, maximize the total prize and assign a sequence of customers to each truck of the fleet minimizing the total distance traveled such that a predefined amount of demands are satisfied and the total demand served by each truck does not exceed its capacity.

Download the example


Data

The instances provided come from the Long et al. Set A instances. They added a prize and a minimum quantity of demands to satisfy to the Augerat et al. Set A instances.

The format of the data files is as follows:

  • The first line: the number of vehicles, vehicle capacity, the predetermined amount needed to be satisfied

  • The second line (depot): node ID, abscissa value, ordinate value

  • The third line to the last line (customers): node ID, abscissa value, ordinate value, demand, prize

Program

This Hexaly model defines a route for each truck k as a list variable (customersSequences[k]). It corresponds to the sequence of customers visited. To ensure that all customers must be served, all the list variables are constrained to form a partition, thanks to the “disjoint” operator.

The number of trucks used for the fleet is defined by the number of trucks that serve at least one customer (if their list variable has at least one element). The definition of these expressions is really straightforward thanks to the count and the comparison operators.

The quantity delivered on each visit is the demand on the node of this visit. This expression is just defined with an at operator to access the array demands. The total quantity delivered by each truck is computed with a function to apply the sum operator over all customers visited by a truck. Note that the number of terms in this sum varies automatically with the size of the list. This quantity is constrained to be lower than the truck capacity. The sum over each truck of this quantity is the total quantity and must be “greater or equal than” the predetermined amount of demands to satisfy.

The prize earned on each visit is the prize on the node of this visit. We also use a function to sum prizes along each route. The total prize is the sum of the prizes of each route.

For each truck, the distance traveled from the visit n-1 to the visit n is accessed with an at operator to the multi-dimensional array distanceMatrix, with the first index. Here again we use a function to sum distances along each route.

The three objectives are defined in lexicographical order: we first minimize the number of trucks used, maximize the total prize and then we minimize the total distance traveled by all the trucks.

If you are interested in the more classical variant CVRP (without prize), you can now study our CVRP model.

Execution:
localsolver pcvrp.lsp inFileName=instances/A-n32-k5_1.txt [lsTimeLimit=] [solFileName=]
use io;

/* Read instance data. The input files follow the "longjianyu" format */
function input() {
    usage = "Usage: localsolver pcvrp.lsp inFileName=inputFile "
        + "[solFileName=outputFile] [lsTimeLimit=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 (lsTimeLimit == nil) lsTimeLimit = 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=%LS_HOME%\bin\python
python pcvrp.py instances\A-n32-k5_1.txt
Execution (Linux)
export PYTHONPATH=/opt/localsolver_12_5/bin/python
python pcvrp.py instances/A-n32-k5_1.txt
import localsolver
import sys
import math


def read_elem(filename):
    with open(filename) as f:
        return [str(elem) for elem in f.read().split()]


def main(instance_file, str_time_limit, 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 localsolver.LocalSolver() as ls:
        #
        # Declare the optimization model
        #
        model = ls.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 LocalSolver 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()
        for n in range(nb_customers):
            dist_matrix.add_operand(model.array(dist_matrix_data[n]))
        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 solver
        ls.param.time_limit = int(str_time_limit)

        ls.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%LS_HOME%\include /link %LS_HOME%\bin\localsolver125.lib
pcvrp instances\A-n32-k5_1.txt
Compilation / Execution (Linux)
g++ pcvrp.cpp -I/opt/localsolver_12_5/include -llocalsolver125 -lpthread -o pcvrp
./pcvrp instances/A-n32-k5_1.txt
#include "localsolver.h"
#include <cmath>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>

using namespace localsolver;
using namespace std;

class Pcvrp {
public:
    // LocalSolver
    LocalSolver localsolver;

    // 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<LSExpression> customersSequences;

    // Are the trucks actually used
    vector<LSExpression> trucksUsed;

    // Number of trucks used in the solution
    LSExpression nbTrucksUsed;

    // Distance traveled by all the trucks
    LSExpression totalDistance;
        
    // Total prize of the solution
    LSExpression totalPrize;

    // Total nb demands satisfied in the solution
    LSExpression totalQuantity;

    /* Read instance data */
    void readInstance(const string& fileName) {
        readInputPcvrp(fileName);
    }

    void solve(int limit) {
        // Declare the optimization model
        LSModel model = localsolver.getModel();
       
        trucksUsed.resize(nbTrucks);
        customersSequences.resize(nbTrucks);
        vector<LSExpression> distRoutes(nbTrucks);
        vector<LSExpression> routeQuantities(nbTrucks);
        vector<LSExpression> 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 LocalSolver arrays to be able to access them with an "at" operator
        LSExpression demands = model.array(demandsData.begin(), demandsData.end());
        LSExpression prizes = model.array(prizesData.begin(), prizesData.end());
        LSExpression distMatrix = model.array();
        for (int n = 0; n < nbCustomers; ++n) {
            distMatrix.addOperand(model.array(distMatrixData[n].begin(), distMatrixData[n].end()));
        }
        LSExpression distDepot = model.array(distDepotData.begin(), distDepotData.end());

        for (int k = 0; k < nbTrucks; ++k) {
            LSExpression sequence = customersSequences[k];
            LSExpression 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
            LSExpression demandLambda =
                model.createLambdaFunction([&](LSExpression j) { return demands[j]; });
            routeQuantities[k] = model.sum(sequence, demandLambda);
            model.constraint(routeQuantities[k] <= truckCapacity);

            // Distance traveled by truck k
            LSExpression distLambda = model.createLambdaFunction(
                [&](LSExpression 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
            LSExpression prizeLambda = 
                model.createLambdaFunction([&](LSExpression 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 solver
        localsolver.getParam().setTimeLimit(limit);

        localsolver.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)
            LSCollection 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 %LS_HOME%\bin\localsolvernet.dll .
csc Pcvrp.cs /reference:localsolvernet.dll
Pcvrp instances\A-n32-k5_1.txt
using System;
using System.IO;
using localsolver;
using System.Collections.Generic;

public class Pcvrp : IDisposable
{
    // LocalSolver
    LocalSolver localsolver;

    // 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
    LSExpression[] customersSequences;

    // Are the trucks actually used
    LSExpression[] trucksUsed;

    // Number of trucks used in the solution
    LSExpression nbTrucksUsed;

    // Distance traveled by all the trucks
    LSExpression totalDistance;

    // Total prize of the solution
    LSExpression totalPrize;

    // Total nb demands satisfied in the solution
    LSExpression totalQuantity;

    public Pcvrp()
    {
        localsolver = new LocalSolver();
    }

    /* Read instance data */
    void ReadInstance(string fileName)
    {
        ReadInputPcvrp(fileName);
    }

    public void Dispose()
    {
        if (localsolver != null)
            localsolver.Dispose();
    }

    void Solve(int limit)
    {
        // Declare the optimization model
        LSModel model = localsolver.GetModel();

        trucksUsed = new LSExpression[nbTrucks];
        customersSequences = new LSExpression[nbTrucks];
        LSExpression[] distRoutes = new LSExpression[nbTrucks];
        LSExpression[] routeQuantities = new LSExpression[nbTrucks];
        LSExpression[] routePrizes = new LSExpression[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 LocalSolver arrays to be able to access them with an "at" operator
        LSExpression demands = model.Array(demandsData);
        LSExpression prizes = model.Array(prizesData);
        LSExpression distDepot = model.Array(distDepotData);
        LSExpression distMatrix = model.Array(distMatrixData);

        for (int k = 0; k < nbTrucks; ++k)
        {
            LSExpression sequence = customersSequences[k];
            LSExpression 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
            LSExpression demandLambda = model.LambdaFunction(j => demands[j]);
            routeQuantities[k] = model.Sum(sequence, demandLambda);
            model.Constraint(routeQuantities[k] <= truckCapacity);

            // Distance traveled by truck k
            LSExpression 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
            LSExpression 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 solver
        localsolver.GetParam().SetTimeLimit(limit);

        localsolver.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)
                LSCollection 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 %LS_HOME%\bin\localsolver.jar
java -cp %LS_HOME%\bin\localsolver.jar;. Pcvrp instances\A-n32-k5_1.txt
Compilation / Execution (Linux)
javac Pcvrp.java -cp /opt/localsolver_12_5/bin/localsolver.jar
java -cp /opt/localsolver_12_5/bin/localsolver.jar:. Pcvrp instances/A-n32-k5_1.txt
import java.util.*;
import java.io.*;
import localsolver.*;

public class Pcvrp {
    // LocalSolver
    private final LocalSolver localsolver;

    // 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 LSExpression[] customersSequences;

    // Are the trucks actually used
    private LSExpression[] trucksUsed;

    // Number of trucks used in the solution
    private LSExpression nbTrucksUsed;

    // Distance traveled by all the trucks
    private LSExpression totalDistance;

    // Total prize of the solution
    private LSExpression totalPrize;

    // Total nb demands satisfied in the solution
    private LSExpression totalQuantity;

    private Pcvrp(LocalSolver localsolver) {
        this.localsolver = localsolver;
    }

    private void solve(int limit) {
        // Declare the optimization model
        LSModel model = localsolver.getModel();

        trucksUsed = new LSExpression[nbTrucks];
        customersSequences = new LSExpression[nbTrucks];
        LSExpression[] distRoutes = new LSExpression[nbTrucks];
        LSExpression[] routeQuantities = new LSExpression[nbTrucks];
        LSExpression[] routePrizes = new LSExpression[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 LocalSolver arrays to be able to access them with an "at" operator
        LSExpression demands = model.array(demandsData);
        LSExpression prizes = model.array(prizesData);
        LSExpression distDepot = model.array(distDepotData);
        LSExpression distMatrix = model.array(distMatrixData);

        for (int k = 0; k < nbTrucks; ++k) {
            LSExpression sequence = customersSequences[k];
            LSExpression 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
            LSExpression 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
            LSExpression 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
            LSExpression 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 solver
        localsolver.getParam().setTimeLimit(limit);

        localsolver.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)
                LSCollection 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 (LocalSolver localsolver = new LocalSolver()) {
            String instanceFile = args[0];
            String outputFile = args.length > 1 ? args[1] : null;
            String strTimeLimit = args.length > 2 ? args[2] : "20";

            Pcvrp model = new Pcvrp(localsolver);
            model.readInstance(instanceFile);
            model.solve(Integer.parseInt(strTimeLimit));
            if (outputFile != null) {
                model.writeSolution(outputFile);
            }
        } catch (Exception ex) {
            System.err.println(ex);
            ex.printStackTrace();
            System.exit(1);
        }
    }
}