From 798db0eac5e7331db5c7ace5c8b62d64b04a45f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Dekanovsk=C3=BD?= Date: Sat, 28 Nov 2020 23:25:57 +0100 Subject: [PATCH] Confirm that travelling salesman alg. found the shortest path. --- .../Driving Distance between two places.ipynb | 3167 ++++++++++++++++- 1 file changed, 3115 insertions(+), 52 deletions(-) diff --git a/Maps/Driving Distance/Driving Distance between two places.ipynb b/Maps/Driving Distance/Driving Distance between two places.ipynb index fdbf6fe..f3b1661 100644 --- a/Maps/Driving Distance/Driving Distance between two places.ipynb +++ b/Maps/Driving Distance/Driving Distance between two places.ipynb @@ -12,26 +12,38 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": { "ExecuteTime": { - "end_time": "2020-11-26T21:28:10.482062Z", - "start_time": "2020-11-26T21:28:09.479896Z" + "end_time": "2020-11-28T22:05:07.453436Z", + "start_time": "2020-11-28T22:05:06.018903Z" } }, "outputs": [], "source": [ "import pandas as pd\n", - "from geopy import distance" + "from geopy import distance\n", + "import requests # to call the openmap/google apis\n", + "import json\n", + "import datetime\n", + "import math\n", + "import itertools\n", + "\n", + "# you may run into this problem https://stackoverflow.com/questions/61867945/python-import-error-cannot-import-name-six-from-sklearn-externals\n", + "import mlrose # for travelling salesman problem\n", + "\n", + "# that was fixed in mlrose_hiive, though the outputs are different\n", + "import mlrose_hiive\n", + "import numpy as np" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": { "ExecuteTime": { - "end_time": "2020-11-26T21:28:10.513811Z", - "start_time": "2020-11-26T21:28:10.483872Z" + "end_time": "2020-11-28T22:05:07.485286Z", + "start_time": "2020-11-28T22:05:07.454379Z" } }, "outputs": [ @@ -66,7 +78,7 @@ " \n", " \n", " \n", - " 0\n", + " 0\n", " Somaliland\n", " Hargeisa\n", " 9.550000\n", @@ -75,7 +87,7 @@ " Africa\n", " \n", " \n", - " 1\n", + " 1\n", " South Georgia and South Sandwich Islands\n", " King Edward Point\n", " -54.283333\n", @@ -84,7 +96,7 @@ " Antarctica\n", " \n", " \n", - " 2\n", + " 2\n", " French Southern and Antarctic Lands\n", " Port-aux-Français\n", " -49.350000\n", @@ -108,7 +120,7 @@ "2 70.216667 TF Antarctica " ] }, - "execution_count": 3, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -138,11 +150,11 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 3, "metadata": { "ExecuteTime": { - "end_time": "2020-11-26T21:47:19.526487Z", - "start_time": "2020-11-26T21:47:19.513553Z" + "end_time": "2020-11-28T22:05:07.501279Z", + "start_time": "2020-11-28T22:05:07.488341Z" } }, "outputs": [ @@ -178,7 +190,7 @@ " \n", " \n", " \n", - " 0\n", + " 0\n", " 81\n", " France\n", " Paris\n", @@ -188,7 +200,7 @@ " Europe\n", " \n", " \n", - " 1\n", + " 1\n", " 110\n", " Italy\n", " Rome\n", @@ -207,14 +219,15 @@ "1 110 Italy Rome 41.900000 12.483333 IT Europe" ] }, - "execution_count": 47, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# to start with let's filter only 2 capitals. Rome and Paris.\n", - "cities = df[df[\"capital\"].isin([\"Rome\",\"Paris\"])].reset_index()\n", + "ropa = df[df[\"capital\"].isin([\"Rome\",\"Paris\"])].reset_index()\n", + "cities = ropa.copy()\n", "cities" ] }, @@ -244,11 +257,11 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 4, "metadata": { "ExecuteTime": { - "end_time": "2020-11-26T21:47:21.240412Z", - "start_time": "2020-11-26T21:47:21.235426Z" + "end_time": "2020-11-28T22:05:07.516777Z", + "start_time": "2020-11-28T22:05:07.503271Z" } }, "outputs": [ @@ -258,7 +271,7 @@ "(Distance(1107.8818760940028), 1107.8818760940028, 688.4058822066647)" ] }, - "execution_count": 48, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -270,11 +283,11 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 5, "metadata": { "ExecuteTime": { - "end_time": "2020-11-26T21:47:21.831007Z", - "start_time": "2020-11-26T21:47:21.813073Z" + "end_time": "2020-11-28T22:05:07.532846Z", + "start_time": "2020-11-28T22:05:07.517777Z" } }, "outputs": [ @@ -284,7 +297,7 @@ "1107.8818760940028" ] }, - "execution_count": 49, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -295,11 +308,11 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 6, "metadata": { "ExecuteTime": { - "end_time": "2020-11-26T21:47:22.486467Z", - "start_time": "2020-11-26T21:47:22.445577Z" + "end_time": "2020-11-28T22:05:07.612199Z", + "start_time": "2020-11-28T22:05:07.534842Z" } }, "outputs": [ @@ -347,7 +360,7 @@ " \n", " \n", " \n", - " geodesic\n", + " geodesic\n", " 3.634783e+06\n", " 3.634783e+06\n", " 1107.881876\n", @@ -358,7 +371,7 @@ " 598.208356\n", " \n", " \n", - " great_circle\n", + " great_circle\n", " 3.630457e+06\n", " 3.630457e+06\n", " 1106.563205\n", @@ -384,7 +397,7 @@ "great_circle 687.586498 687.586498 597.496331 597.496331 " ] }, - "execution_count": 50, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -415,11 +428,11 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 7, "metadata": { "ExecuteTime": { - "end_time": "2020-11-26T21:47:30.610686Z", - "start_time": "2020-11-26T21:47:30.562814Z" + "end_time": "2020-11-28T22:05:07.689528Z", + "start_time": "2020-11-28T22:05:07.615166Z" } }, "outputs": [ @@ -467,7 +480,7 @@ " \n", " \n", " \n", - " geodesic\n", + " geodesic\n", " 3.634783e+06\n", " 3.634783e+06\n", " 1107.881876\n", @@ -478,7 +491,7 @@ " 598.208356\n", " \n", " \n", - " geodesic: Airy (1830)\n", + " geodesic: Airy (1830)\n", " 3.634455e+06\n", " 3.634455e+06\n", " 1107.781964\n", @@ -489,7 +502,7 @@ " 598.154408\n", " \n", " \n", - " geodesic: Clarke (1880)\n", + " geodesic: Clarke (1880)\n", " 3.634851e+06\n", " 3.634851e+06\n", " 1107.902624\n", @@ -500,7 +513,7 @@ " 598.219559\n", " \n", " \n", - " geodesic: GRS-67\n", + " geodesic: GRS-67\n", " 3.634796e+06\n", " 3.634796e+06\n", " 1107.885873\n", @@ -511,7 +524,7 @@ " 598.210515\n", " \n", " \n", - " geodesic: GRS-80\n", + " geodesic: GRS-80\n", " 3.634783e+06\n", " 3.634783e+06\n", " 1107.881876\n", @@ -522,7 +535,7 @@ " 598.208356\n", " \n", " \n", - " geodesic: Intl 1924\n", + " geodesic: Intl 1924\n", " 3.634927e+06\n", " 3.634927e+06\n", " 1107.925804\n", @@ -533,7 +546,7 @@ " 598.232075\n", " \n", " \n", - " geodesic: WGS-84\n", + " geodesic: WGS-84\n", " 3.634783e+06\n", " 3.634783e+06\n", " 1107.881876\n", @@ -544,7 +557,7 @@ " 598.208356\n", " \n", " \n", - " great_circle\n", + " great_circle\n", " 3.630457e+06\n", " 3.630457e+06\n", " 1106.563205\n", @@ -582,7 +595,7 @@ "great_circle 687.586498 687.586498 597.496331 597.496331 " ] }, - "execution_count": 51, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -609,27 +622,28 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 8, "metadata": { "ExecuteTime": { - "end_time": "2020-11-26T21:47:45.943112Z", - "start_time": "2020-11-26T21:47:45.935133Z" + "end_time": "2020-11-28T22:05:07.705353Z", + "start_time": "2020-11-28T22:05:07.691360Z" } }, "outputs": [ { "data": { "text/plain": [ - "397.7633096859937" + "397.76330968599365" ] }, - "execution_count": 54, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "cities = df[df[\"capital\"].isin([\"Helsinki\",\"Stockholm\"])].reset_index()\n", + "hest = df[df[\"capital\"].isin([\"Helsinki\",\"Stockholm\"])].reset_index()\n", + "cities = hest.copy()\n", "d = distance.distance((cities.loc[0, \"lat\"], cities.loc[0, \"lon\"]), (cities.loc[1, \"lat\"], cities.loc[1, \"lon\"]))\n", "d.km" ] @@ -646,7 +660,3056 @@ "Even though the distance between Helsinky, the capita of Finland and Stockholm in Sweden less than 400km, if you decide to drive it's more than 1750km and 20 hours. Even if you take ferries you will drive almost 500km. Paris is located only 1107km from Rome, but roads connecting these cities have at least 1420km. \n", "\n", "\n", - "That's why for many application you want to know the real travel distnace, which no mathematical function can return. You need to call some map service API - e.g. google routes or osrm route service (http://project-osrm.org/docs/v5.5.1/api/#route-service) " + "That's why for many application you want to know the real travel distnace, which no mathematical function can return. You need to call some map service API - e.g. google routes or osrm route service (http://project-osrm.org/docs/v5.5.1/api/#route-service). The documentation says that in order to get the driving distance we need to call this API endoint - `/route/v1/{profile}/{coordinates}?alternatives={true|false}&steps={true|false}&geometries={polyline|polyline6|geojson}&overview={full|simplified|false}&annotations={true|false}` having parameters: \n", + "\n", + "* `profile` - car, bike, foot \n", + "* `coordinates` - lat,lon of the first point; lon, lat of the second point, e.g. 2.333333,48.866667;12.483333,41.900000\n", + "* `alternative` - whether to return only the first option or more alternatives\n", + "* `steps` - whether to return route steps - e.g. at the crossroad turn left\n", + "* `geometries` - how is the route returned, either `polyline` (def`ault), `polyline6` , `geojson`\n", + "* `overview` - how to return the route - `simplified` (default), `full`, `false`\n", + "* `annotations` - if aditional metadata are provided for each point on the route\n", + "\n", + "For our purpose, we can run the simplest request, having everything set to false or default. We will simply call: `http://router.project-osrm.org/route/v1/driving/cities.loc[0, \"lon\"],cities.loc[0, \"lat\"];cities.loc[1, \"lon\"],cities.loc[1, \"lat\"]?overview=false`\n", + "\n", + "To get the API's response, we will use the python's requests method. " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:07.938822Z", + "start_time": "2020-11-28T22:05:07.707316Z" + } + }, + "outputs": [], + "source": [ + "cities = ropa.copy()\n", + "r = requests.get(f\"\"\"http://router.project-osrm.org/route/v1/car/{cities.loc[0, \"lon\"]},{cities.loc[0, \"lat\"]};{cities.loc[1, \"lon\"]},{cities.loc[1, \"lat\"]}?overview=false\"\"\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It returns whatever is provided by the API in the `content` parameter." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:07.954778Z", + "start_time": "2020-11-28T22:05:07.940816Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "b'{\"code\":\"Ok\",\"waypoints\":[{\"hint\":\"Hjy1lP___39RAAAAYQAAAAkAAAAAAAAAUTdZQvgsIUGPZrZAAAAAAFEAAABhAAAACQAAAAAAAADVFwEA45kjAJ6l6QKVmiMAa6XpAgEAvwzYtvhl\",\"distance\":14.236102,\"location\":[2.333155,48.866718],\"name\":\"Rue Saint-Roch\"},{\"hint\":\"AYnhjv___38VAAAAZgAAAAAAAAAAAAAA3AxzQf1MYEIAAAAAAAAAABUAAABmAAAAAAAAAAAAAADVFwEAbXu-AFBXfwIFe74A4Fd_AgAAHw_Ytvhl\",\"distance\":18.175415,\"location\":[12.483437,41.899856],\"name\":\"Via dell\\'Umilt\\xc3\\xa0\"}],\"routes\":[{\"legs\":[{\"steps\":[],\"weight\":54305.8,\"distance\":1432281.4,\"summary\":\"\",\"duration\":54279.7}],\"weight_name\":\"routability\",\"weight\":54305.8,\"distance\":1432281.4,\"duration\":54279.7}]}'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "r.content" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that it returns a json object, which looks nices, when passed to the python's json library. " + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:07.969741Z", + "start_time": "2020-11-28T22:05:07.958770Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'code': 'Ok',\n", + " 'waypoints': [{'hint': 'Hjy1lP___39RAAAAYQAAAAkAAAAAAAAAUTdZQvgsIUGPZrZAAAAAAFEAAABhAAAACQAAAAAAAADVFwEA45kjAJ6l6QKVmiMAa6XpAgEAvwzYtvhl',\n", + " 'distance': 14.236102,\n", + " 'location': [2.333155, 48.866718],\n", + " 'name': 'Rue Saint-Roch'},\n", + " {'hint': 'AYnhjv___38VAAAAZgAAAAAAAAAAAAAA3AxzQf1MYEIAAAAAAAAAABUAAABmAAAAAAAAAAAAAADVFwEAbXu-AFBXfwIFe74A4Fd_AgAAHw_Ytvhl',\n", + " 'distance': 18.175415,\n", + " 'location': [12.483437, 41.899856],\n", + " 'name': \"Via dell'Umiltà\"}],\n", + " 'routes': [{'legs': [{'steps': [],\n", + " 'weight': 54305.8,\n", + " 'distance': 1432281.4,\n", + " 'summary': '',\n", + " 'duration': 54279.7}],\n", + " 'weight_name': 'routability',\n", + " 'weight': 54305.8,\n", + " 'distance': 1432281.4,\n", + " 'duration': 54279.7}]}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "json.loads(r.content)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We're mainly interested about the driving `distance` and/or driving `duration`. These parameters are included into the `route` subelement which contains a list of routes. Because we haven't asked for any alternative, this list has only one item. " + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:07.984709Z", + "start_time": "2020-11-28T22:05:07.974726Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(1432281.4, 54279.7)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "route_1 = json.loads(r.content)[\"routes\"][0]\n", + "route_1[\"distance\"], route_1[\"duration\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-27T18:31:42.373161Z", + "start_time": "2020-11-27T18:31:42.359903Z" + } + }, + "source": [ + "If the resulted numbers look strange, beware that the distnace is in meters and duration in seconds. You can easily check that the values are correct online - https://www.openstreetmap.org/directions?engine=fossgis_osrm_car&route=48.867%2C2.333%3B41.900%2C12.484 (for driving distnace by car from Paris to Rome). \n", + "\n", + "Maybe you prefer more human readable format. That can be achieved by passing the received duration as a parameter to `timedelta` function of `datetime` and return the string representation. In case it's more than 1 day (I've added 100000 seconds the Paris-Rome distnace for that purpose), than it's displayed. " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:08.000656Z", + "start_time": "2020-11-28T22:05:07.987692Z" + } + }, + "outputs": [], + "source": [ + "x = datetime.timedelta(seconds=route_1[\"duration\"]+100000)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:08.031575Z", + "start_time": "2020-11-28T22:05:08.008635Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'1 day, 18:51:19.700000'" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "str(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-27T18:42:05.353501Z", + "start_time": "2020-11-27T18:42:05.341134Z" + } + }, + "source": [ + "You can also use pandas, if you specify the type of the `duration` column to be `timedelta64[s]`." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:08.063487Z", + "start_time": "2020-11-28T22:05:08.037559Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
duration
01 days 18:51:19
\n", + "
" + ], + "text/plain": [ + " duration\n", + "0 1 days 18:51:19" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dftime = pd.DataFrame({\"duration\":route_1[\"duration\"]+100000}, index=[0])\n", + "dftime[\"duration\"] = dftime[\"duration\"].astype(\"timedelta64[s]\")\n", + "dftime" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-27T18:47:34.278375Z", + "start_time": "2020-11-27T18:47:34.268290Z" + } + }, + "source": [ + "You should use the OSRM project responsibly, because it's there to server everyone. Read the rules of the service https://github.com/Project-OSRM/osrm-backend/wiki/Api-usage-policy. \n", + "\n", + "If you are building a business application which needs hundreds or thousands of route requests, opt for a commercial product like google direction service. https://developers.google.com/maps/documentation/directions/overview. Again you use the requests library, specify correct url and get the result. Beware that you need to make an agreement with Google and these requests are paid. You have to prove yourself with valid API key and you are billed according to the service policies. Usually these services offer some free amount of searches, which can be used for your proof of concept. E.g. google offer \\\\$200/month worth of credit, while 1K requests costs \\\\$10. \n", + "\n", + "In order to avoid surprises, always restrict your API keys to the purpose you need it for and set up the billing ceiling. " + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:08.079445Z", + "start_time": "2020-11-28T22:05:08.069473Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "https://maps.googleapis.com/maps/api/directions/json?origin=48.86666666666667,2.333333&destination=41.9,12.483333&mode=driving&key=AIzaSyCKkc56FLjxR3Z7Gj_uT9HvBAG3hDTJ1t8\n" + ] + } + ], + "source": [ + "origin_coor = \",\".join([str(cities.loc[0,\"lat\"]), str(cities.loc[0,\"lon\"])])\n", + "destination_coor = \",\".join([str(cities.loc[1,\"lat\"]), str(cities.loc[1,\"lon\"])])\n", + "API_KEY = \"AIzaSyCKkc56FLjxR3Z7Gj_uT9HvBAG3hDTJ1t8\"\n", + "url = f\"https://maps.googleapis.com/maps/api/directions/json?origin={origin_coor}&destination={destination_coor}&mode=driving&key={API_KEY}\"\n", + "\n", + "# alternatively you can specify the start point (origin) and the destination using the places' names\n", + "url_alt = f\"https://maps.googleapis.com/maps/api/directions/json?origin=Paris&destination=Rome&mode=driving&key={API_KEY}\"\n", + "print(url)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:08.607266Z", + "start_time": "2020-11-28T22:05:08.083440Z" + } + }, + "outputs": [], + "source": [ + "r = requests.get(url_alt)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case you find the results in the the appropriate keys of the response. " + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:08.622179Z", + "start_time": "2020-11-28T22:05:08.609244Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "({'text': '13 hours 48 mins', 'value': 49652},\n", + " {'text': '1,421 km', 'value': 1420893})" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results = json.loads(r.content)\n", + "legs = results[\"routes\"][0][\"legs\"]\n", + "legs[0][\"duration\"], legs[0][\"distance\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Distances between multiple cities and optimal route\n", + "The route doesn't have to be limited to two cities only. Maybe you need to visit several addreses/cities and you're trying to optimize the process. Let's try on an example of exploring all the capitals of the Central Europe. \n", + "\n", + "First we will list these capitals. Looking to Wikipedia (https://en.wikipedia.org/wiki/Central_Europe) you will find that Central Europe include 9 countries - Austria, Czech Republic, Germany, Hungary, Liechtenstein, Poland, Slovenia, Slovakia, Switzerland. I've listed their ISO2 codes below. " + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:08.638137Z", + "start_time": "2020-11-28T22:05:08.623176Z" + } + }, + "outputs": [], + "source": [ + "ce_countries = [\"AT\",\"CZ\",\"DE\",\"HU\",\"LI\",\"PL\",\"SK\",\"SI\",\"CH\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:08.654093Z", + "start_time": "2020-11-28T22:05:08.640131Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Countrycapitallatloncodecontinent
0AustriaVienna48.20000016.366667ATEurope
1Czech RepublicPrague50.08333314.466667CZEurope
2GermanyBerlin52.51666713.400000DEEurope
3HungaryBudapest47.50000019.083333HUEurope
4LiechtensteinVaduz47.1333339.516667LIEurope
5PolandWarsaw52.25000021.000000PLEurope
6SlovakiaBratislava48.15000017.116667SKEurope
7SloveniaLjubljana46.05000014.516667SIEurope
8SwitzerlandBern46.9166677.466667CHEurope
\n", + "
" + ], + "text/plain": [ + " Country capital lat lon code continent\n", + "0 Austria Vienna 48.200000 16.366667 AT Europe\n", + "1 Czech Republic Prague 50.083333 14.466667 CZ Europe\n", + "2 Germany Berlin 52.516667 13.400000 DE Europe\n", + "3 Hungary Budapest 47.500000 19.083333 HU Europe\n", + "4 Liechtenstein Vaduz 47.133333 9.516667 LI Europe\n", + "5 Poland Warsaw 52.250000 21.000000 PL Europe\n", + "6 Slovakia Bratislava 48.150000 17.116667 SK Europe\n", + "7 Slovenia Ljubljana 46.050000 14.516667 SI Europe\n", + "8 Switzerland Bern 46.916667 7.466667 CH Europe" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ce_cities = df[df[\"code\"].isin(ce_countries)].reset_index(drop=True)\n", + "ce_cities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's wrap the OSRM distnace service into a function." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:08.670052Z", + "start_time": "2020-11-28T22:05:08.656089Z" + } + }, + "outputs": [], + "source": [ + "def get_distance(point1: dict, point2: dict) -> tuple:\n", + " \"\"\"Gets distance between two points en route using http://project-osrm.org/docs/v5.10.0/api/#nearest-service\"\"\"\n", + " \n", + " url = f\"\"\"http://router.project-osrm.org/route/v1/driving/{point1[\"lon\"]},{point1[\"lat\"]};{point2[\"lon\"]},{point2[\"lat\"]}?overview=false&alternatives=false\"\"\"\n", + " r = requests.get(url)\n", + " \n", + " # get the distance from the returned values\n", + " route = json.loads(r.content)[\"routes\"][0]\n", + " return (route[\"distance\"], route[\"duration\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:08.749878Z", + "start_time": "2020-11-28T22:05:08.673043Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(347770.1, 13949)" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# let's try on the first two cities\n", + "# confirm that it's correct on https://www.openstreetmap.org/directions?engine=fossgis_osrm_car&route=48.208%2C16.372%3B50.087%2C14.421\n", + "get_distance({\"lat\": 48.200000,\"lon\": 16.366667}, {\"lat\": 50.083333,\"lon\": 14.466667})" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-27T19:22:46.718135Z", + "start_time": "2020-11-27T19:22:46.709028Z" + } + }, + "source": [ + "Now we can run the distnace calculation for all the combinations of the cities. There's \n", + "\\begin{equation*} \n", + "{9 \\choose 2} \n", + "\\end{equation*}\n", + "of combinations." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:08.764805Z", + "start_time": "2020-11-28T22:05:08.755822Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# https://stackoverflow.com/questions/464864/how-to-get-all-possible-combinations-of-a-list-s-elements\n", + "# https://stackoverflow.com/questions/3025162/statistics-combinations-in-python\n", + "len([c for c in itertools.combinations(list(cities[\"capital\"]),2)])" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:08.779756Z", + "start_time": "2020-11-28T22:05:08.768787Z" + } + }, + "outputs": [], + "source": [ + "# from python 3.8 you can use math.comb to get just the number. \n", + "# math.comb(9,2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-27T19:37:09.133127Z", + "start_time": "2020-11-27T19:37:09.126150Z" + } + }, + "source": [ + "Let's get the distance and duration from OSRM API for all our combinations." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:35.576147Z", + "start_time": "2020-11-28T22:05:08.782756Z" + } + }, + "outputs": [], + "source": [ + "dist_array = []\n", + "for i , r in ce_cities.iterrows():\n", + " point1 = {\"lat\": r[\"lat\"], \"lon\": r[\"lon\"]}\n", + " for j, o in ce_cities[ce_cities.index != i].iterrows():\n", + " point2 = {\"lat\": o[\"lat\"], \"lon\": o[\"lon\"]}\n", + " dist, duration = get_distance(point1, point2)\n", + " #dist = geodesic((i_lat, i_lon), (o[\"CapitalLatitude\"], o[\"CapitalLongitude\"])).km\n", + " dist_array.append((i, j, duration, dist))" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:35.607036Z", + "start_time": "2020-11-28T22:05:35.583100Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
origindestinationduration(s)distnace(m)
00113949.0347770.1
10227424.4695795.8
2039661.4246594.9
30424166.4647192.7
40528893.5686021.4
...............
678341862.11102405.6
68849856.0235662.0
698552582.91503175.3
708635718.7936757.8
718732832.1804506.1
\n", + "

72 rows × 4 columns

\n", + "
" + ], + "text/plain": [ + " origin destination duration(s) distnace(m)\n", + "0 0 1 13949.0 347770.1\n", + "1 0 2 27424.4 695795.8\n", + "2 0 3 9661.4 246594.9\n", + "3 0 4 24166.4 647192.7\n", + "4 0 5 28893.5 686021.4\n", + ".. ... ... ... ...\n", + "67 8 3 41862.1 1102405.6\n", + "68 8 4 9856.0 235662.0\n", + "69 8 5 52582.9 1503175.3\n", + "70 8 6 35718.7 936757.8\n", + "71 8 7 32832.1 804506.1\n", + "\n", + "[72 rows x 4 columns]" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "distances_df = pd.DataFrame(dist_array,columns=[\"origin\",\"destination\",\"duration(s)\",\"distnace(m)\"])\n", + "distances_df" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:35.652943Z", + "start_time": "2020-11-28T22:05:35.613036Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
origindestinationduration(s)distnace(m)origin_namedestination_name
00113949.0347770.1ViennaPrague
172113651.1347924.9BerlinPrague
253119597.0527313.7BudapestPrague
334122708.2619664.0VaduzPrague
415126667.0673676.6WarsawPrague
.....................
324024148.1646604.7VaduzVienna
405028903.3676943.6WarsawVienna
48603533.180739.1BratislavaVienna
567014274.2374135.9LjubljanaVienna
648032685.3848610.9BernVienna
\n", + "

72 rows × 6 columns

\n", + "
" + ], + "text/plain": [ + " origin destination duration(s) distnace(m) origin_name destination_name\n", + "0 0 1 13949.0 347770.1 Vienna Prague\n", + "17 2 1 13651.1 347924.9 Berlin Prague\n", + "25 3 1 19597.0 527313.7 Budapest Prague\n", + "33 4 1 22708.2 619664.0 Vaduz Prague\n", + "41 5 1 26667.0 673676.6 Warsaw Prague\n", + ".. ... ... ... ... ... ...\n", + "32 4 0 24148.1 646604.7 Vaduz Vienna\n", + "40 5 0 28903.3 676943.6 Warsaw Vienna\n", + "48 6 0 3533.1 80739.1 Bratislava Vienna\n", + "56 7 0 14274.2 374135.9 Ljubljana Vienna\n", + "64 8 0 32685.3 848610.9 Bern Vienna\n", + "\n", + "[72 rows x 6 columns]" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "distances_df = distances_df.merge(ce_cities[[\"capital\"]], left_on = \"origin\", right_index=True).rename(columns={\"capital\":\"origin_name\"})\n", + "distances_df = distances_df.merge(ce_cities[[\"capital\"]], left_on = \"destination\", right_index=True).rename(columns={\"capital\":\"destination_name\"})\n", + "distances_df" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-27T19:53:21.205624Z", + "start_time": "2020-11-27T19:53:19.301370Z" + } + }, + "source": [ + "# Travelling Salesperson Problem\n", + "To find the optimal route between a list of points based on a parameter (e.g. duration or distance between the places) we can use the Travelling Salesperson Problem (https://en.wikipedia.org/wiki/Travelling_salesman_problem). Travelling Sales person wants to visit all the points with the minimal effort. \n", + "\n", + "Python implementation is described in the following article. https://towardsdatascience.com/solving-travelling-salesperson-problems-with-python-5de7e883d847. You can also read the documentation of the mlrose package https://mlrose.readthedocs.io/en/stable/source/tutorial2.html.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:35.667872Z", + "start_time": "2020-11-28T22:05:35.653909Z" + } + }, + "outputs": [], + "source": [ + "# we plan to visit 9 cities\n", + "length = ce_cities.shape[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "we plan to use the list of distnaces (durations in our case), that's why we initialize with `distances = dist_list` param. E.g. distance between the `place 0` and `place 1` is `13949.0` second --> `(0,1,13949.0)`" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:35.699788Z", + "start_time": "2020-11-28T22:05:35.669868Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[(0, 1, 13949.),\n", + " (0, 2, 27424.4),\n", + " (0, 3, 9661.4),\n", + " (0, 4, 24166.4),\n", + " (0, 5, 28893.5),\n", + " (0, 6, 3518.),\n", + " (0, 7, 14222.),\n", + " (0, 8, 32672.5),\n", + " (1, 0, 13859.6),\n", + " (1, 2, 13629.7),\n", + " (1, 3, 19576.5),\n", + " (1, 4, 22679.4),\n", + " (1, 5, 26760.7),\n", + " (1, 6, 12284.1),\n", + " (1, 7, 27358.1),\n", + " (1, 8, 30022.5),\n", + " (2, 0, 27361.7),\n", + " (2, 1, 13651.1),\n", + " (2, 3, 33078.6),\n", + " (2, 4, 27867.7),\n", + " (2, 5, 20594.7),\n", + " (2, 6, 25786.2),\n", + " (2, 7, 36316.1),\n", + " (2, 8, 34951.9),\n", + " (3, 0, 9697.1),\n", + " (3, 1, 19597.),\n", + " (3, 2, 33072.4),\n", + " (3, 4, 33392.2),\n", + " (3, 5, 34541.5),\n", + " (3, 6, 7881.1),\n", + " (3, 7, 17594.9),\n", + " (3, 8, 41898.3),\n", + " (4, 0, 24148.1),\n", + " (4, 1, 22708.2),\n", + " (4, 2, 27798.),\n", + " (4, 3, 33324.9),\n", + " (4, 5, 45307.5),\n", + " (4, 6, 27181.5),\n", + " (4, 7, 24768.4),\n", + " (4, 8, 9946.2),\n", + " (5, 0, 28903.3),\n", + " (5, 1, 26667.),\n", + " (5, 2, 20528.8),\n", + " (5, 3, 34620.2),\n", + " (5, 4, 45463.),\n", + " (5, 6, 27327.8),\n", + " (5, 7, 42475.5),\n", + " (5, 8, 52673.4),\n", + " (6, 0, 3533.1),\n", + " (6, 1, 12395.9),\n", + " (6, 2, 25871.3),\n", + " (6, 3, 7913.7),\n", + " (6, 4, 27228.2),\n", + " (6, 5, 27340.4),\n", + " (6, 7, 16743.1),\n", + " (6, 8, 35734.3),\n", + " (7, 0, 14274.2),\n", + " (7, 1, 27570.4),\n", + " (7, 2, 36495.7),\n", + " (7, 3, 17635.6),\n", + " (7, 4, 24958.4),\n", + " (7, 5, 42514.9),\n", + " (7, 6, 16759.1),\n", + " (7, 8, 32792.6),\n", + " (8, 0, 32685.3),\n", + " (8, 1, 30032.9),\n", + " (8, 2, 35073.4),\n", + " (8, 3, 41862.1),\n", + " (8, 4, 9856.),\n", + " (8, 5, 52582.9),\n", + " (8, 6, 35718.7),\n", + " (8, 7, 32832.1)]" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# turn the first three columns of the dataframe into the list of tuples\n", + "dist_list = list(distances_df[[\"origin\",\"destination\",\"duration(s)\"]].sort_values(by=[\"origin\",\"destination\"]).to_records(index=False))\n", + "dist_list" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:35.715744Z", + "start_time": "2020-11-28T22:05:35.701782Z" + } + }, + "outputs": [], + "source": [ + "# we plan to use the list of distnaces (durations in our case), that's why we initialize with `distances = dist_list` param.\n", + "fitness_dists = mlrose.TravellingSales(distances = dist_array)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:35.731702Z", + "start_time": "2020-11-28T22:05:35.717740Z" + } + }, + "outputs": [], + "source": [ + "problem_fit = mlrose.TSPOpt(length = length, fitness_fn = fitness_dists,\n", + " maximize=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:36.287216Z", + "start_time": "2020-11-28T22:05:35.733696Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([1, 4, 8, 0, 7, 3, 6, 5, 2]), 166548.09999999998)" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mlrose.genetic_alg(problem_fit, random_state = 2)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:36.795536Z", + "start_time": "2020-11-28T22:05:36.288123Z" + } + }, + "outputs": [], + "source": [ + "# Solve problem using the genetic algorithm - suboptimal solution\n", + "best_state, best_fitness = mlrose.genetic_alg(problem_fit, random_state = 2)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:05:36.810757Z", + "start_time": "2020-11-28T22:05:36.797331Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The best state found is: [1 4 8 0 7 3 6 5 2], taking 166548.09999999998 (1 day, 22:15:48.100000)\n" + ] + } + ], + "source": [ + "print(f\"The best state found is: {best_state}, taking {best_fitness} ({str(datetime.timedelta(seconds=best_fitness))})\")" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:29.554931Z", + "start_time": "2020-11-28T22:05:36.811707Z" + } + }, + "outputs": [], + "source": [ + "# better but more resource intensive solutions\n", + "best_state, best_fitness = mlrose.genetic_alg(problem_fit, mutation_prob = 0.2, max_attempts = 500, random_state = 2)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:29.570917Z", + "start_time": "2020-11-28T22:06:29.557969Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The best state found is: [4 8 1 2 5 0 6 3 7], taking 156969.9 (1 day, 19:36:09.900000)\n" + ] + } + ], + "source": [ + "print(f\"The best state found is: {best_state}, taking {best_fitness} ({str(datetime.timedelta(seconds=best_fitness))})\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `best_state` contains the order of the places to visit. Let's map it to our list of cities to see, the order in which we can to visit them." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:29.602881Z", + "start_time": "2020-11-28T22:06:29.576884Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{4: 0, 8: 1, 1: 2, 2: 3, 5: 4, 0: 5, 6: 6, 3: 7, 7: 8}" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "orders = {city: order for order, city in enumerate(best_state)}\n", + "orders" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:29.649739Z", + "start_time": "2020-11-28T22:06:29.612784Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Countrycapitallatloncodecontinentorder
4LiechtensteinVaduz47.1333339.516667LIEurope0
8SwitzerlandBern46.9166677.466667CHEurope1
1Czech RepublicPrague50.08333314.466667CZEurope2
2GermanyBerlin52.51666713.400000DEEurope3
5PolandWarsaw52.25000021.000000PLEurope4
0AustriaVienna48.20000016.366667ATEurope5
6SlovakiaBratislava48.15000017.116667SKEurope6
3HungaryBudapest47.50000019.083333HUEurope7
7SloveniaLjubljana46.05000014.516667SIEurope8
\n", + "
" + ], + "text/plain": [ + " Country capital lat lon code continent order\n", + "4 Liechtenstein Vaduz 47.133333 9.516667 LI Europe 0\n", + "8 Switzerland Bern 46.916667 7.466667 CH Europe 1\n", + "1 Czech Republic Prague 50.083333 14.466667 CZ Europe 2\n", + "2 Germany Berlin 52.516667 13.400000 DE Europe 3\n", + "5 Poland Warsaw 52.250000 21.000000 PL Europe 4\n", + "0 Austria Vienna 48.200000 16.366667 AT Europe 5\n", + "6 Slovakia Bratislava 48.150000 17.116667 SK Europe 6\n", + "3 Hungary Budapest 47.500000 19.083333 HU Europe 7\n", + "7 Slovenia Ljubljana 46.050000 14.516667 SI Europe 8" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ce_cities[\"order\"] = ce_cities.index.map(orders)\n", + "ce_cities = ce_cities.sort_values(by=\"order\")\n", + "ce_cities" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-27T20:52:24.150431Z", + "start_time": "2020-11-27T20:52:24.139432Z" + } + }, + "source": [ + "Let's confirm that the distance is really the `best_fitness`. Based on the order we will add the `next_city` column and specially handle the last city which is followed by the city with order == 0 (or the minimum order)." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:29.680656Z", + "start_time": "2020-11-28T22:06:29.652736Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Countrycapitallatloncodecontinentordernext_city
4LiechtensteinVaduz47.1333339.516667LIEurope0Bern
8SwitzerlandBern46.9166677.466667CHEurope1Prague
1Czech RepublicPrague50.08333314.466667CZEurope2Berlin
2GermanyBerlin52.51666713.400000DEEurope3Warsaw
5PolandWarsaw52.25000021.000000PLEurope4Vienna
0AustriaVienna48.20000016.366667ATEurope5Bratislava
6SlovakiaBratislava48.15000017.116667SKEurope6Budapest
3HungaryBudapest47.50000019.083333HUEurope7Ljubljana
7SloveniaLjubljana46.05000014.516667SIEurope8Vaduz
\n", + "
" + ], + "text/plain": [ + " Country capital lat lon code continent order \\\n", + "4 Liechtenstein Vaduz 47.133333 9.516667 LI Europe 0 \n", + "8 Switzerland Bern 46.916667 7.466667 CH Europe 1 \n", + "1 Czech Republic Prague 50.083333 14.466667 CZ Europe 2 \n", + "2 Germany Berlin 52.516667 13.400000 DE Europe 3 \n", + "5 Poland Warsaw 52.250000 21.000000 PL Europe 4 \n", + "0 Austria Vienna 48.200000 16.366667 AT Europe 5 \n", + "6 Slovakia Bratislava 48.150000 17.116667 SK Europe 6 \n", + "3 Hungary Budapest 47.500000 19.083333 HU Europe 7 \n", + "7 Slovenia Ljubljana 46.050000 14.516667 SI Europe 8 \n", + "\n", + " next_city \n", + "4 Bern \n", + "8 Prague \n", + "1 Berlin \n", + "2 Warsaw \n", + "5 Vienna \n", + "0 Bratislava \n", + "6 Budapest \n", + "3 Ljubljana \n", + "7 Vaduz " + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ce_cities[\"next_city\"] = ce_cities[\"capital\"].shift(-1)\n", + "\n", + "# the last connection is between the last city and the first one\n", + "ce_cities.loc[ce_cities[\"order\"] == max(ce_cities[\"order\"]), \"next_city\"] = ce_cities.loc[ce_cities[\"order\"] == min(ce_cities[\"order\"]), \"capital\"].values[0]\n", + "ce_cities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then we join the distances in seconds from `distance_df`" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:29.695800Z", + "start_time": "2020-11-28T22:06:29.681653Z" + } + }, + "outputs": [], + "source": [ + "ordered_ce_cities = ce_cities.merge(distances_df[[\"origin_name\",\"destination_name\",\"duration(s)\"]], left_on=[\"capital\",\"next_city\"], right_on=[\"origin_name\",\"destination_name\"], how=\"left\")" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:29.710762Z", + "start_time": "2020-11-28T22:06:29.696799Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Countrycapitallatloncodecontinentordernext_cityorigin_namedestination_nameduration(s)
0LiechtensteinVaduz47.1333339.516667LIEurope0BernVaduzBern9946.2
1SwitzerlandBern46.9166677.466667CHEurope1PragueBernPrague30032.9
2Czech RepublicPrague50.08333314.466667CZEurope2BerlinPragueBerlin13629.7
3GermanyBerlin52.51666713.400000DEEurope3WarsawBerlinWarsaw20594.7
4PolandWarsaw52.25000021.000000PLEurope4ViennaWarsawVienna28903.3
5AustriaVienna48.20000016.366667ATEurope5BratislavaViennaBratislava3518.0
6SlovakiaBratislava48.15000017.116667SKEurope6BudapestBratislavaBudapest7913.7
7HungaryBudapest47.50000019.083333HUEurope7LjubljanaBudapestLjubljana17594.9
8SloveniaLjubljana46.05000014.516667SIEurope8VaduzLjubljanaVaduz24958.4
\n", + "
" + ], + "text/plain": [ + " Country capital lat lon code continent order \\\n", + "0 Liechtenstein Vaduz 47.133333 9.516667 LI Europe 0 \n", + "1 Switzerland Bern 46.916667 7.466667 CH Europe 1 \n", + "2 Czech Republic Prague 50.083333 14.466667 CZ Europe 2 \n", + "3 Germany Berlin 52.516667 13.400000 DE Europe 3 \n", + "4 Poland Warsaw 52.250000 21.000000 PL Europe 4 \n", + "5 Austria Vienna 48.200000 16.366667 AT Europe 5 \n", + "6 Slovakia Bratislava 48.150000 17.116667 SK Europe 6 \n", + "7 Hungary Budapest 47.500000 19.083333 HU Europe 7 \n", + "8 Slovenia Ljubljana 46.050000 14.516667 SI Europe 8 \n", + "\n", + " next_city origin_name destination_name duration(s) \n", + "0 Bern Vaduz Bern 9946.2 \n", + "1 Prague Bern Prague 30032.9 \n", + "2 Berlin Prague Berlin 13629.7 \n", + "3 Warsaw Berlin Warsaw 20594.7 \n", + "4 Vienna Warsaw Vienna 28903.3 \n", + "5 Bratislava Vienna Bratislava 3518.0 \n", + "6 Budapest Bratislava Budapest 7913.7 \n", + "7 Ljubljana Budapest Ljubljana 17594.9 \n", + "8 Vaduz Ljubljana Vaduz 24958.4 " + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ordered_ce_cities" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-27T21:07:29.108268Z", + "start_time": "2020-11-27T21:07:29.095306Z" + } + }, + "source": [ + "Let's check that the total of this distance is really, what Travelling Salesman Problem identified as the `best_fitness`" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:29.726721Z", + "start_time": "2020-11-28T22:06:29.712757Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "157091.8" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ordered_ce_cities[\"duration(s)\"].sum()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-27T21:11:11.391405Z", + "start_time": "2020-11-27T21:11:11.377409Z" + } + }, + "source": [ + "Since the route is a cyclical you can start at any of the capitals. To get the minimal travel time, you should end in the city which is followed by the longest duration. " + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:29.742713Z", + "start_time": "2020-11-28T22:06:29.731708Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "30032.9" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ordered_ce_cities[\"duration(s)\"].max()" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:08:58.531709Z", + "start_time": "2020-11-28T22:08:58.513758Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Country capital lat lon code continent order next_city origin_name destination_name duration(s)
0LiechtensteinVaduz47.1333339.516667LIEurope0BernVaduzBern9946.200000
1SwitzerlandBern46.9166677.466667CHEurope1PragueBernPrague30032.900000
2Czech RepublicPrague50.08333314.466667CZEurope2BerlinPragueBerlin13629.700000
3GermanyBerlin52.51666713.400000DEEurope3WarsawBerlinWarsaw20594.700000
4PolandWarsaw52.25000021.000000PLEurope4ViennaWarsawVienna28903.300000
5AustriaVienna48.20000016.366667ATEurope5BratislavaViennaBratislava3518.000000
6SlovakiaBratislava48.15000017.116667SKEurope6BudapestBratislavaBudapest7913.700000
7HungaryBudapest47.50000019.083333HUEurope7LjubljanaBudapestLjubljana17594.900000
8SloveniaLjubljana46.05000014.516667SIEurope8VaduzLjubljanaVaduz24958.400000
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ordered_ce_cities.style.highlight_max(color = 'lightgreen', axis = 0, subset=\"duration(s)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, in our case, if you can start in Bern and end in Ljublana, the distance would be the smallest." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:08:30.868770Z", + "start_time": "2020-11-28T22:08:30.858830Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(127058.9, '1 day, 11:17:38.900000')" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "duration_s = ordered_ce_cities[\"duration(s)\"].sum() - ordered_ce_cities[\"duration(s)\"].max()\n", + "duration_s, str(datetime.timedelta(seconds=duration_s))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-27T21:18:52.686379Z", + "start_time": "2020-11-27T21:18:52.673412Z" + } + }, + "source": [ + "# Confirm that this is really the shorted possible route" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:29.993967Z", + "start_time": "2020-11-28T22:06:29.978764Z" + } + }, + "outputs": [], + "source": [ + "l = [0,1,2,3,4,5,6,7,8]" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:10:26.426261Z", + "start_time": "2020-11-28T22:10:26.410302Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "181440.0" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# There's possible (n-1)!/2 combinations; 1/2 because path 1-2-3 is the same as 3-2-1\n", + "math.factorial(length)/2" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T21:44:36.185177Z", + "start_time": "2020-11-28T21:44:36.181188Z" + } + }, + "source": [ + "Let's generate all the possible paths" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:30.071939Z", + "start_time": "2020-11-28T22:06:30.010174Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "362880" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "perm = [l for l in itertools.permutations(l, 9)]\n", + "len(perm)" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:30.087911Z", + "start_time": "2020-11-28T22:06:30.073062Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[(0, 1, 2, 3, 4, 5, 6, 7, 8),\n", + " (0, 1, 2, 3, 4, 5, 6, 8, 7),\n", + " (0, 1, 2, 3, 4, 5, 7, 6, 8),\n", + " (0, 1, 2, 3, 4, 5, 7, 8, 6),\n", + " (0, 1, 2, 3, 4, 5, 8, 6, 7)]" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "perm[:5]" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:30.103120Z", + "start_time": "2020-11-28T22:06:30.088682Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "13949.0" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "distances = {(int(d[0]), int(d[1])):d[2] for d in distances_df[[\"origin\",\"destination\",\"duration(s)\"]].values.tolist()}\n", + "distances[(0,1)]" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:30.117938Z", + "start_time": "2020-11-28T22:06:30.103969Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(0, 1) 13949.0\n", + "(1, 2) 13629.7\n", + "(2, 3) 33078.6\n", + "(3, 4) 33392.2\n", + "(4, 5) 45307.5\n", + "(5, 6) 27327.8\n", + "(6, 7) 16743.1\n", + "(7, 8) 32792.6\n", + "(8, 0) 32685.3\n", + "248905.8\n" + ] + }, + { + "data": { + "text/plain": [ + "248905.8" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# pick the first path\n", + "path = perm[0]\n", + "\n", + "# add th first element to conclude the circular path\n", + "path = path + (path[0],)\n", + "\n", + "# iterate through the path and sum all the distnaces\n", + "total_path_distance = 0\n", + "for i in range(len(path)-1):\n", + " edge = (path[i], path[i+1])\n", + " total_path_distance += distances[edge]\n", + " print(edge, distances[edge])\n", + "print(total_path_distance)\n", + "\n", + "# list comprehension\n", + "sum([distances[(path[i],path[i+1])] for i in range(len(path)-1)])" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:31.064417Z", + "start_time": "2020-11-28T22:06:30.127909Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "156770.1 [(0, 5, 2, 1, 8, 4, 7, 3, 6, 0), (1, 8, 4, 7, 3, 6, 0, 5, 2, 1), (2, 1, 8, 4, 7, 3, 6, 0, 5, 2), (3, 6, 0, 5, 2, 1, 8, 4, 7, 3), (4, 7, 3, 6, 0, 5, 2, 1, 8, 4), (5, 2, 1, 8, 4, 7, 3, 6, 0, 5), (6, 0, 5, 2, 1, 8, 4, 7, 3, 6), (7, 3, 6, 0, 5, 2, 1, 8, 4, 7), (8, 4, 7, 3, 6, 0, 5, 2, 1, 8)]\n" + ] + } + ], + "source": [ + "mn = np.inf\n", + "min_paths = []\n", + "\n", + "for p in perm:\n", + " \n", + " # add th first element to conclude the circular path\n", + " p = p + (p[0],)\n", + " \n", + " total_path_distance = sum([distances[(p[i],p[i+1])] for i in range(len(path)-1)])\n", + " \n", + " if total_path_distance < mn:\n", + " mn = total_path_distance\n", + " min_paths = [p]\n", + " elif total_path_distance == mn:\n", + " min_paths.append(p)\n", + " else:\n", + " pass\n", + " \n", + "print(mn, min_paths)" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:06:31.080062Z", + "start_time": "2020-11-28T22:06:31.065380Z" + } + }, + "outputs": [], + "source": [ + "def path_to_df(path, cities, distances_df):\n", + " orders = {city: order for order, city in enumerate(path[0])}\n", + " \n", + " cities[\"order\"] = cities.index.map(orders)\n", + " cities = cities.sort_values(by=\"order\")\n", + " \n", + " cities[\"next_city\"] = cities[\"capital\"].shift(-1)\n", + "\n", + " # the last connection is between the last city and the first one\n", + " cities.loc[cities[\"order\"] == max(cities[\"order\"]), \"next_city\"] = cities.loc[cities[\"order\"] == min(cities[\"order\"]), \"capital\"].values[0]\n", + " \n", + " ordered_ce_cities = cities.merge(distances_df[[\"origin_name\",\"destination_name\",\"duration(s)\"]], left_on=[\"capital\",\"next_city\"], right_on=[\"origin_name\",\"destination_name\"], how=\"left\")\n", + " \n", + " return ordered_ce_cities[\"duration(s)\"].sum(), ordered_ce_cities" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:12:47.699374Z", + "start_time": "2020-11-28T22:12:47.664468Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Country capital lat lon code continent order next_city origin_name destination_name duration(s)
0PolandWarsaw52.25000021.000000PLEurope1BerlinWarsawBerlin20528.800000
1GermanyBerlin52.51666713.400000DEEurope2PragueBerlinPrague13651.100000
2Czech RepublicPrague50.08333314.466667CZEurope3BernPragueBern30022.500000
3SwitzerlandBern46.9166677.466667CHEurope4VaduzBernVaduz9856.000000
4LiechtensteinVaduz47.1333339.516667LIEurope5LjubljanaVaduzLjubljana24768.400000
5SloveniaLjubljana46.05000014.516667SIEurope6BudapestLjubljanaBudapest17635.600000
6HungaryBudapest47.50000019.083333HUEurope7BratislavaBudapestBratislava7881.100000
7SlovakiaBratislava48.15000017.116667SKEurope8ViennaBratislavaVienna3533.100000
8AustriaVienna48.20000016.366667ATEurope9WarsawViennaWarsaw28893.500000
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 72, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "total_duration, path_df = path_to_df(min_paths, ce_cities, distances_df)\n", + "path_df.style.highlight_max(color = 'lightgreen', axis = 0, subset=\"duration(s)\")" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:09:56.692430Z", + "start_time": "2020-11-28T22:09:56.682493Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "156770.09999999998" + ] + }, + "execution_count": 66, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "total_duration" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:13:29.083262Z", + "start_time": "2020-11-28T22:13:29.068268Z" + } + }, + "source": [ + "Let's shift the order so that the path start in Ljubljana" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:21:37.618056Z", + "start_time": "2020-11-28T22:21:37.590130Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Countrycapitallatloncodecontinentordernext_cityorigin_namedestination_nameduration(s)
5SloveniaLjubljana46.05000014.516667SIEurope23BudapestLjubljanaBudapest17635.6
6HungaryBudapest47.50000019.083333HUEurope24BratislavaBudapestBratislava7881.1
7SlovakiaBratislava48.15000017.116667SKEurope25ViennaBratislavaVienna3533.1
8AustriaVienna48.20000016.366667ATEurope26WarsawViennaWarsaw28893.5
0PolandWarsaw52.25000021.000000PLEurope27BerlinWarsawBerlin20528.8
1GermanyBerlin52.51666713.400000DEEurope28PragueBerlinPrague13651.1
2Czech RepublicPrague50.08333314.466667CZEurope29BernPragueBern30022.5
3SwitzerlandBern46.9166677.466667CHEurope33VaduzBernVaduz9856.0
4LiechtensteinVaduz47.1333339.516667LIEurope50LjubljanaVaduzLjubljana24768.4
\n", + "
" + ], + "text/plain": [ + " Country capital lat lon code continent order \\\n", + "5 Slovenia Ljubljana 46.050000 14.516667 SI Europe 23 \n", + "6 Hungary Budapest 47.500000 19.083333 HU Europe 24 \n", + "7 Slovakia Bratislava 48.150000 17.116667 SK Europe 25 \n", + "8 Austria Vienna 48.200000 16.366667 AT Europe 26 \n", + "0 Poland Warsaw 52.250000 21.000000 PL Europe 27 \n", + "1 Germany Berlin 52.516667 13.400000 DE Europe 28 \n", + "2 Czech Republic Prague 50.083333 14.466667 CZ Europe 29 \n", + "3 Switzerland Bern 46.916667 7.466667 CH Europe 33 \n", + "4 Liechtenstein Vaduz 47.133333 9.516667 LI Europe 50 \n", + "\n", + " next_city origin_name destination_name duration(s) \n", + "5 Budapest Ljubljana Budapest 17635.6 \n", + "6 Bratislava Budapest Bratislava 7881.1 \n", + "7 Vienna Bratislava Vienna 3533.1 \n", + "8 Warsaw Vienna Warsaw 28893.5 \n", + "0 Berlin Warsaw Berlin 20528.8 \n", + "1 Prague Berlin Prague 13651.1 \n", + "2 Bern Prague Bern 30022.5 \n", + "3 Vaduz Bern Vaduz 9856.0 \n", + "4 Ljubljana Vaduz Ljubljana 24768.4 " + ] + }, + "execution_count": 97, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "filter_cities_before_bern = path_df[\"order\"] < path_df.loc[path_df[\"capital\"]==\"Ljubljana\",\"order\"].values[0]\n", + "path_df.loc[filter_cities_before_bern, \"order\"] += path_df[\"order\"].max()\n", + "path_df.sort_values(by=\"order\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:22:00.788177Z", + "start_time": "2020-11-28T22:22:00.780197Z" + } + }, + "source": [ + "Let's now reverse the order, so that we won't go from Ljublana to Vaduz, but vice versa" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:22:03.312805Z", + "start_time": "2020-11-28T22:22:03.280892Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Countrycapitallatloncodecontinentordernext_cityorigin_namedestination_nameduration(s)
8LiechtensteinVaduz47.1333339.516667LIEurope50LjubljanaVaduzLjubljana24768.4
7SwitzerlandBern46.9166677.466667CHEurope33VaduzBernVaduz9856.0
6Czech RepublicPrague50.08333314.466667CZEurope29BernPragueBern30022.5
5GermanyBerlin52.51666713.400000DEEurope28PragueBerlinPrague13651.1
4PolandWarsaw52.25000021.000000PLEurope27BerlinWarsawBerlin20528.8
3AustriaVienna48.20000016.366667ATEurope26WarsawViennaWarsaw28893.5
2SlovakiaBratislava48.15000017.116667SKEurope25ViennaBratislavaVienna3533.1
1HungaryBudapest47.50000019.083333HUEurope24BratislavaBudapestBratislava7881.1
0SloveniaLjubljana46.05000014.516667SIEurope23BudapestLjubljanaBudapest17635.6
\n", + "
" + ], + "text/plain": [ + " Country capital lat lon code continent order \\\n", + "8 Liechtenstein Vaduz 47.133333 9.516667 LI Europe 50 \n", + "7 Switzerland Bern 46.916667 7.466667 CH Europe 33 \n", + "6 Czech Republic Prague 50.083333 14.466667 CZ Europe 29 \n", + "5 Germany Berlin 52.516667 13.400000 DE Europe 28 \n", + "4 Poland Warsaw 52.250000 21.000000 PL Europe 27 \n", + "3 Austria Vienna 48.200000 16.366667 AT Europe 26 \n", + "2 Slovakia Bratislava 48.150000 17.116667 SK Europe 25 \n", + "1 Hungary Budapest 47.500000 19.083333 HU Europe 24 \n", + "0 Slovenia Ljubljana 46.050000 14.516667 SI Europe 23 \n", + "\n", + " next_city origin_name destination_name duration(s) \n", + "8 Ljubljana Vaduz Ljubljana 24768.4 \n", + "7 Vaduz Bern Vaduz 9856.0 \n", + "6 Bern Prague Bern 30022.5 \n", + "5 Prague Berlin Prague 13651.1 \n", + "4 Berlin Warsaw Berlin 20528.8 \n", + "3 Warsaw Vienna Warsaw 28893.5 \n", + "2 Vienna Bratislava Vienna 3533.1 \n", + "1 Bratislava Budapest Bratislava 7881.1 \n", + "0 Budapest Ljubljana Budapest 17635.6 " + ] + }, + "execution_count": 99, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "path_df_reversed = path_df.sort_values(by=\"order\").reset_index(drop=True)\n", + "path_df_reversed = path_df_reversed.reindex(index=path_df_reversed.index[::-1])\n", + "path_df_reversed" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-28T22:23:25.616823Z", + "start_time": "2020-11-28T22:23:25.597874Z" + } + }, + "source": [ + "This path is exactly the same our Travelling Salesman algorithm found. You can see in the `min_paths` variable, that all combinations of these roads are the shortest (considering that we return to the place where we have started)." ] } ], @@ -666,7 +3729,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.8.5" }, "toc": { "base_numbering": 1,