Lösung des Problems des Handlungsreisenden
Optimieren Sie die Routen zur Paketzustellung mit Python, um Lieferkosten zu sparen (Studienfall: Paketzustellung in Bandung, Indonesien mit einfachen Daten)
Das Problem des Handlungsreisenden oder TSP ist ein kombinatorisches Optimierungsproblem, das versucht, die Frage zu beantworten: „Wenn eine Liste von Städten und die Entfernungen zwischen jedem Städtepaar gegeben sind, was ist die kürzestmögliche Route, die jede Stadt genau einmal besucht und zur Ursprungsstadt zurückkehrt? “. Auf einfache Weise wird TSP beauftragt, die kürzeste Route zwischen einer Reihe von Punkten zu finden, die besucht werden müssen. Es wird zu einem echten Problem in der Lieferketten- und Logistikbranche, da sie nach dem Aufstieg des E-Commerce in die Höhe schnellten.
Also, warum ist TSP wichtig? Im Wesentlichen hilft es dem Unternehmen, mehr Gewinn zu erzielen, indem es Zeit und Ressourcen spart. In der Logistikbranche ist eine der Möglichkeiten, mehr Gewinn zu erzielen, die Effizienz bei Lieferkosten und -zeit. Es gibt verschiedene Möglichkeiten, dies zu erreichen, eine davon ist die Lösung des Problems des Handlungsreisenden (Traveling Salesman Problem, TSP), bei dem Sie versuchen, die effizientesten Routen für unterwegs zu finden. Je effizienter es gelöst wird, desto weniger Zeit wird mit dem Fahren verbracht, desto mehr Kraftstoff wird gespart, desto weniger Stunden werden benötigt und desto mehr Pakete werden zugestellt . Dadurch profitiert die Bilanz jeder lieferbasierten Geschäftstätigkeit gleich doppelt.
Daher versuche ich, für einen Kurier eines Paketzustellers in Bandung eine Demo dieses Traveling Salesman Problems zu erstellen, um die Lieferkosten zu senken .
Hinweis : Da es sich um eine einfache Demo handelt, handelt es sich bei den verwendeten Daten nur um 8 Dummy-Punkte für die Lieferung, und die Daten sind zufällig generierte Daten.
Szenario
Sie sind Manager in einem Paketdienstzentrum in Bandung. Sie sollten Pakete von Ihrem Depot aus mit 1 Fahrzeug an einige Kunden liefern. Die Verteilung der Lieferstellen und des Depots ist unten ersichtlich (Abbildung 1).
**Jedenfalls können Sie die Anzahl der Fahrzeuge und die Anzahl der Lieferpunkte beliebig anpassen, indem Sie den unten stehenden Quellcode bearbeiten
Ziele
Finden Sie die besten Routen, um alle Pakete an die Kunden zu liefern.
Um zu verfolgen, wie gut das Modell oder die Lösung ist, die wir zu implementieren versuchen, ist es besser, die Geschäftsmetriken zu definieren. Da die Daten verwandter Fälle nicht verfügbar sind, werde ich nur das Potenzial erwähnen, ohne zu messen, wie gut die Lösung ist. Diese potenziellen Geschäftsmetriken sind:
- Versandkosten
- Zeitaufwand
- Pünktliche Lieferrate
- Kundenzufriedenheitswert (CSAT-Wert)
Unter Verwendung der Google OR-Tools ist die beste Route mit einem Fahrzeug:
Depot -> Adresse 5 -> Adresse 6 -> Adresse 7 -> Adresse 4 -> Adresse 3 -> Adresse 2 -> Adresse 1 -> Depot
Streckenlänge: 7,44 km
Technische Schritte
Den vollständigen Code finden Sie hier .
I. Bibliothek importieren und Daten lesen
Zuvor müssen wir die Bibliotheken in Google Colab importieren. Wir werden einige Bibliotheken verwenden.
- numpy und pandas : für numerische Operationen und Datenmanipulation
- itertools : zum Erstellen von Iteratoren für effiziente Schleifen
- ast : um Bäume der abstrakten Python
- Anfragen : um HTTP-Anfragen zu stellen
- json : um mit JSON-Daten zu arbeiten
- networkx : für die Erstellung, Manipulation und Untersuchung der Struktur, Dynamik und Funktionen komplexer Netzwerke
- osmnx : Zum Herunterladen von Geodaten von OpenStreetMap und zum Modellieren, Projizieren, Visualisieren und Analysieren von realen Straßennetzen und anderen Geogeometrien
- folium : zum Visualisieren von Daten, die in Python manipuliert wurden, auf einer interaktiven Broschürenkarte
- ortools : um kombinatorische Optimierungsprobleme zu lösen
# Install and Import Library
pip install osmnx
pip install ortools
import numpy as np
import pandas as pd
import itertools
import ast
import requests
import json
import networkx as nx
import osmnx as ox
import folium
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
from google.colab import drive
drive.mount('/content/drive')
import warnings
warnings.filterwarnings('ignore')
# Read data
df_points = pd.read_excel('/content/drive/MyDrive/dataset/delivery_destinations.xlsx')
II. Geokodierung
Geocodierung ist der Prozess der Umwandlung einer Beschreibung eines Ortes – wie z. B. eines Koordinatenpaars, einer Adresse oder eines Ortsnamens – in einen Ort auf der Erdoberfläche. Kurz gesagt, wir versuchen, den Breiten- und Längengrad aus der Adresse des Datensatzes abzurufen. Diese Breiten- und Längengrade werden später verwendet, um die Reiseentfernung zwischen Punkten zu berechnen und die beste Lieferroute zu bestimmen.
Ich verwende die HERE-API für die Geokodierung, da sie ziemlich genau und kostenlos zu verwenden ist (mit begrenzten Aufrufen). Sie können diesen Artikel für die Details dieses Prozesses lesen .
# Define variables
apikey = '<YOUR-API-KEY-HERE>'
url = 'https://discover.search.hereapi.com/v1/discover'
headers = {'Content-Type': 'application/json'}
origin_point = '-6.8918336,107.6103212'
latlong_origin = []
latlong_points = []
# Get lat long using HERE Map API
for i, data in df_points.iterrows():
# your place name is defined below
place_name = data.address
my_params = {'at' : origin_point,
'limit' : 1,
'apikey' : apikey,
'q' : place_name}
r = requests.get(url, params=my_params, headers=headers)
output = json.loads(r.text)
latlong_dict = output['items'][0]['position']
latlong = ', '.join(map(str, latlong_dict.values()))
latlong_points += [latlong]
# Create dataframe
df_points['lat_lng'] = latlong_points
df_points['lat_lng'] = df_points.lat_lng.apply(ast.literal_eval)
Beachten Sie, dass wir jetzt ein Feld namens lat_lng haben , das den Breiten- und Längengrad der Punkte enthält.
III. Reisestrecke zwischen Punkten berechnen
Als nächstes wird die Reisedistanz zwischen Punkten berechnet. Es ist beim Problem des Handlungsreisenden wichtig, da die kürzeste Route aus der Gesamtentfernung jeder möglichen Route generiert wird. Anstelle der euklidischen Entfernung (Zeichnen einer Linie zwischen Punkten) verwenden wir eine relevantere Entfernung, die einen Pfad erstellt, der der Straße zwischen Punkten folgt. Dieser Pfad stellt den Abstand zwischen Punkten dar.
Dazu verwenden wir die Methode shortest_path von networkx. Aber vorher sollten wir den Ursprungs-Ziel-DataFrame erstellen, um die Entfernung zwischen Punkten einfacher berechnen zu können. Dieser DataFrame wird generiert, indem alle möglichen Kombinationen/Permutationen von Ursprungs- und Zielpunkten vorgenommen werden.
# Create dataframe of origin-destination #
# Create variables
list_points_name = df_points['point_name']
# list_points_latlong = df_points['lat_long']
list_points_latlong = df_points['lat_lng']
# All points combinations
list_pair_name = [i for i in itertools.product(list_points_name, repeat=2)]
list_pair_latlong = [i for i in itertools.product(list_points_latlong, repeat=2)]
df_nodes = pd.DataFrame({'pair_name': list_pair_name, 'pair_latlong': list_pair_latlong})
# Source/Target
df_nodes['origin'] = df_nodes['pair_name'].apply(lambda t: t[0])
df_nodes['destination'] = df_nodes['pair_name'].apply(lambda t: t[1])
df_nodes['latlong_origin'] = df_nodes['pair_latlong'].apply(lambda t: t[0])
df_nodes['latlong_destination'] = df_nodes['pair_latlong'].apply(lambda t: t[1])
df_nodes.drop(['pair_name', 'pair_latlong'], axis =1, inplace = True)
Jetzt können wir die Entfernung zwischen Punkten mit networkx berechnen.
# Calculate distance from point to point using networkx #
# Location where you want to find your route
place = 'Bandung, West Java, Indonesia'
# Find shortest route based on the mode of travel
mode = 'drive' # 'drive', 'bike', 'walk'
# Find shortest path based on distance or time
optimizer = 'length' # 'length','time'
# Create graph from OSM within the boundaries of some geocodable place(s)
graph = ox.graph_from_place(place, network_type = mode)
# Create lists
list_route = []
list_route_length = []
# Determining distance and route
for i in range(len(df_nodes)):
# find the nearest node to the start location
orig_node = ox.get_nearest_node(graph, df_nodes.iloc[i]['latlong_origin'])
# find the nearest node to the end location
dest_node = ox.get_nearest_node(graph, df_nodes.iloc[i]['latlong_destination'])
# find the shortest path
shortest_route = nx.shortest_path(graph,
orig_node,
dest_node,
weight=optimizer)
shortest_route_length = nx.shortest_path_length(graph, orig_node, dest_node, weight=optimizer)
# append list
list_route.append(shortest_route)
list_route_length.append(shortest_route_length)
IV. Bestimmen Sie die beste Fahrzeugroute
Dann können wir mithilfe von Google OR-Tools die beste Route für die Zustellung von Paketen an alle Kunden ermitteln . Zuerst müssen wir die Funktionen definieren, die uns helfen, die kürzeste Route zu finden. Es verweist auf diese Seite mit einigen Modifikationen.
def create_data_model(distance_matrix, num_vehicles, depot_order):
"""Stores the data for the problem."""
data = {}
data['distance_matrix'] = distance_matrix
data['num_vehicles'] = num_vehicles
data['depot'] = depot_order
return data
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]
def print_solution(data, manager, routing, solution):
"""Prints solution on console."""
print(f'Objective: {solution.ObjectiveValue()}')
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 get_routes(solution, routing, manager):
"""Get vehicle routes from a solution and store them in an array."""
# Get vehicle routes and store them in a two dimensional array whose
# i,j entry is the jth location visited by vehicle i along its route.
routes = []
for route_nbr in range(routing.vehicles()):
index = routing.Start(route_nbr)
route = [manager.IndexToNode(index)]
while not routing.IsEnd(index):
index = solution.Value(routing.NextVar(index))
route.append(manager.IndexToNode(index))
routes.append(route)
return routes
# Instantiate the data problem.
data = create_data_model(distance_matrix=distance_matrix,
num_vehicles=1,
depot_order=0)
# Create the routing index manager.
manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
data['num_vehicles'], data['depot'])
# Create Routing Model.
routing = pywrapcp.RoutingModel(manager)
# Create and register a transit callback.
transit_callback_index = routing.RegisterTransitCallback(distance_callback)
# Define cost of each arc.
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# Add Distance constraint.
dimension_name = 'Distance'
routing.AddDimension(
transit_callback_index,
0, # no slack
50000, # vehicle maximum travel distance
True, # start cumul to zero
dimension_name)
distance_dimension = routing.GetDimensionOrDie(dimension_name)
distance_dimension.SetGlobalSpanCostCoefficient(100)
# 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)
else:
print('No solution found !')
Depot -> Adresse 5 -> Adresse 6 -> Adresse 7 -> Adresse 4 -> Adresse 3 -> Adresse 2 -> Adresse 1 -> Depot
Diese kürzeste Route dauert etwa 7,44 km.
V. Erstellen Sie einen Pfad für die effektivste Route
Datenvisualisierung ist eine der besten Möglichkeiten, um diesen Fall besser nachvollziehen zu können. Daher werden wir in diesem letzten Schritt eine räumliche Datenvisualisierung erstellen, um die beste Route darzustellen. Wir sollten einen neuen DataFrame erstellen, der nur die beste Route enthält, und dann den Pfad erstellen.
# Create path for the most effective route #
# Create DataFrame of route order
df_nodes_best = pd.DataFrame({'origin' : route_name[:-1],
'destination': route_name[1:]})
df_nodes_best = df_nodes_best.merge(df_nodes, on=['origin', 'destination'], how='inner')
df_nodes_best["route_order"] = [f"Route {i}" for i in range(1, len(df_nodes_best)+1)]
df_nodes_best
# Create lists
list_route_best = []
list_route_length_best = []
# Determining distance and route
for i in range(len(df_nodes_best)):
# find the nearest node to the start location
orig_node_best = ox.get_nearest_node(graph, df_nodes_best.iloc[i]['latlong_origin'])
# find the nearest node to the end location
dest_node_best = ox.get_nearest_node(graph, df_nodes_best.iloc[i]['latlong_destination'])
# find the shortest path
shortest_route_best = nx.shortest_path(graph,
orig_node_best,
dest_node_best,
weight=optimizer)
shortest_route_length_best = nx.shortest_path_length(graph, orig_node_best, dest_node_best, weight=optimizer)
# append list
list_route_best.append(shortest_route_best)
list_route_length_best.append(shortest_route_length_best)
# Store best route distance in a list
distance_result_best = list_route_length_best.copy()
# Convert nodes code into lat long
all_list_nodes_latlong = []
for i in range(len(list_route_best)):
list_nodes_latlong = []
for j in range(len(list_route_best[i])):
tuple_nodes_latlong = (graph.nodes[list_route_best[i][j]]['y'], graph.nodes[list_route_best[i][j]]['x'])
list_nodes_latlong.append(tuple_nodes_latlong)
all_list_nodes_latlong.append(list_nodes_latlong)
# Load map
my_map = folium.Map(location=[-6.897097, 107.621903],
zoom_start=15,
tiles='cartodbpositron')
# Plot route polyline
for i in range(len(all_list_nodes_latlong)):
folium.PolyLine(all_list_nodes_latlong[i],
tooltip=f"{df_nodes_best['route_order'].iloc[i].upper()}:\
{df_nodes_best['origin'].iloc[i]} to {df_nodes_best['destination'].iloc[i]}\
- Distance: {round(distance_result_best[i]/1000, 2)} km",
# color=random_color(),
weight=5).add_to(my_map)
# Plot points
for i in range(len(df_points)):
if i != 0:
folium.Marker(location = df_points['lat_lng'][i],
popup = df_points['point_name'][i]).add_to(my_map)
else:
folium.Marker(location = df_points['lat_lng'][i],
popup = df_points['point_name'][i],
icon = folium.Icon(color='crimson')).add_to(my_map) # mark depot with red symbology
print(f'Total distance: {round(sum(distance_result_best)/1000, 2)} km')
my_map
Figure 8. The shortest route
Die Lösung kann uns helfen, die besten Routen zu finden, um alle Pakete an die Kunden zu liefern. Die kürzeste Reisestrecke beträgt 7,44 km bei folgender Route:
Depot -> Adresse 5 -> Adresse 6 -> Adresse 7 -> Adresse 4 -> Adresse 3 -> Adresse 2 -> Adresse 1 -> Depot
Die potenziellen geschäftlichen Auswirkungen oder Vorteile dieses Projekts sind:
- Versandkosten sparen
- reduzieren Sie die Zeit, die Sie auf der Straße verbringen
- die Rate pünktlicher Lieferungen erhöhen
- verbessert den CSAT-Score
- stabiles/verbessertes SLA
- Verringern Sie die Anzahl der Kundendienst (CS)-Tickets
https://www.sciencedirect.com/science/article/pii/S1877050921001125
https://www.samirsaci.com/optimize-e-commerce-last-mile-delivery-with-python/
https://en.wikipedia.org/wiki/Travelling_salesman_problem
https://en.wikipedia.org/wiki/Vehicle_routing_problem
https://blog.devgenius.io/creating-route-polyline-using-folium-and-here-api-256fcf46ef9c
https://desktop.arcgis.com/en/arcmap/latest/manage-data/geocoding/what-is-geocoding.htm

![Was ist überhaupt eine verknüpfte Liste? [Teil 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































