Search code examples
pythonor-toolsvehicle-routing

OR-TOOLS: PCVRP with delivery patterns


Please help me on my periodic capacitated vehicle routing problem.

Find the same question here on google-groups.

What I am trying to model:

  • 1 depot

  • multiple customers (let's assume 4)

  • multiple days (let's assume 3)

  • Multiple vehicle types with price per km and specific capacity

  • Every customer has: delivery frequency and demand per delivery

  • Every customer has to be assigned to a delivery pattern. Multiple delivery patterns are possible for each frequency. The pattern indicates if a delivery is possible on that day or not. For a frequency of 1 and 3 working days: [[1,0,0],[0,1,0],[0,0,1]], a frequency of 2 [[1,1,0],[1,0,1],[1,1,0]], a frequency of 3: [1,1,1]these are the possible patterns.

The demand of each customer per delivery is the same, for every day a delivery is performed

So we have a set of customers that have to be delivered by one depo. Multiple Vehicle Types are available for the job. The day of service is flexible because only the frequency is fixed to a customer, but there are multiple possibilities to fulfill the demand.

What I did so far:

  • I copied every node by the number of working days.

  • I restrict the start and end to the depo nodes of each day.

  • I multiply the vehicles by the number of days.

  • Furthermore, I handle the days as different kinds of cargo. I do assign a capacity of zero to the vehicle when it should not be used on that day.

     [[10, 15, 15, 0, 0, 0, 0, 0, 0], 
     [0, 0, 0, 10, 15, 15, 0, 0, 0], 
     [0, 0, 0, 0, 0, 0, 10, 15, 15]]
    

    The first 3 vehicles are for day one, the second three are day two and the last 3 are day 3.

For the demand matrix I use the same trick. In a case of 4 customers + 1 depot the demand matrix could look like this

   [[0, 3, 0, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 0, 3, 4, 5, 2, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 2]]

The second entry in the first list (3) and the 7th entry in the second list (3) are for the same customer just on two different days. The nodes were copied.

This works good so far. But it's not what I want to do.

I do assign a demand for every customer and day by hand, but I want to assign the demand by a chosen delivery pattern for each customer.

The model could assign pattern [1,0,1] to one customer with a frequency of 2 and a pattern of [0,0,1] to another customer. This would result in the possibility to plan a tour on day 3 with both demands combined.

I need help to define a dimension that manages the assignment of patterns to customers which results in "flexible" demand matrix.

Thank you for your help.

Full Code:

"""Periodic Capacited Vehicles Routing Problem (PCVRP)."""

from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
import numpy as np

def dublicate_nodes(base_distance_matrix, num_days):
    matrix = []
    for i in range(num_days):
        day_element = []
        for node in range(len(base_distance_matrix)):
            node_element = []
            for day in range(num_days):
                node_element.append(base_distance_matrix[node])
            day_element.append(np.concatenate(node_element).tolist())
        matrix.extend(day_element)
    #print(matrix)
    return matrix

def depo_nodes(num_days, num_verhicles, num_nodes):
    depo_array = []
    for day in range(num_days):
        for veh in range(int(num_verhicles/num_days)):
            depo_array.append(day*num_nodes)
    #print(depo_array)
    return depo_array

def veh_types_costs(cost_array, num_per_type, num_days):
    cost_array_km = []
    for day in range(num_days):
        for i in range(len(cost_array)):
            for num in range(num_per_type[i]):
                cost_array_km.append(cost_array[i])
    #print(cost_array_km)
    return(cost_array_km)

def veh_capacity_matrix(num_vehicle_per_type, vehicle_capacity_type, num_days):
    matrix= []
    for index in range(num_days):
        matrix_element = []
        for day in range(num_days):
            for i, num in enumerate(num_vehicle_per_type):
                for times in range(num):
                    if day == index:
                        matrix_element.append(vehicle_capacity_type[i]);
                    else:
                        matrix_element.append(0)
        matrix.append(matrix_element)
    #print(matrix)
    return matrix


def create_data_model():
    data = {}
    data["num_days"] = 3 #Anzahl Tage

    ### Definition der Fahrezugtypen
    data["num_vehicle_per_type"] = [1, 2] #Anzahl Fahrzeuge pro Typ
    data["vehicle_costs_type_per_km"] = [1, 0.01] #Kosten pro km pro Fahrzeugtyp
    data["vehicle_costs_type_per_stop"] = [1, 1000] #Kosten pro Stop pro Fahrzeugtyp
    data['price_per_km'] = veh_types_costs(data["vehicle_costs_type_per_km"], data["num_vehicle_per_type"], data["num_days"]) #Matrix für price_per_km je Fahrzeug ertsellen
    data["price_per_stop"] = veh_types_costs(data["vehicle_costs_type_per_stop"], data["num_vehicle_per_type"], data["num_days"])
    data["vehicle_capacity_type"] = [10, 15]  # Kapaität pro Fahrzeugtyp
    data['vehicle_capacities_matrix'] = veh_capacity_matrix(data["num_vehicle_per_type"], data["vehicle_capacity_type"], data["num_days"]) #Kapazitäten pro Fahrzeugs pro Tag
    print('vehicle_capacities_matrix')
    print(data['vehicle_capacities_matrix'])

    data["num_vehicles_per_day"] = sum(data["num_vehicle_per_type"]) #Gesamtanzahl der Fahrzeuge pro Tag
    data['num_vehicles'] = data["num_days"] * data["num_vehicles_per_day"]# Gesamtanzahl der Fahrzeuge in gesamten Zeitraum

    ###Distanzmatrix bestimmen
    data["base_distance_matrix"] = [
        [
            0, 548, 776, 696, 582
        ],
        [
            548, 0, 684, 308, 194
        ],
        [
            776, 684, 0, 992, 878
        ],
        [
            696, 308, 992, 0, 114
        ],
        [
            582, 194, 878, 114, 0
        ]
    ] #Distanzmatrix mit allen Kunden einzeln
    data['distance_matrix'] = dublicate_nodes(data["base_distance_matrix"], data["num_days"])  # Distanzmatrix mit mehrfachen Nodes pro Kunden, je Tag ein Node

    ###Start und Ende festlegen
    data["num_nodes"] = len(data["base_distance_matrix"])  # Anzahl Kunden
    data['starts'] = depo_nodes(data["num_days"], data['num_vehicles'], data["num_nodes"]) #Deponodes (Start) für die einzelnen Fahrzeuge (Tage)
    data['ends'] = depo_nodes(data["num_days"], data['num_vehicles'], data["num_nodes"]) #Deponodes (Ende) für die einzelnen Fahrzeuge (Tage)

    ###Demand pro Kunde
    data['demands_matrix'] =     [[0, 3, 0, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 3, 4, 5, 2, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 2]] #Demandmatrix mit je einer list pro Tag
    return data


def print_solution(data, manager, routing, solution):
        """Prints solution on console."""
        total_distance = 0
        total_load = 0


        for vehicle_id in range(data['num_vehicles']):

            if vehicle_id % data["num_vehicles_per_day"] == 0:
                print(("------Tag {}------").format(int(vehicle_id/data["num_vehicles_per_day"])))

            if routing.IsVehicleUsed(assignment= solution, vehicle=vehicle_id) == False:
                continue


            index = routing.Start(vehicle_id)
            if vehicle_id >= data["num_vehicles_per_day"]:
                plan_output = 'Route for vehicle {}:\n'.format(abs(vehicle_id-data["num_vehicles_per_day"]*int(vehicle_id/data["num_vehicles_per_day"])))
            else:
                plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
            route_costs = 0
            route_load = 0
            while not routing.IsEnd(index):
                node_index = manager.IndexToNode(index)

                capacity_ID = int(vehicle_id/data["num_vehicles_per_day"])
                route_load += data['demands_matrix'][capacity_ID][node_index]
                plan_output += ' {0} Load({1}) -> '.format(node_index, route_load)
                previous_index = index
                index = solution.Value(routing.NextVar(index))
                route_costs += routing.GetArcCostForVehicle(
                    previous_index, index, vehicle_id)
            plan_output += ' {0} Load({1})\n'.format(manager.IndexToNode(index),
                                                     route_load)
            plan_output += 'Costs of the route: {}€\n'.format(route_costs)
            plan_output += 'Load of the route: {}\n'.format(route_load)
            print(plan_output)
            total_distance += route_costs
            total_load += route_load
        print('Total costs of all routes: {}€'.format(total_distance))
        print('Total load of all routes: {}'.format(total_load))


def main():
    """Periodic CVRP problem."""
    # Instantiate the data problem.
    data = create_data_model()

    # Create the routing index manager.
    manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']), data['num_vehicles'], data['starts'],
                                           data['ends'])

    # Create Routing Model.
    routing = pywrapcp.RoutingModel(manager)

    ### Kostenfunktion je Fahrzeug festlegen ###
    def create_cost_callback(dist_matrix, km_costs, stop_costs):
        # Create a callback to calculate distances between cities.

        def distance_callback(from_index, to_index):
            from_node = manager.IndexToNode(from_index)
            to_node = manager.IndexToNode(to_index)
            return int(dist_matrix[from_node][to_node]) * (km_costs) + (stop_costs)

        return distance_callback

    for i in range(data['num_vehicles']):
        cost_callback = create_cost_callback(data['distance_matrix'], data["price_per_km"][i],
                                             data["price_per_stop"][i])  # Callbackfunktion erstellen
        cost_callback_index = routing.RegisterTransitCallback(cost_callback)  # registrieren
        routing.SetArcCostEvaluatorOfVehicle(cost_callback_index, i)  # Vehicle zuordnen


    #Define Capacities for Vehicles for every Day
    def create_demand_callback(demand_matrix, demand_index):
        # Create a callback to calculate capacity usage.

        def demand_callback(from_index):
            #Returns the demand of the node.
            # Convert from routing variable Index to demands NodeIndex.
            from_node = manager.IndexToNode(from_index)
            return demand_matrix[demand_index][from_node]

        return demand_callback

    for i in range(data["num_days"]): #Jedes Fahrzeug hat pro Tag eine andere Kapazität
        demand_callback = create_demand_callback(data['demands_matrix'], i)
        demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)
        dimension_name = 'Capacity_day_{}'.format(i)

        routing.AddDimensionWithVehicleCapacity(
            demand_callback_index,
            0,  # null capacity slack
            data['vehicle_capacities_matrix'][i],  # vehicle maximum capacities
            True,  # start cumul to zero
            dimension_name)


    # Setting first solution heuristic.
    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.AUTOMATIC)
    search_parameters.local_search_metaheuristic = (
        routing_enums_pb2.LocalSearchMetaheuristic.AUTOMATIC)
    search_parameters.time_limit.FromSeconds(1)

    # Solve the problem.
    solution = routing.SolveWithParameters(search_parameters)

    # Print solution on console.
    if solution:
        print_solution(data, manager, routing, solution)

    return solution

if __name__ == '__main__':
    solution_array = []
    solution = main()

Solution

  • So I found a solution for my problem.

    The solving time for problems greater than 40 customers could be better.

    I would appreciate improvement suggestions.

    """Periodic Capacited Vehicles Routing Problem with delivery pattern(PCVRP)."""
    
    from ortools.constraint_solver import routing_enums_pb2
    from ortools.constraint_solver import pywrapcp
    import numpy as np
    import random
    import math
    import pandas as pd
    
    def dublicate_nodes(base_distance_matrix, num_days):
        matrix = []
        for i in range(num_days):
            day_element = []
            for node in range(len(base_distance_matrix)):
                node_element = []
                for day in range(num_days):
                    node_element.append(base_distance_matrix[node])
                day_element.append(np.concatenate(node_element).tolist())
            matrix.extend(day_element)
        #print(matrix)
        return matrix
    
    def depo_nodes(num_days, num_verhicles, num_nodes):
        depo_array = []
        for day in range(num_days):
            for veh in range(int(num_verhicles/num_days)):
                depo_array.append(day*num_nodes)
        #print(depo_array)
        return depo_array
    
    def veh_types_costs(cost_array, num_per_type, num_days):
        cost_array_km = []
        for day in range(num_days):
            for i in range(len(cost_array)):
                for num in range(num_per_type[i]):
                    cost_array_km.append(cost_array[i])
        #print(cost_array_km)
        return(cost_array_km)
    
    def veh_capacity_matrix(num_vehicle_per_type, vehicle_capacity_type, num_days):
        matrix= []
        for index in range(num_days):
            matrix_element = []
            for day in range(num_days):
                for i, num in enumerate(num_vehicle_per_type):
                    for times in range(num):
                        if day == index:
                            matrix_element.append(vehicle_capacity_type[i]);
                        else:
                            matrix_element.append(0)
            matrix.append(matrix_element)
        #print(matrix)
        return matrix
    
    def dist_matrix(koordianten):
        matrix =[]
    
    
        for k_1 in koordianten:
            matrix_element = []
            for k_2 in koordianten:
                matrix_element.append(np.linalg.norm(k_1-k_2))
            matrix.append(matrix_element)
        #print(matrix)
        return matrix
    
    def demand_matrix(demand_matrix_day,num_days):
        matrix = []
        for index in range(num_days):
            matrix_element = []
            for day in range(num_days):
                if day == index:
                    matrix_element = matrix_element +demand_matrix_day
                else:
                    matrix_element = matrix_element +(list(np.zeros(len(demand_matrix_day))))
            matrix.append(matrix_element)
        return matrix
    
    def kunden_indizes(ID_list, num_days):
        indizes = {}
        for ID in ID_list:
            indizes[ID] = []
    
        for index, key in enumerate(indizes):
            for i in range(len(ID_list)*num_days):
                if (i % len(ID_list)) == index:
                    indizes[key].append(i)
    
        #print(indizes)
        return indizes
    
    def create_data_model(node_num):
        data = {}
        data["num_days"] = 5 #Anzahl Tage
    
        ###Kundenset
        num_IDs = node_num
        base = [0]
    
        data["IDs"] = list(range(0, num_IDs))
        #data["IDs"] = [0, 1, 2, 3, 4]
        print(data["IDs"])
    
        data["demand"] = [random.randint(1,30) for i in range(0,num_IDs)]
        #data["demand"] =[0, 16, 8, 1, 7]
        data["demand"][0] = 0
        print("demand", data["demand"])
    
        data["frequenz"] = [random.randint(1,5) for i in range(0,num_IDs)]
        #data["frequenz"] =[0, 3, 5, 2, 1]
        data["frequenz"][0] = 0
        print("freq ",data["frequenz"])
    
        summe_dem =0
        for index, demand in enumerate(data["demand"]):
            summe_dem += data["demand"][index] * data["frequenz"][index]
    
        print("DEMANDSUMME: ",summe_dem)
    
        data["koordianten"] = np.random.rand(num_IDs, 2)*1000
        #data["koordianten"] = np.array([(0, 0), (4, 0), (4, 4), (0, 4), (3, 3)])*100
        data["koordianten"][0] = (0,0)
        #print("koord ", data["koordianten"])
    
        data["PAT"] = {
            5:
                [[1, 1, 1, 1, 1]],
            4:
                [[0, 1, 1, 1, 1],
                 [1, 0, 1, 1, 1],
                 [1, 1, 0, 1, 1],
                 [1, 1, 1, 0, 1],
                 [1, 1, 1, 1, 0]],
    
            3: [[0, 1, 0, 1, 1],
                [1, 0, 1, 0, 1],
                [1, 1, 0, 1, 0],
                [0, 1, 1, 0, 1],
                [1, 0, 1, 1, 0],
                ],
    
            2: [[1, 0, 1, 0, 0],
                [0, 1, 0, 1, 0],
                [0, 0, 1, 0, 1],
                [1, 0, 0, 1, 0],
                [0, 1, 0, 0, 1]],
    
            1: [[1, 0, 0, 0, 0],
                [0, 1, 0, 0, 0],
                [0, 0, 1, 0, 0],
                [0, 0, 0, 1, 0],
                [0, 0, 0, 0, 1]],
        }
    
        ### Definition der Fahrezugtypen
    
        data["vehicle_costs_type_per_km"] = [1, 0.01] #Kosten pro km pro Fahrzeugtyp
        data["vehicle_costs_type_per_stop"] = [1, 1000] #Kosten pro Stop pro Fahrzeugtyp
        data["vehicle_capacity_type"] = [100, 200]  # Kapaität pro Fahrzeugtyp
        data["num_vehicle_per_type"] = [math.ceil(summe_dem /data["vehicle_capacity_type"][0]), math.ceil(summe_dem /data["vehicle_capacity_type"][1])]  # Anzahl Fahrzeuge pro Typ
        data['price_per_km'] = veh_types_costs(data["vehicle_costs_type_per_km"], data["num_vehicle_per_type"], data["num_days"]) #Matrix für price_per_km je Fahrzeug ertsellen
        data["price_per_stop"] = veh_types_costs(data["vehicle_costs_type_per_stop"], data["num_vehicle_per_type"], data["num_days"])
    
        data['vehicle_capacities_matrix'] = veh_capacity_matrix(data["num_vehicle_per_type"], data["vehicle_capacity_type"], data["num_days"]) #Kapazitäten pro Fahrzeugs pro Tag
        #print('vehicle_capacities_matrix')
        #print(data['vehicle_capacities_matrix'])
    
        data["num_vehicles_per_day"] = sum(data["num_vehicle_per_type"]) #Gesamtanzahl der Fahrzeuge pro Tag
        data['num_vehicles'] = data["num_days"] * data["num_vehicles_per_day"]# Gesamtanzahl der Fahrzeuge in gesamten Zeitraum
    
        ###Distanzmatrix bestimmen
        data["base_distance_matrix"] = dist_matrix(data["koordianten"])
        data['distance_matrix'] = dublicate_nodes(data["base_distance_matrix"], data["num_days"])  # Distanzmatrix mit mehrfachen Nodes pro Kunden, je Tag ein Node
    
        ###Start und Ende festlegen
        data["num_nodes"] = len(data["base_distance_matrix"])  # Anzahl Kunden
        data['starts'] = depo_nodes(data["num_days"], data['num_vehicles'], data["num_nodes"]) #Deponodes (Start) für die einzelnen Fahrzeuge (Tage)
        data['ends'] = depo_nodes(data["num_days"], data['num_vehicles'], data["num_nodes"]) #Deponodes (Ende) für die einzelnen Fahrzeuge (Tage)
    
        ###Demand pro Kunde
        data["kunden_indizes"] = kunden_indizes(data["IDs"], data["num_days"])
    
        data["demands_matrix"] = demand_matrix(demand_matrix_day=data["demand"], num_days=data["num_days"])
        #print(data["demands_matrix"])
    
        return data
    
    def print_solution(data, manager, routing, solution):
            """Prints solution on console."""
            total_distance = 0
            total_load = 0
    
    
    
            for vehicle_id in range(data['num_vehicles']):
    
                if vehicle_id % data["num_vehicles_per_day"] == 0:
                    print(("------Tag {}------").format(int(vehicle_id/data["num_vehicles_per_day"])))
    
                if routing.IsVehicleUsed(assignment= solution, vehicle=vehicle_id) == False:
                    continue
    
    
                index = routing.Start(vehicle_id)
                if vehicle_id >= data["num_vehicles_per_day"]:
                    plan_output = 'Route for vehicle {}:\n'.format(abs(vehicle_id-data["num_vehicles_per_day"]*int(vehicle_id/data["num_vehicles_per_day"])))
                else:
                    plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
                route_costs = 0
                route_load = 0
                while not routing.IsEnd(index):
                    node_index = manager.IndexToNode(index)
    
                    capacity_ID = int(vehicle_id/data["num_vehicles_per_day"])
                    route_load += data['demands_matrix'][capacity_ID][node_index]
                    plan_output += ' {0} Load({1}) -> '.format(node_index, route_load)
                    previous_index = index
                    index = solution.Value(routing.NextVar(index))
                    route_costs += routing.GetArcCostForVehicle(
                        previous_index, index, vehicle_id)
                plan_output += ' {0} Load({1})\n'.format(manager.IndexToNode(index),
                                                         route_load)
                plan_output += 'Costs of the route: {}€\n'.format(route_costs)
                plan_output += 'Load of the route: {}\n'.format(route_load)
                print(plan_output)
                total_distance += route_costs
                total_load += route_load
            print('Total costs of all routes: {}€'.format(total_distance))
            print('Total load of all routes: {}'.format(total_load))
    
    
    def main(node_num):
        """Periodic CVRP problem."""
        # Instantiate the data problem.
        data = create_data_model(node_num)
    
        # Create the routing index manager.
        manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']), data['num_vehicles'], data['starts'],
                                               data['ends'])
    
        # Create Routing Model.
        routing = pywrapcp.RoutingModel(manager)
    
        ### Kostenfunktion je Fahrzeug festlegen ###
        def create_cost_callback(dist_matrix, km_costs, stop_costs):
            # Create a callback to calculate distances between cities.
    
            def distance_callback(from_index, to_index):
                from_node = manager.IndexToNode(from_index)
                to_node = manager.IndexToNode(to_index)
                return int(dist_matrix[from_node][to_node]) * (km_costs) + (stop_costs)
    
            return distance_callback
    
        for i in range(data['num_vehicles']):
            cost_callback = create_cost_callback(data['distance_matrix'], data["price_per_km"][i],
                                                 data["price_per_stop"][i])  # Callbackfunktion erstellen
            cost_callback_index = routing.RegisterTransitCallback(cost_callback)  # registrieren
            routing.SetArcCostEvaluatorOfVehicle(cost_callback_index, i)  # Vehicle zuordnen
    
    
        #Define Capacities for Vehicles for every Day
        def create_demand_callback(demand_matrix, demand_index):
            # Create a callback to calculate capacity usage.
    
            def demand_callback(from_index):
                #Returns the demand of the node.
                # Convert from routing variable Index to demands NodeIndex.
                from_node = manager.IndexToNode(from_index)
                return demand_matrix[demand_index][from_node]
    
            return demand_callback
    
        for i in range(data["num_days"]): #Jedes Fahrzeug hat pro Tag eine andere Kapazität
            demand_callback = create_demand_callback(data['demands_matrix'], i)
            demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)
            dimension_name = 'Capacity_day_{}'.format(i)
    
            routing.AddDimensionWithVehicleCapacity(
                demand_callback_index,
                0,  # null capacity slack
                data['vehicle_capacities_matrix'][i],  # vehicle maximum capacities
                True,  # start cumul to zero
                dimension_name)
    
        #Drop visits that would exceed the frequency
        for index, key in enumerate(data["kunden_indizes"]):
            if index == 0:
                continue
    
            #routing.solver().Add(sum(routing.ActiveVar(manager.NodeToIndex(i)) for i in data["kunden_indizes"][key]) == data["frequenz"][index])
    
        bool_array = []
        for index, freq in enumerate(data["frequenz"]):
            if index == 0:
                continue
            bool_array_part =[]
            for index_pat, pat in enumerate(data["PAT"][freq]):
                #print(index,freq,index_pat)
                bool_name = str(index) +str(freq) + str(index_pat)
                bool_array_part.append(routing.solver().BoolVar(bool_name))
            bool_array.append(bool_array_part)
        #print(bool_array)
    
        for i in bool_array:
            routing.solver().Add(sum(i) == 1)
    
        node_array = []
        for index, freq in enumerate(data["frequenz"]):
            if index == 0:
                continue
            node_array_part = []
            for index_pat, pat in enumerate(data["PAT"][freq]):
                node_array_sub_part = []
                for index_day, day_value in enumerate(pat):
                    if day_value == 1:
                        node_array_sub_part.append(data["kunden_indizes"][index][index_day])
                #print(node_array_part)
                node_array_part.append(node_array_sub_part)
            node_array.append(node_array_part)
        #print(node_array)
    
        for index, bool in enumerate(bool_array):
            for i in range(len(bool)):
                #print(node_array[index][i])
                #print(bool_array[index][i])
                routing.solver().Add(sum(routing.ActiveVar(manager.NodeToIndex(i)) for i in node_array[index][i]) * bool_array[index][i] == bool_array[index][i] * len(node_array[index][i]))
    
    
        #routing.solver().Add(routing.ActiveVar(manager.NodeToIndex(5)) == 1)
    
        penalty = 0
        for node in range(0, len(data['distance_matrix'])):
            if node in data["kunden_indizes"][0]:
                continue
            else:
                routing.AddDisjunction([manager.NodeToIndex(node)], penalty)
    
    
        # Setting first solution heuristic.
        search_parameters = pywrapcp.DefaultRoutingSearchParameters()
        search_parameters.first_solution_strategy = (
            routing_enums_pb2.FirstSolutionStrategy.AUTOMATIC)
        search_parameters.local_search_metaheuristic = (
            routing_enums_pb2.LocalSearchMetaheuristic.AUTOMATIC)
        #search_parameters.time_limit.FromSeconds(300)
    
        # Solve the problem.
        solution = routing.SolveWithParameters(search_parameters)
    
        # Print solution on console.
        if solution:
    
            print_solution(data, manager, routing, solution)
            print()
            print("Statistics")
            print('  - wall time : %f s' % (int(routing.solver().WallTime())/1000))
            solvingtime = int(routing.solver().WallTime())/1000
    
        return solvingtime
    
    if __name__ == '__main__':
        node_num_array = [10,20,30,40,50,60,70,80,90,100]
        solvingtime_array = []
        num_array =[]
        for i in node_num_array:
            for j in range(0,4):
                solvingtime = main(i)
                solvingtime_array.append(solvingtime)
                num_array.append(i)
                print(i, "-->",solvingtime)
        print(solvingtime_array)
        print(num_array)
        df_results = pd.DataFrame(data= {"num_nodes":num_array, "solvingtime":solvingtime_array})
        
                #encoding="latin_1", sep=";")
        print(df_results)