Dial-A-Ride Problem (DARP)

Principles learned

  • Add multiple list decision variables

  • Use a lambda expression to define a recursive array

  • Define a sequence of expressions

  • Work with JSON files

  • Use of “contains” and “indexOf” operators

Problem

../_images/darp.svg

In the Dial-A-Ride Problem, a fleet of transport vehicles must provide a ride to clients from one site (node) to another, while respecting the pickup and delivery time requested by each client. A sequence of nodes visited by a given vehicle forms a route.

The vehicles start and end their routes at a common depot. Each vehicle has a maximum capacity, and each client can only be served by one vehicle. All clients must be picked up and delivered. Each client gives a time window for its pickup time and a time window for its delivery time. Each client has a loading time at its pickup site, and a loading time at its delivery site. Each client also provides a maximum travel time for going from its pickup site to its delivery site.

Download the example


Data

Each data file is a Json file containing:

  • the number of vehicles,

  • the number of nodes,

  • the maximum travel time common to all clients,

  • the capacity of the vehicles,

  • a scaling factor for computing the travel time between two different nodes,

  • the speed of the vehicles,

  • the number of clients,

  • the matrix of distances between each pair of nodes,

  • information about the depot:

    • its identifier (node index),

    • its loading time,

    • the number of clients at the depot,

    • the starting time of the routes,

    • the maximum ending time of the routes,

    • the maximum travel time of the clients picked up at the depot (if any),

  • information about each customer: for each node, the file indicates

    • the number of customers that requested a vehicle,

    • information about their pickup (index of the pickup node, minimum pickup time requested, maximum pickup time requested, loading time in the vehicle, maximum time of travel),

    • information about their delivery (index of the delivery node, minimum delivery time requested, maximum delivery time requested, loading time to exit the vehicle, maximum time of travel).

Program

The Hexaly model uses a list containing node numbers to represent the route of each vehicle (omitting the node representing the depot from which each vehicle starts and finishes its route). For each client, the delivery node is the sum of the pickup node and the number of nodes (excluding the depot). If a client is picked up in a route, he or she must be delivered in that same route. In particular, the pickup index of this client must come before its delivery index in that same route. Each node must be visited exactly by one vehicle, so that the ensemble of all the routes represents a partition of all the integers between zero (included) and the number of nodes (excluded), if zero is the index of the first node in the data file (different from the depot).

The lateness and the total distance traveled is measured for each route. The sum of the lateness of all clients is also measured. The model minimizes (in this order of priority):

  • the sum of the lateness of all routes (including for each route the lateness to go from its last node to the depot),

  • the sum of the lateness of all clients,

  • the total distance traveled.

Execution:
hexaly darp.hxm inFileName=instances/a5-40.json [hxTimeLimit=] [solFileName=]
/********** darp.hxm **********/

use io;
use json;

function input() {
    usage = "\nUsage: hexaly darp.hxm inFileName=inputFile"
        + "[solFileName=outputFile] [hxTimeLimit=timeLimit]\n";
    if (inFileName == nil) throw usage;
    instance = json.parse(inFileName);
    for [k in 0...instance.nbClients] {
        quantities[k] = instance.clients[k].nbClients;
        quantities[k+instance.nbClients] = -instance.clients[k].nbClients;
    }
    distances = instance.distanceMatrix;
    for [k in 0...instance.nbClients] {
        starts[k] = instance.clients[k].pickup.start;
        ends[k] = instance.clients[k].pickup.end;
        starts[k+instance.nbClients] = instance.clients[k].delivery.start;
        ends[k+instance.nbClients] = instance.clients[k].delivery.end;
    }
    for [k in 0...instance.nbClients] {
        loadingTimes[k] = instance.clients[k].pickup.loadingTime;
        loadingTimes[k+instance.nbClients] = instance.clients[k].delivery.loadingTime;
    }
    for [k in 0...instance.nbClients] {
        maxTravelTimes[k] = instance.clients[k].pickup.maxTravelTime;
        maxTravelTimes[k+instance.nbClients] = instance.clients[k].delivery.maxTravelTime;
    }
    factor = 1 / (instance.scale * instance.speed);

    for [k in 0...instance.nbNodes] {
        distanceWarehouse[k] = distances[0][k+1];
        timeWarehouse[k] = distanceWarehouse[k] * factor;
    }

    for [i in 0...instance.nbNodes][j in 0...instance.nbNodes] {
        distanceMatrix[i][j] = distances[i+1][j+1];
        timeMatrix[i][j] = distanceMatrix[i][j] * factor;
    }
}

function model() {
    // routes[k] represents the nodes visited by vehicle k
    routes[0...instance.nbVehicles] <- list(instance.nbNodes);
    depotStarts[0...instance.nbVehicles] <- float(0, instance.depot.twEnd);
    // waiting[k] is the waiting time at node k
    waiting[0...instance.nbNodes] <- float(0, instance.depot.twEnd);
    // Each node is taken by one vehicle
    constraint partition[k in 0...instance.nbVehicles](routes[k]);

    for [k in 0...instance.nbVehicles] {
        route <- routes[k];
        c <- count(route);

        // routeQuantities[k][i] indicates the number of clients in vehicle k
        // at its i-th taken node
        routeQuantities[k] <- array(0...c, (i, prev) => prev + quantities[route[i]]);
        // Vehicles have a maximum capacity
        constraint and(0...c, i => routeQuantities[k][i] <= instance.capacity);
        // times[k][i] is the time at which vehicle k leaves the i-th node
        // (after waiting and loading time at node i)
        times[k] <- array(0...c, (i, prev) => max(starts[route[i]],
                            i == 0 ?
                            (depotStarts[k] + timeWarehouse[route[0]]) :
                            (prev + timeMatrix[route[i-1]][route[i]]))
                        + waiting[route[i]] + loadingTimes[route[i]]);
        // Total lateness of the k-th route
        lateness[k] <- sum(
            0...c,
            i => max(0, times[k][i] - loadingTimes[route[i]] - ends[route[i]])
        );
        homeLateness[k] <- (c > 0) ?
            max(0, times[k][c-1] + timeWarehouse[route[c-1]] - instance.depot.twEnd) : 0;
        routeDistances[k] <- sum(1...c, i => distanceMatrix[route[i-1]][route[i]])
             + (c > 0 ? distanceWarehouse[route[0]] + distanceWarehouse[route[c-1]] : 0);
    }

    for [k in 0...instance.nbClients] {
        // For each pickup node k, its associated delivery node is k + instance.nbClients
        pickupListIndex <- find(routes, k);
        deliveryListIndex <- find(routes, k + instance.nbClients);
        // A client picked up in route i is delivered in route i
        constraint pickupListIndex == deliveryListIndex;

        clientList <- routes[pickupListIndex];
        pickupIndex <- indexOf(clientList, k);
        deliveryIndex <- indexOf(clientList, k + instance.nbClients);
        // Pickup before delivery
        constraint pickupIndex < deliveryIndex;

        pickupTime <- times[pickupListIndex][pickupIndex];
        deliveryTime <- times[deliveryListIndex][deliveryIndex] - loadingTimes[k + instance.nbClients];
        travelTime <- deliveryTime - pickupTime;
        clientLateness[k] <- max(travelTime - maxTravelTimes[k], 0);
    }

    totalLateness <- sum[k in 0...instance.nbVehicles](lateness[k] + homeLateness[k]);
    totalClientLateness <- sum[k in 0...instance.nbClients](clientLateness[k]);
    totalDistance <- sum[k in 0...instance.nbVehicles](routeDistances[k]);

    minimize totalLateness;
    minimize totalClientLateness;
    minimize totalDistance / instance.scale;
}

function param() {
    if (hxTimeLimit == nil) hxTimeLimit = 20;
}

function output() {
    if (solFileName == nil) return;
    local outfile = io.openWrite(solFileName);

    /* Write the solution in a file with the following format:
     *  - total lateness on the routes, total client lateness, total distance
     *  - for each vehicle, the depot start time, the nodes visited (omitting the start/end at the
     * depot), and the waiting time at each node */
    outfile.println(totalLateness.value, " ", totalClientLateness.value, " ", totalDistance.value);
    for [k in 0...instance.nbVehicles] {
        outfile.print("Vehicle ", k + 1, " (", depotStarts[k].value, "): ");
        for [node in routes[k].value]
            outfile.print(node, " (", waiting[node].value, "), ");
        outfile.println();
    }
}
Execution (Windows)
set PYTHONPATH=%HX_HOME%\bin\python
python darp.py instances\a5-40.json
Execution (Linux)
export PYTHONPATH=/opt/hexaly_13_5/bin/python
python darp.py instances/a5-40.json
import hexaly.optimizer
import sys
import json

def read_data(filename):
    with open(filename) as f:
        return json.load(f)
    
def read_input_darp(instance_file):
    instance = read_data(instance_file)

    nb_clients = instance["nbClients"]
    nb_nodes = instance["nbNodes"]
    nb_vehicles = instance["nbVehicles"]
    depot_tw_end = instance["depot"]["twEnd"]
    capacity = instance["capacity"]
    scale = instance["scale"]

    quantities = [-1 for i in range(2 * nb_clients)]
    distances = instance["distanceMatrix"]
    starts = [-1.0 for i in range(2 * nb_clients)]
    ends = [-1.0 for i in range(2 * nb_clients)]
    loading_times = [-1.0 for i in range(2 * nb_clients)]
    max_travel_times = [-1.0 for i in range(2 * nb_clients)]
    for k in range(nb_clients):
        quantities[k] = instance["clients"][k]["nbClients"]
        quantities[k+nb_clients] = -instance["clients"][k]["nbClients"]

        starts[k] = instance["clients"][k]["pickup"]["start"]
        ends[k] = instance["clients"][k]["pickup"]["end"]
        
        starts[k+nb_clients] = instance["clients"][k]["delivery"]["start"]
        ends[k+nb_clients] = instance["clients"][k]["delivery"]["end"]

        loading_times[k] = instance["clients"][k]["pickup"]["loadingTime"]
        loading_times[k+nb_clients] = instance["clients"][k]["delivery"]["loadingTime"]

        max_travel_times[k] = instance["clients"][k]["pickup"]["maxTravelTime"]
        max_travel_times[k+nb_clients] = instance["clients"][k]["delivery"]["maxTravelTime"]
        
    factor = 1.0 / (scale * instance["speed"])

    distance_warehouse = [-1.0 for i in range(nb_nodes)]
    time_warehouse = [-1.0 for i in range(nb_nodes)]
    distance_matrix = [[-1.0 for i in range(nb_nodes)] for j in range(nb_nodes)]
    time_matrix = [[-1.0 for i in range(nb_nodes)] for j in range(nb_nodes)]
    for i in range(nb_nodes):
        distance_warehouse[i] = distances[0][i+1]
        time_warehouse[i] = distance_warehouse[i] * factor
        for j in range(nb_nodes):
            distance_matrix[i][j] = distances[i+1][j+1]
            time_matrix[i][j] = distance_matrix[i][j] * factor

    return nb_clients, nb_nodes, nb_vehicles, depot_tw_end, capacity, scale, quantities, \
        starts, ends, loading_times, max_travel_times, distance_warehouse, time_warehouse, \
        distance_matrix, time_matrix

def main(instance_file, str_time_limit, sol_file):

    nb_clients, nb_nodes, nb_vehicles, depot_tw_end, capacity, scale, quantities_data, \
        starts_data, ends_data, loading_times_data, max_travel_times, distance_warehouse_data, \
        time_warehouse_data, distance_matrix_data, time_matrix_data = read_input_darp(instance_file)

    with hexaly.optimizer.HexalyOptimizer() as optimizer:
        model = optimizer.model

        # routes[k] represents the nodes visited by vehicle k
        routes = [model.list(nb_nodes) for k in range(nb_vehicles)]
        depot_starts = [model.float(0, depot_tw_end) for k in range(nb_vehicles)]
        # Each node is taken by one vehicle
        model.constraint(model.partition(routes))

        quantities = model.array(quantities_data)
        time_warehouse = model.array(time_warehouse_data)
        time_matrix = model.array(time_matrix_data)
        loading_times = model.array(loading_times_data)
        starts = model.array(starts_data)
        ends = model.array(ends_data)
        # waiting[k] is the waiting time at node k
        waiting = [model.float(0, depot_tw_end) for k in range(nb_nodes)]
        waiting_array = model.array(waiting)
        distance_matrix = model.array(distance_matrix_data)
        distance_warehouse = model.array(distance_warehouse_data)

        times = [None] * nb_vehicles
        lateness = [None] * nb_vehicles
        home_lateness = [None] * nb_vehicles
        route_distances = [None] * nb_vehicles

        for k in range(nb_vehicles):
            route = routes[k]
            c = model.count(route)

            demand_lambda = model.lambda_function(lambda i, prev: prev + quantities[route[i]])
            # route_quantities[k][i] indicates the number of clients in vehicle k
            # at its i-th taken node
            route_quantities = model.array(model.range(0, c), demand_lambda)
            quantity_lambda = model.lambda_function(lambda i: route_quantities[i] <= capacity)
            # Vehicles have a maximum capacity
            model.constraint(model.and_(model.range(0, c), quantity_lambda))

            times_lambda = model.lambda_function(
                lambda i, prev: model.max(
                    starts[route[i]],
                    model.iif(
                        i == 0,
                        depot_starts[k] + time_warehouse[route[0]],
                        prev + time_matrix[route[i-1]][route[i]]
                    )
                ) + waiting_array[route[i]] + loading_times[route[i]]
            )
            # times[k][i] is the time at which vehicle k leaves the i-th node
            # (after waiting and loading time at node i)
            times[k] = model.array(model.range(0, c), times_lambda)

            lateness_lambda = model.lambda_function(
                lambda i: model.max(
                    0,
                    times[k][i] - loading_times[route[i]] - ends[route[i]]
                )
            )
            # Total lateness of the k-th route
            lateness[k] = model.sum(model.range(0, c), lateness_lambda)

            home_lateness[k] = model.iif(
                c > 0,
                model.max(0, times[k][c-1] + time_warehouse[route[c-1]] - depot_tw_end),
                0
            )

            route_dist_lambda = model.lambda_function(
                lambda i: distance_matrix[route[i-1]][route[i]]
            )
            route_distances[k] = model.sum(
                model.range(1, c),
                route_dist_lambda
            ) + model.iif(
                c > 0,
                distance_warehouse[route[0]] + distance_warehouse[route[c-1]],
                0
            )

        routes_array = model.array(routes)
        times_array = model.array(times)
        client_lateness = [None] * nb_clients
        for k in range(nb_clients):
            # For each pickup node k, its associated delivery node is k + nb_clients
            pickup_list_index = model.find(routes_array, k)
            delivery_list_index = model.find(routes_array, k + nb_clients)
            # A client picked up in route i is delivered in route i
            model.constraint(pickup_list_index == delivery_list_index)

            client_list = routes_array[pickup_list_index]
            pickup_index = model.index(client_list, k)
            delivery_index = model.index(client_list, k + nb_clients)
            # Pickup before delivery
            model.constraint(pickup_index < delivery_index)

            pickup_time = times_array[pickup_list_index][pickup_index]
            delivery_time = times_array[delivery_list_index][delivery_index] \
                - loading_times[k + nb_clients]
            travel_time = delivery_time - pickup_time
            client_lateness[k] = model.max(travel_time - max_travel_times[k], 0)

        total_lateness = model.sum(lateness + home_lateness)
        total_client_lateness = model.sum(client_lateness)
        total_distance = model.sum(route_distances)

        model.minimize(total_lateness)
        model.minimize(total_client_lateness)
        model.minimize(total_distance / scale)

        model.close()
        optimizer.param.time_limit = int(str_time_limit)
        optimizer.solve()

        #
        # Write the solution in a file with the following format:
        #  - total lateness on the routes, total client lateness, and total distance
        #  - for each vehicle, the depot start time, the nodes visited (omitting the start/end at the
        # depot), and the waiting time at each node
        #
        if sol_file is not None:
            with open(sol_file, 'w') as f:
                f.write("%d %d %.2f\n" % (
                    total_lateness.value,
                    total_client_lateness.value,
                    total_distance.value
                ))
                for k in range(nb_vehicles):
                    f.write("Vehicle %d (%.2f): " %(k + 1, depot_starts[k].value))
                    for node in routes[k].value:
                        f.write("%d (%.2f), " % (node, waiting[node].value))
                    f.write("\n")
    return 0

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python darp.py input_file [output_file] [time_limit]")
        sys.exit(1)

    instance_file = sys.argv[1]
    sol_file = sys.argv[2] if len(sys.argv) > 2 else None
    str_time_limit = sys.argv[3] if len(sys.argv) > 3 else "20"

    main(instance_file, str_time_limit, sol_file)
Compilation / Execution (Windows)
cl /EHsc darp.cpp -Ilibs\lib-cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly135.lib
darp instances\a5-40.json
Compilation / Execution (Linux)
g++ darp.cpp -I/opt/hexaly_13_5/include -lhexaly135 -lpthread -Ilibs/lib-cpp -o darp
./darp instances/a5-40.json
#include "optimizer/hexalyoptimizer.h"
#include <fstream>
#include <iostream>
#include <json.hpp>
#include <sstream>
#include <vector>

using namespace hexaly;
using namespace std;
using json = nlohmann::json;

class Darp {
public:
    HexalyOptimizer optimizer;

    int nbClients;
    int nbNodes;
    int nbVehicles;
    double factor;
    double depotTwEnd;
    int capacity;
    double scale;

    vector<int> quantitiesData;
    vector<vector<double>> distances;
    vector<double> startsData;
    vector<double> endsData;
    vector<double> loadingTimesData;
    vector<double> maxTravelTimes;
    vector<double> distanceWarehouseData;
    vector<double> timeWarehouseData;
    vector<vector<double>> distanceMatrixData;
    vector<vector<double>> timeMatrixData;

    vector<HxExpression> routes;
    vector<HxExpression> depotStarts;
    vector<HxExpression> waiting;

    HxExpression totalLateness;
    HxExpression totalClientLateness;
    HxExpression totalDistance;

    void readInstance(const string& fileName) {
        ifstream infile;
        infile.exceptions(ifstream::failbit | ifstream::badbit);
        infile.open(fileName.c_str());

        json instance;
        instance << infile;
        infile.close();

        nbClients = instance["nbClients"];
        nbNodes = instance["nbNodes"];
        nbVehicles = instance["nbVehicles"];
        depotTwEnd = instance["depot"]["twEnd"];
        capacity = instance["capacity"];
        scale = instance["scale"];

        quantitiesData.resize(2 * nbClients);
        startsData.resize(2 * nbClients);
        endsData.resize(2 * nbClients);
        loadingTimesData.resize(2 * nbClients);
        maxTravelTimes.resize(2 * nbClients);

        for (int k = 0; k < nbClients; ++k) {
            quantitiesData[k] = instance["clients"][k]["nbClients"].get<int>();
            quantitiesData[k + nbClients] = -instance["clients"][k]["nbClients"].get<int>();

            startsData[k] = instance["clients"][k]["pickup"]["start"].get<double>();
            startsData[k + nbClients] = instance["clients"][k]["delivery"]["start"].get<double>();

            endsData[k] = instance["clients"][k]["pickup"]["end"].get<double>();
            endsData[k + nbClients] = instance["clients"][k]["delivery"]["end"].get<double>();

            loadingTimesData[k] = instance["clients"][k]["pickup"]["loadingTime"].get<double>();
            loadingTimesData[k + nbClients] = instance["clients"][k]["delivery"]["loadingTime"].get<double>();

            maxTravelTimes[k] = instance["clients"][k]["pickup"]["maxTravelTime"].get<double>();
            maxTravelTimes[k + nbClients] = instance["clients"][k]["delivery"]["maxTravelTime"].get<double>();
        }

        distances.resize(nbNodes + 1, vector<double>(nbNodes + 1));
        for (int i = 0; i < nbNodes + 1; ++i) {
            for (int j = 0; j < nbNodes + 1; ++j) {
                distances[i][j] = instance["distanceMatrix"][i][j].get<double>();
            }
        }

        factor = 1.0 / (instance["scale"].get<double>() * instance["speed"].get<double>());

        distanceWarehouseData.resize(nbNodes);
        timeWarehouseData.resize(nbNodes);
        for (int k = 0; k < nbNodes; ++k) {
            distanceWarehouseData[k] = distances[0][k+1];
            timeWarehouseData[k] = distanceWarehouseData[k] * factor;
        }

        distanceMatrixData.resize(nbNodes, vector<double>(nbNodes));
        timeMatrixData.resize(nbNodes, vector<double>(nbNodes));
        for (int i = 0; i < nbNodes; ++i) {
            for (int j = 0; j < nbNodes; ++j){
                distanceMatrixData[i][j] = distances[i+1][j+1];
                timeMatrixData[i][j] = distanceMatrixData[i][j] * factor;
            }
        }
    }
    
    void solve(int limit) {
        HxModel model = optimizer.getModel();

        // routes[k] represents the nodes visited by vehicle k
        routes.resize(nbVehicles);
        depotStarts.resize(nbVehicles);
        for (int k = 0; k < nbVehicles; ++k) {
            routes[k] = model.listVar(nbNodes);
            depotStarts[k] = model.floatVar(0.0, depotTwEnd);
        }
        // waiting[k] is the waiting time at node k
        waiting.resize(nbNodes);
        for (int k = 0; k < nbNodes; ++k) {
            waiting[k] = model.floatVar(0.0, depotTwEnd);
        }
        // Each node is taken by one vehicle
        model.constraint(model.partition(routes.begin(), routes.end()));

        HxExpression quantities = model.array(quantitiesData.begin(), quantitiesData.end());
        HxExpression timeWarehouse = model.array(timeWarehouseData.begin(), timeWarehouseData.end());
        HxExpression timeMatrix = model.array();
        for (int i = 0; i < nbNodes; ++i) {
            timeMatrix.addOperand(model.array(timeMatrixData[i].begin(), timeMatrixData[i].end()));
        }
        HxExpression loadingTimes = model.array(loadingTimesData.begin(), loadingTimesData.end());
        HxExpression starts = model.array(startsData.begin(), startsData.end());
        HxExpression ends = model.array(endsData.begin(), endsData.end());
        HxExpression waitingArray = model.array(waiting.begin(), waiting.end());
        HxExpression distanceMatrix = model.array();
        for (int i = 0; i < nbNodes; ++i) {
            distanceMatrix.addOperand(model.array(distanceMatrixData[i].begin(), distanceMatrixData[i].end()));
        }
        HxExpression distanceWarehouse = model.array(distanceWarehouseData.begin(), distanceWarehouseData.end());
        vector<HxExpression> times(nbVehicles);
        vector<HxExpression> lateness(nbVehicles);
        vector<HxExpression> homeLateness(nbVehicles);
        vector<HxExpression> routeDistances(nbVehicles);

        for (int k = 0; k < nbVehicles; ++k) {
            HxExpression route = routes[k];
            HxExpression c = model.count(route);

            HxExpression demandLambda = model.createLambdaFunction(
                [&](HxExpression i, HxExpression prev) { return prev + quantities[route[i]]; });
            // routeQuantities[k][i] indicates the number of clients in vehicle k
            // at its i-th taken node
            HxExpression routeQuantities = model.array(model.range(0, c), demandLambda);
            HxExpression quantityLambda = model.createLambdaFunction(
                [&](HxExpression i) { return (routeQuantities[i] <= capacity); });
            // Vehicles have a maximum capacity
            model.constraint(model.and_(model.range(0, c), quantityLambda));

            HxExpression timesLambda = model.createLambdaFunction(
                [&](HxExpression i, HxExpression prev) {
                    return model.max(starts[route[i]], model.iif(
                        i == 0,
                        depotStarts[k] + timeWarehouse[route[0]],
                        prev + timeMatrix[route[i-1]][route[i]]
                    )) + waitingArray[route[i]] + loadingTimes[route[i]];
                }
            );
            // times[k][i] is the time at which vehicle k leaves the i-th node
            // (after waiting and loading time at node i)
            times[k] = model.array(model.range(0, c), timesLambda);

            HxExpression latenessLambda = model.createLambdaFunction(
                [&](HxExpression i) {
                    return model.max(
                        0,
                        times[k][i] - loadingTimes[route[i]] - ends[route[i]]
                    );
                }
            );
            // Total lateness of the k-th route
            lateness[k] = model.sum(model.range(0, c), latenessLambda);

            homeLateness[k] = model.iif(
                c > 0,
                model.max(0, times[k][c-1] + timeWarehouse[route[c-1]] - depotTwEnd),
                0
            );

            HxExpression routeDistLambda = model.createLambdaFunction(
                [&](HxExpression i) { return distanceMatrix[route[i-1]][route[i]]; });
            routeDistances[k] = model.sum(model.range(1, c), routeDistLambda)
                + model.iif(
                    c > 0,
                    distanceWarehouse[route[0]] + distanceWarehouse[route[c-1]],
                    0
                );
        }

        HxExpression routesArray = model.array(routes.begin(), routes.end());
        HxExpression timesArray = model.array(times.begin(), times.end());
        vector<HxExpression> clientLateness(nbClients);

        for (int k = 0; k < nbClients; ++k) {
            // For each pickup node k, its associated delivery node is k + instance.nbClients
            HxExpression pickupListIndex = model.find(routesArray, k);
            HxExpression deliveryListIndex = model.find(routesArray, k + nbClients);
            // A client picked up in route i is delivered in route i
            model.constraint(pickupListIndex == deliveryListIndex);

            HxExpression clientList = routesArray[pickupListIndex];
            HxExpression pickupIndex = model.indexOf(clientList, k);
            HxExpression deliveryIndex = model.indexOf(clientList, k + nbClients);
            // Pickup before delivery
            model.constraint(pickupIndex < deliveryIndex);

            HxExpression pickupTime = timesArray[pickupListIndex][pickupIndex];
            HxExpression deliveryTime =
                timesArray[deliveryListIndex][deliveryIndex] - loadingTimes[k + nbClients];
            HxExpression travelTime = deliveryTime - pickupTime;
            clientLateness[k] = model.max(travelTime - maxTravelTimes[k], 0);
        }

        vector<HxExpression> latenessPlusHomeLateness(nbVehicles);
        for (int k = 0; k < nbVehicles; ++k) {
            latenessPlusHomeLateness[k] = lateness[k] + homeLateness[k];
        }

        totalLateness = model.sum(latenessPlusHomeLateness.begin(), latenessPlusHomeLateness.end());
        totalClientLateness = model.sum(clientLateness.begin(), clientLateness.end());
        totalDistance = model.sum(routeDistances.begin(), routeDistances.end());

        model.minimize(totalLateness);
        model.minimize(totalClientLateness);
        model.minimize(totalDistance / scale);

        model.close();

        optimizer.getParam().setTimeLimit(limit);

        optimizer.getParam().setSeed(5);
        optimizer.solve();
    }

    /* Write the solution in a file with the following format:
     *  - total lateness on the routes, total client lateness, total distance
     *  - for each vehicle, the depot start time, the nodes visited (omitting the start/end at the
     * depot), and the waiting time at each node */
    void writeSolution(const string& fileName) {
        ofstream outfile;
        outfile.exceptions(ofstream::failbit | ofstream::badbit);
        outfile.open(fileName.c_str());

        outfile << totalLateness.getDoubleValue() << " " << totalClientLateness.getDoubleValue()
            << " " << totalDistance.getDoubleValue() << endl;
        for (int k = 0; k < nbVehicles; ++k) {
            HxCollection route = routes[k].getCollectionValue();
            outfile << "Vehicle " << k << " (" << depotStarts[k].getDoubleValue() << "): ";
            for (int i = 0; i < route.count(); ++i) {
                outfile << route[i] << " (" << waiting[route[i]].getDoubleValue() << "), ";
            }
            outfile << endl;
        }
    }
};

int main(int argc, char** argv) {
    if (argc < 2) {
        cerr << "Usage: darp 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 {
        Darp 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 .
copy libs\lib-cs\Newtonsoft.Json.dll .
csc Darp.cs /reference:Hexaly.NET.dll;Newtonsoft.Json.dll
Darp instances\a5-40.json
using System;
using System.IO;
using System.Collections.Generic;

using Hexaly.Optimizer;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;

public class Darp : IDisposable
{
    HexalyOptimizer optimizer;

    int nbClients;
    int nbNodes;
    int nbVehicles;
    double depotTwEnd;
    int capacity;
    double scale;
    double factor;

    int[] quantitiesData;
    double[] startsData;
    double[] endsData;
    double[] loadingTimesData;
    double[] maxTravelTimes;

    double[][] distances;
    double[] distanceWarehouseData;
    double[] timeWarehouseData;
    double[][] distanceMatrixData;
    double[][] timeMatrixData;

    HxExpression[] routes;
    HxExpression[] depotStarts;
    HxExpression[] waiting;

    HxExpression[] clientLateness;
    HxExpression[] latenessPlusHomeLateness;

    HxExpression totalLateness;
    HxExpression totalClientLateness;
    HxExpression totalDistance;

    public Darp()
    {
        optimizer = new HexalyOptimizer();
    }

    /* Read instance data */
    void ReadInstance(string fileName)
    {
        JObject instance = JObject.Parse(File.ReadAllText(fileName));

        nbClients = (int)instance["nbClients"];
        nbNodes = (int)instance["nbNodes"];
        nbVehicles = (int)instance["nbVehicles"];
        depotTwEnd = (double)instance["depot"]["twEnd"];
        capacity = (int)instance["capacity"];
        scale = (double)instance["scale"];

        quantitiesData = new int[2 * nbClients];
        startsData = new double[2 * nbClients];
        endsData = new double[2 * nbClients];
        loadingTimesData = new double[2 * nbClients];
        maxTravelTimes = new double[2 * nbClients];

        for (int k = 0; k < nbClients; ++k)
        {
            quantitiesData[k] = (int)instance["clients"][k]["nbClients"];
            quantitiesData[k + nbClients] = -(int)instance["clients"][k]["nbClients"];

            startsData[k] = (double)instance["clients"][k]["pickup"]["start"];
            startsData[k + nbClients] = (double)instance["clients"][k]["delivery"]["start"];

            endsData[k] = (double)instance["clients"][k]["pickup"]["end"];
            endsData[k + nbClients] = (double)instance["clients"][k]["delivery"]["end"];

            loadingTimesData[k] = (double)instance["clients"][k]["pickup"]["loadingTime"];
            loadingTimesData[k + nbClients] = (double)instance["clients"][k]["delivery"]["loadingTime"];

            maxTravelTimes[k] = (double)instance["clients"][k]["pickup"]["maxTravelTime"];
            maxTravelTimes[k + nbClients] =
                (double)instance["clients"][k]["delivery"]["maxTravelTime"];
        }

        distances = new double[nbNodes + 1][];
        for (int i = 0; i < nbNodes + 1; ++i)
        {
            distances[i] = new double[nbNodes + 1];
            for (int j = 0; j < nbNodes + 1; ++j)
            {
                distances[i][j] = (double)instance["distanceMatrix"][i][j];
            }
        }

        factor = 1 / ((double)instance["scale"] * (double)instance["speed"]);

        distanceWarehouseData = new double[nbNodes];
        timeWarehouseData = new double[nbNodes];
        for (int k = 0; k < nbNodes; ++k)
        {
            distanceWarehouseData[k] = distances[0][k+1];
            timeWarehouseData[k] = distanceWarehouseData[k] * factor;
        }

        distanceMatrixData = new double[nbNodes][];
        timeMatrixData = new double[nbNodes][];
        for (int i = 0; i < nbNodes; ++i)
        {
            distanceMatrixData[i] = new double[nbNodes];
            timeMatrixData[i] = new double[nbNodes];
            for (int j = 0; j < nbNodes; ++j)
            {
                distanceMatrixData[i][j] = distances[i+1][j+1];
                timeMatrixData[i][j] = distanceMatrixData[i][j] * factor;
            }
        }

    }

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

    void Solve(int limit)
    {
        HxModel model = optimizer.GetModel();

        // routes[k] represents the nodes visited by vehicle k
        routes = new HxExpression[nbVehicles];
        depotStarts = new HxExpression[nbVehicles];
        for (int k = 0; k < nbVehicles; ++k)
        {
            routes[k] = model.List(nbNodes);
            depotStarts[k] = model.Float(0.0, depotTwEnd);
        }
        // waiting[k] is the waiting time at node k
        waiting = new HxExpression[nbNodes];
        for (int k = 0; k < nbNodes; ++k)
        {
            waiting[k] = model.Float(0.0, depotTwEnd);
        }
        // Each node is taken by one vehicle
        model.Constraint(model.Partition(routes));

        HxExpression quantities = model.Array(quantitiesData);
        HxExpression timeWarehouse = model.Array(timeWarehouseData);
        HxExpression loadingTimes = model.Array(loadingTimesData);
        HxExpression starts = model.Array(startsData);
        HxExpression ends = model.Array(endsData);
        HxExpression waitingArray = model.Array(waiting);
        HxExpression timeMatrix = model.Array();
        HxExpression distanceMatrix = model.Array();
        for (int i = 0; i < nbNodes; ++i)
        {
            timeMatrix.AddOperand(model.Array(timeMatrixData[i]));
            distanceMatrix.AddOperand(model.Array(distanceMatrixData[i]));
        }
        HxExpression distanceWarehouse = model.Array(distanceWarehouseData);

        HxExpression[] times = new HxExpression[nbVehicles];
        HxExpression[] lateness = new HxExpression[nbVehicles];
        HxExpression[] homeLateness = new HxExpression[nbVehicles];
        HxExpression[] routeDistances = new HxExpression[nbVehicles];

        for (int k = 0; k < nbVehicles; ++k)
        {
            HxExpression route = routes[k];
            HxExpression c = model.Count(route);

            HxExpression demandLambda = model.LambdaFunction(
                (i, prev) => prev + quantities[route[i]]
            );
            // routeQuantities[k][i] indicates the number of clients in vehicle k
            // at its i-th taken node
            HxExpression routeQuantities = model.Array(model.Range(0, c), demandLambda);
            HxExpression quantityLambda = model.LambdaFunction(i => routeQuantities[i] <= capacity);
            // Vehicles have a maximum capacity
            model.Constraint(model.And(model.Range(0, c), quantityLambda));

            HxExpression timesLambda = model.LambdaFunction(
                (i, prev) =>
                    model.Max(
                        starts[route[i]],
                        model.If(
                            i == 0,
                            depotStarts[k] + timeWarehouse[route[0]],
                            prev + timeMatrix[route[i-1]][route[i]]
                        )
                    ) + waitingArray[route[i]] + loadingTimes[route[i]]);
            // times[k][i] is the time at which vehicle k leaves the i-th node
            // (after waiting and loading time at node i)
            times[k] = model.Array(model.Range(0, c), timesLambda);

            HxExpression latenessLambda = model.LambdaFunction(
                i =>
                    model.Max(
                        0,
                        times[k][i] - loadingTimes[route[i]] - ends[route[i]]
                    )
            );
            // Total lateness of the k-th route
            lateness[k] = model.Sum(model.Range(0, c), latenessLambda);

            homeLateness[k] = model.If(
                c > 0,
                model.Max(0, times[k][c-1] + timeWarehouse[route[c-1]] - depotTwEnd),
                0
            );

            HxExpression routeDistLambda = model.LambdaFunction(
                i => distanceMatrix[route[i-1]][route[i]]
            );
            routeDistances[k] = model.Sum(
                model.Range(1, c),
                routeDistLambda
            ) + model.If(
                c > 0,
                distanceWarehouse[route[0]] + distanceWarehouse[route[c-1]],
                0
            );
        }
     
        HxExpression routesArray = model.Array(routes);
        HxExpression timesArray = model.Array(times);
        clientLateness = new HxExpression[nbClients];

        for (int k = 0; k < nbClients; ++k)
        {
            // For each pickup node k, its associated delivery node is k + nbClients
            HxExpression pickupListIndex = model.Find(routesArray, k);
            HxExpression deliveryListIndex = model.Find(routesArray, k + nbClients);
            // A client picked up in route i is delivered in route i
            model.Constraint(pickupListIndex == deliveryListIndex);

            HxExpression clientList = routesArray[pickupListIndex];
            HxExpression pickupIndex = model.IndexOf(clientList, k);
            HxExpression deliveryIndex = model.IndexOf(clientList, k + nbClients);
            // Pickup before delivery
            model.Constraint(pickupIndex < deliveryIndex);

            HxExpression pickupTime = timesArray[pickupListIndex][pickupIndex];
            HxExpression deliveryTime =
                timesArray[deliveryListIndex][deliveryIndex] - loadingTimes[k + nbClients];
            HxExpression travelTime = deliveryTime - pickupTime;
            clientLateness[k] = model.Max(travelTime - maxTravelTimes[k], 0);
        }

        latenessPlusHomeLateness = new HxExpression[nbVehicles];
        for (int k = 0; k < nbVehicles; ++k)
        {
            latenessPlusHomeLateness[k] = lateness[k] + homeLateness[k];
        }
        totalLateness = model.Sum(latenessPlusHomeLateness);
        totalClientLateness = model.Sum(clientLateness);
        totalDistance = model.Sum(routeDistances);

        model.Minimize(totalLateness);
        model.Minimize(totalClientLateness);
        model.Minimize(totalDistance / scale);

        model.Close();

        optimizer.GetParam().SetTimeLimit(limit);

        optimizer.GetParam().SetSeed(5);
        optimizer.Solve();
    }

    /* Write the solution in a file with the following format:
     *  - total lateness on the routes, total client lateness, total distance
     *  - for each vehicle, the depot start time, the nodes visited (omitting the start/end at the
     * depot), and the waiting time at each node */
    void WriteSolution(string fileName)
    {
        using (StreamWriter output = new StreamWriter(fileName))
        {
            output.WriteLine(
                totalLateness.GetDoubleValue()
                + " "
                + totalClientLateness.GetDoubleValue()
                + " "
                + totalDistance.GetDoubleValue()
            );
            for (int k = 0; k < nbVehicles; ++k)
            {
                HxCollection route = routes[k].GetCollectionValue();
                output.Write("Vehicle " + (k + 1) + " (" + depotStarts[k].GetDoubleValue() + "): ");
                for (int i = 0; i < route.Count(); ++i)
                    output.Write(route[i] + " (" + (waiting[route[i]].GetDoubleValue() + "), "));
                output.WriteLine();
            }
        }
    }

    public static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: Darp 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 (Darp model = new Darp())
        {
            model.ReadInstance(instanceFile);
            model.Solve(int.Parse(strTimeLimit));
            if (outputFile != null)
                model.WriteSolution(outputFile);
        }
    }
}
Compilation / Execution (Windows)
javac Darp.java -cp %HX_HOME%\bin\hexaly.jar;libs\lib-java\gson-2.8.8.jar
java -cp %HX_HOME%\bin\hexaly.jar;libs\lib-java\gson-2.8.8.jar;. Darp instances\a5-40.json
Compilation / Execution (Linux)
javac Darp.java -cp /opt/hexaly_13_5/bin/hexaly.jar:libs/lib-java/gson-2.8.8.jar
java -cp /opt/hexaly_13_5/bin/hexaly.jar:libs/lib-java/gson-2.8.8.jar:. Darp instances/a5-40.json
import com.hexaly.optimizer.*;

import java.io.*;
import java.util.*;

import com.google.gson.JsonObject;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonReader;

public class Darp {
    private final HexalyOptimizer optimizer;

    int nbClients;
    int nbNodes;
    int nbVehicles;
    double depotTwEnd;
    int capacity;
    double scale;
    double factor;

    int[] quantitiesData;
    double[] startsData;
    double[] endsData;
    double[] loadingTimesData;
    double[] maxTravelTimes;

    double[][] distances;
    double[] distanceWarehouseData;
    double[] timeWarehouseData;
    double[][] distanceMatrixData;
    double[][] timeMatrixData;

    HxExpression[] routes;
    HxExpression[] depotStarts;
    HxExpression[] waiting;

    HxExpression totalLateness;
    HxExpression totalClientLateness;
    HxExpression totalDistance;

    private Darp() {
        optimizer = new HexalyOptimizer();
    }

    // Read instance data
    private void readInstance(String fileName) throws IOException {
        JsonReader reader = new JsonReader(new InputStreamReader(new FileInputStream(fileName)));
        JsonObject instance = JsonParser.parseReader(reader).getAsJsonObject();

        nbClients = instance.get("nbClients").getAsInt();
        nbNodes = instance.get("nbNodes").getAsInt();
        nbVehicles = instance.get("nbVehicles").getAsInt();
        JsonObject depot = (JsonObject) instance.get("depot");
        depotTwEnd = depot.get("twEnd").getAsDouble();
        capacity = instance.get("capacity").getAsInt();
        scale = instance.get("scale").getAsDouble();
        JsonArray clients = (JsonArray) instance.get("clients");

        quantitiesData = new int[2 * nbClients];
        startsData = new double[2 * nbClients];
        endsData = new double[2 * nbClients];
        loadingTimesData = new double[2 * nbClients];
        maxTravelTimes = new double[2 * nbClients];

        for (int k = 0; k < nbClients; ++k){
            JsonObject clientk = (JsonObject) clients.get(k);

            quantitiesData[k] = clientk.get("nbClients").getAsInt();
            quantitiesData[k + nbClients] = -clientk.get("nbClients").getAsInt();

            JsonObject clientkPickup = (JsonObject) clientk.get("pickup");
            JsonObject clientkDelivery = (JsonObject) clientk.get("delivery");

            startsData[k] = clientkPickup.get("start").getAsDouble();
            startsData[k + nbClients] = clientkDelivery.get("start").getAsDouble();

            endsData[k] = clientkPickup.get("end").getAsDouble();
            endsData[k + nbClients] = clientkDelivery.get("end").getAsDouble();

            loadingTimesData[k] = clientkPickup.get("loadingTime").getAsDouble();
            loadingTimesData[k + nbClients] = clientkDelivery.get("loadingTime").getAsDouble();

            maxTravelTimes[k] = clientkPickup.get("maxTravelTime").getAsDouble();
            maxTravelTimes[k + nbClients] = clientkDelivery.get("maxTravelTime").getAsDouble();
        }

        distances = new double[nbNodes + 1][nbNodes + 1];
        JsonArray distanceMatrixJson = (JsonArray) instance.get("distanceMatrix");
        for (int i = 0; i < nbNodes + 1; ++i){
            JsonArray distanceMatrixJsoni = (JsonArray) distanceMatrixJson.get(i);
            for (int j = 0; j < nbNodes + 1; ++j){
                distances[i][j] = distanceMatrixJsoni.get(j).getAsDouble();
            }
        }

        factor = 1.0 / (instance.get("scale").getAsDouble() * instance.get("speed").getAsDouble());

        distanceWarehouseData = new double[nbNodes];
        timeWarehouseData = new double[nbNodes];
        for (int k = 0; k < nbNodes; ++k){
            distanceWarehouseData[k] = distances[0][k+1];
            timeWarehouseData[k] = distanceWarehouseData[k] * factor;
        }

        distanceMatrixData = new double[nbNodes][nbNodes];
        timeMatrixData = new double[nbNodes][nbNodes];
        for (int i = 0; i < nbNodes; ++i){
            for (int j = 0; j < nbNodes; ++j){
                distanceMatrixData[i][j] = distances[i+1][j+1];
                timeMatrixData[i][j] = distanceMatrixData[i][j] * factor;
            }
        }
    }

    private void solve(int limit) {
        HxModel model = optimizer.getModel();

        // routes[k] represents the nodes visited by vehicle k
        routes = new HxExpression[nbVehicles];
        depotStarts = new HxExpression[nbVehicles];
        for (int k = 0; k < nbVehicles; ++k){
            routes[k] = model.listVar(nbNodes);
            depotStarts[k] = model.floatVar(0.0, depotTwEnd);
        }
        // waiting[k] is the waiting time at node k
        waiting = new HxExpression[nbNodes];
        for (int k = 0; k < nbNodes; ++k){
            waiting[k] = model.floatVar(0.0, depotTwEnd);
        }
        // Each node is taken by one vehicle
        model.constraint(model.partition(routes));

        HxExpression quantities = model.array(quantitiesData);
        HxExpression timeWarehouse = model.array(timeWarehouseData);
        HxExpression timeMatrix = model.array(timeMatrixData);
        for (int i = 0; i < nbNodes; ++i){
            timeMatrix.addOperand(model.array(timeMatrixData[i]));
        }
        HxExpression loadingTimes = model.array(loadingTimesData);
        HxExpression starts = model.array(startsData);
        HxExpression ends = model.array(endsData);
        HxExpression waitingArray = model.array(waiting);
        HxExpression distanceMatrix = model.array();
        for (int i = 0; i < nbNodes; ++i){
            distanceMatrix.addOperand(model.array(distanceMatrixData[i]));
        }
        HxExpression distanceWarehouse = model.array(distanceWarehouseData);

        HxExpression[] times = new HxExpression[nbVehicles];
        HxExpression[] lateness = new HxExpression[nbVehicles];
        HxExpression[] homeLateness = new HxExpression[nbVehicles];
        HxExpression[] routeDistances = new HxExpression[nbVehicles];

        for (int k = 0; k < nbVehicles; ++k){
            HxExpression route = routes[k];
            HxExpression c = model.count(route);

            HxExpression demandLambda = model.lambdaFunction(
                (i, prev) -> model.sum(prev, model.at(quantities, model.at(route, i))));
            // routeQuantities[k][i] indicates the number of clients in vehicle k
            // at its i-th taken node
            HxExpression routeQuantities = model.array(model.range(0, c), demandLambda);
            HxExpression quantityLambda = model.createLambdaFunction(
                i -> model.leq(model.at(routeQuantities, i), capacity));
            // Vehicles have a maximum capacity
            model.constraint(model.and(model.range(0, c), quantityLambda));

            HxExpression depotStartsk = depotStarts[k];
            HxExpression timesLambda = model.lambdaFunction(
                (i, prev) -> model.sum(
                    model.max(
                        model.at(starts, model.at(route, i)),
                        model.iif(
                            model.eq(i, 0),
                            model.sum(
                                depotStartsk,
                                model.at(timeWarehouse, model.at(route, 0))
                            ),
                            model.sum(
                                prev,
                                model.at(
                                    timeMatrix,
                                    model.at(route, model.sub(i, 1)),
                                    model.at(route, i)
                                )
                            )
                        )
                    ),
                    model.sum(
                        model.at(waitingArray, model.at(route, i)),
                        model.at(loadingTimes, model.at(route, i))
                    )
                )
            );
            HxExpression timesk = model.array(model.range(0, c), timesLambda);
            // times[k][i] is the time at which vehicle k leaves the i-th node
            // (after waiting and loading time at node i)
            times[k] = timesk;
            HxExpression latenessLambda = model.lambdaFunction(
                i -> model.max(
                    0,
                    model.sub(
                        model.sub(
                            model.at(timesk, i),
                            model.at(loadingTimes, model.at(route, i))
                        ),
                        model.at(ends, model.at(route, i))
                    )
                )
            );
            // Total lateness of the k-th route
            lateness[k] = model.sum(model.range(0, c), latenessLambda);

            homeLateness[k] = model.iif(
                model.gt(c, 0),
                model.max(
                    0,
                    model.sum(
                        model.at(timesk, model.sub(c, 1)),
                        model.sub(
                            model.at(timeWarehouse, model.at(route, model.sub(c, 1))),
                            depotTwEnd
                        )
                    )
                ),
                0
            );

            HxExpression routeDistLambda = model.lambdaFunction(
                i -> model.at(
                    distanceMatrix,
                    model.at(route, model.sub(i, 1)),
                    model.at(route, i)
                )
            );
            routeDistances[k] = model.sum(
                model.sum(model.range(1, c), routeDistLambda),
                model.iif(
                    model.gt(c, 0),
                    model.sum(
                        model.at(distanceWarehouse, model.at(route, 0)),
                        model.at(distanceWarehouse, model.at(route, model.sub(c, 1)))
                    ),
                    0
                )
            );
        }

        HxExpression routesArray = model.array(routes);
        HxExpression timesArray = model.array(times);
        HxExpression[] clientLateness = new HxExpression[nbClients];

        for (int k = 0; k < nbClients; ++k) {
            // For each pickup node k, its associated delivery node is k + nbClients
            HxExpression pickupListIndex = model.find(routesArray, k);
            HxExpression deliveryListIndex = model.find(routesArray, k + nbClients);
            // A client picked up in route i is delivered in route i
            model.constraint(model.eq(pickupListIndex, deliveryListIndex));

            HxExpression clientList = model.at(routesArray, pickupListIndex);
            HxExpression pickupIndex = model.indexOf(clientList, k);
            HxExpression deliveryIndex = model.indexOf(clientList, k + nbClients);
            // Pickup before delivery
            model.constraint(model.lt(pickupIndex, deliveryIndex));

            HxExpression pickupTime = model.at(timesArray, pickupListIndex, pickupIndex);
            HxExpression deliveryTime = model.sub(
                model.at(timesArray, deliveryListIndex, deliveryIndex),
                model.at(loadingTimes, k + nbClients)
            );
            HxExpression travelTime = model.sub(deliveryTime, pickupTime);
            clientLateness[k] = model.max(model.sub(travelTime, maxTravelTimes[k]), 0);
        }

        HxExpression[] latenessPlusHomeLateness = new HxExpression[nbVehicles];
        for (int k = 0; k < nbVehicles; ++k) {
            latenessPlusHomeLateness[k] = model.sum(lateness[k], homeLateness[k]);
        }

        totalLateness = model.sum(latenessPlusHomeLateness);
        totalClientLateness = model.sum(clientLateness);
        totalDistance = model.sum(routeDistances);

        model.minimize(totalLateness);
        model.minimize(totalClientLateness);
        model.minimize(model.div(totalDistance, scale));

        model.close();

        optimizer.getParam().setTimeLimit(limit);

        optimizer.solve();
    }

    /* Write the solution in a file with the following format:
     *  - total lateness on the routes, total client lateness, total distance
     *  - for each vehicle, the depot start time, the nodes visited (omitting the start/end at the
     * depot), and the waiting time at each node */
    private void writeSolution(String fileName) throws IOException {
        try (PrintWriter output = new PrintWriter(fileName)) {
            output.println(
                totalLateness.getDoubleValue()
                + " "
                + totalClientLateness.getDoubleValue()
                + " "
                + totalDistance.getDoubleValue()
            );
            for (int k = 0; k < nbVehicles; ++k) {
                HxCollection route = routes[k].getCollectionValue();
                output.print("Vehicle " + (k + 1) + " (" + depotStarts[k].getDoubleValue() + "): ");
                for (int i = 0; i < route.count(); ++i) {
                    int routei = (int) route.get(i); // route.get(i) casted to long
                    output.print(routei + " (" + (waiting[routei].getDoubleValue() + "), "));
                }
                output.println();
            }
        }
    }

    public static void main(String[] args) {

        if (args.length < 1) {
            System.err.println("Usage: java Darp inputFile [outputFile] [timeLimit]");
            System.exit(1);
        }

        try {
            String instanceFile = args[0];
            String outputFile = args.length > 1 ? args[1] : null;
            String strTimeLimit = args.length > 2 ? args[2] : "20";

            Darp model = new Darp();
            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);
        }
    }
}