Python ortools VRP: A Comprehensive Guide

The Vehicle Routing Problem (VRP) is a combinatorial optimization problem that involves determining the optimal routes for a fleet of vehicles to deliver goods or services to a set of customers. Solving VRP can help businesses reduce costs, improve efficiency, and enhance customer satisfaction. In this article, we will explore how to solve VRP using the Python ortools library with code examples.

Installing ortools

To get started, you need to install the ortools library. Open your terminal and run the following command:

pip install ortools

Problem Statement

Let's consider a simplified version of the VRP, where we have a single depot and a fixed number of customers. Each customer has a demand for goods, and the goal is to find the optimal routes for a fleet of vehicles to satisfy all customer demands while minimizing the total distance traveled.

To solve this problem, we will use the Capacitated Vehicle Routing Problem (CVRP) formulation, which assumes that each vehicle has a limited capacity and cannot exceed it.

Modeling the Problem

We can model the CVRP using the ortools library. Let's start by importing the necessary modules and defining the problem parameters:

from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp

def create_data_model():
    data = {}
    data['distance_matrix'] = [
        [0, 10, 20, 5],
        [10, 0, 15, 10],
        [20, 15, 0, 30],
        [5, 10, 30, 0]
    ]  # Example distance matrix
    data['demands'] = [0, 1, 2, 3]  # Example demands
    data['vehicle_capacity'] = 3  # Example vehicle capacity
    data['num_vehicles'] = 2  # Example number of vehicles
    data['depot'] = 0  # Index of the depot
    return data

data = create_data_model()

In this example, we have 4 locations represented by a distance matrix. The demands list represents the demand of each customer (0 represents the depot). The vehicle_capacity parameter indicates the maximum capacity of each vehicle, and num_vehicles represents the total number of vehicles available. Finally, the depot parameter specifies the index of the depot location.

Initializing the Routing Solver

Next, we need to initialize the routing solver and set the search parameters:

def solve_vrp(data):
    # Create the routing index manager.
    manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']), data['num_vehicles'], data['depot'])

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

    # Define distance callback.
    def distance_callback(from_index, to_index):
        return data['distance_matrix'][manager.IndexToNode(from_index)][manager.IndexToNode(to_index)]

    transit_callback_index = routing.RegisterTransitCallback(distance_callback)

    # Define cost of each arc.
    routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)

    # Define vehicle capacity constraints.
    def demand_callback(from_index):
        return data['demands'][manager.IndexToNode(from_index)]

    demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)
    routing.AddDimensionWithVehicleCapacity(
        demand_callback_index,
        0,  # no slack
        data['vehicle_capacity'],  # vehicle maximum capacity
        True,  # start cumul to zero
        'Capacity')

    # 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)
    return solution, routing, manager

solution, routing, manager = solve_vrp(data)

In this code snippet, we create the routing index manager and the routing model. We also define the distance and demand callbacks to calculate the distance between two locations and the demand of a customer, respectively. The demand callback is used to add the vehicle capacity constraints. Finally, we set the first solution heuristic to the PATH_CHEAPEST_ARC strategy for the solver to find an initial solution.

Extracting the Solution

To extract the solution and visualize it, we need to parse the solver output:

def get_routes(solution, routing, manager):
    routes = []
    num_routes = 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):
            node_index = manager.IndexToNode(index)
            plan_output += ' {} ->'.format(node_index)
            route_distance += routing.GetArcCostForVehicle(index, vehicle_id)
            index = solution.Value(routing.NextVar(index))
        node_index = manager.IndexToNode(index)
        plan_output += ' {}\n'.format(node_index)
        plan_output += 'Distance of the route: {}m\n'.format(route_distance)
        print