Capacitated Arc Routing Problem (CARP)

Problem

In the Capacitated Arc Routing Problem (CARP), a fleet of delivery vehicles with uniform capacity must service edges with known demand. The vehicles start and end their routes at a common depot. Each edge is served by exactly one vehicle. Additionally, the total demand served by each truck must not exceed its capacity. The objective is to assign a sequence of edges to each truck of the fleet minimizing the total traveled distance.

Principles learned

Data

The instances provided are from the DIMACS website. The format of the data files is as follows:

  • The number of nodes
  • The number of required edges (edges with positive demand)
  • The number of non-required edges (edges with zero demand)
  • The number of vehicles available
  • The capacity of a truck
  • For each required edge, its demand and its cost
  • For each non-required edge, its cost
  • The index of the depot node.

Program

This Hexaly model for the Capacitated Arc Routing Problem (CARP) uses list variables to represent the sequence of edges assigned to each truck. These edges are the ones that the truck actually visits and services. The truck may also merely pass through other edges along its route.

Edges may be visited in both directions, but their demand must only be satisfied once. Therefore, we constrain each edge to be present in at most one list variable, using the ‘disjoint’ operator. Thanks to the ‘contains’ operator, we also ensure that each edge is visited either in one direction or in the other.

We can access the demand for each edge in a sequence using the ‘at’ operator on the demand array. We compute the total quantity delivered by each truck with a lambda function to apply the ‘sum’ operator over all visited edges. Note that the number of terms in this sum varies during the search, along with the size of the list. We can then constrain this quantity to be lower than the truck capacity.

Similarly, we access the distance traveled from one edge to the next using the ‘at’ operator on the two-dimensional distance matrix. If the origin of the current edge is equal to the destination of the previous one, then this distance is zero. Otherwise, it is the distance of the shortest path between these two points (passing through other edges that are not serviced). We compute the total distance traveled by each truck using another lambda function to sum the distances along its route.

Execution
hexaly capacitated_arc_routing.hxm inFileName=instances/egl-e1-A.dat [hxTimeLimit=] [solFileName=]
use io;

/* Read instance data. The input files follow the format of the DIMACS challenge */
function input() {
    usage = "Usage: hexaly capacitated_arc_routing.hxm "
            + "inFileName=inputFile [solFileName=outputFile] [hxTimeLimit=timeLimit]";

    if (inFileName == nil) throw usage;
    readData();
}

/* Declare the optimization model */
function model() {
    // Sequence of edges visited and "serviced" by each truck
    // A sequence can contain an edge in one direction or its reverse
    edgesSequences[k in 0...nbTrucks] <- list(2 * nbRequiredEdges);

    // An edge must be serviced by at most one truck
    constraint disjoint(edgesSequences);

    // An edge can be travelled in both directions but its demand must be
    // satisfied only once
    for [i in 0...nbRequiredEdges] {
        constraint contains(edgesSequences, 2 * i) + contains(edgesSequences, 2 * i + 1) == 1;
    }

    for [k in 0...nbTrucks] {
        local sequence <- edgesSequences[k];
        local c <- count(sequence);

        // Quantity in each truck
        routeQuantity <- sum(sequence, j => requiredEdgesDemands[j]);
        // Capacity constraint : a truck must not exceed its capacity
        constraint routeQuantity <= truckCapacity;

        // Distance travelled by each truck
        routeDistance[k] <- sum(1...c, i => requiredEdgesCosts[sequence[i]] +
                distanceBetweenEdges[sequence[i - 1]][sequence[i]]) +
                (c > 0 ? requiredEdgesCosts[sequence[0]] + distanceFromDepot[sequence[0]] +
                distanceToDepot[sequence[c - 1]] : 0);
    }

    // Total distance travelled
    totalDistance <- sum[k in 0...nbTrucks](routeDistance[k]);

    // Objective: minimize the distance travelled
    minimize totalDistance;
}

/* Parametrize the solver */
function param() {
    if (hxTimeLimit == nil) hxTimeLimit = 20;
}

/* Write the solution in a file with the following format:
 * - total distance
 * - number of routes
 * - for each truck, the edges visited */
function output() {
    if (solFileName == nil) return;
    local outfile = io.openWrite(solFileName);
    outfile.println("Objective function value : ", totalDistance.value);
    outfile.println("Number of routes : ", nbTrucks);
    for [k in 0...nbTrucks] {
        outfile.print("Sequence of truck ", k + 1, ": ");
        local sequence = edgesSequences[k].value;
        for [i in 0...sequence.count()] {
            outfile.print("(", requiredEdgesOrigin[sequence[i]], 
                    ", ", requiredEdgesDestination[sequence[i]], ")  ");
        }
        outfile.println();
    }
}

function readData() {
    local inFile = io.openRead(inFileName);
    for [_ in 0...2] inFile.readln();
    nbNodes = inFile.readln().trim().split(":")[1].toInt();
    nbRequiredEdges = inFile.readln().trim().split(":")[1].toInt();
    nbNotRequiredEdges = inFile.readln().trim().split(":")[1].toInt();
    nbTrucks = inFile.readln().trim().split(":")[1].toInt();
    truckCapacity = inFile.readln().trim().split(":")[1].toInt();
    for [_ in 0...2] inFile.readln();

    inFile.readln();
    requiredNodes[0] = -1;
    for [i in 0...nbNodes][j in 0...nbNodes] nodeNeighbors[i][j] = 0;
    for [i in 0...nbRequiredEdges] {
        local elements = inFile.readln().trim().split("   ");
        local firstNode = elements[0].trim().split(",")[0].trim().split()[1].toInt();
        local secondNode = elements[0].trim().split(",")[1].trim().replace(")", "").toInt();
        local cost = elements[1].trim().split()[1].toInt();
        local demand = elements[2].trim().split()[1].toInt();
        // Even indices store direct edges, and odd indices store reverse edges
        requiredEdgesCosts[2 * i] = cost;
        requiredEdgesDemands[2 * i] = demand;
        requiredEdgesCosts[2 * i + 1] = cost;
        requiredEdgesDemands[2 * i + 1] = demand;
        requiredEdgesOrigin[2 * i] = firstNode;
        requiredEdgesDestination[2 * i] = secondNode;
        requiredEdgesOrigin[2 * i + 1] = secondNode;
        requiredEdgesDestination[2 * i + 1] = firstNode;
        addRequiredNodes(firstNode);
        addRequiredNodes(secondNode);
        nodeNeighbors[firstNode - 1][secondNode - 1] = cost;
        nodeNeighbors[secondNode - 1][firstNode - 1] = cost;
    }

    if (nbNotRequiredEdges > 0) {
        inFile.readln();
        for [i in 0...nbNotRequiredEdges] {
            local elements = inFile.readln().trim().split("   ");
            local firstNode = elements[0].trim().split(",")[0].trim().split()[1].toInt();
            local secondNode = elements[0].trim().split(",")[1].trim().replace(")", "").toInt();
            local cost = elements[1].trim().split()[1].toInt();
            nodeNeighbors[firstNode - 1][secondNode - 1] = cost;
            nodeNeighbors[secondNode - 1][firstNode - 1] = cost;
        }
    }
    depotNode = inFile.readln().trim().split(":")[1].toInt();
    nbRequiredNodes = requiredNodes.count();
    for [i in 0...nbNodes] {
        shortestPath[i] = 0;
    }
    findRequiredPaths(shortestPath);
    findDistanceBetweenEdges();
    findDistanceToDepot();
    findDistanceFromDepot(shortestPath);
}

/* Finds the shortest path from one node "origin" to all the other nodes of the graph
   thanks to the Dijkstra's algorithm */
function minDistance(shortestPath, sptSet) {
    minValue = +inf;
    minIndex = -1;
    for [i in 0...nbNodes] {
        if (sptSet[i] == false && shortestPath[i] <= minValue) {
            minValue = shortestPath[i];
            minIndex = i;
        }
    }
    return minIndex;
}

function shortestPathFinder(origin, shortestPath) {
    local positiveInfinity =  +inf;
    for [i in 0...nbNodes] {
        shortestPath[i] = positiveInfinity;
        sptSet[i] = false;
    }
    shortestPath[origin - 1] = 0;
    for [_ in 0...nbNodes] {
        currentNode = minDistance(shortestPath, sptSet);
        sptSet[currentNode] = true;
        currentNeighbors = nodeNeighbors[currentNode];
        for [neighbor in 0...nbNodes] {
            if (currentNeighbors[neighbor] != 0) {
                distance = currentNeighbors[neighbor];
                if (!sptSet[neighbor] &&
                        shortestPath[currentNode] + distance < shortestPath[neighbor]) {
                    shortestPath[neighbor] = shortestPath[currentNode] + distance;
                }
            }
        }
    }
}

function findRequiredPaths(shortestPath) {
    for [i in 0...nbRequiredNodes] {
        node = requiredNodes[i];
        shortestPathFinder(node, shortestPath);
        for [j in 0...nbNodes] {
            requiredDistance[i][j] = shortestPath[j];
        }
    }
}

function findDistanceBetweenEdges() {
    for [i in 0...2*nbRequiredEdges] {
        for [j in 0...2*nbRequiredEdges] {
            if (requiredEdgesDestination[i] == requiredEdgesOrigin[j]) {
                distanceBetweenEdges[i][j] = 0;
            } else {
                for [k in 0...nbRequiredNodes] {
                    if (requiredNodes[k] == requiredEdgesDestination[i]) {
                        distanceBetweenEdges[i][j] = requiredDistance[k][requiredEdgesOrigin[j] - 1];
                    }
                }
            }
        }
    }
}

function findDistanceToDepot() {
    for [i in 0...2*nbRequiredEdges] {
        if (requiredEdgesDestination[i] == depotNode) {
            distanceToDepot[i] = 0;
        } else {
            for [k in 0...nbRequiredNodes] {
                if (requiredNodes[k] == requiredEdgesDestination[i]) {
                    distanceToDepot[i] = requiredDistance[k][depotNode - 1];
                }
            }
        }
    }
}

function findDistanceFromDepot(shortestPath) {
    for [i in 0...2*nbRequiredEdges] {
        if (depotNode == requiredEdgesOrigin[i]) {
            distanceFromDepot[i] = 0;
        } else {
            depotIsRequiredNode = false;
            for [k in 0...nbRequiredNodes] {
                if (requiredNodes[k] == depotNode) {
                    depotIsRequiredNode = true;
                    distanceFromDepot[i] = requiredDistance[k][requiredEdgesOrigin[i] - 1];
                }
            }
            if (!depotIsRequiredNode) {
                shortestPathFinder(depotNode, shortestPath);
                distanceFromDepot[i] = shortestPath[requiredEdgesOrigin[i] - 1];
            }
        }
    }
}

function addRequiredNodes(node) {
    local n = requiredNodes.count();
    local found = false;
    for [i in 0...n] {
        if (requiredNodes[i] == node) {
            found = true;
        }
    }
    if (!found) {
        if (requiredNodes[0] == -1) {
            requiredNodes[0] = node;
        } else {
            requiredNodes[n] = node;
        }
    }
}
Execution (Windows)
set PYTHONPATH=%HX_HOME%\bin\python
python capacitated_arc_routing.py instances\egl-e1-A.dat
Execution (Linux)
export PYTHONPATH=/opt/hexaly_13_0/bin/python
python capacitated_arc_routing.py instances/egl-e1-A.dat
import sys
import hexaly.optimizer


def main(instance_file, output_file, str_time_limit):
    #
    # Read instance data
    #
    instance = CarpInstance(instance_file)

    with hexaly.optimizer.HexalyOptimizer() as optimizer:
        #
        # Declare the optimization model
        #
        model = optimizer.model

        # Sequence of edges visited and "serviced" by each truck
        edges_sequences_vars = [model.list(2 * instance.nb_required_edges)
                                for _ in range(instance.nb_trucks)]
        edges_sequences = model.array(edges_sequences_vars)

        # Create distance and cost arrays to be able to access it with an "at" operator
        demands = model.array(instance.demands_data)
        costs = model.array(instance.costs_data)
        dist_from_depot = model.array(instance.dist_from_depot_data)
        dist_to_depot = model.array(instance.dist_to_depot_data)
        edges_dist = model.array()
        for n in range(2 * instance.nb_required_edges):
            edges_dist.add_operand(model.array(instance.edges_dist_data[n]))

        # An edge must be serviced by at most one truck
        model.constraint(model.disjoint(edges_sequences))

        # An edge can be travelled in both directions but its demand must be 
        # satisfied only once
        for i in range(instance.nb_required_edges):
            model.constraint(
                model.contains(edges_sequences, 2 * i)
                + model.contains(edges_sequences, 2 * i + 1) 
                == 1)

        route_distances = [None] * instance.nb_trucks
        for k in range(instance.nb_trucks):
            sequence = edges_sequences_vars[k]
            c = model.count(sequence)

            # Quantity in each truck
            demand_lambda = model.lambda_function(lambda j: demands[j])
            route_quantity = model.sum(sequence, demand_lambda)
            # Capacity constraint : a truck must not exceed its capacity
            model.constraint(route_quantity <= instance.truck_capacity)

            # Distance travelled by each truck
            dist_lambda = model.lambda_function(
                lambda i:
                    costs[sequence[i]]
                    + model.at(
                        edges_dist,
                        sequence[i - 1],
                        sequence[i]))
            route_distances[k] = model.sum(model.range(1, c), dist_lambda) \
                + model.iif(
                    c > 0,
                    costs[sequence[0]] + dist_from_depot[sequence[0]] \
                        + dist_to_depot[sequence[c - 1]],
                    0)

        # Total distance travelled
        total_distance = model.sum(route_distances)

        # Objective: minimize the distance travelled
        model.minimize(total_distance)

        model.close()

        # Parameterize the optimizer
        optimizer.param.time_limit = int(str_time_limit)

        optimizer.solve()

        #
        # Write the solution in a file with the following format:
        #  - total distance
        #  - number of routes
        #  - for each truck, the edges visited
        #
        if output_file:
            with open(output_file, 'w') as f:
                f.write("Objective function value : {}\nNumber of routes : {}\n".format(
                    total_distance.value, instance.nb_trucks))
                for k in range(instance.nb_trucks):
                    f.write("Sequence of truck {}: ".format(k + 1))
                    sequence = edges_sequences_vars[k].value
                    c = len(sequence)
                    for i in range(c):
                        f.write("({}, {})  ".format(instance.origins_data[sequence[i]],
                                instance.destinations_data[sequence[i]]))
                    f.write("\n")


class CarpInstance:

    def read_elem(self, filename):
        with open(filename) as f:
            return [str(elem) for elem in f.read().strip().split("\n")]

    # The input files follow the format of the DIMACS challenge
    def __init__(self, filename):
        file_it = iter(self.read_elem(filename))
        for _ in range(2):
            next(file_it)
        nb_nodes = int(next(file_it).strip().split(":")[1])
        self.nb_required_edges = int(next(file_it).strip().split(":")[1])
        nb_not_required_edges = int(next(file_it).strip().split(":")[1])
        self.nb_trucks = int(next(file_it).strip().split(":")[1])
        self.truck_capacity = int(next(file_it).strip().split(":")[1])
        for _ in range(3):
            next(file_it)
        self.demands_data = list()
        self.costs_data = list()
        self.origins_data = list()
        self.destinations_data = list()
        required_nodes = list()
        node_neighbors = [([0] * nb_nodes) for _ in range(nb_nodes)]
        for _ in range(self.nb_required_edges):
            elements = next(file_it)
            edge = tuple(map(int, elements.strip().split("   ")[0][2:-1].strip().split(",")))
            cost = int(elements.strip().split("   ")[1].strip().split()[1])
            demand = int(elements.strip().split("   ")[2].strip().split()[1])
            for _ in range(2):
                self.costs_data.append(cost)
                self.demands_data.append(demand)
            # even indices store direct edges, and odd indices store reverse edges
            self.origins_data.append(edge[0])
            self.destinations_data.append(edge[1])
            self.origins_data.append(edge[1])
            self.destinations_data.append(edge[0])
            if edge[0] not in required_nodes:
                required_nodes.append(edge[0])
            if edge[1] not in required_nodes:
                required_nodes.append(edge[1])
            node_neighbors[edge[0] - 1][edge[1] - 1] = cost
            node_neighbors[edge[1] - 1][edge[0] - 1] = cost
        if nb_not_required_edges > 0:
            next(file_it)
            for _ in range(nb_not_required_edges):
                elements = next(file_it)
                edge = tuple(map(int, elements.strip().split("   ")[0][2:-1].strip().split(",")))
                cost = int(elements.strip().split("   ")[1].strip().split()[1])
                node_neighbors[edge[0] - 1][edge[1] - 1] = cost
                node_neighbors[edge[1] - 1][edge[0] - 1] = cost
        depot_node = int(next(file_it).strip().split(":")[1])
        # Finds the shortest path from one "required node" to another
        nb_required_nodes = len(required_nodes)
        required_distances = list()
        for node in required_nodes:
            paths = self.shortest_path_finder(node, nb_nodes, node_neighbors)
            required_distances.append(paths)
        # Since we can explore the edges in both directions, we will represent all possible
        # edges with an index
        self.edges_dist_data = None
        self.find_distance_between_edges(nb_required_nodes, required_nodes, required_distances)
        self.dist_to_depot_data = None
        self.find_distance_to_depot(nb_required_nodes, depot_node,
                                    required_nodes, required_distances)
        self.dist_from_depot_data = None
        self.find_distance_from_depot(nb_required_nodes, nb_nodes, depot_node,
                                      required_nodes, required_distances, node_neighbors)

    # Finds the shortest path from one node "origin" to all the other nodes of the graph
    # thanks to the Dijkstra's algorithm
    def min_distance(self, nb_nodes, shortest_path, sptSet):
        min = sys.maxsize
        for i in range(nb_nodes):
            if shortest_path[i] < min and sptSet[i] == False:
                min = shortest_path[i]
                min_index = i
        return min_index

    def shortest_path_finder(self, origin, nb_nodes, node_neighbors):
        shortest_path = [sys.maxsize] * nb_nodes
        shortest_path[origin - 1] = 0
        sptSet = [False] * nb_nodes
        for _ in range(nb_nodes):
            current_node = self.min_distance(nb_nodes, shortest_path, sptSet)
            sptSet[current_node] = True
            current_neighbors = node_neighbors[current_node]
            for neighbor in range(nb_nodes):
                if current_neighbors[neighbor] != 0:
                    distance = current_neighbors[neighbor]
                    if ((sptSet[neighbor] == False) and
                            (shortest_path[current_node] + distance < shortest_path[neighbor])):
                        shortest_path[neighbor] = distance + shortest_path[current_node]
        return shortest_path

    def find_distance_between_edges(self, nb_required_nodes, required_nodes, required_distances):
        self.edges_dist_data = [[None] * (2 * self.nb_required_edges)
                                for _ in range(2 * self.nb_required_edges)]
        for i in range(2 * self.nb_required_edges):
            for j in range(2 * self.nb_required_edges):
                if self.destinations_data[i] == self.origins_data[j]:
                    self.edges_dist_data[i][j] = 0
                else:
                    for k in range(nb_required_nodes):
                        if required_nodes[k] == self.destinations_data[i]:
                            self.edges_dist_data[i][j] = required_distances[k][
                                self.origins_data[j] - 1]

    def find_distance_to_depot(
            self, nb_required_nodes, depot_node, required_nodes, required_distances):
        self.dist_to_depot_data = [None] * (2 * self.nb_required_edges)
        for i in range(2 * self.nb_required_edges):
            if self.destinations_data[i] == depot_node:
                self.dist_to_depot_data[i] = 0
            else:
                for k in range(nb_required_nodes):
                    if required_nodes[k] == self.destinations_data[i]:
                        self.dist_to_depot_data[i] = required_distances[k][depot_node-1]

    def find_distance_from_depot(self, nb_required_nodes, nb_nodes, depot_node,
                                 required_nodes, required_distances, node_neighbors):
        self.dist_from_depot_data = [None] * (2 * self.nb_required_edges)
        for i in range(2 * self.nb_required_edges):
            if depot_node == self.origins_data[i]:
                self.dist_from_depot_data[i] = 0
            else:
                depot_is_required_node = False
                for k in range(nb_required_nodes):
                    if required_nodes[k] == depot_node:
                        depot_is_required_node = True
                        self.dist_from_depot_data[i] = required_distances[k][
                            self.origins_data[i] - 1]
                if not depot_is_required_node:
                    shortest_paths_from_depot = self.shortest_path_finder(
                        depot_node, nb_nodes, node_neighbors)
                    self.dist_from_depot_data[i] = shortest_paths_from_depot[self.origins_data[i] - 1]


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python capacitated_arc_routing.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, output_file, str_time_limit)
Compilation / Execution (Windows)
cl /EHsc capacitated_arc_routing.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.lib
capacitated_arc_routing instances\egl-e1-A.dat
Compilation / Execution (Linux)
g++ capacitated_arc_routing .cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o capacitated_arc_routing 
./capacitated_arc_routing  instances/egl-e1-A.dat
#include "optimizer/hexalyoptimizer.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>

using namespace hexaly;
using namespace std;

class CapacitatedArcRouting {

public:
    // Data from the problem
    int nbTrucks;
    int truckCapacity;
    int nbNodes;
    int nbRequiredEdges;
    int nbRequiredNodes;
    int nbNotRequiredNodes;
    int depotNode;
    vector<int> demandsData;
    vector<int> costsData;
    vector<int> originsData;
    vector<int> destinationsData;
    vector<int> requiredNodes;
    vector<vector<int>> nodesDistData;
    vector<vector<int>> edgesDistData;
    vector<int> distToDepotData;
    vector<int> distFromDepotData;
    vector<vector<int>> nodeNeighborsData;

    // Hexaly Optimizer
    HexalyOptimizer optimizer;

    // Decision variables
    vector<HxExpression> edgesSequencesVars;
    HxExpression totalDistance;

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

    void solve(int limit) {
        // Declare the optimization model
        HxModel model = optimizer.getModel();

        // Sequence of edges visited and "serviced" by each truck
        edgesSequencesVars.resize(nbTrucks);
        for (int i = 0; i < nbTrucks; ++i) {
            // A sequence can contain an edge in one direction or its reverse
            edgesSequencesVars[i] = model.listVar(2 * nbRequiredEdges);
        }
        HxExpression edgesSequences = model.array(edgesSequencesVars.begin(), edgesSequencesVars.end());

        // Create distance and cost arrays to be able to access it with an "at" operator
        HxExpression demands = model.array(demandsData.begin(), demandsData.end());
        HxExpression costs = model.array(costsData.begin(), costsData.end());
        HxExpression distFromDepot = model.array(distFromDepotData.begin(), distFromDepotData.end());
        HxExpression distToDepot = model.array(distToDepotData.begin(), distToDepotData.end());
        HxExpression edgesDist = model.array();
        for (int n = 0; n < 2 * nbRequiredEdges; ++n) {
            edgesDist.addOperand(model.array(edgesDistData[n].begin(), edgesDistData[n].end()));
        }

        // An edge must be serviced by at most one truck
        model.constraint(model.disjoint(edgesSequences));

        // An edge can be travelled in both directions but its demand must be satisfied only once
        for (int i = 0; i < nbRequiredEdges; ++i) {
            model.constraint(model.contains(edgesSequences, 2 * i) + model.contains(edgesSequences, 2 * i + 1) == 1);
        }

        vector<HxExpression> routeDistances(nbTrucks);
        for (int k = 0; k < nbTrucks; ++k) {
            HxExpression sequence = edgesSequencesVars[k];
            HxExpression c = model.count(sequence);

            // Quantity in each truck
            HxExpression demandLambda =
                model.createLambdaFunction([&](HxExpression j) { return demands[j]; });
            HxExpression routeQuantity = model.sum(sequence, demandLambda);
            // Capacity constraint : a truck must not exceed its capacity
            model.constraint(routeQuantity <= truckCapacity);

            // Distance travelled by each truck
            HxExpression distLambda = model.createLambdaFunction(
                [&](HxExpression i) { return costs[sequence[i]] + model.at(edgesDist, sequence[i - 1], sequence[i]); });
            routeDistances[k] =
                model.sum(model.range(1, c), distLambda) +
                model.iif(c > 0, costs[sequence[0]] + distFromDepot[sequence[0]] + distToDepot[sequence[c - 1]], 0);
        }

        // Total distance travelled
        totalDistance = model.sum(routeDistances.begin(), routeDistances.end());

        // Objective : minimize the distance travelled
        model.minimize(totalDistance);

        model.close();

        // Parametrize the optimizer
        optimizer.getParam().setTimeLimit(limit);

        optimizer.solve();
    }

    /* Write the solution in a file with the following format:
     * - total distance
     * - number of routes
     * - for each truck, the edges visited */
    void writeSolution(const string& fileName) {
        ofstream outfile;
        outfile.exceptions(ofstream::failbit | ofstream::badbit);
        outfile.open(fileName.c_str());

        outfile << "Objective function value : " << totalDistance.getValue() << endl;
        outfile << "Number of routes : " << nbTrucks << endl;
        for (int k = 0; k < nbTrucks; ++k) {
            outfile << "Sequence of truck " << k + 1 << ": ";
            HxCollection sequence = edgesSequencesVars[k].getCollectionValue();
            int c = sequence.count();
            for (int i = 0; i < c; ++i) {
                outfile << "(" << originsData[sequence[i]] << ", " << destinationsData[sequence[i]] << ")  ";
            }
            outfile << endl;
        }
    }

private:
    // The input files follow the format of the DIMACS challenge
    void readCarp(const string& fileName) {
        ifstream infile(fileName.c_str());
        if (!infile.is_open()) {
            throw std::runtime_error("File cannot be opened.");
        }

        string str;
        char* pch;
        char* line;
        int first = -1;
        int second = -1;
        for (int i = 0; i < 2; ++i)
            getline(infile, str);
        getline(infile, str);
        line = strdup(str.c_str());
        pch = strtok(line, " : ");
        pch = strtok(NULL, " : ");
        nbNodes = atoi(pch);
        getline(infile, str);
        line = strdup(str.c_str());
        pch = strtok(line, " : ");
        pch = strtok(NULL, " : ");
        nbRequiredEdges = atoi(pch);
        getline(infile, str);
        line = strdup(str.c_str());
        pch = strtok(line, " : ");
        pch = strtok(NULL, " : ");
        nbNotRequiredNodes = atoi(pch);
        getline(infile, str);
        line = strdup(str.c_str());
        pch = strtok(line, " : ");
        pch = strtok(NULL, " : ");
        nbTrucks = atoi(pch);
        getline(infile, str);
        line = strdup(str.c_str());
        pch = strtok(line, " : ");
        pch = strtok(NULL, " : ");
        truckCapacity = atoi(pch);
        for (int i = 0; i < 3; ++i)
            getline(infile, str);
        nodeNeighborsData.resize(nbNodes);
        for (int i = 0; i < nbNodes; ++i) {
            nodeNeighborsData[i].resize(nbNodes);
        }
        for (int i = 0; i < nbNodes; ++i) {
            for (int j = 0; j < nbNodes; ++j) {
                nodeNeighborsData[i][j] = 0;
            }
        }
        getline(infile, str);
        for (int i = 0; i < nbRequiredEdges; ++i) {
            extractEdge(str, pch, line, first, second, true);
            extractCost(str, pch, line, first, second, true);
            extractDemand(str, pch, line);
            getline(infile, str);
        }
        if (nbNotRequiredNodes > 0) {
            getline(infile, str);
            for (int i = 0; i < nbNotRequiredNodes; ++i) {
                extractEdge(str, pch, line, first, second, false);
                extractCost(str, pch, line, first, second, false);
                getline(infile, str);
            }
        }
        line = strdup(str.c_str());
        pch = strtok(line, " : ");
        pch = strtok(NULL, " : ");
        depotNode = atoi(pch);
        nbRequiredNodes = requiredNodes.size();
        vector<int> shortestPath;
        shortestPath.resize(nbNodes);
        for (int i = 0; i < nbRequiredNodes; ++i) {
            shortestPathFinder(requiredNodes[i], shortestPath);
            for (int j = 0; j < nbNodes; ++j) {
                if (shortestPath[j] == std::numeric_limits<int>::max()) {
                }
            }
            nodesDistData.push_back(shortestPath);
        }
        findDistanceBetweenEdges();
        findDistanceToDepot();
        findDistanceFromDepot(shortestPath);
    }

    // Finds the shortest path from one node "origin" to all the other nodes of the graph
    // thanks to the Dijkstra's algorithm.
    int minDistance(vector<int> shortestPath, vector<bool> sptSet) {
        // Initializes min value
        int min = std::numeric_limits<int>::max(), minIndex = -1;
        for (int i = 0; i < nbNodes; ++i) {
            if (sptSet[i] == false && shortestPath[i] <= min) {
                min = shortestPath[i];
                minIndex = i;
            }
        }
        return minIndex;
    }

    void shortestPathFinder(int origin, vector<int>& shortestPath) {
        vector<int> currentNeighbors;
        int currentNode;
        int distance;
        vector<bool> sptSet;
        sptSet.resize(nbNodes);
        for (int i = 0; i < nbNodes; ++i) {
            shortestPath[i] = std::numeric_limits<int>::max();
            sptSet[i] = false;
        }
        shortestPath[origin - 1] = 0;
        for (int count = 0; count < nbNodes; ++count) {
            currentNode = minDistance(shortestPath, sptSet);
            sptSet[currentNode] = true;
            currentNeighbors = nodeNeighborsData[currentNode];
            for (int neighbor = 0; neighbor < nbNodes; ++neighbor) {
                if (currentNeighbors[neighbor] != 0) {
                    distance = currentNeighbors[neighbor];
                    if ((sptSet[neighbor] == false) &&
                        (shortestPath[currentNode] + distance < shortestPath[neighbor])) {
                        shortestPath[neighbor] = shortestPath[currentNode] + distance;
                    }
                }
            }
        }
    }

    void findDistanceBetweenEdges() {
        edgesDistData.resize(2 * nbRequiredEdges);
        for (int i = 0; i < 2 * nbRequiredEdges; ++i) {
            edgesDistData[i].resize(2 * nbRequiredEdges);
        }
        for (int i = 0; i < 2 * nbRequiredEdges; ++i) {
            for (int j = 0; j < 2 * nbRequiredEdges; ++j) {
                if (destinationsData[i] == originsData[j]) {
                    edgesDistData[i][j] = 0;
                } else {
                    for (int k = 0; k < nbRequiredNodes; ++k) {
                        if (requiredNodes[k] == destinationsData[i]) {
                            edgesDistData[i][j] = nodesDistData[k][originsData[j] - 1];
                        }
                    }
                }
            }
        }
    }

    void findDistanceToDepot() {
        distToDepotData.resize(2 * nbRequiredEdges);
        for (int i = 0; i < 2 * nbRequiredEdges; ++i) {
            if (destinationsData[i] == depotNode) {
                distToDepotData[i] = 0;
            } else {
                for (int k = 0; k < nbRequiredNodes; ++k) {
                    if (requiredNodes[k] == destinationsData[i]) {
                        distToDepotData[i] = nodesDistData[k][depotNode - 1];
                    }
                }
            }
        }
    }

    void findDistanceFromDepot(vector<int>& shortestPath) {
        distFromDepotData.resize(2 * nbRequiredEdges);
        for (int i = 0; i < 2 * nbRequiredEdges; ++i) {
            if (depotNode == originsData[i]) {
                distFromDepotData[i] = 0;
            } else {
                bool depotIsRequiredNode = false;
                for (int k = 0; k < nbRequiredNodes; ++k) {
                    if (requiredNodes[k] == depotNode) {
                        depotIsRequiredNode = true;
                        distFromDepotData[i] = nodesDistData[k][originsData[i] - 1];
                    }
                }
                if (!depotIsRequiredNode) {
                    shortestPathFinder(depotNode, shortestPath);
                    distFromDepotData[i] = shortestPath[originsData[i] - 1];
                }
            }
        }
    }

    void extractEdge(const string& str, char* pch, char* line, int& first, int& second, bool req) {
        line = strdup(str.c_str());
        pch = strtok(line, " ");
        if (strcmp(pch, "(") == 0) {
            pch = strtok(NULL, ",");
            first = atoi(pch);
            pch = strtok(NULL, ")");
            second = atoi(pch);
        }

        if (req == true) {
            // Even indices store direct edges, and odd indices store reverse edges
            originsData.push_back(first);
            destinationsData.push_back(second);
            originsData.push_back(second);
            destinationsData.push_back(first);
            if (find(requiredNodes.begin(), requiredNodes.end(), first) == requiredNodes.end()) {
                requiredNodes.push_back(first);
            }
            if (find(requiredNodes.begin(), requiredNodes.end(), second) == requiredNodes.end()) {
                requiredNodes.push_back(second);
            }
        }
    }

    void extractCost(const string& str, char* pch, char* line, int& first, int& second, bool req) {
        line = strdup(str.c_str());
        pch = strtok(line, "coste");
        int cost;
        pch = strtok(NULL, "coste");
        cost = atoi(pch);
        if (req == true) {
            for (int i = 0; i < 2; ++i)
                costsData.push_back(cost);
        }
        nodeNeighborsData[first - 1][second - 1] = cost;
        nodeNeighborsData[second - 1][first - 1] = cost;
    }

    void extractDemand(const string& str, char* pch, char* line) {
        line = strdup(str.c_str());
        pch = strtok(line, "demanda");
        int demand;
        pch = strtok(NULL, "demanda");
        demand = atoi(pch);
        for (int i = 0; i < 2; ++i)
            demandsData.push_back(demand);
    }
};

int main(int argc, char** argv) {
    if (argc < 2) {
        cerr << "Usage: capacitated_arc_routing inputFile [outputFile] [timeLimit]" << endl;
        return 1;
    }

    const char* instanceFile = argv[1];
    const char* outputFile = argc > 2 ? argv[2] : NULL;
    const char* strTimeLimit = argc > 3 ? argv[3] : "20";

    try {
        CapacitatedArcRouting model;
        model.readInstance(instanceFile);
        model.solve(atoi(strTimeLimit));
        if (outputFile != NULL)
            model.writeSolution(outputFile);
        return 0;
    } catch (const exception& e) {
        cerr << "An error occurred: " << e.what() << endl;
        return 1;
    }
}
Compilation / Execution (Windows)
copy %HX_HOME%\bin\Hexaly.NET.dll .
csc CapacitatedArcRouting.cs /reference:Hexaly.NET.dll
CapacitatedArcRouting instances\egl-e1-A.dat
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Hexaly.Optimizer;

public class CapacitatedArcRouting : IDisposable
{
    // Data from the problem
    int nbTrucks;
    int truckCapacity;
    int nbNodes;
    int nbRequiredEdges;
    int nbRequiredNodes;
    int nbNotRequiredEdges;
    int depotNode;
    int[] demandsData;
    int[] costsData;
    List<int> requiredNodes;
    int[] originsData;
    int[] destinationsData;
    int[] distToDepotData;
    int[] distFromDepotData;
    int[][] nodesDistData;
    int[][] edgesDistData;
    int[][] nodeNeighborsData;

    // Hexaly Optimizer
    HexalyOptimizer optimizer;

    // Decision variables
    HxExpression[] edgesSequencesVars;
    HxExpression totalCost;

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

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

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

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

        // Sequence of edges visited and "serviced" by each truck
        edgesSequencesVars = new HxExpression[nbTrucks];
        for (int i = 0; i < nbTrucks; ++i)
        {
            // A sequence can contain an edge in one direction or its reverse
            edgesSequencesVars[i] = model.List(2 * nbRequiredEdges);
        }
        HxExpression edgesSequences = model.Array(edgesSequencesVars);

        // Creates distance and cost arrays to be able to access it with an "at" operator
        HxExpression demands = model.Array(demandsData);
        HxExpression costs = model.Array(costsData);
        HxExpression distFromDepot = model.Array(distFromDepotData);
        HxExpression distToDepot = model.Array(distToDepotData);
        HxExpression edgesDist = model.Array(edgesDistData);

        // An edge must be serviced by at most one truck
        model.Constraint(model.Disjoint(edgesSequences));

        // An edge can be travelled in both directions but its demand must be satisfied only once
        for (int i = 0; i < nbRequiredEdges; ++i)
        {
            model.Constraint(
                model.Contains(edgesSequences, 2 * i) + model.Contains(edgesSequences, 2 * i + 1)
                    == 1
            );
        }

        HxExpression[] routeDistances = new HxExpression[nbTrucks];
        for (int k = 0; k < nbTrucks; ++k)
        {
            HxExpression sequence = edgesSequencesVars[k];
            HxExpression c = model.Count(sequence);

            // Quantity in each truck
            HxExpression demandLambda = model.LambdaFunction(j => demands[j]);
            HxExpression routeQuantity = model.Sum(sequence, demandLambda);
            // Capacity constraint : a truck must not exceed its capacity
            model.Constraint(routeQuantity <= truckCapacity);

            // Distance travelled by each truck
            HxExpression distLambda = model.LambdaFunction(
                i => costs[sequence[i]] + edgesDist[sequence[i - 1], sequence[i]]
            );
            routeDistances[k] =
                model.Sum(model.Range(1, c), distLambda)
                + model.If(
                    c > 0,
                    costs[sequence[0]] + distFromDepot[sequence[0]] + distToDepot[sequence[c - 1]],
                    0
                );
        }

        // Total Distance travelled
        totalCost = model.Sum(routeDistances);

        // Objective : minimize the distance travelled
        model.Minimize(totalCost);

        model.Close();

        // Parametrize the optimizer
        optimizer.GetParam().SetTimeLimit(limit);

        optimizer.Solve();
    }

    /* Write the solution in a file with the following format:
    - total distance
    - number of routes
    - for each truck, the edges visited */
    void WriteSolution(string fileName)
    {
        using (StreamWriter output = new StreamWriter(fileName))
        {
            output.WriteLine("Objective function value : " + totalCost.GetValue());
            output.WriteLine("Number of routes : " + nbTrucks);
            for (int k = 0; k < nbTrucks; ++k)
            {
                output.Write("Sequence of truck " + (k + 1) + ": ");
                HxCollection sequence = edgesSequencesVars[k].GetCollectionValue();
                int c = sequence.Count();
                for (int i = 0; i < c; ++i)
                {
                    output.Write(
                        "("
                            + originsData[sequence[i]]
                            + ", "
                            + destinationsData[sequence[i]]
                            + ")  "
                    );
                }
                output.WriteLine();
            }
        }
    }

    public static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: CapacitatedArcRouting 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 (CapacitatedArcRouting model = new CapacitatedArcRouting())
        {
            model.ReadInstance(instanceFile);
            model.Solve(int.Parse(strTimeLimit));
            if (outputFile != null)
                model.WriteSolution(outputFile);
        }
    }

    // The input files follow the format of the DIMACS challenge
    private void ReadInputCarp(string fileName)
    {
        using (StreamReader input = new StreamReader(fileName))
        {
            string[] splitted;
            for (int i = 0; i < 2; ++i)
                input.ReadLine();
            splitted = input.ReadLine().Split(':');
            nbNodes = int.Parse(splitted[1]);
            splitted = input.ReadLine().Split(':');
            nbRequiredEdges = int.Parse(splitted[1]);
            splitted = input.ReadLine().Split(':');
            nbNotRequiredEdges = int.Parse(splitted[1]);
            splitted = input.ReadLine().Split(':');
            nbTrucks = int.Parse(splitted[1]);
            splitted = input.ReadLine().Split(':');
            truckCapacity = int.Parse(splitted[1]);
            for (int i = 0; i < 2; ++i)
                input.ReadLine();
            // Even indices store direct edges, and odd indices store reverse edges
            originsData = new int[2 * nbRequiredEdges];
            destinationsData = new int[2 * nbRequiredEdges];
            demandsData = new int[2 * nbRequiredEdges];
            costsData = new int[2 * nbRequiredEdges];
            nodeNeighborsData = new int[nbNodes][];
            for (int i = 0; i < nbNodes; ++i)
                nodeNeighborsData[i] = new int[nbNodes];
            for (int i = 0; i < nbNodes; ++i)
            {
                for (int j = 0; j < nbNodes; ++j)
                    nodeNeighborsData[i][j] = 0;
            }
            requiredNodes = new List<int>();
            input.ReadLine();
            for (int i = 0; i < nbRequiredEdges; ++i)
            {
                splitted = input.ReadLine().Split(' ');
                int firstNode = int.Parse(splitted[2].Remove(splitted[2].Length - 1));
                int secondNode = int.Parse(splitted[3].Remove(splitted[3].Length - 1));
                int cost = int.Parse(splitted[7]);
                int demand = int.Parse(splitted[11]);
                costsData[2 * i] = cost;
                demandsData[2 * i] = demand;
                costsData[2 * i + 1] = cost;
                demandsData[2 * i + 1] = demand;
                originsData[2 * i] = firstNode;
                destinationsData[2 * i] = secondNode;
                originsData[2 * i + 1] = secondNode;
                destinationsData[2 * i + 1] = firstNode;
                if (!requiredNodes.Contains(firstNode))
                    requiredNodes.Add(firstNode);
                if (!requiredNodes.Contains(secondNode))
                    requiredNodes.Add(secondNode);
                nodeNeighborsData[firstNode - 1][secondNode - 1] = cost;
                nodeNeighborsData[secondNode - 1][firstNode - 1] = cost;
            }
            if (nbNotRequiredEdges > 0)
            {
                input.ReadLine();
                for (int i = 0; i < nbNotRequiredEdges; ++i)
                {
                    splitted = input.ReadLine().Split(' ');
                    int firstNode = int.Parse(splitted[2].Remove(splitted[2].Length - 1));
                    int secondNode = int.Parse(splitted[3].Remove(splitted[3].Length - 1));
                    int cost = int.Parse(splitted[7]);
                    nodeNeighborsData[firstNode - 1][secondNode - 1] = cost;
                    nodeNeighborsData[secondNode - 1][firstNode - 1] = cost;
                }
            }
            splitted = input.ReadLine().Split(':');
            depotNode = int.Parse(splitted[1]);
        }
        nbRequiredNodes = requiredNodes.Count;
        int[] shortestPath = new int[nbNodes];
        FindRequiredPaths(ref shortestPath);
        FindDistanceBetweenEdges();
        FindDistanceToDepot();
        FindDistanceFromDepot(ref shortestPath);
    }

    // Finds the shortest path from one node "origin" to all the other nodes of the graph
    // thanks to the Dijkstra's algorithm
    public int MinDistance(int[] shortestPath, bool[] sptSet)
    {
        // Initializes min value
        int min = int.MaxValue,
            minIndex = -1;
        for (int i = 0; i < nbNodes; ++i)
            if (sptSet[i] == false && shortestPath[i] <= min)
            {
                min = shortestPath[i];
                minIndex = i;
            }
        return minIndex;
    }

    public void ShortestPathFinder(int origin, ref int[] shortestPath)
    {
        int[] currentNeighbors;
        int currentNode,
            distance;
        bool[] sptSet = new bool[nbNodes];
        for (int i = 0; i < nbNodes; ++i)
        {
            shortestPath[i] = int.MaxValue;
            sptSet[i] = false;
        }
        shortestPath[origin - 1] = 0;
        for (int count = 0; count < nbNodes; ++count)
        {
            currentNode = MinDistance(shortestPath, sptSet);
            sptSet[currentNode] = true;
            currentNeighbors = nodeNeighborsData[currentNode];
            for (int neighbor = 0; neighbor < nbNodes; ++neighbor)
            {
                if (currentNeighbors[neighbor] != 0)
                {
                    distance = currentNeighbors[neighbor];
                    if (
                        !sptSet[neighbor]
                        && shortestPath[currentNode] + distance < shortestPath[neighbor]
                    )
                    {
                        shortestPath[neighbor] = shortestPath[currentNode] + distance;
                    }
                }
            }
        }
    }

    public void FindRequiredPaths(ref int[] shortestPath)
    {
        nodesDistData = new int[nbRequiredNodes][];
        for (int i = 0; i < nbRequiredNodes; ++i)
            nodesDistData[i] = new int[nbNodes];
        for (int i = 0; i < nbRequiredNodes; ++i)
        {
            int node = requiredNodes[i];
            ShortestPathFinder(node, ref shortestPath);
            for (int j = 0; j < nbNodes; ++j)
                nodesDistData[i][j] = shortestPath[j];
        }
    }

    public void FindDistanceBetweenEdges()
    {
        edgesDistData = new int[2 * nbRequiredEdges][];
        for (int i = 0; i < 2 * nbRequiredEdges; ++i)
            edgesDistData[i] = new int[2 * nbRequiredEdges];
        for (int i = 0; i < 2 * nbRequiredEdges; ++i)
        {
            for (int j = 0; j < 2 * nbRequiredEdges; ++j)
            {
                if (destinationsData[i] == originsData[j])
                    edgesDistData[i][j] = 0;
                else
                {
                    for (int k = 0; k < nbRequiredNodes; ++k)
                    {
                        if (requiredNodes[k] == destinationsData[i])
                            edgesDistData[i][j] = nodesDistData[k][originsData[j] - 1];
                    }
                }
            }
        }
    }

    public void FindDistanceToDepot()
    {
        distToDepotData = new int[2 * nbRequiredEdges];
        for (int i = 0; i < 2 * nbRequiredEdges; ++i)
        {
            if (destinationsData[i] == depotNode)
                distToDepotData[i] = 0;
            else
            {
                for (int k = 0; k < nbRequiredNodes; ++k)
                {
                    if (requiredNodes[k] == destinationsData[i])
                        distToDepotData[i] = nodesDistData[k][depotNode - 1];
                }
            }
        }
    }

    public void FindDistanceFromDepot(ref int[] shortestPath)
    {
        distFromDepotData = new int[2 * nbRequiredEdges];
        for (int i = 0; i < 2 * nbRequiredEdges; ++i)
        {
            if (depotNode == originsData[i])
                distFromDepotData[i] = 0;
            else
            {
                bool depotIsRequiredNode = false;
                for (int k = 0; k < nbRequiredNodes; ++k)
                {
                    if (requiredNodes[k] == depotNode)
                    {
                        depotIsRequiredNode = true;
                        distFromDepotData[i] = nodesDistData[k][originsData[i] - 1];
                    }
                }
                if (!depotIsRequiredNode)
                {
                    ShortestPathFinder(depotNode, ref shortestPath);
                    distFromDepotData[i] = shortestPath[originsData[i] - 1];
                }
            }
        }
    }
}
Compilation / Execution (Windows)
javac CapacitatedArcRouting.java -cp %HX_HOME%\bin\hexaly.jar
java -cp %HX_HOME%\bin\hexaly.jar;. CapacitatedArcRouting instances\egl-e1-A.dat
Compilation / Execution (Linux)
javac CapacitatedArcRouting.java -cp /opt/hexaly_13_0/bin/hexaly.jar
java -cp /opt/hexaly_13_0/bin/hexaly.jar:. CapacitatedArcRouting instances/egl-e1-A.dat
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;

public class CapacitatedArcRouting {

    // Data from the problem
    int nbTrucks;
    int truckCapacity;
    int nbNodes;
    int nbRequiredEdges;
    int nbRequiredNodes;
    int nbNotRequiredEdges;
    int depotNode;
    int[] demandsData;
    int[] costsData;
    int[] originsData;
    int[] destinationsData;
    List<Integer> requiredNodes;
    int[] distToDepotData;
    int[] distFromDepotData;
    int[][] nodesDistData;
    int[][] edgesDistData;
    int[][] nodeNeighborsData;

    // Hexaly Optimizer
    private final HexalyOptimizer optimizer;

    // Decision variables
    HxExpression[] edgesSequencesVars;
    HxExpression totalDistance;

    private CapacitatedArcRouting(HexalyOptimizer optimizer) {
        this.optimizer = optimizer;
    }

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

        // Sequence of edges visited and "serviced" by each truck
        edgesSequencesVars = new HxExpression[nbTrucks];
        for (int i = 0; i < nbTrucks; ++i) {
            edgesSequencesVars[i] = model.listVar(2 * nbRequiredEdges);
        }
        HxExpression edgesSequences = model.array(edgesSequencesVars);

        // Create distance and cost arrays to be able to access it with an "at" operator
        HxExpression demands = model.array(demandsData);
        HxExpression costs = model.array(costsData);
        HxExpression distFromDepot = model.array(distFromDepotData);
        HxExpression distToDepot = model.array(distToDepotData);
        HxExpression edgesDist = model.array(edgesDistData);

        // An edge must be serviced by at most one truck
        model.constraint(model.disjoint(edgesSequences));

        // An edge can be travelled in both directions but its demand must be satisfied
        // only once
        for (int i = 0; i < nbRequiredEdges; ++i) {
            model.constraint(model
                .eq(model.sum(model.contains(edgesSequences, 2 * i), model.contains(edgesSequences, (2 * i) + 1)), 1));
        }
        HxExpression[] routeDistances = new HxExpression[nbTrucks];
        for (int k = 0; k < nbTrucks; ++k) {
            HxExpression sequence = edgesSequencesVars[k];
            HxExpression c = model.count(sequence);

            // Quantity in each truck
            HxExpression demandLambda = model.lambdaFunction(j -> model.at(demands, j));
            HxExpression routeQuantity = model.sum(sequence, demandLambda);
            // Capacity constraint : a truck must not exceed its capacity
            model.constraint(model.leq(routeQuantity, truckCapacity));

            // Distance travelled by each truck
            HxExpression distLambda = model.lambdaFunction(i -> model.sum(model.at(costs, model.at(sequence, i)),
                model.at(edgesDist, model.at(sequence, model.sub(i, 1)), model.at(sequence, i))));
            routeDistances[k] = model.sum(model.sum(model.range(1, c), distLambda),
                model.iif(model.gt(c, 0),
                    model.sum(model.at(costs, model.at(sequence, 0)), model.at(distFromDepot, model.at(sequence, 0)),
                        model.at(distToDepot, model.at(sequence, model.sub(c, 1)))),
                    0));
        }

        // Total Distance travelled
        totalDistance = model.sum(routeDistances);

        // Objective : minimize the distance travelled
        model.minimize(totalDistance);

        model.close();

        // Parametrize the optimizer
        optimizer.getParam().setTimeLimit(limit);

        optimizer.solve();
    }

    // Write the solution in a file with the following format:
    // - total distance
    // - number of routes
    // - for each truck, the edges visited
    private void WriteSolution(String filename) throws IOException {
        try (PrintWriter output = new PrintWriter(filename)) {
            output.println("Objective function value : " + totalDistance.getValue());
            output.println("Number of routes : " + nbTrucks);
            for (int k = 0; k < nbTrucks; ++k) {
                output.print("Sequence of truck " + (k + 1) + ": ");
                HxCollection sequence = edgesSequencesVars[k].getCollectionValue();
                int c = sequence.count();
                for (int i = 0; i < c; ++i) {
                    output.print("(" + originsData[(int) sequence.get(i)] + ", "
                        + destinationsData[(int) sequence.get(i)] + ")  ");
                }
                output.println();
            }
        }
    }

    // The input files follow the format of the DIMACS challenge
    private void readInstance(String filename) throws IOException {
        try (Scanner input = new Scanner(new File(filename))) {
            String[] splitted;
            for (int i = 0; i < 2; ++i)
                input.nextLine();
            splitted = input.nextLine().split(":");
            nbNodes = Integer.parseInt(splitted[1].trim());
            splitted = input.nextLine().split(":");
            nbRequiredEdges = Integer.parseInt(splitted[1].trim());
            splitted = input.nextLine().split(":");
            nbNotRequiredEdges = Integer.parseInt(splitted[1].trim());
            splitted = input.nextLine().split(":");
            nbTrucks = Integer.parseInt(splitted[1].trim());
            splitted = input.nextLine().split(":");
            truckCapacity = Integer.parseInt(splitted[1].trim());
            for (int i = 0; i < 2; ++i)
                input.nextLine();
            requiredNodes = new ArrayList<Integer>();
            // Even indices store direct edges, and odd indices store reverse edges
            demandsData = new int[2 * nbRequiredEdges];
            costsData = new int[2 * nbRequiredEdges];
            originsData = new int[2 * nbRequiredEdges];
            destinationsData = new int[2 * nbRequiredEdges];
            nodeNeighborsData = new int[nbNodes][];
            for (int j = 0; j < nbNodes; ++j) {
                nodeNeighborsData[j] = new int[nbNodes];
            }
            input.nextLine();
            for (int i = 0; i < nbRequiredEdges; ++i) {
                splitted = input.nextLine().split(" ");
                int firstNode = Integer.parseInt(splitted[2].substring(0, splitted[2].length() - 1));
                int secondNode = Integer.parseInt(splitted[3].substring(0, splitted[3].length() - 1));
                int cost = Integer.parseInt(splitted[7]);
                int demand = Integer.parseInt(splitted[11]);
                costsData[2 * i] = cost;
                demandsData[2 * i] = demand;
                costsData[2 * i + 1] = cost;
                demandsData[2 * i + 1] = demand;
                originsData[2 * i] = firstNode;
                destinationsData[2 * i] = secondNode;
                originsData[2 * i + 1] = secondNode;
                destinationsData[2 * i + 1] = firstNode;
                if (!requiredNodes.contains(firstNode)) {
                    requiredNodes.add(firstNode);
                }
                if (!requiredNodes.contains(secondNode)) {
                    requiredNodes.add(secondNode);
                }
                nodeNeighborsData[firstNode - 1][secondNode - 1] = cost;
                nodeNeighborsData[secondNode - 1][firstNode - 1] = cost;
            }
            if (nbNotRequiredEdges > 0) {
                input.nextLine();
                for (int i = 0; i < nbNotRequiredEdges; ++i) {
                    splitted = input.nextLine().split(" ");
                    int firstNode = Integer.parseInt(splitted[2].substring(0, splitted[2].length() - 1));
                    int secondNode = Integer.parseInt(splitted[3].substring(0, splitted[3].length() - 1));
                    int cost = Integer.parseInt(splitted[7]);
                    nodeNeighborsData[firstNode - 1][secondNode - 1] = cost;
                    nodeNeighborsData[secondNode - 1][firstNode - 1] = cost;
                }
            }
            splitted = input.nextLine().split(":");
            depotNode = Integer.parseInt(splitted[1].trim());
            nbRequiredNodes = requiredNodes.size();
            int[] shortestPath = new int[nbNodes];
            findRequiredPaths(shortestPath);
            findDistanceBetweenEdges();
            findDistanceToDepot();
            findDistanceFromDepot(shortestPath);
        }
    }

    // Finds the shortest path from one node "origin" to all the other nodes of the
    // graph
    // thanks to the Dijkstra's algorithm
    public int minDistance(int[] shortestPath, boolean[] sptSet) {
        int min = Integer.MAX_VALUE, minIndex = -1;
        for (int i = 0; i < nbNodes; ++i)
            if (sptSet[i] == false && shortestPath[i] <= min) {
                min = shortestPath[i];
                minIndex = i;
            }
        return minIndex;
    }

    public void shortestPathFinder(int origin, int[] shortestPath) {
        int[] currentNeighbors;
        int currentNode, distance;
        boolean[] sptSet = new boolean[nbNodes];
        for (int i = 0; i < nbNodes; ++i) {
            shortestPath[i] = Integer.MAX_VALUE;
            sptSet[i] = false;
        }
        shortestPath[origin - 1] = 0;
        for (int count = 0; count < nbNodes; ++count) {
            currentNode = minDistance(shortestPath, sptSet);
            sptSet[currentNode] = true;
            currentNeighbors = nodeNeighborsData[currentNode];
            for (int neighbor = 0; neighbor < nbNodes; ++neighbor) {
                if (currentNeighbors[neighbor] != 0) {
                    distance = currentNeighbors[neighbor];
                    if (!sptSet[neighbor] && shortestPath[currentNode] + distance < shortestPath[neighbor]) {
                        shortestPath[neighbor] = shortestPath[currentNode] + distance;
                    }
                }
            }
        }
    }

    public void findRequiredPaths(int[] shortestPath) {
        nodesDistData = new int[nbRequiredNodes][];
        for (int i = 0; i < nbRequiredNodes; ++i) {
            nodesDistData[i] = new int[nbNodes];
        }
        for (int i = 0; i < nbRequiredNodes; ++i) {
            int node = requiredNodes.get(i);
            shortestPathFinder(node, shortestPath);
            for (int j = 0; j < nbNodes; ++j) {
                nodesDistData[i][j] = shortestPath[j];
            }
        }
    }

    public void findDistanceBetweenEdges() {
        edgesDistData = new int[2 * nbRequiredEdges][];
        for (int i = 0; i < 2 * nbRequiredEdges; ++i) {
            edgesDistData[i] = new int[2 * nbRequiredEdges];
        }
        for (int i = 0; i < 2 * nbRequiredEdges; ++i) {
            for (int j = 0; j < 2 * nbRequiredEdges; ++j) {
                if (destinationsData[i] == originsData[j]) {
                    edgesDistData[i][j] = 0;
                } else {
                    for (int k = 0; k < nbRequiredNodes; ++k) {
                        if (requiredNodes.get(k) == destinationsData[i]) {
                            edgesDistData[i][j] = nodesDistData[k][originsData[j] - 1];
                        }
                    }
                }
            }
        }
    }

    public void findDistanceToDepot() {
        distToDepotData = new int[2 * nbRequiredEdges];
        for (int i = 0; i < 2 * nbRequiredEdges; ++i) {
            if (destinationsData[i] == depotNode) {
                distToDepotData[i] = 0;
            } else {
                for (int k = 0; k < nbRequiredNodes; ++k) {
                    if (requiredNodes.get(k) == destinationsData[i]) {
                        distToDepotData[i] = nodesDistData[k][depotNode - 1];
                    }
                }
            }
        }
    }

    public void findDistanceFromDepot(int[] shortestPath) {
        distFromDepotData = new int[2 * nbRequiredEdges];
        for (int i = 0; i < 2 * nbRequiredEdges; ++i) {
            if (depotNode == originsData[i]) {
                distFromDepotData[i] = 0;
            } else {
                boolean depotIsRequiredNode = false;
                for (int k = 0; k < nbRequiredNodes; ++k) {
                    if (requiredNodes.get(k) == depotNode) {
                        depotIsRequiredNode = true;
                        distFromDepotData[i] = nodesDistData[k][originsData[i] - 1];
                    }
                }
                if (!depotIsRequiredNode) {
                    shortestPathFinder(depotNode, shortestPath);
                    distFromDepotData[i] = shortestPath[originsData[i] - 1];
                }
            }
        }
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            System.err.println("Usage: java CapacitatedArcRouting inputFile [outputFile] [timeLimit]");
            System.exit(1);
        }
        try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
            String instanceFile = args[0];
            String outputFile = args.length > 1 ? args[1] : null;
            String strTimeLimit = args.length > 2 ? args[2] : "20";

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