Search code examples
python-3.xor-toolsvehicle-routing

OR-Tools for VRP without constraints wrongly grouped


I am using OR tools to solve VRP without any constraints. Here is the source code:

def create_data_model():
    """Stores the data for the problem."""
    data = {}
    data['distance_matrix'] = [
        [0, 20079, 2613, 8005, 19277, 12468, 13701],
        [0, 0, 21285, 16012, 32574, 35394, 28806],
        [0, 18233, 0, 5392, 19965, 19650, 13064],
        [0, 15013, 5639, 0, 22883, 22570, 15982],
        [0, 32991, 19256, 21815, 0, 18414, 9112],
        [0, 34348, 16976, 23122, 15678, 0, 14647],
        [0, 27652, 13917, 16476, 8043, 14820, 0]
    ]
    data['time_matrix'] = [
        [0, 1955, 508, 1331, 1474, 1427, 1292],
        [0, 0, 1795, 1608, 2057, 2410, 2036],
        [0, 1485, 0, 823, 1370, 1541, 1100],
        [0, 1402, 924, 0, 1533, 1637, 1263],
        [0, 2308, 1663, 1853, 0, 1766, 1104],
        [0, 2231, 1373, 1660, 1441, 0, 1554],
        [0, 1998, 1353, 1543, 764, 1550, 0]
    ]
    data['num_vehicles'] = 6
    data['depot'] = 0
    return data

def print_solution(data, manager, routing, solution):
    """Prints solution on console."""
    max_route_distance = 0
    for vehicle_id in range(data['num_vehicles']):
        index = routing.Start(vehicle_id)
        plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
        route_distance = 0
        while not routing.IsEnd(index):
            plan_output += ' {} -> '.format(manager.IndexToNode(index))
            previous_index = index
            index = solution.Value(routing.NextVar(index))
            route_distance += routing.GetArcCostForVehicle(
                previous_index, index, vehicle_id)
        plan_output += '{}\n'.format(manager.IndexToNode(index))
        plan_output += 'Distance of the route: {}m\n'.format(route_distance)
        print(plan_output)
        max_route_distance = max(route_distance, max_route_distance)
    print('Maximum of the route distances: {}m'.format(max_route_distance))

def test(request):
    # Instantiate the data problem.
    data = create_data_model()
    # Create the routing index manager.
    manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
                                           data['num_vehicles'], data['depot'])
    # Create Routing Model.
    routing = pywrapcp.RoutingModel(manager)
    
    def distance_callback(from_index, to_index):
        """Returns the distance between the two nodes."""
        # Convert from routing variable Index to distance matrix NodeIndex.
        from_node = manager.IndexToNode(from_index)
        to_node = manager.IndexToNode(to_index)
        return data['distance_matrix'][from_node][to_node]

    transit_callback_index = routing.RegisterTransitCallback(distance_callback)
    
    routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)

    dimension_name = 'Distance'
    routing.AddDimension(
        transit_callback_index,
        0,  # no slack
        1000000000,  # vehicle maximum travel distance
        True,  # start cumul to zero
        dimension_name)
    distance_dimension = routing.GetDimensionOrDie(dimension_name)
    distance_dimension.SetGlobalSpanCostCoefficient(35394)
    
    # Setting first solution heuristic.
    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)

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

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

    return HttpResponse('')

Everything above was copy-pasted from google's example, except for the following:

  1. My own distance matrix & num of vehicles
  2. very large vehicle maximum travel distance in AddDimension function
  3. SetGlobalSpanCostCoefficient(35394)
  4. I followed OR-Tools solve traveling salesman (TSP) without returning to the home node to set distance from all nodes to 0 (the depot) to 0.

The result for the above code is shown below:

Route for vehicle 0:
 0 ->  1 -> 0
Distance of the route: 20079m

Route for vehicle 1:
 0 ->  5 -> 0
Distance of the route: 12468m

Route for vehicle 2:
 0 ->  4 -> 0
Distance of the route: 19277m

Route for vehicle 3:
 0 ->  2 ->  3 -> 0
Distance of the route: 8005m

Route for vehicle 4:
 0 ->  6 -> 0
Distance of the route: 13701m

Route for vehicle 5:
 0 -> 0
Distance of the route: 0m

Maximum of the route distances: 20079m

To verify the above output, I marked the points in the google map. The numbering order is same as the order of the distance matrix.

Coords on map

The depot(starting point) is in Watthana, which can be seen near marker B.

Clearly, from Watthana, the cheapest path should be 2-1-3 in a single trip. But Google OR returns it as two trips (as seen in routes for vehicles 0 and 3). This can also be verified by manually adding the distances.

Dist from home to 2 to 1 to 3 = 2613+5392+15013 = 23018m

Dist of vehicles 0 and 3 = 20079+8005 = 28084m

What am I doing wrong? How can I get google to not separate out the point 1? Also please note that ideally points E,F,D could have also been grouped but they were not.

Thanks in advance!


Solution

  • From the question, I think what you want is to reduce the cumulative distance traveled by all vehicles.

        distance_dimension.SetGlobalSpanCostCoefficient(35394)
    

    Conversely, This code makes sure that the distance traveled by each vehicle is minimized by adding a Span Cost to the objective function weighted by 35394.

    global_span_cost =
    coefficient * (Max(dimension end value) - Min(dimension start value))
    

    In your case this is not a very high priority, hence the solution would be to comment that line or reduce the coefficient to a small value like 1 or 2, to reduce the sole importance of it.

    Read more about GSpanCoeff

    Now the solution should be

    Route for vehicle 0:
     0 ->  2 ->  3 ->  1 -> 0
    Distance of the route: 23018m
    
    Route for vehicle 1:
     0 ->  6 ->  4 -> 0
    Distance of the route: 21744m
    
    Route for vehicle 2:
     0 ->  5 -> 0
    Distance of the route: 12468m
    
    Maximum of the route distances: 23018m
    Sum of the route distances: 57230m