From dd5a13772f6cb1994c987b65f0f301537733ba6c Mon Sep 17 00:00:00 2001 From: iroy Date: Tue, 6 Dec 2022 15:48:05 -0600 Subject: [PATCH 1/5] update notebooks --- .../cost_matrix_creation-checkpoint.ipynb | 442 +++++++++ .../cpdptw-reoptmization-checkpoint.ipynb | 878 ++++++++++++++++++ ...w_intra-factory_transport-checkpoint.ipynb | 610 ++++++++++++ .../cvrp_daily_deliveries-checkpoint.ipynb | 452 +++++++++ .../cvrpstw_priority_routing-checkpoint.ipynb | 548 +++++++++++ ...nchmark_gehring_homberger-checkpoint.ipynb | 462 +++++++++ ...rptw_service_team_routing-checkpoint.ipynb | 457 +++++++++ .../microservice/cost_matrix_creation.ipynb | 4 +- .../microservice/cpdptw-reoptmization.ipynb | 8 +- .../cpdptw_intra-factory_transport.ipynb | 4 +- .../microservice/cvrp_daily_deliveries.ipynb | 6 +- .../cvrpstw_priority_routing.ipynb | 8 +- .../cvrptw_benchmark_gehring_homberger.ipynb | 6 +- .../cvrptw_service_team_routing.ipynb | 6 +- .../notebook_utils/notebook_helpers.py | 23 +- .../cost_matrix_creation-checkpoint.ipynb | 442 +++++++++ ...w_intra-factory_transport-checkpoint.ipynb | 599 ++++++++++++ .../cvrp_daily_deliveries-checkpoint.ipynb | 368 ++++++++ .../cvrpstw_priority_routing-checkpoint.ipynb | 651 +++++++++++++ ...nchmark_gehring_homberger-checkpoint.ipynb | 380 ++++++++ ...rptw_service_team_routing-checkpoint.ipynb | 398 ++++++++ .../pdptw_mixed_fleet-checkpoint.ipynb | 458 +++++++++ notebooks/routing/python/README.md | 18 +- .../routing/python/cost_matrix_creation.ipynb | 4 +- .../cpdptw_intra-factory_transport.ipynb | 7 +- .../python/cvrp_daily_deliveries.ipynb | 4 +- .../python/cvrpstw_priority_routing.ipynb | 18 +- .../cvrptw_benchmark_gehring_homberger.ipynb | 7 +- .../python/cvrptw_service_team_routing.ipynb | 8 +- .../python/notebook_utils/notebook_helpers.py | 24 +- .../routing/python/pdptw_mixed_fleet.ipynb | 7 +- 31 files changed, 7212 insertions(+), 95 deletions(-) create mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb create mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cpdptw-reoptmization-checkpoint.ipynb create mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb create mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb create mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb create mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb create mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb create mode 100644 notebooks/routing/python/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb create mode 100644 notebooks/routing/python/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb create mode 100644 notebooks/routing/python/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb create mode 100644 notebooks/routing/python/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb create mode 100644 notebooks/routing/python/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb create mode 100644 notebooks/routing/python/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb create mode 100644 notebooks/routing/python/.ipynb_checkpoints/pdptw_mixed_fleet-checkpoint.ipynb diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb new file mode 100644 index 0000000..f1fa5f3 --- /dev/null +++ b/notebooks/routing/microservice/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb @@ -0,0 +1,442 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "dfba40d6", + "metadata": {}, + "outputs": [], + "source": [ + "from cuopt import routing\n", + "from cuopt import distance_engine\n", + "import cudf\n", + "from scipy.spatial import distance\n", + "import numpy as np\n", + "import requests\n", + "import polyline\n", + "import folium\n", + "import json" + ] + }, + { + "cell_type": "markdown", + "id": "cd4716e9", + "metadata": {}, + "source": [ + "# Cost Matrix Calculation" + ] + }, + { + "cell_type": "markdown", + "id": "bdff7c68", + "metadata": {}, + "source": [ + "The cost matrix represents the user defined cost of traversing from one state/location in the optimization problem to another. This matrix is what cuOpt uses to assess the quality of a given solution as it seeks to minimize the total cost.\n", + "\n", + "The cost matrix is a square matrix of dimension equal to the number of locations in a given problem. In the example below we see an illustration of one such matrix." + ] + }, + { + "cell_type": "markdown", + "id": "991cad72", + "metadata": {}, + "source": [ + "\"cost_matrix.png" + ] + }, + { + "cell_type": "markdown", + "id": "ed85b5b1", + "metadata": {}, + "source": [ + "Additionally:\n", + "- The cost of going from a location to itself (e.g Cost(A,A)) is typically 0 \n", + "- Cost(A,B) need not be equal to Cost(B,A)" + ] + }, + { + "cell_type": "markdown", + "id": "2fe0488b", + "metadata": {}, + "source": [ + "## Simple Metric" + ] + }, + { + "cell_type": "markdown", + "id": "667af128", + "metadata": {}, + "source": [ + "In some simple cases a cost matrix can be generated from a list of points according to a user defined metric (e.g. Euclidean, Manhattan, etc.)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "01c4a7de", + "metadata": {}, + "outputs": [], + "source": [ + "points = cudf.DataFrame({\"x_coord\": [1, 1, 2, 3], \"y_coord\":[3, 1, 4, 1]})\n", + "points" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b52d43bb", + "metadata": {}, + "outputs": [], + "source": [ + "cost_matrix = distance.cdist(points.to_pandas().values, points.to_pandas().values, \"euclidean\")\n", + "print(f\"Simple Metric Cost Matrix:\\n\\n{cost_matrix}\")" + ] + }, + { + "cell_type": "markdown", + "id": "eb39ea0f", + "metadata": {}, + "source": [ + "## Weighted Waypoint Graph" + ] + }, + { + "cell_type": "markdown", + "id": "a72d76a3", + "metadata": {}, + "source": [ + "In cases where a unique environment needs to be described such as in the case of factories or warehouses it can be useful to define a waypoint graph that defines the cost of travel between adjacent accessible points in the environment.\n", + "\n", + "cuOpt has built in functionality to compute a cost matrix based on key target locations within a given waypoint graph. In the graph below we model 10 distinct waypoints. The target locations are 0, 4, 5, and 6." + ] + }, + { + "cell_type": "markdown", + "id": "d8afe1d6", + "metadata": {}, + "source": [ + "\"waypoint_graph.png" + ] + }, + { + "cell_type": "markdown", + "id": "fadc253b", + "metadata": {}, + "source": [ + "#### Graph Description\n", + "A simple description of each node, it's outgoing edges and corresponding weights" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8bf07e3", + "metadata": {}, + "outputs": [], + "source": [ + "graph = {\n", + " 0:{\n", + " \"edges\":[2], \n", + " \"weights\":[2]},\n", + " 1:{\n", + " \"edges\":[2, 4], \n", + " \"weights\":[2, 2]},\n", + " 2:{\n", + " \"edges\":[0, 1, 3, 5], \n", + " \"weights\":[2, 2, 2, 2]},\n", + " 3:{\n", + " \"edges\":[2, 6], \n", + " \"weights\":[2, 2]},\n", + " 4:{\n", + " \"edges\":[1, 7], \n", + " \"weights\":[2, 1]},\n", + " 5:{\n", + " \"edges\":[2, 8], \n", + " \"weights\":[2, 1]},\n", + " 6:{\n", + " \"edges\":[3, 9], \n", + " \"weights\":[2, 1]},\n", + " 7:{\n", + " \"edges\":[4, 8], \n", + " \"weights\":[1, 2]},\n", + " 8:{\n", + " \"edges\":[5, 7, 9], \n", + " \"weights\":[1, 2, 2]},\n", + " 9:{\n", + " \"edges\":[6, 8], \n", + " \"weights\":[1, 2]}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "54d51f36", + "metadata": {}, + "source": [ + "#### Convert to CSR\n", + "cuOpt requires that the graph be in compressed sparse row (CSR) format. Here we define a simple function that converts our graph to CSR." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ace5c271", + "metadata": {}, + "outputs": [], + "source": [ + "def convert_to_csr(graph):\n", + " num_nodes = len(graph)\n", + " \n", + " offsets = []\n", + " edges = []\n", + " weights = []\n", + " \n", + " cur_offset = 0\n", + " for node in range(num_nodes):\n", + " offsets.append(cur_offset)\n", + " cur_offset += len(graph[node][\"edges\"])\n", + " \n", + " edges = edges + graph[node][\"edges\"]\n", + " weights = weights + graph[node][\"weights\"]\n", + " \n", + " offsets.append(cur_offset)\n", + " \n", + " return np.array(offsets), np.array(edges), np.array(weights)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fed80f3a", + "metadata": {}, + "outputs": [], + "source": [ + "offsets, edges, weights = convert_to_csr(graph)\n", + "print(f\"offsets = {list(offsets)}\")\n", + "print(f\"edges = {list(edges)}\")\n", + "print(f\"weights = {list(weights)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "17f18f56", + "metadata": {}, + "source": [ + "#### Define desired target locations and calculate the cost matrix " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45ddc548", + "metadata": {}, + "outputs": [], + "source": [ + "target_locations = np.array([0, 4, 5, 6])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "475edfd9", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "waypoint_graph = distance_engine.WaypointMatrix(\n", + " offsets,\n", + " edges,\n", + " weights\n", + ")\n", + "cost_matrix = waypoint_graph.compute_cost_matrix(target_locations)\n", + "target_map = {k:v for k, v in enumerate(target_locations)}\n", + "\n", + "print(f\"Index <-> Waypoint Mapping: \\n{target_map}\\n\\n Waypoint Graph Cost Matrix: \\n{cost_matrix}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cc7349c1", + "metadata": {}, + "source": [ + "## Map Based" + ] + }, + { + "cell_type": "markdown", + "id": "c1ea0b6e", + "metadata": {}, + "source": [ + "When dealing with problems in shipping and logistics, road distance and/or time is often used as a cost metric. In these cases there are a number of tools available to calculate drive distance and/or time. One such tool is the [Open Source Routing Machine](http://project-osrm.org/)(OSRM). In the below example we create a cost matrix using OSRM from a list of lat/lon coordinates." + ] + }, + { + "cell_type": "markdown", + "id": "0c6a374a", + "metadata": {}, + "source": [ + "#### Define Points of Interest" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3671b3e9", + "metadata": {}, + "outputs": [], + "source": [ + "lat_lon_coords = [\n", + " [33.698206, -117.851364],\n", + " [33.672260, -117.838925], \n", + " [33.721003, -117.864121], \n", + " [33.695563, -117.824500]\n", + "] " + ] + }, + { + "cell_type": "markdown", + "id": "2bf8d58a", + "metadata": {}, + "source": [ + "#### Create Distance Matrix via OSRM" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b1e0a74", + "metadata": {}, + "outputs": [], + "source": [ + "locations=\"\"\n", + "for loc in lat_lon_coords:\n", + " locations = locations + \"{},{};\".format(loc[1], loc[0])\n", + "r = requests.get(\"http://router.project-osrm.org/table/v1/driving/\"+ locations[:-1])\n", + "\n", + "routes = json.loads(r.content)\n", + "cols = [str(i) for i in lat_lon_coords]\n", + "cost_matrix = cudf.DataFrame(routes['durations'], columns = cols, index= cols)\n", + "print(f\"Cost Matrix via OSRM:\\n\")\n", + "cost_matrix" + ] + }, + { + "cell_type": "markdown", + "id": "3f6e69fc", + "metadata": {}, + "source": [ + "#### Map Visualization" + ] + }, + { + "cell_type": "markdown", + "id": "d71a3260", + "metadata": {}, + "source": [ + "Visualization can be a helpful tool for understanding and communication. Here we demonstrate a sample visualization implementation showing the routes represented by the cost matrix above." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15508e51", + "metadata": {}, + "outputs": [], + "source": [ + "def get_map(my_lat_longs):\n", + " m = folium.Map(location=[33.7, -117.83], #[52.52, 13.41],\n", + " zoom_start=13)\n", + " folium.Marker(\n", + " location=[my_lat_longs[0][0],my_lat_longs[0][1]] ,\n", + " icon=folium.Icon(icon='play', color='red')\n", + " ).add_to(m)\n", + " for loc in my_lat_longs[1:]:\n", + " folium.Marker(\n", + " location=[loc[0], loc[1]],\n", + " icon=folium.Icon(icon='stop', color='green')\n", + " ).add_to(m)\n", + " \n", + " for src_idx in range(len(lat_lon_coords)):\n", + " for dst_idx in range(len(lat_lon_coords)):\n", + " if src_idx == dst_idx:\n", + " break\n", + " source = lat_lon_coords[src_idx]\n", + " destination = lat_lon_coords[dst_idx]\n", + " loc = \"{},{};{},{}\".format(source[1], source[0], destination[1], destination[0])\n", + " url = \"http://router.project-osrm.org/route/v1/driving/\"\n", + " r = requests.get(url + loc) \n", + "\n", + " res = r.json() \n", + " routes = polyline.decode(res['routes'][0]['geometry'])\n", + "\n", + " folium.PolyLine(\n", + " routes,\n", + " weight=5,\n", + " color='blue',\n", + " opacity=0.6\n", + " ).add_to(m)\n", + "\n", + " return m\n", + "get_map(lat_lon_coords)" + ] + }, + { + "cell_type": "markdown", + "id": "745930d2", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + }, + "vscode": { + "interpreter": { + "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cpdptw-reoptmization-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cpdptw-reoptmization-checkpoint.ipynb new file mode 100644 index 0000000..385d6a1 --- /dev/null +++ b/notebooks/routing/microservice/.ipynb_checkpoints/cpdptw-reoptmization-checkpoint.ipynb @@ -0,0 +1,878 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "f2a6732c", + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "import pandas as pd\n", + "import notebook_utils.notebook_helpers as utils" + ] + }, + { + "cell_type": "markdown", + "id": "30be751d", + "metadata": {}, + "source": [ + "# Re-optimization\n", + "\n", + "## Capacitated Pickup and Delivery Problem with Time Windows\n" + ] + }, + { + "cell_type": "markdown", + "id": "afa87cd0", + "metadata": {}, + "source": [ + "In a factory set-up, routing needs to be optimized as and when a new set of tasks/requests are dispatched, this would help re-route robots to any other new task which got dispatched on the floor. This would require knowledge of all the robot's statuses like current location, tasks yet to done, and many other factors. \n", + "\n", + "In this scenario, we have 4 robots which are in a factory and serves 5 locations/nodes in the factory. Factory would be receiving new tasks of pickup and delivery for 3 different time stamps, and this would trigger rerouting to efficiently complete all the tasks.\n", + "\n", + "### Problem Details:\n", + "\n", + "- 5 locations\n", + "- 4 vehicles/robots\n", + " - capacity: [3, 3, 3, 3]\n", + " - vehicle start location : [0, 0, 0, 0]\n", + " - vehicle end location: [0, 0, 0, 0]\n", + " \n", + "At each time stamp, a set of tasks gets added:\n", + "\n", + "- Time Stamp: 0 time unit\n", + " - tasks : [1, 2, 3, 4, 3, 2, 1, 3]\n", + "- Time Stamp: 4 time units\n", + " - tasks : [4, 2, 3, 1]\n", + "- Time Stamp: 6 time units\n", + " - tasks : [2, 3]\n", + " \n", + "**API Reference**: [cuOpt Server Documentation](https://docs.nvidia.com/cuopt/serv_api.html)" + ] + }, + { + "cell_type": "markdown", + "id": "8686c3cb", + "metadata": {}, + "source": [ + "### Assumptions\n", + "- Fleet size and capacity would remain same\n", + "- Vehicles en route to service a particular task will finish that task first\n", + "- The original optimized plan is going according to the plan, so we can determine the finished tasks just based on the time stamp\n", + "- The problem is pickup and delivery only\n", + "- There is only one capacity demand dimension" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ff2a1a1", + "metadata": {}, + "outputs": [], + "source": [ + "# Provide URL information of the server\n", + "ip = \"0.0.0.0\"\n", + "port = \"5000\"\n", + "url = \"http://\" + ip + \":\" + port + \"/cuopt/\"\n", + "\n", + "# Test server health\n", + "assert requests.get(url + \"health\").status_code == 200" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9bbbf3f0", + "metadata": {}, + "outputs": [], + "source": [ + " # Processes input data, communicates data to cuOpt server and returns Optimized routes\n", + "def get_optimized_route(data, url, update=False):\n", + " \n", + " data_params = {\"return_data_state\": False}\n", + " \n", + "\n", + " # Set/Update cost matrix\n", + " \n", + " if \"cost_matrices\" in data:\n", + " if not update:\n", + " resp = requests.post(\n", + " url + \"set_cost_matrix\", params=data_params, json={\"cost_matrix\": data[\"cost_matrices\"]}\n", + " )\n", + " else:\n", + " resp = requests.put(\n", + " url + \"update_cost_matrix\", params=data_params, json={\"cost_matrix\": data[\"cost_matrices\"]}\n", + " )\n", + " assert resp.status_code == 200\n", + " \n", + " \n", + " # Set/Update Transit time matrix \n", + " \n", + " if \"transit_time_matrices\" in data:\n", + " if not update:\n", + " resp = requests.post(\n", + " url + \"set_travel_time_matrix\", params=data_params, json={\"cost_matrix\": data[\"transit_time_matrices\"]}\n", + " )\n", + " else:\n", + " resp = requests.put(\n", + " url + \"update_travel_time_matrix\", params=data_params, json={\"cost_matrix\": data[\"transit_time_matrices\"]}\n", + " )\n", + " assert resp.status_code == 200\n", + " \n", + " \n", + " # Set/Update Task data\n", + " \n", + " task_data = {}\n", + " if \"task_locations\" in data:\n", + " task_data[\"task_locations\"] = data[\"task_locations\"]\n", + " if \"pickup_indices\" in data and \"delivery_indices\" in data:\n", + " task_data[\"pickup_and_delivery_pairs\"] = [\n", + " [pickup_idx, delivery_idx] for pickup_idx, delivery_idx in zip(\n", + " data[\"pickup_indices\"], data[\"delivery_indices\"]\n", + " )\n", + " ]\n", + " elif \"pickup_indices\" in data or \"delivery_indices\" in data:\n", + " raise ValueError(\"Pick indices or Delivery indices are missing, both should be provided\")\n", + " \n", + " if \"task_earliest_time\" in data and \"task_latest_time\" in data and \"task_service_time\" in data:\n", + " task_data[\"task_time_windows\"] = [\n", + " [earliest, latest] for earliest, latest in zip(data[\"task_earliest_time\"], data[\"task_latest_time\"])\n", + " ]\n", + " task_data[\"service_times\"] = data[\"task_service_time\"]\n", + " elif \"task_earliest_time\" in data or \"task_latest_time\" in data or \"task_service_time\" in data:\n", + " raise ValueError(\"Earliest, Latest and Service time should be provided, one or more are missing\")\n", + " \n", + " if \"demand\" in data:\n", + " task_data[\"demand\"] = data[\"demand\"]\n", + " \n", + " if \"order_vehicle_match\" in data:\n", + " task_data[\"order_vehicle_match\"] = data[\"order_vehicle_match\"]\n", + " \n", + " if len(task_data) > 0:\n", + " resp = requests.post(url + \"set_task_data\", json=task_data)\n", + " assert resp.status_code == 200\n", + "\n", + " \n", + " # Set/Update Fleet data\n", + " \n", + " fleet_data = {}\n", + " \n", + " if \"vehicle_locations\" in data:\n", + " fleet_data[\"vehicle_locations\"] = data[\"vehicle_locations\"]\n", + " \n", + " if \"capacity\" in data:\n", + " fleet_data[\"capacities\"] = data[\"capacity\"]\n", + " \n", + " if \"vehicle_earliest\" in data and \"vehicle_latest\" in data:\n", + " fleet_data[\"vehicle_time_windows\"] = [\n", + " [earliest, latest] for earliest, latest in zip(data[\"vehicle_earliest\"], data[\"vehicle_latest\"])\n", + " ]\n", + " elif \"vehicle_earliest\" in data or \"vehicle_latest\" in data:\n", + " raise ValueError(\"vehicle_earliest and vehicle_latest both should be provided, one of them is missing\")\n", + " \n", + " if len(fleet_data) > 0:\n", + " if not update:\n", + " resp = requests.post(url + \"set_fleet_data\", json=fleet_data)\n", + " else:\n", + " resp = requests.put(url + \"update_fleet_data\", json=fleet_data)\n", + " assert resp.status_code == 200\n", + " \n", + " # Set Solver settings\n", + " \n", + " solver_config = {\n", + " \"time_limit\": 0.01\n", + " }\n", + " resp = requests.post(url + \"set_solver_config\", json=solver_config) \n", + " assert resp.status_code == 200 \n", + " \n", + " # Get optimized route\n", + " \n", + " solver_response = requests.get(url + \"get_optimized_routes\")\n", + " assert solver_response.status_code == 200\n", + " \n", + " return solver_response.json()[\"response\"][\"solver_response\"]" + ] + }, + { + "cell_type": "markdown", + "id": "0df9f137", + "metadata": {}, + "source": [ + " ### Following function accumulates status of tasks and vehicles\n", + " \n", + " - Collect status of tasks [Completed, Picked-up but not yet Delivered, Yet to Pick-up]\n", + " - Along with that update vehicle locations, vehicle earliest times at a given time.\n", + " - It is also required to keep tabs on tasks which have been picked up by particular vehicle, to ensure that\n", + " that task is fulfilled by the same vehicle." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "266f5a6b", + "metadata": {}, + "outputs": [], + "source": [ + "def collect_current_status(previous_data, optimized_route_data, reroute_from_time):\n", + " \n", + " transit_times = previous_data[\"transit_time_matrices\"][0]\n", + " task_locations = previous_data[\"task_locations\"]\n", + " vehicle_start_locations = [val[0] for val in previous_data[\"vehicle_locations\"]]\n", + " vehicle_return_locations = [val[1] for val in previous_data[\"vehicle_locations\"]]\n", + " \n", + " # Create a mapping between pickup and delivery\n", + " pickup_of_delivery = {\n", + " previous_data[\"delivery_indices\"][i]: previous_data[\"pickup_indices\"][i]\n", + " for i in range(len(previous_data[\"pickup_indices\"]))\n", + " }\n", + " \n", + " # Update vehicle earliest if needs to be changed to current time\n", + " vehicle_earliest = [max(earliest, reroute_from_time) for earliest in previous_data[\"vehicle_earliest\"]]\n", + " \n", + " # Collect completed and partial set of tasks, so we can add partialy completed tasks back\n", + " completed_tasks = []\n", + " picked_up_but_not_delivered = {}\n", + " picked_up_task_to_vehicle = {}\n", + " \n", + " for veh_id, veh_data in optimized_route_data[\"vehicle_data\"].items():\n", + " route_len = len(veh_data['route'])\n", + " task_len = len(veh_data[\"task_id\"])\n", + " vehicle_id = int(veh_id)\n", + " \n", + " # In this case, all the tasks are already completed, or waiting on last task service time\n", + " if veh_data['arrival_stamp'][-1] <= reroute_from_time:\n", + " intra_task_id = task_len\n", + " else:\n", + " try:\n", + " # Look for a task that is yet to be completed\n", + " intra_task_id, time = next(\n", + " (i, el)\n", + " for i, el in enumerate(veh_data['arrival_stamp'])\n", + " if el > reroute_from_time\n", + " )\n", + " except StopIteration:\n", + " # In case none of the tasks are completed\n", + " intra_task_id = 0\n", + " time = max(vehicle_earliest[vehicle_id], reroute_from_time)\n", + "\n", + " # All the tasks are completed and vehicle is on the way to return location or already reached\n", + "\n", + " picked_up_but_not_delivered[vehicle_id] = []\n", + " \n", + " # There are tasks that are still pending\n", + " if intra_task_id < task_len:\n", + " last_task = veh_data[\"task_id\"][intra_task_id]\n", + " \n", + " # Update vehicle start location\n", + " vehicle_start_locations[int(vehicle_id)] = task_locations[last_task]\n", + " \n", + " # Update vehicle earliest\n", + " vehicle_earliest[int(vehicle_id)] = min(\n", + " max(time, reroute_from_time), previous_data[\"vehicle_latest\"][vehicle_id]\n", + " )\n", + " \n", + " for j in range(0, intra_task_id):\n", + " task = veh_data[\"task_id\"][j]\n", + " if task in previous_data[\"pickup_indices\"]:\n", + " picked_up_but_not_delivered[vehicle_id].append(task)\n", + " picked_up_task_to_vehicle[task] = vehicle_id\n", + " else:\n", + " # Moves any delivered pick-up tasks to completed.\n", + " corresponding_pickup = pickup_of_delivery[task]\n", + " picked_up_but_not_delivered[vehicle_id].remove(\n", + " corresponding_pickup\n", + " )\n", + " completed_tasks.append(corresponding_pickup)\n", + " completed_tasks.append(task)\n", + " picked_up_task_to_vehicle.pop(corresponding_pickup)\n", + " else:\n", + " completed_tasks.extend(veh_data[\"task_id\"])\n", + " # In this case vehicle is at last location about to finish the task,\n", + " # so vehicle start location would last task location and accordingly the earliest vehicle time as well\n", + " if (veh_data['arrival_stamp'][-1] == reroute_from_time) and (veh_data['arrival_stamp'][-1]+previous_data[\"task_service_time\"][veh_data[\"task_id\"][-1]] >= reroute_from_time):\n", + " vehicle_start_locations[vehicle_id] = task_locations[veh_data[\"task_id\"][-1]]\n", + " vehicle_earliest[vehicle_id] = veh_data['arrival_stamp'][-1] + previous_data[\"task_service_time\"][veh_data[\"task_id\"][-1]] \n", + " else:\n", + " # In this case vehicle completed last task and may be enroute to vehicle return location or might have reached.\n", + " end_time = (\n", + " veh_data['arrival_stamp'][-1] + previous_data[\"task_service_time\"][veh_data[\"task_id\"][-1]] + transit_times[task_locations[veh_data[\"task_id\"][-1]]][vehicle_return_locations[vehicle_id]]\n", + " )\n", + " time = max(end_time, reroute_from_time)\n", + " print(\"For vehicle ID updating\", vehicle_id)\n", + " vehicle_start_locations[vehicle_id] = vehicle_return_locations[vehicle_id]\n", + " vehicle_earliest[vehicle_id] = min(time, previous_data[\"vehicle_earliest\"][vehicle_id])\n", + " \n", + " return (\n", + " vehicle_earliest, vehicle_start_locations,\n", + " vehicle_return_locations, completed_tasks,\n", + " picked_up_but_not_delivered, picked_up_task_to_vehicle)" + ] + }, + { + "cell_type": "markdown", + "id": "179691ce", + "metadata": {}, + "source": [ + "### Redefine old data at current time\n", + "- Unfinished task data gets moved up the task locations\n", + "- This changes task id which affects pick-up and delivery indices, so they get remapped\n", + "- Similarly update the earliest time appropriately, since this might change with current time" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99804c87", + "metadata": {}, + "outputs": [], + "source": [ + "def redefine_old_data_to_current_time(\n", + " previous_data, vehicle_earliest, vehicle_start_locations,\n", + " completed_tasks, picked_up_task_to_vehicle, reroute_from_time\n", + "):\n", + " data = {\n", + " \"task_locations\" : [],\n", + " \"pickup_indices\" : [],\n", + " \"delivery_indices\" : [],\n", + " \"task_earliest_time\" : [],\n", + " \"task_latest_time\" : [],\n", + " \"task_service_time\" : [],\n", + " # assume that fleet is not changing\n", + " \"vehicle_capacity\" : previous_data[\"capacity\"],\n", + " \"task_demand\" : [[]*len(previous_data[\"capacity\"])],\n", + " }\n", + " \n", + " # If there are unfinished tasks (picked up but not delivered yet) at\n", + " # the time of re-routing, create new pickup tasks from the vehicle\n", + " # locations at time zero (i.e. re-routing start time)\n", + " \n", + " cnt = 0\n", + " # Mapping new task Id to old task Id and old task Id to new task Id\n", + " new_task_to_old_task = {}\n", + " old_task_to_new_task = {}\n", + " \n", + " ntasks = len(previous_data[\"task_locations\"])\n", + " \n", + " for task_id in range(0, ntasks):\n", + " if completed_tasks.count(task_id) == 0:\n", + " new_task_to_old_task[cnt] = task_id\n", + " old_task_to_new_task[task_id] = cnt\n", + " cnt = cnt + 1\n", + " \n", + " # Convert pickup and delivery indices to new numbering\n", + " for i in range(len(previous_data[\"pickup_indices\"])):\n", + " pickup = previous_data[\"pickup_indices\"][i]\n", + " delivery = previous_data[\"delivery_indices\"][i]\n", + " if pickup not in completed_tasks:\n", + " new_pickup = old_task_to_new_task[pickup]\n", + " new_delivery = old_task_to_new_task[delivery]\n", + " data[\"pickup_indices\"].append(new_pickup)\n", + " data[\"delivery_indices\"].append(new_delivery)\n", + " \n", + " # Convert task to vehicle to new numbering\n", + " new_picked_up_task_to_vehicle = [\n", + " {\n", + " \"order_id\" :old_task_to_new_task[pickup],\n", + " \"vehicle_ids\" :[vehicle]\n", + " }\n", + " for pickup, vehicle in picked_up_task_to_vehicle.items()\n", + " ]\n", + " \n", + " new_picked_up_task_list = [val[\"order_id\"] for val in new_picked_up_task_to_vehicle]\n", + " \n", + " # Extract the task info of incomplete tasks\n", + " for i in range(0, len(new_task_to_old_task)):\n", + " old_id = new_task_to_old_task[i]\n", + " is_pickup_task = i in data[\"pickup_indices\"]\n", + "\n", + " # If this task is already picked up, make sure that in the new problem\n", + " # it is picked up at time zero\n", + " if is_pickup_task and i in new_picked_up_task_list:\n", + " vehicle = picked_up_task_to_vehicle[old_id]\n", + " new_loc = vehicle_start_locations[vehicle]\n", + " pickup_time = vehicle_earliest[vehicle]\n", + " data[\"task_locations\"].append(new_loc)\n", + " data[\"task_earliest_time\"].append(pickup_time)\n", + " data[\"task_latest_time\"].append(pickup_time)\n", + " data[\"task_service_time\"].append(0)\n", + " else:\n", + " data[\"task_locations\"].append(previous_data[\"task_locations\"][old_id])\n", + " data[\"task_earliest_time\"].append(max(previous_data[\"task_earliest_time\"][old_id], reroute_from_time))\n", + " data[\"task_latest_time\"].append(previous_data[\"task_latest_time\"][old_id])\n", + " data[\"task_service_time\"].append(previous_data[\"task_service_time\"][old_id])\n", + "\n", + " for idx, demands in enumerate(previous_data[\"demand\"]):\n", + " # assume that fleet is not changing\n", + " demand = demands[old_id]\n", + " data[\"task_demand\"][idx].append(demand)\n", + " \n", + " data[\"order_vehicle_match\"] = new_picked_up_task_to_vehicle\n", + " \n", + " return data, new_task_to_old_task, old_task_to_new_task" + ] + }, + { + "cell_type": "markdown", + "id": "6b6146ef", + "metadata": {}, + "source": [ + "### Reconstructs data adding pending tasks with new tasks taking current env status" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67284da4", + "metadata": {}, + "outputs": [], + "source": [ + "def reconstruct_task_data(\n", + " previous_data, optimized_route_data, reroute_from_time, new_task_data,\n", + "): \n", + " # Approach:\n", + " # ---------\n", + " # 1. Using the optimized route and the time of re-optimization, we figure\n", + " # out which tasks are fulfilled (picked up and delivered), partially\n", + " # fulfilled (picked up but not delivered), and not initiated\n", + " # 2. We remove the tasks that are fulfilled while keeping the tasks that\n", + " # are not initiated. For the partially fulfilled tasks, we create dummy\n", + " # pickup tasks at vehicle start locations\n", + " \n", + " if new_task_data is not None:\n", + " expected_entries_in_new_task_data = [\n", + " \"task_locations\",\n", + " \"task_earliest_time\",\n", + " \"task_latest_time\",\n", + " \"task_service_time\",\n", + " \"pickup_indices\",\n", + " \"delivery_indices\",\n", + " \"demand\",\n", + " ]\n", + " for entry in expected_entries_in_new_task_data:\n", + " if entry not in new_task_data:\n", + " raise ValueError(f\"{entry} is missing in new task data\")\n", + "\n", + " for entry, value in new_task_data.items():\n", + " if entry not in expected_entries_in_new_task_data:\n", + " raise NotImplementedError(\n", + " f\"{entry} is not implemented for re-optimization\"\n", + " )\n", + " \n", + " if \"cost_matrices\" in new_task_data:\n", + " if not len(new_task_data[\"cost_matrices\"][0]) == len(previous_data[\"cost_matrices\"][0]):\n", + " raise ValueError(\"Shape of cost matrices is not matching\")\n", + " \n", + " if \"transit_time_matrices\" in new_task_data:\n", + " if not len(new_task_data[\"transit_time_matrices\"][0]) == len(previous_data[\"transit_time_matrices\"][0]):\n", + " raise ValueError(\"Shape of transit time matrices is not matching\")\n", + " \n", + " \n", + " vehicle_num = len(previous_data[\"vehicle_locations\"])\n", + " n_locations = len(previous_data[\"cost_matrices\"][0])\n", + " task_locations = previous_data[\"task_locations\"]\n", + " \n", + " # - Collect status of tasks [Completed, Picked-up note yet Delivered, Yet to Pick-up]\n", + " # - Along with that update vehicle locations, vehicle earliest times at given time.\n", + " # - It is also required to keep tabs on tasks which have been picked up by particular vehicle, to ensure that\n", + " # that task is fulfilled by same vehicle.\n", + " (\n", + " vehicle_earliest, vehicle_start_locations,\n", + " vehicle_return_locations, completed_tasks,\n", + " picked_up_but_not_delivered, picked_up_task_to_vehicle\n", + " ) = collect_current_status(previous_data, optimized_route_data, reroute_from_time)\n", + " \n", + " \n", + " # Map old task as new tasks with given time, in this the old task gets new ids since they are moved\n", + " # upfront in task locations removing completed tasks.\n", + " restructured_data, new_task_to_old_task, old_task_to_new_task = redefine_old_data_to_current_time(\n", + " previous_data, vehicle_earliest, vehicle_start_locations,\n", + " completed_tasks, picked_up_task_to_vehicle, reroute_from_time\n", + " )\n", + " \n", + " n_leftover_tasks = len(restructured_data[\"task_locations\"])\n", + " \n", + " # Append new task data to pending tasks details\n", + " \n", + " restructured_data[\"task_locations\"].extend(new_task_data[\"task_locations\"])\n", + " restructured_data[\"task_earliest_time\"].extend(new_task_data[\"task_earliest_time\"])\n", + " restructured_data[\"task_latest_time\"].extend(new_task_data[\"task_latest_time\"])\n", + " restructured_data[\"task_service_time\"].extend(new_task_data[\"task_service_time\"])\n", + "\n", + " # new task data consists of indices with respect to new task data\n", + " adjusted_pickup_indices = [\n", + " id + n_leftover_tasks for id in new_task_data[\"pickup_indices\"]\n", + " ]\n", + " adjusted_delivery_indices = [\n", + " id + n_leftover_tasks for id in new_task_data[\"delivery_indices\"]\n", + " ]\n", + " restructured_data[\"pickup_indices\"].extend(adjusted_pickup_indices)\n", + " restructured_data[\"delivery_indices\"].extend(adjusted_delivery_indices)\n", + " \n", + " restructured_data[\"task_demand\"] = [\n", + " old_demand + new_demand for old_demand, new_demand in zip(\n", + " restructured_data[\"task_demand\"], new_task_data[\"demand\"]\n", + " )\n", + " ]\n", + " \n", + " restructured_data[\"vehicle_locations\"] = [\n", + " [start, ret] for start, ret in zip(vehicle_start_locations, vehicle_return_locations)\n", + " ]\n", + " \n", + " # Form the output data\n", + " \n", + " reconstructed_data = {\n", + " \"cost_matrices\" : (\n", + " new_task_data[\"cost_matrices\"] if \"cost_matrices\" in new_task_data else previous_data[\"cost_matrices\"]\n", + " ),\n", + " \"transit_time_matrices\" : (\n", + " new_task_data[\"transit_time_matrices\"] if \"transit_time_matrices\" in new_task_data else previous_data[\"transit_time_matrices\"]\n", + " ),\n", + " \"task_locations\": restructured_data[\"task_locations\"],\n", + " \"pickup_indices\": restructured_data[\"pickup_indices\"], \n", + " \"delivery_indices\": restructured_data[\"delivery_indices\"],\n", + " \"task_earliest_time\": restructured_data[\"task_earliest_time\"],\n", + " \"task_latest_time\": restructured_data[\"task_latest_time\"],\n", + " \"task_service_time\": restructured_data[\"task_service_time\"],\n", + " \"demand\": restructured_data[\"task_demand\"],\n", + " \"capacity\": restructured_data[\"vehicle_capacity\"],\n", + " \"vehicle_locations\": restructured_data[\"vehicle_locations\"],\n", + " \"vehicle_earliest\": vehicle_earliest,\n", + " \"vehicle_latest\": previous_data[\"vehicle_latest\"],\n", + " \"order_vehicle_match\": restructured_data[\"order_vehicle_match\"]\n", + " }\n", + " \n", + " return reconstructed_data, completed_tasks" + ] + }, + { + "cell_type": "markdown", + "id": "b4574c43", + "metadata": {}, + "source": [ + "## Reoptimization across different time stamps\n", + "________________________________________________\n" + ] + }, + { + "cell_type": "markdown", + "id": "871910af", + "metadata": {}, + "source": [ + "## Time Stamp : 0 time unit\n", + "- Solver gets first set of tasks to plan for their optimized routes\n", + "- Vehicles/Robots all start from their start locations" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c734805", + "metadata": {}, + "outputs": [], + "source": [ + "# Input data - 1\n", + "\n", + "input_data_1 = {}\n", + "\n", + "input_data_1[\"cost_matrices\"] = {0:[\n", + " [0, 1, 2, 3, 4],\n", + " [1, 0, 2, 3, 4],\n", + " [2, 3, 0, 4, 1],\n", + " [3, 4, 1, 0, 2],\n", + " [4, 1, 2, 3, 0]\n", + "]}\n", + "\n", + "input_data_1[\"transit_time_matrices\"] = {0:[\n", + " [0, 1, 1, 1, 1],\n", + " [1, 0, 1, 1, 1],\n", + " [1, 1, 0, 1, 1],\n", + " [1, 1, 1, 0, 1],\n", + " [1, 1, 1, 1, 0]\n", + "]}\n", + " \n", + "# Task data\n", + "input_data_1[\"task_locations\"] = [1, 2, 3, 4, 3, 2, 1, 3]\n", + "input_data_1[\"pickup_indices\"] = [0, 2, 4, 6]\n", + "input_data_1[\"delivery_indices\"] = [1, 3, 5, 7]\n", + "input_data_1[\"task_earliest_time\"] = [0, 2, 1, 2, 0, 0, 2, 1]\n", + "input_data_1[\"task_latest_time\"] = [5, 4, 5, 5, 7, 7, 10, 9]\n", + "input_data_1[\"task_service_time\"] = [1, 1, 1, 1, 1, 1, 1, 1]\n", + "input_data_1[\"demand\"] = [[1, -1, 1, -1, 1, -1, 1, -1]]\n", + "\n", + "# Vehicle data\n", + "input_data_1[\"vehicle_locations\"] = [[0, 0]] * 4 \n", + "input_data_1[\"vehicle_earliest\"] = [0.0, 0.0, 2.0, 2.0]\n", + "input_data_1[\"vehicle_latest\"] = [8.0, 8.0, 15.0, 15.0]\n", + "input_data_1[\"capacity\"] = [[3, 3, 3, 3]]\n", + "\n", + "# Solve for optimized route\n", + "resp = get_optimized_route(input_data_1, url)" + ] + }, + { + "cell_type": "markdown", + "id": "fb5b6c58", + "metadata": {}, + "source": [ + "#### Optimized route" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f15cf585", + "metadata": {}, + "outputs": [], + "source": [ + "utils.print_vehicle_data(resp)" + ] + }, + { + "cell_type": "markdown", + "id": "84e00e2d", + "metadata": {}, + "source": [ + "## Time Stamp: 4 time units\n", + "- 4 New tasks gets as added\n", + "- it's a pair of pick-up and delivery tasks\n", + "- As per observation of the result above, at time stamp 4, only 2 pair of pick-up and delivery is completed, 2 are still pendng" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8148bfb8", + "metadata": {}, + "outputs": [], + "source": [ + "# time stamnp\n", + "current_time = 4\n", + "\n", + "# new set of tasks\n", + "\n", + "new_task_data = {}\n", + "new_task_data[\"task_locations\"] = [4, 2, 3, 1]\n", + "new_task_data[\"pickup_indices\"] = [0, 2]\n", + "new_task_data[\"delivery_indices\"] = [1, 3]\n", + "new_task_data[\"task_earliest_time\"] = [3, 4, 8, 9]\n", + "new_task_data[\"task_latest_time\"] = [8, 9, 10, 10]\n", + "new_task_data[\"task_service_time\"] = [1, 1, 1, 1]\n", + "new_task_data[\"demand\"] = [[1, -1, 1, -1]]" + ] + }, + { + "cell_type": "markdown", + "id": "304a79cf", + "metadata": {}, + "source": [ + "#### Reconstruct task data adding pending tasks and new tasks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07dcb09a", + "metadata": {}, + "outputs": [], + "source": [ + "updated_task_data, completed_tasks = reconstruct_task_data(input_data_1, resp, current_time, new_task_data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dc8e3a7f", + "metadata": {}, + "outputs": [], + "source": [ + "utils.print_data(updated_task_data, completed_tasks)" + ] + }, + { + "cell_type": "markdown", + "id": "ca58dea3", + "metadata": {}, + "source": [ + "#### Analyzing updated task data\n", + "\n", + "- It can be observed that now the updated task data has 4 more tasks along with 4 new tasks, which makes it 4 sets of pickup and delivery\n", + "- The vehicle earliest, vehicle start locations have been updated with current location of the robots\n", + "- The previous task which were picked-up but not yet delivered are assigned to the same vehicles which had picked them up through task to vehicle match\n", + "- All the vehicle earliest is changed to current time if they were less than that" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff1d4edd", + "metadata": {}, + "outputs": [], + "source": [ + "resp = get_optimized_route(updated_task_data, url, update=True)" + ] + }, + { + "cell_type": "markdown", + "id": "4707d474", + "metadata": {}, + "source": [ + "#### Optimized route" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1ab4768", + "metadata": {}, + "outputs": [], + "source": [ + "utils.print_vehicle_data(resp)" + ] + }, + { + "cell_type": "markdown", + "id": "76a5a3ed", + "metadata": {}, + "source": [ + "## Time Stamp: 6 time units\n", + "- 2 New tasks gets as added\n", + "- it's a pick-up and delivery\n", + "- As per observation of the result above, at time stamp 6, only 2 pair of pick-up and delivery is completed, 2 are still pendng and now one more pickup and delivery is added" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9107e6b1", + "metadata": {}, + "outputs": [], + "source": [ + "# time stamnp\n", + "current_time = 6\n", + "\n", + "# new set of tasks\n", + "\n", + "new_task_data = {}\n", + "new_task_data[\"task_locations\"] = [2, 3]\n", + "new_task_data[\"pickup_indices\"] = [0]\n", + "new_task_data[\"delivery_indices\"] = [1]\n", + "new_task_data[\"task_earliest_time\"] = [10, 9]\n", + "new_task_data[\"task_latest_time\"] = [12, 14]\n", + "new_task_data[\"task_service_time\"] = [1, 1]\n", + "new_task_data[\"demand\"] = [[1, -1]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89a14294", + "metadata": {}, + "outputs": [], + "source": [ + "updated_task_data, completed_tasks = reconstruct_task_data(updated_task_data, resp, current_time, new_task_data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10c59203", + "metadata": {}, + "outputs": [], + "source": [ + "utils.print_data(updated_task_data, completed_tasks)" + ] + }, + { + "cell_type": "markdown", + "id": "6a964990", + "metadata": {}, + "source": [ + "#### Analyzing updated task data\n", + "- Out of 8 tasks, 4 are completed and 4 are pending and we are adding 2 more, at the end we have 3 pair of pick-up and delivery\n", + "- Vehicle start locations have changed to location 2 and 3 for vehicles 2 and 3.\n", + "- Only task 0 is bound to a vehicle sice it already picked-up before" + ] + }, + { + "cell_type": "markdown", + "id": "87786df2", + "metadata": {}, + "source": [ + "#### Optimized route" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "783e555f", + "metadata": {}, + "outputs": [], + "source": [ + "resp = get_optimized_route(updated_task_data, url, update=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04cc5332", + "metadata": {}, + "outputs": [], + "source": [ + "utils.print_vehicle_data(resp)" + ] + }, + { + "cell_type": "markdown", + "id": "f989f957", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb new file mode 100644 index 0000000..471d852 --- /dev/null +++ b/notebooks/routing/microservice/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb @@ -0,0 +1,610 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "3631e3f7", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import requests\n", + "import notebook_utils.notebook_helpers as utils" + ] + }, + { + "cell_type": "markdown", + "id": "9326712e", + "metadata": {}, + "source": [ + "# Intra-factory Transport\n", + "## Capacitated Pickup and Delivery Problem with Time Windows" + ] + }, + { + "cell_type": "markdown", + "id": "382afbd9", + "metadata": {}, + "source": [ + "Factory automation allows companies to raise the quality and consistency of manufacturing processes while also allowing human workers to focus on safer, less repetitive tasks that have higher cognitive and creative demands.\n", + "\n", + "In this scenario we have a set of intra-factory transport orders to move products at various stages in the assembly process from one processing station to another. Each station represents a particular type of manufacturing process and a given product may need to visit each processing station more than once. Multiple autonomous mobile robots (AMRs) with a fixed capacity will execute pickup and delivery orders between target locations, all with corresponding time_windows." + ] + }, + { + "cell_type": "markdown", + "id": "c3bc4ad4", + "metadata": {}, + "source": [ + "### Problem Details:\n", + "- 4 Locations each with an associated demand\n", + " - 1 Start Location for AMRs\n", + "\n", + " - 3 Process Stations\n", + "\n", + "- 3 AMRs with associated capacity" + ] + }, + { + "cell_type": "markdown", + "id": "e6090764", + "metadata": {}, + "source": [ + "- Hours of operation" + ] + }, + { + "cell_type": "markdown", + "id": "24cff46b", + "metadata": {}, + "source": [ + "**API Reference**: [cuOpt Server Documentation](https://docs.nvidia.com/cuopt/serv_api.html)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d12f05d", + "metadata": {}, + "outputs": [], + "source": [ + "factory_open_time = 0\n", + "factory_close_time = 100" + ] + }, + { + "cell_type": "markdown", + "id": "32c76994", + "metadata": {}, + "source": [ + "### Setup the cuOpt server and test the health of the server\n", + "\n", + "**NOTE**: Please update **ip** and **port** on which the server is running." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c1316c4", + "metadata": {}, + "outputs": [], + "source": [ + "ip = \"0.0.0.0\"\n", + "port = \"5000\"\n", + "url = \"http://\" + ip + \":\" + port + \"/cuopt/\"\n", + "\n", + "# Test server health\n", + "assert requests.get(url + \"health\").status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "e67a05ed", + "metadata": {}, + "source": [ + "![waypoint_graph.png not found](./notebook_utils/images/waypoint_graph.png \"Waypoint Graph\")" + ] + }, + { + "cell_type": "markdown", + "id": "c89d0f91", + "metadata": {}, + "source": [ + "### Set location names" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0828c9c7", + "metadata": {}, + "outputs": [], + "source": [ + "location_names = [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"]" + ] + }, + { + "cell_type": "markdown", + "id": "d90ba90d", + "metadata": {}, + "source": [ + "### Waypoint Graph" + ] + }, + { + "cell_type": "markdown", + "id": "6febdb57", + "metadata": {}, + "source": [ + "#### Compressed Sparse Row (CSR) representation of above weighted waypoint graph.\n", + "For details on the CSR encoding of the above graph see the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c824c99", + "metadata": {}, + "outputs": [], + "source": [ + "offsets = [0, 1, 3, 7, 9, 11, 13, 15, 17, 20, 22]\n", + "edges = [2, 2, 4, 0, 1, 3, 5, 2, 6, 1, 7, 2, 8, 3, 9, 4, 8, 5, 7, 9, 6, 8]\n", + "weights = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2]" + ] + }, + { + "cell_type": "markdown", + "id": "dbfcfa33", + "metadata": {}, + "source": [ + "#### Select specific waypoints in the graph as target locations\n", + "In this case we would like the AMRs to begin from waypoint 0 and service locations 4, 5, and 6." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e08f664", + "metadata": {}, + "outputs": [], + "source": [ + "# Setup service locations\n", + "target_locations = [0, 4, 5, 6]" + ] + }, + { + "cell_type": "markdown", + "id": "4f11fdb2", + "metadata": {}, + "source": [ + "### Transport Orders\n", + "Setup Transport Order Data\n", + "\n", + "The transport orders dictate the movement of parts from one area of the factory to another. In this example nodes 4, 5, and 6 represent the processing stations that parts must travel between and deliveries to node 0 represent the movement of parts off the factory floor." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12c3c595", + "metadata": {}, + "outputs": [], + "source": [ + "transport_order_data = pd.DataFrame({\n", + " \"pickup_location\": [4, 5, 6, 6, 5, 4],\n", + " \"delivery_location\": [5, 6, 0, 5, 4, 0],\n", + " \"order_demand\": [1, 1, 1, 1, 1, 1],\n", + " \"earliest_pickup\": [0, 0, 0, 0, 0, 0],\n", + " \"latest_pickup\": [10, 20, 30, 10, 20, 30],\n", + " \"pickup_service_time\": [2, 2, 2, 2, 2, 2],\n", + " \"earliest_delivery\": [0, 0, 0, 0, 0, 0],\n", + " \"latest_delivery\": [45, 45, 45, 45, 45, 45],\n", + " \"delivery_service_time\": [2, 2, 2, 2, 2, 2]\n", + "})\n", + "transport_order_data" + ] + }, + { + "cell_type": "markdown", + "id": "7af883ad", + "metadata": {}, + "source": [ + "### Set Waypoint Graph" + ] + }, + { + "cell_type": "markdown", + "id": "52bdc1d0", + "metadata": {}, + "source": [ + "cuOpt will use this waypoint graph along with task locations and vehicle locations to determine cost matrix internally from one location to another. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9975bf1a", + "metadata": {}, + "outputs": [], + "source": [ + "graph_data = {\n", + " \"edges\": edges,\n", + " \"offsets\": offsets,\n", + " \"weights\": weights,\n", + "}\n", + "cost_waypoint_graph_data = {'waypoint_graph': {0: graph_data}}\n", + "response_set = requests.post(\n", + " url + \"set_cost_waypoint_graph\", json=cost_waypoint_graph_data\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "1644ce90", + "metadata": {}, + "source": [ + "### Set Order/Task data\n" + ] + }, + { + "cell_type": "markdown", + "id": "449ec36b", + "metadata": {}, + "source": [ + "#### Process Order locations\n", + "\n", + "Order locations, pickup and delivery pairs are processed and created to be digested bu cuOpt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1440c936", + "metadata": {}, + "outputs": [], + "source": [ + "task_data = {}\n", + "\n", + "pickup_order_locations = transport_order_data['pickup_location']\n", + "delivery_order_locations = transport_order_data['delivery_location']\n", + "order_locations = pd.concat([pickup_order_locations, delivery_order_locations], ignore_index=True)\n", + "\n", + "# Set the task locations in the task data json datastructure\n", + "task_data[\"task_locations\"] = order_locations.to_list()\n", + "print(order_locations)" + ] + }, + { + "cell_type": "markdown", + "id": "a0c838b6", + "metadata": {}, + "source": [ + "\n", + "#### Process demand data\n", + "\n", + "From the perspective of the cuOpt solver_settings, each distinct transaction (pickup order or delivery order) are treated separately with demand for pickup denoted as positive and the corresponding delivery treated as negative demand." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86be7280", + "metadata": {}, + "outputs": [], + "source": [ + "# This is the number of parts that needs to be moved\n", + "raw_demand = transport_order_data[\"order_demand\"]\n", + "\n", + "# When dropping off parts we want to remove one unit of demand from the robot\n", + "drop_off_demand = raw_demand * -1\n", + "\n", + "# Create pickup and delivery demand\n", + "order_demand = pd.concat([raw_demand, drop_off_demand], ignore_index=True)\n", + "\n", + "# Add demand to the task data\n", + "task_data[\"demand\"] = [order_demand.to_list()]\n", + "print(order_demand)" + ] + }, + { + "cell_type": "markdown", + "id": "a1f03f4c", + "metadata": {}, + "source": [ + "#### Process task time windows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b1a0b1d", + "metadata": {}, + "outputs": [], + "source": [ + "# create earliest times\n", + "order_time_window_earliest = pd.concat([transport_order_data[\"earliest_pickup\"], transport_order_data[\"earliest_delivery\"]], ignore_index=True)\n", + "\n", + "# create latest times\n", + "order_time_window_latest = pd.concat([transport_order_data[\"latest_pickup\"], transport_order_data[\"latest_delivery\"]], ignore_index=True)\n", + "\n", + "# create service times\n", + "order_service_time = pd.concat([transport_order_data[\"pickup_service_time\"],transport_order_data[\"delivery_service_time\"]], ignore_index=True)\n", + "\n", + "# add time window constraints\n", + "task_data[\"task_time_windows\"] = list(zip(order_time_window_earliest.to_list(),\n", + " order_time_window_latest.to_list()))\n", + "task_data[\"service_times\"] = order_service_time.to_list()" + ] + }, + { + "cell_type": "markdown", + "id": "200614ab", + "metadata": {}, + "source": [ + "#### Mapping pickups to deliveries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32272d7b", + "metadata": {}, + "outputs": [], + "source": [ + "# IMPORTANT NOTE : pickup and delivery pairs are indexed into the order locations array.\n", + "npair_orders = int(len(order_locations)/2)\n", + "pickup_order_ids = pd.Series([i for i in range(npair_orders)])\n", + "delivery_order_ids = pd.Series([i + npair_orders for i in range(npair_orders)])\n", + "\n", + "# add pickup and delivery pairs.\n", + "task_data[\"pickup_and_delivery_pairs\"] = list(zip(pickup_order_ids.to_list(),\n", + " delivery_order_ids.to_list()))" + ] + }, + { + "cell_type": "markdown", + "id": "15c673de", + "metadata": {}, + "source": [ + "#### Precedence Constraints\n", + "\n", + "We have decided to model the deliveries to index 0 as removing items from the factory floor. In some cases it may be necessary which operations are complete prior to exiting. Here we set precedence constraints on specific deliveries which must occur before parts can exit the factory floor" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "731fb876", + "metadata": {}, + "outputs": [], + "source": [ + "precedences = [\n", + " {\n", + " \"order_id\": 8,\n", + " \"preceding_orders\": [6, 7],\n", + " },\n", + " {\n", + " \"order_id\": 11,\n", + " \"preceding_orders\": [9, 10],\n", + " },\n", + "]\n", + "task_data[\"precedences\"] = precedences" + ] + }, + { + "cell_type": "markdown", + "id": "4ef964ca", + "metadata": {}, + "source": [ + "#### Dispatch the task data to the server" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9aa8c8f", + "metadata": {}, + "outputs": [], + "source": [ + "# Send the task data to the server\n", + "response_set = requests.post(\n", + " url + \"set_task_data\", json=task_data\n", + ")\n", + "\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "31db9053", + "metadata": {}, + "source": [ + "### Set AMR data" + ] + }, + { + "cell_type": "markdown", + "id": "731fdcbe", + "metadata": {}, + "source": [ + "Accumulate AMR fleet data such as its start and end locations, capacity, break/charging times and other details that relate to a vehicle." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e765325", + "metadata": {}, + "outputs": [], + "source": [ + "n_robots = 2\n", + "robot_data = {}\n", + "\n", + "# Add start and end locations for AMRs, assuming all AMRs start and end at location 0.\n", + "robot_data[\"vehicle_locations\"] = [[0, 0]] * n_robots\n", + "\n", + "# Add carrying capacity for AMRs, assuming all robots have capacity of 2,\n", + "# means, they can carry at the max two items at any point\n", + "robot_data[\"capacities\"] = [[2] * n_robots]\n", + "\n", + "robot_data[\"vehicle_time_windows\"] = [[factory_open_time, factory_close_time]] * n_robots\n", + "\n", + "# Dispatch the fleet data to the server\n", + "response_set = requests.post(\n", + " url + \"set_fleet_data\", json=robot_data\n", + ")\n", + "\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "b0d06888", + "metadata": {}, + "source": [ + "### Set Solver Settings" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a6babc11", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# Setup the solver settings json datastructure\n", + "solver_settings = {\n", + " # solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", + " \"time_limit\": 0.05,\n", + " # set number of climbers that will try to search for an optimal routes in parallel\n", + " \"number_of_climbers\": 128,\n", + "}\n", + "\n", + "\n", + "# dispatch the solver settings to the server\n", + "response_set = requests.post(\n", + " url + \"set_solver_config\", json=solver_settings\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "854e9519", + "metadata": {}, + "source": [ + "### Get optimized route" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28a05ace", + "metadata": {}, + "outputs": [], + "source": [ + "solver_response = requests.get(url + \"get_optimized_routes\")\n", + "\n", + "assert solver_response.status_code == 200\n", + "\n", + "# Process returned data\n", + "solver_resp = solver_response.json()[\"response\"][\"solver_response\"]\n", + "\n", + "if solver_resp[\"status\"] == 0:\n", + " print(\"Cost for the routing in time: \", solver_resp[\"solution_cost\"])\n", + " print(\"Vehicle count to complete routing: \", solver_resp[\"num_vehicles\"])\n", + " utils.show_vehicle_routes(solver_resp, location_names)\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_resp[\"status\"])" + ] + }, + { + "cell_type": "markdown", + "id": "bba4accd", + "metadata": {}, + "source": [ + "#### Waypoint level routes for AMRs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c13cfbf3", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "solver_resp_df = utils.get_solution_df(solver_resp)\n", + "unique_robot_ids = solver_resp_df['truck_id'].unique()\n", + "all_routes = solver_resp_df\n", + "\n", + "for robot in unique_robot_ids:\n", + " route = all_routes[all_routes['truck_id']==robot]\n", + " unique_target_locs = all_routes[all_routes['truck_id']==robot]['route'].unique()\n", + " \n", + " print(f\"Waypoint level route for robot {robot}:\\n{all_routes[all_routes['truck_id']==robot]['route']}\\n\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "2668327f", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + }, + "vscode": { + "interpreter": { + "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb new file mode 100644 index 0000000..04699f0 --- /dev/null +++ b/notebooks/routing/microservice/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb @@ -0,0 +1,452 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "e67c5c1d", + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "import pandas as pd\n", + "import notebook_utils.notebook_helpers as utils" + ] + }, + { + "cell_type": "markdown", + "id": "ba50d71a", + "metadata": {}, + "source": [ + "# Daily Deliveries\n", + "## Capacitated Vehicle Routing Problem (CVRP)" + ] + }, + { + "cell_type": "markdown", + "id": "3ec34cd8", + "metadata": {}, + "source": [ + "Micro fulfillment centers allow retailers to move predictable, high volume products closer to the end consumer allowing for lower costs and shorter overall delivery times.\n", + "\n", + "In this scenario we have a number of same-day delivery orders that we would like to process for a given area from a given micro fulfillment center. We have the requisite number of delivery vehicles and enough time to deliver all packages over the course of a single day. Each delivery vehicle has a maximum capacity of orders it can carry and we are looking for the route assignment that minimizes the total distance driven by all vehicles." + ] + }, + { + "cell_type": "markdown", + "id": "4fc9ef31", + "metadata": {}, + "source": [ + "### Problem Details:\n", + "- 8 Locations each with an associated demand\n", + " - 1 MFC \n", + " - demand: [0]\n", + " - 7 Delivery Locations\n", + " - demand: [4, 4, 2, 2, 1, 2, 1]\n", + " \n", + "\n", + "- 3 Delivery vehicles each with an associated capacity\n", + " - 2 trucks\n", + " - capacity: [8, 8]\n", + " - 1 van\n", + " - capacity: [4]\n", + " \n", + "**API Reference**: [cuOpt Server Documentation](https://docs.nvidia.com/cuopt/serv_api.html)" + ] + }, + { + "cell_type": "markdown", + "id": "ed3c2736", + "metadata": {}, + "source": [ + "Below we visualize the delivery locations with respect to the MFC. The cost from all locations to all other locations (a cost matrix) will be required for optimization. To see an example of cost matrix generation from map data or a waypoint graph, refer to the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook. For the purpose of this simple example we will omit the cost matrix calculation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "01b12b30", + "metadata": {}, + "outputs": [], + "source": [ + "location_names = [ \"MFC\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\" ]\n", + "location_coordinates = [ [4, 4], [1, 3], [8, 1], [2, 1], [6, 7], [0, 2], [7, 6], [5, 3] ]\n", + "location_coordinates_df = pd.DataFrame(location_coordinates, columns=['xcord', 'ycord'], index=location_names)\n", + "utils.gen_plot(location_coordinates_df).show()" + ] + }, + { + "cell_type": "markdown", + "id": "42ba94fb", + "metadata": {}, + "source": [ + "### Cost Matrix" + ] + }, + { + "cell_type": "markdown", + "id": "82edd816", + "metadata": {}, + "source": [ + "The cost matrix dictates the cost of travel between locations of interest. The cost itself can be anything relevant to the user. In this case we simply use distance as our cost.\n", + "\n", + "Here is the cost(distance) matrix corresponding to the above locations:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfa64aee", + "metadata": {}, + "outputs": [], + "source": [ + "distance_matrix = [\n", + " [0.0, 3.1, 5.0, 3.6, 3.6, 4.5, 3.6, 1.4],\n", + " [3.1, 0.0, 7.3, 2.2, 6.4, 1.4, 6.7, 4.0],\n", + " [5.0, 7.3, 0.0, 6.0, 6.3, 8.1, 5.1, 3.6],\n", + " [3.6, 2.2, 6.0, 0.0, 7.2, 2.2, 7.1, 3.6],\n", + " [3.6, 6.4, 6.3, 7.2, 0.0, 7.8, 1.4, 4.1],\n", + " [4.5, 1.4, 8.1, 2.2, 7.8, 0.0, 8.1, 5.1],\n", + " [3.6, 6.7, 5.1, 7.1, 1.4, 8.1, 0.0, 3.6],\n", + " [1.4, 4.0, 3.6, 3.6, 4.1, 5.1, 3.6, 0.0]\n", + "]\n", + "\n", + "distance_matrix" + ] + }, + { + "cell_type": "markdown", + "id": "161b18aa", + "metadata": {}, + "source": [ + " ### Demand and Capacity" + ] + }, + { + "cell_type": "markdown", + "id": "3b038198", + "metadata": {}, + "source": [ + "Set up the demand for each location and the capacity for each vehicle" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9cb56810", + "metadata": {}, + "outputs": [], + "source": [ + "location_ids = [i+1 for i in range(len(location_names)-1)] # exclude the fulfillment center from task data\n", + "# \"A\" \"B\" \"C\" \"D\" \"E\" \"F\" \"G\"\n", + "location_demand = [ 4, 4, 2, 2, 1, 2, 1]\n", + "# Vehicle 0 Vehicle 1 Vehicle 2\n", + "vehicle_capacity = [ 8, 8, 4 ]\n", + "# Vehicle 0 loc, Vehicel 1 loc, Vehicle 2 loc\n", + "vehicle_locs = [ [0, 0 ], [0, 0], [0, 0] ]\n", + "n_vehicles = len(vehicle_locs)" + ] + }, + { + "cell_type": "markdown", + "id": "65ae9e05", + "metadata": {}, + "source": [ + "# Setup the cuOpt server\n", + "\n", + "**NOTE**: Please update **ip** and **port** on which the server is running." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65505db8", + "metadata": {}, + "outputs": [], + "source": [ + "ip = \"0.0.0.0\"\n", + "port = \"5000\"\n", + "url = \"http://\" + ip + \":\" + port + \"/cuopt/\"\n", + "\n", + "# Test server health\n", + "assert requests.get(url + \"health\").status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "9312c733", + "metadata": {}, + "source": [ + "### Set Cost Matrix\n", + "\n", + "Dispatch cost matrix to the server" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a02105ba", + "metadata": {}, + "outputs": [], + "source": [ + "data_params = {\"return_data_state\": False}\n", + "\n", + "cost_data = {\"cost_matrix\": {0: distance_matrix}}\n", + "\n", + "# Set the cost matrix\n", + "response_set = requests.post(\n", + " url + \"set_cost_matrix\", params=data_params, json=cost_data\n", + ")\n", + "assert response_set.status_code == 200\n" + ] + }, + { + "cell_type": "markdown", + "id": "3139d541", + "metadata": {}, + "source": [ + "### Set Task Data\n", + "\n", + "Dispatch task data to the server" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b930156", + "metadata": {}, + "outputs": [], + "source": [ + "task_data = {\n", + " \"task_locations\": location_ids,\n", + " \"demand\": [location_demand],\n", + "}\n", + "response_set = requests.post(url + \"set_task_data\", json=task_data)\n", + "assert response_set.status_code == 200\n" + ] + }, + { + "cell_type": "markdown", + "id": "ef924325", + "metadata": {}, + "source": [ + "### Set Vehicle Data\n", + "\n", + "Dispatch vehicle data to the server" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee8859c8", + "metadata": {}, + "outputs": [], + "source": [ + "fleet_data = {\n", + " \"vehicle_locations\": vehicle_locs,\n", + " \"capacities\": [vehicle_capacity],\n", + "}\n", + "response_set = requests.post(url + \"set_fleet_data\", json=fleet_data)\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "bc3d347a", + "metadata": {}, + "source": [ + "### Set Solver Settings\n", + "\n", + "\n", + "Dispatch solver settings to the server" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd600ffa", + "metadata": {}, + "outputs": [], + "source": [ + "solver_config = {\n", + " \"time_limit\": 0.01,\n", + " \"number_of_climbers\": 128,\n", + "}\n", + "response_set = requests.post(url + \"set_solver_config\", json=solver_config)\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "e6bf223a", + "metadata": {}, + "source": [ + "### Get Optimized Routes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4141fd5", + "metadata": {}, + "outputs": [], + "source": [ + "# Solve the problem\n", + "solver_response = requests.get(url + \"get_optimized_routes\")\n", + "\n", + "assert solver_response.status_code == 200\n", + "\n", + "# Process returned data\n", + "solver_resp = solver_response.json()[\"response\"][\"solver_response\"]\n", + "\n", + "if solver_resp[\"status\"] == 0:\n", + " print(\"Cost for the routing in distance: \", solver_resp[\"solution_cost\"])\n", + " print(\"Vehicle count to complete routing: \", solver_resp[\"num_vehicles\"])\n", + " utils.show_vehicle_routes(solver_resp, location_names)\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_resp[\"status\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8618e29a", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "vehicle_colors = [\"red\", \"green\", \"blue\"]\n", + "utils.map_vehicle_routes(location_coordinates_df, solver_resp, vehicle_colors).show()" + ] + }, + { + "cell_type": "markdown", + "id": "37ccafc5", + "metadata": {}, + "source": [ + "### Additional Constraints \n", + "##### Minimum Vehicles" + ] + }, + { + "cell_type": "markdown", + "id": "c560394e", + "metadata": {}, + "source": [ + "cuOpt has found a solution that does not require all available vehicles because the combined capacity of the two larger vehicles (16) is equal to total location demand (16). In some cases, this is a great solution as it gives the option to save on the costs associated with additional vehicles. In other cases there is value to assigning all available resources. In the latter case we can require that cuOpt use all 3 available vehicles and re-solve the problem with this constraint." + ] + }, + { + "cell_type": "markdown", + "id": "a646ca3a", + "metadata": {}, + "source": [ + "**Update the existing solver configuration in server and re-optimize**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fab4aebb", + "metadata": {}, + "outputs": [], + "source": [ + "# Set the minimum vehicles through solver config\n", + "fleet_data = {\n", + " \"min_vehicles\": n_vehicles,\n", + "}\n", + "response_set = requests.put(url + \"update_fleet_data\", json=fleet_data)\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7637f20", + "metadata": {}, + "outputs": [], + "source": [ + "# Re-Solve the problem\n", + "solver_response = requests.get(url + \"get_optimized_routes\")\n", + "\n", + "assert response_set.status_code == 200\n", + "\n", + "solver_resp = solver_response.json()[\"response\"][\"solver_response\"]\n", + "\n", + "if solver_resp[\"status\"] == 0:\n", + " print(\"Cost for the routing in distance: \", solver_resp[\"solution_cost\"])\n", + " print(\"Vehicle count to complete routing: \", solver_resp[\"num_vehicles\"])\n", + " utils.show_vehicle_routes(solver_resp, location_names)\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_resp[\"status\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "adb83802", + "metadata": {}, + "outputs": [], + "source": [ + "utils.map_vehicle_routes(location_coordinates_df, solver_resp, vehicle_colors).show()" + ] + }, + { + "cell_type": "markdown", + "id": "97b987b2", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + }, + "vscode": { + "interpreter": { + "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb new file mode 100644 index 0000000..0ddb6a7 --- /dev/null +++ b/notebooks/routing/microservice/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb @@ -0,0 +1,548 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "65705fab", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import requests\n", + "import notebook_utils.notebook_helpers as utils" + ] + }, + { + "cell_type": "markdown", + "id": "fbb36201", + "metadata": {}, + "source": [ + "# Priority Routing\n", + "## Capacitated Vehicle Routing Problem with Soft Time Windows (CVRPSTW)" + ] + }, + { + "cell_type": "markdown", + "id": "bffb5f0c", + "metadata": {}, + "source": [ + "Loyalty (or Preferred) customer programs help companies to reward repeat customers and enhance their overall business offering. While the best possible customer service is always the goal, loyalty programs provide a mechanism for reinforcing the relationship with the customers that drive business revenue.\n", + "\n", + "In this scenario we have a set of deliveries with target time windows for delivery that do not represent a feasible solution given the delivery vehicles that are available. We would still like to deliver all the packages even if some of them are a little behind schedule. However, we would like to prioritize the deliveries of customers in our loyalty program to minimize the delay these customers experience.\n", + "\n", + "We also want to optimize according to a business defined cost objective that is a combination of business relevant metrics. To track time window constraints we will pass a time matrix as a constraint checking \"secondary matrix\".\n" + ] + }, + { + "cell_type": "markdown", + "id": "cb33a971", + "metadata": {}, + "source": [ + "### Problem Details:\n", + "- 8 Locations each with an associated demand\n", + " - 1 Distribution Center \n", + " - distribution center demand: [0]\n", + " - hours of operation: [0,24]\n", + " - 7 Service Locations\n", + " - demand for deliveries: [1, 1, 1, 1, 1, 1, 1]\n", + " - delivery time windows: [[9,10],[9,10],[9,10],[10,11],[10,11],[10,11],[9,10]]\n", + " - service location service times: [ 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25]\n", + " - loyalty program member: [1, 0, 0, 0, 1, 0, 1]\n", + "\n", + "- 3 Delivery vehicles each with an associated capacity\n", + " - 3 delivery vehicles\n", + " - capacity for deliveries: [3, 3, 3]\n", + " \n", + "**API Reference**: [cuOpt Server Documentation](https://docs.nvidia.com/cuopt/serv_api.html)" + ] + }, + { + "cell_type": "markdown", + "id": "baa93a42", + "metadata": {}, + "source": [ + "Below we visualize the delivery locations with respect to the distribution center. The cost from all locations to all other locations (a cost matrix) will be required for optimization. To see an example of cost matrix generation from map data or a waypoint graph, refer to the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook. For the purpose of this simple example we will omit the cost matrix calculation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e747d30d", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "location_names = [ \"DC\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\" ]\n", + "location_coordinates = [ [4, 4], [1, 3], [8, 1], [2, 1], [6, 7], [0, 2], [7, 6], [5, 3] ]\n", + "location_coordinates_df = pd.DataFrame(location_coordinates, columns=['xcord', 'ycord'], index=location_names)\n", + "utils.gen_plot(location_coordinates_df).show()" + ] + }, + { + "cell_type": "markdown", + "id": "413aeb52", + "metadata": {}, + "source": [ + "## Setup the cuOpt server and test its health\n", + "\n", + "**NOTE**: Please update **ip** and **port** on which the server is running." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b5b9102", + "metadata": {}, + "outputs": [], + "source": [ + "ip = \"0.0.0.0\"\n", + "port = \"5000\"\n", + "url = \"http://\" + ip + \":\" + port + \"/cuopt/\"\n", + "\n", + "# Test server health\n", + "assert requests.get(url + \"health\").status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "1307fc95", + "metadata": {}, + "source": [ + "### Cost Matrix : Primary" + ] + }, + { + "cell_type": "markdown", + "id": "27525bf2", + "metadata": {}, + "source": [ + "The cost matrix dictates the cost of travel between locations of interest. The cost itself can be anything relevant to the user. In this case we are using a business defined cost objective as a primary cost matrix and a secondary time matrix to verify our time based constraints. \n", + "\n", + "Here is the cost(business metric) matrix corresponding to the locations above:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef01144e", + "metadata": {}, + "outputs": [], + "source": [ + "business_metric_cost_matrix = [\n", + " [0.0, 3.1, 5.0, 3.6, 3.6, 4.5, 3.6, 1.4],\n", + " [3.1, 0.0, 7.3, 2.2, 6.4, 1.4, 6.7, 4.0],\n", + " [5.0, 7.3, 0.0, 6.0, 6.3, 8.1, 5.1, 3.6],\n", + " [3.6, 2.2, 6.0, 0.0, 7.2, 2.2, 7.1, 3.6],\n", + " [3.6, 6.4, 6.3, 7.2, 0.0, 7.8, 1.4, 4.1],\n", + " [4.5, 1.4, 8.1, 2.2, 7.8, 0.0, 8.1, 5.1],\n", + " [3.6, 6.7, 5.1, 7.1, 1.4, 8.1, 0.0, 3.6],\n", + " [1.4, 4.0, 3.6, 3.6, 4.1, 5.1, 3.6, 0.0]\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "9671d772", + "metadata": {}, + "source": [ + "### Cost Matrix : Secondary" + ] + }, + { + "cell_type": "markdown", + "id": "d226f72b", + "metadata": {}, + "source": [ + "Here is the constraint checking (time) secondary matrix:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "145a2560", + "metadata": {}, + "outputs": [], + "source": [ + "constraint_checking_time_matrix = [\n", + " [0.00, 0.39, 0.63, 0.45, 0.45, 0.55, 0.45, 0.18 ],\n", + " [0.39, 0.00, 0.90, 0.28, 0.80, 0.18, 0.84, 0.50 ],\n", + " [0.63, 0.90, 0.00, 0.75, 0.79, 1.00, 0.64, 0.45 ],\n", + " [0.45, 0.28, 0.75, 0.00, 0.90, 0.28, 0.88, 0.45 ],\n", + " [0.45, 0.80, 0.79, 0.90, 0.00, 0.96, 0.18, 0.51 ],\n", + " [0.55, 0.18, 1.00, 0.28, 0.96, 0.00, 1.00, 0.64 ],\n", + " [0.45, 0.84, 0.64, 0.88, 0.18, 1.00, 0.00, 0.45 ],\n", + " [0.18, 0.50, 0.45, 0.45, 0.51, 0.64, 0.45, 0.00 ]\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "cbd80cd3", + "metadata": {}, + "source": [ + "### Deliveries" + ] + }, + { + "cell_type": "markdown", + "id": "54d6af91", + "metadata": {}, + "source": [ + "Setup the delivery data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28e33246", + "metadata": {}, + "outputs": [], + "source": [ + "delivery_location_data = {\n", + " \"location_ids\": [i+1 for i in range(len(location_names)-1)], # designate zeroth location as start and return points for fleet\n", + " \"delivery_demand\": [1, 1, 1, 1, 1, 1, 1 ],\n", + " \"location_earliest_time\": [9, 9, 9, 10, 10, 10, 9 ],\n", + " \"location_latest_time\": [10, 10, 10, 11, 11, 11, 10],\n", + " \"required_service_time\": [1, 1, 1, 1, 1, 1, 1 ],\n", + " \"loyalty_member\": [0, 1, 0, 1, 0, 1, 0 ]\n", + "}\n", + "print(delivery_location_data)" + ] + }, + { + "cell_type": "markdown", + "id": "dd268248", + "metadata": {}, + "source": [ + "### Set Cost Matrix\n", + "\n", + "Dispatch cost matrix to server" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14b41584", + "metadata": {}, + "outputs": [], + "source": [ + "data_params = {\"return_data_state\": False}\n", + "cost_data = {\"cost_matrix\": {0: business_metric_cost_matrix}}\n", + "response_set = requests.post(\n", + " url + \"set_cost_matrix\", params=data_params, json=cost_data\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "8181e6f1", + "metadata": {}, + "source": [ + "### Set Secondary Cost Matrix\n", + "\n", + "Dispatch secondary cost matrix to server" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a603ed5c", + "metadata": {}, + "outputs": [], + "source": [ + "# set the secondary constraint checking time matrix\n", + "time_data = {\"cost_matrix\": {0: constraint_checking_time_matrix}}\n", + "response_set = requests.post(\n", + " url + \"set_travel_time_matrix\", params=data_params, json=time_data\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "6bace17e", + "metadata": {}, + "source": [ + "### Set Vehicle Data\n", + "Dispatch vehicle data to server" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4931236", + "metadata": {}, + "outputs": [], + "source": [ + "n_vehicles = 3\n", + "vehicle_capacity = 3 # As per problem statement, all vehicles have capacities of 3\n", + "\n", + "# Build the fleet data\n", + "fleet_data = {\n", + " # Vehicle start and end at location 0, since 0 is distribution center\n", + " \"vehicle_locations\": [[0,0]] * n_vehicles,\n", + " \"capacities\": [[vehicle_capacity] * n_vehicles],\n", + " \"vehicle_time_windows\": [[5, 20]] * n_vehicles\n", + "}\n", + "\n", + "# Dispatch the fleet data request to the server\n", + "response_set = requests.post(url + \"set_fleet_data\", json=fleet_data)\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "e5738d68", + "metadata": {}, + "source": [ + "### Set Task Data\n", + "Dispatch task data to server" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0bcf99c", + "metadata": {}, + "outputs": [], + "source": [ + "# Build the task data\n", + "task_data = {\n", + " \"task_locations\": delivery_location_data[\"location_ids\"],\n", + " \"demand\": [delivery_location_data[\"delivery_demand\"]],\n", + "}\n", + "\n", + "# add time window constraints and service time for the locations\n", + "task_data[\"task_time_windows\"] = list(zip(delivery_location_data[\"location_earliest_time\"],\n", + " delivery_location_data[\"location_latest_time\"]))\n", + "\n", + "task_data[\"service_times\"] = delivery_location_data[\"required_service_time\"]\n", + "\n", + "# Dispatch the task request to the server\n", + "response_set = requests.post(url + \"set_task_data\", json=task_data)\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "82f4dd75", + "metadata": {}, + "source": [ + "### Set CuOpt Solver Configuration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0f811c5", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# Setup the solver settings json datastructure\n", + "solver_settings = {\n", + " # solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", + " \"time_limit\": 0.05,\n", + " # set number of climbers that will try to search for an optimal routes in parallel\n", + " \"number_of_climbers\": 128,\n", + "}\n", + "\n", + "# dispatch the solver settings to the server\n", + "response_set = requests.post(\n", + " url + \"set_solver_config\", json=solver_settings\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "1d2997dd", + "metadata": {}, + "source": [ + "### Attempt to obtain optimized routes\n", + "We can attempt to solve this problem as stated, but as previously discussed it is not feasible within the specified target time windows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a63b509b", + "metadata": {}, + "outputs": [], + "source": [ + "solver_response = requests.get(url + \"get_optimized_routes\")\n", + "\n", + "if solver_response.status_code == 200:\n", + " print(\"Cost for the routing in time: \", solver_response[\"solution_cost\"])\n", + " print(\"Vehicle count to complete routing: \", solver_response[\"num_vehicles\"])\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_response.status_code)" + ] + }, + { + "cell_type": "markdown", + "id": "3f863e49", + "metadata": {}, + "source": [ + "cuOpt is unable to find a feasible solution. As previously discussed we would like to allow the deliveries to exceed the latest time windows by using soft time windows" + ] + }, + { + "cell_type": "markdown", + "id": "900028af", + "metadata": {}, + "source": [ + "### Initial Solution\n", + "\n", + "With soft time window option, we can relax time window constraints along with penality to come up with a solution but at a additional cost. \n", + "\n", + "#### Update solver configuration to use Soft Time windows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8f6e8a2c", + "metadata": {}, + "outputs": [], + "source": [ + "solver_settings[\"solution_scope\"] = 1\n", + "# Update the solver settings to the server\n", + "response_set = requests.put(\n", + " url + \"update_solver_config\", json=solver_settings\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "65bcae7d", + "metadata": {}, + "source": [ + "#### Add Penalty\n", + "\n", + "With this, we can prioritize order/customers by providing higher penalties to such jobs compared to others." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "720d9692", + "metadata": {}, + "outputs": [], + "source": [ + "delivery_location_data['penalty'] = [x * 100 for x in delivery_location_data[\"loyalty_member\"]]\n", + "delivery_location_data\n", + "\n", + "\n", + "task_data = {\n", + " \"penalties\": delivery_location_data[\"penalty\"]\n", + "}\n", + "\n", + "# Update the task request to the server\n", + "response_set = requests.put(url + \"update_task_data\", json=task_data)\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "1eb424b0", + "metadata": {}, + "source": [ + "#### Re-optimize " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "098c0061", + "metadata": {}, + "outputs": [], + "source": [ + "solver_response = requests.get(url + \"get_optimized_routes\")\n", + "solver_resp = solver_response.json()[\"response\"][\"solver_response\"]\n", + "\n", + "if solver_resp[\"status\"] == 0: \n", + " solver_resp_df = utils.get_solution_df(solver_resp)\n", + " print(\"Cost for the routing in time: \", solver_resp[\"solution_cost\"])\n", + " print(\"Vehicle count to complete routing: \", solver_resp[\"num_vehicles\"])\n", + " print(solver_resp_df)\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_resp[\"status\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1845a27", + "metadata": {}, + "outputs": [], + "source": [ + "solution_data_priority = utils.get_solution_df(solver_resp)\n", + "solution_data_priority['route'] = [location_names[i] for i in solution_data_priority['route'].to_list()]\n", + "solution_data_priority = solution_data_priority.set_index('route')\n", + "solution_data_priority" + ] + }, + { + "cell_type": "markdown", + "id": "38bc3217", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + }, + "vscode": { + "interpreter": { + "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb new file mode 100644 index 0000000..47215c3 --- /dev/null +++ b/notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb @@ -0,0 +1,462 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a05e01aa", + "metadata": {}, + "source": [ + "
\n", + "\n", + "# Skip notebook test\n", + "\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "afc57ab3", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import requests\n", + "from scipy.spatial import distance\n", + "import notebook_utils.notebook_helpers as utils" + ] + }, + { + "cell_type": "markdown", + "id": "0d500386", + "metadata": {}, + "source": [ + "# Benchmark Gehring & Homberger\n", + "## Capacitated Vehicle Routing Problem with Time Windows (CVRPTW)" + ] + }, + { + "cell_type": "markdown", + "id": "30e63d74", + "metadata": {}, + "source": [ + "While other notebooks such as [cvrptw_service_team_routing.ipynb](cvrptw_service_team_routing.ipynb) focus on the cuOpt API and high level problem modeling, here we focus on performance.\n", + "\n", + "cuOpt offers a unique benefit over other solver_settingss, specifically, time to solution. In addition to achieving world class accuracy, cuOpt also produces these solutions in a time frame that allows for re-optimization in dynamic environments and rapid iteration over possible problem configurations.\n", + "\n", + "Here we are demonstrating this performance on a large popular academic [dataset by Gehing & Homberger](https://www.sintef.no/projectweb/top/vrptw/homberger-benchmark/). These problems are well studied and used as the basis for comparison for VRP research and product offerings. The particular instance we will test with is from the group of largest (1000 location) problems. Each problem instance has an associated best known solution, the one we will measure against is shown below\n", + "\n", + "**API Reference**: [cuOpt Server Documentation](https://docs.nvidia.com/cuopt/serv_api.html)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65860d5d", + "metadata": {}, + "outputs": [], + "source": [ + "homberger_1000_file = 'notebook_utils/data/C1_10_1.TXT'\n", + "\n", + "best_known_solution = {\n", + " \"n_vehicles\": 100,\n", + " \"cost\": 42478.95\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "af25d3f9", + "metadata": {}, + "source": [ + "### Problem Data\n", + "The data for this problem instance are provided via text file. cuOpt has a utility function available specifically for the Gehring & Homberger benchmark which converts the problem into the components required by cuOpt." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd6089b5", + "metadata": {}, + "outputs": [], + "source": [ + "orders, vehicle_capacity, n_vehicles = utils.create_from_file(homberger_1000_file)\n", + "n_locations = orders[\"demand\"].shape[0]-1\n", + "print(\"Number of locations : \", n_locations)\n", + "print(\"Number of vehicles available : \", n_vehicles)\n", + "print(\"Capacity of each vehicle : \", vehicle_capacity)\n", + "print(\"\\nInitial Orders information\")\n", + "print(orders)" + ] + }, + { + "cell_type": "markdown", + "id": "4890f027", + "metadata": {}, + "source": [ + "### Setup the cuOpt server and test the health of the server\n", + "\n", + "**NOTE**: Please update **ip** and **port** on which the server is running." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d57690c9", + "metadata": {}, + "outputs": [], + "source": [ + "ip = \"0.0.0.0\"\n", + "port = \"5000\"\n", + "url = \"http://\" + ip + \":\" + port + \"/cuopt/\"\n", + "\n", + "# Test the health of the cuOpt server\n", + "assert requests.get(url + \"health\").status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "ba4eb34d", + "metadata": {}, + "source": [ + "### Cost Matrix" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0cc3ced9", + "metadata": {}, + "outputs": [], + "source": [ + "coords = list(zip(orders['xcord'].to_list(),\n", + " orders['ycord'].to_list()))\n", + "\n", + "cost_matrix = pd.DataFrame(distance.cdist(coords, coords, 'euclidean')).astype(np.float32)\n", + "print(f\"Shape of cost matrix: {cost_matrix.shape}\")" + ] + }, + { + "cell_type": "markdown", + "id": "c938e463", + "metadata": {}, + "source": [ + "### Set Cost Matrix" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8493081f", + "metadata": {}, + "outputs": [], + "source": [ + "data_params = {\"return_data_state\": False}\n", + "cost_data = {\"cost_matrix\": {0: cost_matrix.values.tolist()}}\n", + "response_set = requests.post(\n", + " url + \"set_cost_matrix\", params=data_params, json=cost_data\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "9ad17098", + "metadata": {}, + "source": [ + "### Set Fleet Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45f8aa47", + "metadata": {}, + "outputs": [], + "source": [ + "# Set the fleet data\n", + "vehicle_locations = [[0, 0]] * n_vehicles\n", + "fleet_data = {\n", + " \"vehicle_locations\": vehicle_locations,\n", + " \"capacities\": [[vehicle_capacity] * n_vehicles],\n", + "}\n", + "\n", + "# Dispatch the fleet data to the cuOpt server\n", + "response_set = requests.post(\n", + " url + \"set_fleet_data\", json=fleet_data\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "800db055", + "metadata": {}, + "source": [ + "### Set Task Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "638df884", + "metadata": {}, + "outputs": [], + "source": [ + "# Set the task data\n", + "task_data = {\n", + " \"task_locations\": orders['vertex'].values.tolist(),\n", + " \"task_time_windows\": list(zip(orders['earliest_time'].values.tolist(),\n", + " orders['latest_time'].values.tolist())),\n", + " \"service_times\": orders['service_time'].values.tolist(),\n", + " \"demand\": [orders['demand'].values.tolist()],\n", + "}\n", + "\n", + "# Dispatch the task data to the cuOpt server\n", + "response_set = requests.post(\n", + " url + \"set_task_data\", json=task_data\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "e4f9a455", + "metadata": {}, + "source": [ + "### Set Solver configuration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3eddb994", + "metadata": {}, + "outputs": [], + "source": [ + "solver_settings = {\n", + " \"time_limit\": 0.5,\n", + " \"number_of_climbers\": 2048,\n", + "}\n", + "# set number of climbers that will try to search for an optimal routes in parallel\n", + "response_set = requests.post(\n", + " url + \"set_solver_config\", json=solver_settings\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "8944c315", + "metadata": {}, + "source": [ + "### Helper functions to solve and process the output" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5382727c", + "metadata": {}, + "outputs": [], + "source": [ + "# Here we will examine the quality of the solution we increase the time budget provided to cuOpt\n", + "def solve_problem(problem_size):\n", + " solver_response = requests.get(url + \"get_optimized_routes\")\n", + " solver_resp = solver_response.json()[\"response\"][\"solver_response\"]\n", + " if solver_resp[\"status\"] == 0:\n", + " print(\"Cost for the routing in time: \", solver_resp[\"solution_cost\"])\n", + " print(\"Vehicle count to complete routing: \", solver_resp[\"num_vehicles\"])\n", + " utils.show_vehicle_routes(solver_resp, [\"Depot\"]+[str(i) for i in range(1, problem_size+1)])\n", + " else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_resp[\"status\"])\n", + " \n", + " return(solver_resp[\"num_vehicles\"], solver_resp[\"solution_cost\"])\n", + "\n", + "def solution_eval(vehicles, cost, best_known_solution):\n", + " \n", + " print(f\"- cuOpt provides a solution using {vehicles} vehicles\")\n", + " print(f\"- This represents {vehicles - best_known_solution['n_vehicles']} more than the best known solution\")\n", + " print(f\"- Vehicle Percent Difference {(vehicles/best_known_solution['n_vehicles'] - 1)*100}% \\n\\n\")\n", + " print(f\"- In addition cuOpt provides a solution cost of {cost}\") \n", + " print(f\"- Best known solution cost is {best_known_solution['cost']}\")\n", + " print(f\"- Cost Percent Difference {(cost/best_known_solution['cost'] - 1)*100}%\")" + ] + }, + { + "cell_type": "markdown", + "id": "24afe2f5", + "metadata": {}, + "source": [ + "### Get Optimized Results\n", + "\n", + "Update solver config and test different run-time " + ] + }, + { + "cell_type": "markdown", + "id": "0941d56f", + "metadata": {}, + "source": [ + "**1 Second Time Limit**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70f12ffa", + "metadata": {}, + "outputs": [], + "source": [ + "solver_settings[\"time_limit\"] = 1\n", + "# update the time limit for solving the problem\n", + "response_set = requests.put(\n", + " url + \"update_solver_config\", json=solver_settings\n", + ")\n", + "assert response_set.status_code == 200\n", + "# re-solve the problem with time limit equals 1\n", + "vehicles, cost = solve_problem(len(cost_matrix))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a2453b1b", + "metadata": {}, + "outputs": [], + "source": [ + "# Evaluation:\n", + "solution_eval(vehicles, cost, best_known_solution)" + ] + }, + { + "cell_type": "markdown", + "id": "04ef0c21", + "metadata": {}, + "source": [ + "**10 Second Time Limit**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3934d8de", + "metadata": {}, + "outputs": [], + "source": [ + "solver_settings[\"time_limit\"] = 10\n", + "# update the time limit for solving the problem\n", + "response_set = requests.put(\n", + " url + \"update_solver_config\", json=solver_settings\n", + ")\n", + "assert response_set.status_code == 200\n", + "# re-solve the problem with time limit equals ten\n", + "vehicles, cost = solve_problem(len(cost_matrix))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ab5e6d1", + "metadata": {}, + "outputs": [], + "source": [ + "# Evaluation:\n", + "solution_eval(vehicles, cost, best_known_solution)" + ] + }, + { + "cell_type": "markdown", + "id": "a9a1b855", + "metadata": {}, + "source": [ + "**20 Second Time Limit**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e0c38643", + "metadata": {}, + "outputs": [], + "source": [ + "solver_settings[\"time_limit\"] = 20\n", + "# update the time limit for solving the problem\n", + "response_set = requests.put(\n", + " url + \"update_solver_config\", json=solver_settings\n", + ")\n", + "assert response_set.status_code == 200\n", + "# re-solve the problem with time limit equals twenty\n", + "vehicles, cost = solve_problem(len(cost_matrix))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff80118b", + "metadata": {}, + "outputs": [], + "source": [ + "# Evaluation:\n", + "solution_eval(vehicles, cost, best_known_solution)" + ] + }, + { + "cell_type": "markdown", + "id": "dc94ab34", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + }, + "vscode": { + "interpreter": { + "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb new file mode 100644 index 0000000..7aa159b --- /dev/null +++ b/notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb @@ -0,0 +1,457 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "b2cba47f", + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "import pandas as pd\n", + "import notebook_utils.notebook_helpers as utils" + ] + }, + { + "cell_type": "markdown", + "id": "371f38f1", + "metadata": {}, + "source": [ + "# Service Team Routing\n", + "## Capacitated Vehicle Routing Problem with Time Windows (CVRPTW)" + ] + }, + { + "cell_type": "markdown", + "id": "25e90f86", + "metadata": {}, + "source": [ + "The ability of service providers to set service time windows allows for easier and more dependable coordination between the service provider and their customers, while increasing overall customer satisfaction.\n", + "\n", + "In this scenario we have a number of service order locations with associated time windows and service times (time on-site to complete service). Each technician has an associated availability, ability to complete certain types of service, and a maximum number of service appointments per day." + ] + }, + { + "cell_type": "markdown", + "id": "63093d54", + "metadata": {}, + "source": [ + "### Problem Details:\n", + "- 8 Locations each with an associated demand\n", + " - 1 Headquarters \n", + " - service type 1 demand: [0]\n", + " - service type 2 demand: [1]\n", + " - headquarters hours of operation: [5,20]\n", + " - 7 Service Locations\n", + " - service type 1 demand: [1, 1, 1, 0, 0, 0, 0]\n", + " - service type 2 demand: [0, 0, 1, 1, 1, 1, 1]\n", + " - service locations time windows: [[9,12],[9,12],[11,14],[13,16],[13,16],[13,16],[13,16]]\n", + " - service location service times: [ 1, 1, 1.5, 0.5, 0.5, 0.5]\n", + "\n", + "- 3 Delivery vehicles each with an associated capacity\n", + " - 3 service technicians\n", + " - capacity for service type 1: [2, 1, 0]\n", + " - capacity for service type 2: [0, 1, 4]\n", + " - technician availability [[9,17], [12,15], [9,17]]\n", + " \n", + "**API Reference**: [cuOpt Server Documentation](https://docs.nvidia.com/cuopt/serv_api.html)" + ] + }, + { + "cell_type": "markdown", + "id": "baeeee39", + "metadata": {}, + "source": [ + "Below we visualize the service locations with respect to the service company headquarters. The cost from all locations to all other locations (a cost matrix) will be required for optimization. To see an example of cost matrix generation from map data or a waypoint graph, refer to the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook. For the purpose of this simple example we will omit the cost matrix calculation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8a0847d", + "metadata": {}, + "outputs": [], + "source": [ + "location_names = [ \"Headquarters\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\" ]\n", + "location_coordinates = [ [4, 4], [1, 3], [8, 1], [2, 1], [6, 7], [0, 2], [7, 6], [5, 3] ]\n", + "location_coordinates_df = pd.DataFrame(location_coordinates, columns=['xcord', 'ycord'], index=location_names)\n", + "utils.gen_plot(location_coordinates_df).show()" + ] + }, + { + "cell_type": "markdown", + "id": "ff1d68f2", + "metadata": {}, + "source": [ + "### Cost Matrix" + ] + }, + { + "cell_type": "markdown", + "id": "210a57e9", + "metadata": {}, + "source": [ + "The cost matrix dictates the cost of travel between locations of interest. The cost itself can be anything relevant to the user. In this case we are constraining time window constraints. When constraining time windows for locations or vehicles it is assumed (if only a single cost matrix is provided) that it represents time. \n", + "\n", + "Here is the cost(time) matrix corresponding to the locations above:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1054c7e3", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "time_matrix = [\n", + " [0.00, 0.31, 0.50, 0.36, 0.36, 0.44, 0.36, 0.14],\n", + " [0.31, 0.00, 0.72, 0.22, 0.64, 0.14, 0.67, 0.40],\n", + " [0.50, 0.72, 0.00, 0.60, 0.63, 0.80, 0.51, 0.36],\n", + " [0.36, 0.22, 0.60, 0.00, 0.72, 0.22, 0.70, 0.36],\n", + " [0.36, 0.64, 0.63, 0.72, 0.00, 0.77, 0.14, 0.41],\n", + " [0.44, 0.14, 0.80, 0.22, 0.77, 0.00, 0.80, 0.51],\n", + " [0.36, 0.67, 0.51, 0.70, 0.14, 0.80, 0.00, 0.36],\n", + " [0.14, 0.40, 0.36, 0.36, 0.41, 0.51, 0.36, 0.00]\n", + "]\n", + "\n", + "# Create a dataframe of this matrix\n", + "time_matrix_df = pd.DataFrame(time_matrix, \n", + " index=location_coordinates_df.index, \n", + " columns=location_coordinates_df.index)\n", + "time_matrix_df" + ] + }, + { + "cell_type": "markdown", + "id": "3397b254", + "metadata": {}, + "source": [ + "### Service Locations" + ] + }, + { + "cell_type": "markdown", + "id": "ce7b7af7", + "metadata": {}, + "source": [ + "Setup the service location data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dc98afdf", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# exclude head quarters from service location names\n", + "service_location_ids = [1, 2, 3, 4, 5, 6, 7]\n", + "service_location_names = [location_names[i] for i in service_location_ids]\n", + "service_location_data = {\n", + " \"service_location_names\": service_location_names,\n", + " \"service_location_ids\": service_location_ids,\n", + " \"service_type1_demand\": [1, 1, 1, 0, 0, 0, 0],\n", + " \"service_type2_demand\": [0, 0, 1, 1, 1, 1, 1],\n", + " \"location_earliest_time\": [9, 9, 11, 13, 13, 13, 13],\n", + " \"location_latest_time\": [12, 12, 14, 16, 16, 16,16],\n", + " \"required_service_time\": [1, 1, 1.5, 0.5, 0.5, 0.5, 0.5]\n", + "}\n", + "service_location_data_df = pd.DataFrame(service_location_data).set_index('service_location_names')\n", + "service_location_data_df" + ] + }, + { + "cell_type": "markdown", + "id": "cd27971f", + "metadata": {}, + "source": [ + "### Vehicles" + ] + }, + { + "cell_type": "markdown", + "id": "d66df281", + "metadata": {}, + "source": [ + "Setup vehicle/technician data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "248e1add", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "n_vehicles = 3\n", + "vehicle_data = {\n", + " \"vehicle_ids\": [i for i in range(n_vehicles)],\n", + " \"capacity_service_type1\":[2, 1, 0],\n", + " \"capacity_service_type2\":[0, 1, 4],\n", + " \"vehicle_availability_earliest\":[9, 11, 9],\n", + " \"vehicle_availability_latest\":[17, 15, 17]\n", + "}\n", + "vehicle_data_df = pd.DataFrame(vehicle_data).set_index('vehicle_ids')\n", + "vehicle_data_df" + ] + }, + { + "cell_type": "markdown", + "id": "40cc2676", + "metadata": {}, + "source": [ + "# Setup the cuOpt server and test its health\n", + "\n", + "**NOTE**: Please update **ip** and **port** on which the server is running." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf2a003f", + "metadata": {}, + "outputs": [], + "source": [ + "ip = \"0.0.0.0\"\n", + "port = \"5000\"\n", + "url = \"http://\" + ip + \":\" + port + \"/cuopt/\"\n", + "\n", + "# Test health\n", + "assert requests.get(url + \"health\").status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "8c70e5b3", + "metadata": {}, + "source": [ + "### Set Cost Matrix" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15214631", + "metadata": {}, + "outputs": [], + "source": [ + "# set the cost matrix\n", + "data_params = {\"return_data_state\": False}\n", + "cost_data = {\"cost_matrix\": {0: time_matrix}}\n", + "response_set = requests.post(\n", + " url + \"set_cost_matrix\", params=data_params, json=cost_data\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "6cb27e14", + "metadata": {}, + "source": [ + "### Set Fleet Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9b07f8ad", + "metadata": {}, + "outputs": [], + "source": [ + "# Build the vehicle data\n", + "fleet_data = {\n", + " \"vehicle_locations\": [[0,0]] * n_vehicles,\n", + " \"capacities\": [vehicle_data[\"capacity_service_type1\"], vehicle_data[\"capacity_service_type2\"]],\n", + "}\n", + "\n", + "# add time windows for vehicle availability\n", + "fleet_data[\"vehicle_time_windows\"] = list(zip(vehicle_data['vehicle_availability_earliest'],\n", + " vehicle_data['vehicle_availability_latest']))\n", + "\n", + "# Dispatch the vehicle data to the cuOpt server\n", + "response_set = requests.post(\n", + " url + \"set_fleet_data\", json=fleet_data\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "d6fce836", + "metadata": {}, + "source": [ + "### Set Task Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b28f01c", + "metadata": {}, + "outputs": [], + "source": [ + "n_vehicles = len(vehicle_data_df)\n", + "\n", + "# Build the task data\n", + "task_data = {\n", + " \"task_locations\": service_location_ids,\n", + " # demand for service type 1 and service type 2\n", + " \"demand\": [service_location_data[\"service_type1_demand\"], service_location_data[\"service_type2_demand\"]],\n", + "}\n", + "\n", + "# add time window constraints and service time for the service locations\n", + "task_data[\"task_time_windows\"] = list(zip(service_location_data[\"location_earliest_time\"],\n", + " service_location_data[\"location_latest_time\"]))\n", + "task_data[\"service_times\"] = service_location_data[\"required_service_time\"]\n", + "\n", + "# Dispatch the task data to the cuOpt server\n", + "response_set = requests.post(\n", + " url + \"set_task_data\", json=task_data\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "be4c7654", + "metadata": {}, + "source": [ + "### Set Solver Configuration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd528c7e", + "metadata": {}, + "outputs": [], + "source": [ + "# Set the time limit \n", + "# Set number of climbers that will try to search for an optimal routes in parallel\n", + "solver_settings = {\n", + " \"time_limit\": 0.05,\n", + " \"number_of_climbers\": 128,\n", + "}\n", + "# Dispatch the solver settings to the cuOpt server\n", + "response_set = requests.post(\n", + " url + \"set_solver_config\", json=solver_settings\n", + ")\n", + "assert response_set.status_code == 200" + ] + }, + { + "cell_type": "markdown", + "id": "c1994f6a", + "metadata": {}, + "source": [ + "### Get Optimized Routes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d243bdef", + "metadata": {}, + "outputs": [], + "source": [ + "# Solve the problem\n", + "solver_response = requests.get(url + \"get_optimized_routes\")\n", + "\n", + "assert solver_response.status_code == 200\n", + "\n", + "# Process the solver results\n", + "solver_resp = solver_response.json()[\"response\"][\"solver_response\"]\n", + "\n", + "if solver_resp[\"status\"] == 0:\n", + " print(\"Cost for the routing in distance: \", solver_resp[\"solution_cost\"])\n", + " print(\"Vehicle count to complete routing: \", solver_resp[\"num_vehicles\"])\n", + " utils.show_vehicle_routes(solver_resp, location_names)\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_resp[\"status\"])" + ] + }, + { + "cell_type": "markdown", + "id": "68b89b87", + "metadata": {}, + "source": [ + "**Notice** that this solution leverages the fact that vehicle 1 is the only vehicle with the ability to perform both service type 1 and service type 2. In addition, vehicle 0 and vehicle 2 also serve the locations they are suited to service and minimize the time taken along these routes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "617cdd8e", + "metadata": {}, + "outputs": [], + "source": [ + "vehicle_colors = [\"red\", \"green\", \"blue\"]\n", + "utils.map_vehicle_routes(location_coordinates_df, solver_resp, vehicle_colors).show()" + ] + }, + { + "cell_type": "markdown", + "id": "47eb4eed", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + }, + "vscode": { + "interpreter": { + "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/microservice/cost_matrix_creation.ipynb b/notebooks/routing/microservice/cost_matrix_creation.ipynb index 328b96d..f1fa5f3 100644 --- a/notebooks/routing/microservice/cost_matrix_creation.ipynb +++ b/notebooks/routing/microservice/cost_matrix_creation.ipynb @@ -383,7 +383,7 @@ }, { "cell_type": "markdown", - "id": "2dd04f7c", + "id": "745930d2", "metadata": {}, "source": [ "_____\n", @@ -429,7 +429,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" }, "vscode": { "interpreter": { diff --git a/notebooks/routing/microservice/cpdptw-reoptmization.ipynb b/notebooks/routing/microservice/cpdptw-reoptmization.ipynb index 2842d49..385d6a1 100644 --- a/notebooks/routing/microservice/cpdptw-reoptmization.ipynb +++ b/notebooks/routing/microservice/cpdptw-reoptmization.ipynb @@ -98,7 +98,7 @@ " if \"cost_matrices\" in data:\n", " if not update:\n", " resp = requests.post(\n", - " url + \"add_cost_matrix\", params=data_params, json={\"cost_matrix\": data[\"cost_matrices\"]}\n", + " url + \"set_cost_matrix\", params=data_params, json={\"cost_matrix\": data[\"cost_matrices\"]}\n", " )\n", " else:\n", " resp = requests.put(\n", @@ -112,7 +112,7 @@ " if \"transit_time_matrices\" in data:\n", " if not update:\n", " resp = requests.post(\n", - " url + \"add_travel_time_matrix\", params=data_params, json={\"cost_matrix\": data[\"transit_time_matrices\"]}\n", + " url + \"set_travel_time_matrix\", params=data_params, json={\"cost_matrix\": data[\"transit_time_matrices\"]}\n", " )\n", " else:\n", " resp = requests.put(\n", @@ -824,7 +824,7 @@ }, { "cell_type": "markdown", - "id": "c16bb860", + "id": "f989f957", "metadata": {}, "source": [ "_____\n", @@ -870,7 +870,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" } }, "nbformat": 4, diff --git a/notebooks/routing/microservice/cpdptw_intra-factory_transport.ipynb b/notebooks/routing/microservice/cpdptw_intra-factory_transport.ipynb index 45f1e23..471d852 100644 --- a/notebooks/routing/microservice/cpdptw_intra-factory_transport.ipynb +++ b/notebooks/routing/microservice/cpdptw_intra-factory_transport.ipynb @@ -551,7 +551,7 @@ }, { "cell_type": "markdown", - "id": "234158a4", + "id": "2668327f", "metadata": {}, "source": [ "_____\n", @@ -597,7 +597,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" }, "vscode": { "interpreter": { diff --git a/notebooks/routing/microservice/cvrp_daily_deliveries.ipynb b/notebooks/routing/microservice/cvrp_daily_deliveries.ipynb index 0298331..04699f0 100644 --- a/notebooks/routing/microservice/cvrp_daily_deliveries.ipynb +++ b/notebooks/routing/microservice/cvrp_daily_deliveries.ipynb @@ -194,7 +194,7 @@ "\n", "# Set the cost matrix\n", "response_set = requests.post(\n", - " url + \"add_cost_matrix\", params=data_params, json=cost_data\n", + " url + \"set_cost_matrix\", params=data_params, json=cost_data\n", ")\n", "assert response_set.status_code == 200\n" ] @@ -393,7 +393,7 @@ }, { "cell_type": "markdown", - "id": "705703c6", + "id": "97b987b2", "metadata": {}, "source": [ "_____\n", @@ -439,7 +439,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" }, "vscode": { "interpreter": { diff --git a/notebooks/routing/microservice/cvrpstw_priority_routing.ipynb b/notebooks/routing/microservice/cvrpstw_priority_routing.ipynb index dcffe8a..0ddb6a7 100644 --- a/notebooks/routing/microservice/cvrpstw_priority_routing.ipynb +++ b/notebooks/routing/microservice/cvrpstw_priority_routing.ipynb @@ -230,7 +230,7 @@ "data_params = {\"return_data_state\": False}\n", "cost_data = {\"cost_matrix\": {0: business_metric_cost_matrix}}\n", "response_set = requests.post(\n", - " url + \"add_cost_matrix\", params=data_params, json=cost_data\n", + " url + \"set_cost_matrix\", params=data_params, json=cost_data\n", ")\n", "assert response_set.status_code == 200" ] @@ -255,7 +255,7 @@ "# set the secondary constraint checking time matrix\n", "time_data = {\"cost_matrix\": {0: constraint_checking_time_matrix}}\n", "response_set = requests.post(\n", - " url + \"add_travel_time_matrix\", params=data_params, json=time_data\n", + " url + \"set_travel_time_matrix\", params=data_params, json=time_data\n", ")\n", "assert response_set.status_code == 200" ] @@ -489,7 +489,7 @@ }, { "cell_type": "markdown", - "id": "ed8fca62", + "id": "38bc3217", "metadata": {}, "source": [ "_____\n", @@ -535,7 +535,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" }, "vscode": { "interpreter": { diff --git a/notebooks/routing/microservice/cvrptw_benchmark_gehring_homberger.ipynb b/notebooks/routing/microservice/cvrptw_benchmark_gehring_homberger.ipynb index 1d208b3..47215c3 100644 --- a/notebooks/routing/microservice/cvrptw_benchmark_gehring_homberger.ipynb +++ b/notebooks/routing/microservice/cvrptw_benchmark_gehring_homberger.ipynb @@ -154,7 +154,7 @@ "data_params = {\"return_data_state\": False}\n", "cost_data = {\"cost_matrix\": {0: cost_matrix.values.tolist()}}\n", "response_set = requests.post(\n", - " url + \"add_cost_matrix\", params=data_params, json=cost_data\n", + " url + \"set_cost_matrix\", params=data_params, json=cost_data\n", ")\n", "assert response_set.status_code == 200" ] @@ -403,7 +403,7 @@ }, { "cell_type": "markdown", - "id": "15192c4a", + "id": "dc94ab34", "metadata": {}, "source": [ "_____\n", @@ -449,7 +449,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" }, "vscode": { "interpreter": { diff --git a/notebooks/routing/microservice/cvrptw_service_team_routing.ipynb b/notebooks/routing/microservice/cvrptw_service_team_routing.ipynb index 41cd598..7aa159b 100644 --- a/notebooks/routing/microservice/cvrptw_service_team_routing.ipynb +++ b/notebooks/routing/microservice/cvrptw_service_team_routing.ipynb @@ -245,7 +245,7 @@ "data_params = {\"return_data_state\": False}\n", "cost_data = {\"cost_matrix\": {0: time_matrix}}\n", "response_set = requests.post(\n", - " url + \"add_cost_matrix\", params=data_params, json=cost_data\n", + " url + \"set_cost_matrix\", params=data_params, json=cost_data\n", ")\n", "assert response_set.status_code == 200" ] @@ -398,7 +398,7 @@ }, { "cell_type": "markdown", - "id": "a1550796", + "id": "47eb4eed", "metadata": {}, "source": [ "_____\n", @@ -444,7 +444,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" }, "vscode": { "interpreter": { diff --git a/notebooks/routing/microservice/notebook_utils/notebook_helpers.py b/notebooks/routing/microservice/notebook_utils/notebook_helpers.py index 14b8d8f..b244934 100644 --- a/notebooks/routing/microservice/notebook_utils/notebook_helpers.py +++ b/notebooks/routing/microservice/notebook_utils/notebook_helpers.py @@ -1,24 +1,5 @@ -# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: MIT -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - +# Copyright (c) 2022, NVIDIA CORPORATION. +# CONFIDENTIAL, provided under NDA. import matplotlib.pyplot as plt import matplotlib.font_manager as fm diff --git a/notebooks/routing/python/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb new file mode 100644 index 0000000..478eda5 --- /dev/null +++ b/notebooks/routing/python/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb @@ -0,0 +1,442 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "dfba40d6", + "metadata": {}, + "outputs": [], + "source": [ + "from cuopt import routing\n", + "from cuopt import distance_engine\n", + "import cudf\n", + "from scipy.spatial import distance\n", + "import numpy as np\n", + "import requests\n", + "import polyline\n", + "import folium\n", + "import json" + ] + }, + { + "cell_type": "markdown", + "id": "cd4716e9", + "metadata": {}, + "source": [ + "# Cost Matrix Calculation" + ] + }, + { + "cell_type": "markdown", + "id": "bdff7c68", + "metadata": {}, + "source": [ + "The cost matrix represents the user defined cost of traversing from one state/location in the optimization problem to another. This matrix is what cuOpt uses to assess the quality of a given solution as it seeks to minimize the total cost.\n", + "\n", + "The cost matrix is a square matrix of dimension equal to the number of locations in a given problem. In the example below we see an illustration of one such matrix." + ] + }, + { + "cell_type": "markdown", + "id": "991cad72", + "metadata": {}, + "source": [ + "\"cost_matrix.png" + ] + }, + { + "cell_type": "markdown", + "id": "ed85b5b1", + "metadata": {}, + "source": [ + "Additionally:\n", + "- The cost of going from a location to itself (e.g Cost(A,A)) is typically 0 \n", + "- Cost(A,B) need not be equal to Cost(B,A)" + ] + }, + { + "cell_type": "markdown", + "id": "2fe0488b", + "metadata": {}, + "source": [ + "## Simple Metric" + ] + }, + { + "cell_type": "markdown", + "id": "667af128", + "metadata": {}, + "source": [ + "In some simple cases a cost matrix can be generated from a list of points according to a user defined metric (e.g. Euclidean, Manhattan, etc.)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "01c4a7de", + "metadata": {}, + "outputs": [], + "source": [ + "points = cudf.DataFrame({\"x_coord\": [1, 1, 2, 3], \"y_coord\":[3, 1, 4, 1]})\n", + "points" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b52d43bb", + "metadata": {}, + "outputs": [], + "source": [ + "cost_matrix = distance.cdist(points.to_pandas().values, points.to_pandas().values, \"euclidean\")\n", + "print(f\"Simple Metric Cost Matrix:\\n\\n{cost_matrix}\")" + ] + }, + { + "cell_type": "markdown", + "id": "eb39ea0f", + "metadata": {}, + "source": [ + "## Weighted Waypoint Graph" + ] + }, + { + "cell_type": "markdown", + "id": "a72d76a3", + "metadata": {}, + "source": [ + "In cases where a unique environment needs to be described such as in the case of factories or warehouses it can be useful to define a waypoint graph that defines the cost of travel between adjacent accessible points in the environment.\n", + "\n", + "cuOpt has built in functionality to compute a cost matrix based on key target locations within a given waypoint graph. In the graph below we model 10 distinct waypoints. The target locations are 0, 4, 5, and 6." + ] + }, + { + "cell_type": "markdown", + "id": "d8afe1d6", + "metadata": {}, + "source": [ + "\"waypoint_graph.png" + ] + }, + { + "cell_type": "markdown", + "id": "fadc253b", + "metadata": {}, + "source": [ + "#### Graph Description\n", + "A simple description of each node, it's outgoing edges and corresponding weights" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8bf07e3", + "metadata": {}, + "outputs": [], + "source": [ + "graph = {\n", + " 0:{\n", + " \"edges\":[2], \n", + " \"weights\":[2]},\n", + " 1:{\n", + " \"edges\":[2, 4], \n", + " \"weights\":[2, 2]},\n", + " 2:{\n", + " \"edges\":[0, 1, 3, 5], \n", + " \"weights\":[2, 2, 2, 2]},\n", + " 3:{\n", + " \"edges\":[2, 6], \n", + " \"weights\":[2, 2]},\n", + " 4:{\n", + " \"edges\":[1, 7], \n", + " \"weights\":[2, 1]},\n", + " 5:{\n", + " \"edges\":[2, 8], \n", + " \"weights\":[2, 1]},\n", + " 6:{\n", + " \"edges\":[3, 9], \n", + " \"weights\":[2, 1]},\n", + " 7:{\n", + " \"edges\":[4, 8], \n", + " \"weights\":[1, 2]},\n", + " 8:{\n", + " \"edges\":[5, 7, 9], \n", + " \"weights\":[1, 2, 2]},\n", + " 9:{\n", + " \"edges\":[6, 8], \n", + " \"weights\":[1, 2]}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "54d51f36", + "metadata": {}, + "source": [ + "#### Convert to CSR\n", + "cuOpt requires that the graph be in compressed sparse row (CSR) format. Here we define a simple function that converts our graph to CSR." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ace5c271", + "metadata": {}, + "outputs": [], + "source": [ + "def convert_to_csr(graph):\n", + " num_nodes = len(graph)\n", + " \n", + " offsets = []\n", + " edges = []\n", + " weights = []\n", + " \n", + " cur_offset = 0\n", + " for node in range(num_nodes):\n", + " offsets.append(cur_offset)\n", + " cur_offset += len(graph[node][\"edges\"])\n", + " \n", + " edges = edges + graph[node][\"edges\"]\n", + " weights = weights + graph[node][\"weights\"]\n", + " \n", + " offsets.append(cur_offset)\n", + " \n", + " return np.array(offsets), np.array(edges), np.array(weights)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fed80f3a", + "metadata": {}, + "outputs": [], + "source": [ + "offsets, edges, weights = convert_to_csr(graph)\n", + "print(f\"offsets = {list(offsets)}\")\n", + "print(f\"edges = {list(edges)}\")\n", + "print(f\"weights = {list(weights)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "17f18f56", + "metadata": {}, + "source": [ + "#### Define desired target locations and calculate the cost matrix " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45ddc548", + "metadata": {}, + "outputs": [], + "source": [ + "target_locations = np.array([0, 4, 5, 6])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "475edfd9", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "waypoint_graph = distance_engine.WaypointMatrix(\n", + " offsets,\n", + " edges,\n", + " weights\n", + ")\n", + "cost_matrix = waypoint_graph.compute_cost_matrix(target_locations)\n", + "target_map = {k:v for k, v in enumerate(target_locations)}\n", + "\n", + "print(f\"Index <-> Waypoint Mapping: \\n{target_map}\\n\\n Waypoint Graph Cost Matrix: \\n{cost_matrix}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cc7349c1", + "metadata": {}, + "source": [ + "## Map Based" + ] + }, + { + "cell_type": "markdown", + "id": "c1ea0b6e", + "metadata": {}, + "source": [ + "When dealing with problems in shipping and logistics, road distance and/or time is often used as a cost metric. In these cases there are a number of tools available to calculate drive distance and/or time. One such tool is the [Open Source Routing Machine](http://project-osrm.org/)(OSRM). In the below example we create a cost matrix using OSRM from a list of lat/lon coordinates." + ] + }, + { + "cell_type": "markdown", + "id": "0c6a374a", + "metadata": {}, + "source": [ + "#### Define Points of Interest" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3671b3e9", + "metadata": {}, + "outputs": [], + "source": [ + "lat_lon_coords = [\n", + " [33.698206, -117.851364],\n", + " [33.672260, -117.838925], \n", + " [33.721003, -117.864121], \n", + " [33.695563, -117.824500]\n", + "] " + ] + }, + { + "cell_type": "markdown", + "id": "2bf8d58a", + "metadata": {}, + "source": [ + "#### Create Distance Matrix via OSRM" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b1e0a74", + "metadata": {}, + "outputs": [], + "source": [ + "locations=\"\"\n", + "for loc in lat_lon_coords:\n", + " locations = locations + \"{},{};\".format(loc[1], loc[0])\n", + "r = requests.get(\"http://router.project-osrm.org/table/v1/driving/\"+ locations[:-1])\n", + "\n", + "routes = json.loads(r.content)\n", + "cols = [str(i) for i in lat_lon_coords]\n", + "cost_matrix = cudf.DataFrame(routes['durations'], columns = cols, index= cols)\n", + "print(f\"Cost Matrix via OSRM:\\n\")\n", + "cost_matrix" + ] + }, + { + "cell_type": "markdown", + "id": "3f6e69fc", + "metadata": {}, + "source": [ + "#### Map Visualization" + ] + }, + { + "cell_type": "markdown", + "id": "d71a3260", + "metadata": {}, + "source": [ + "Visualization can be a helpful tool for understanding and communication. Here we demonstrate a sample visualization implementation showing the routes represented by the cost matrix above." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15508e51", + "metadata": {}, + "outputs": [], + "source": [ + "def get_map(my_lat_longs):\n", + " m = folium.Map(location=[33.7, -117.83], #[52.52, 13.41],\n", + " zoom_start=13)\n", + " folium.Marker(\n", + " location=[my_lat_longs[0][0],my_lat_longs[0][1]] ,\n", + " icon=folium.Icon(icon='play', color='red')\n", + " ).add_to(m)\n", + " for loc in my_lat_longs[1:]:\n", + " folium.Marker(\n", + " location=[loc[0], loc[1]],\n", + " icon=folium.Icon(icon='stop', color='green')\n", + " ).add_to(m)\n", + " \n", + " for src_idx in range(len(lat_lon_coords)):\n", + " for dst_idx in range(len(lat_lon_coords)):\n", + " if src_idx == dst_idx:\n", + " break\n", + " source = lat_lon_coords[src_idx]\n", + " destination = lat_lon_coords[dst_idx]\n", + " loc = \"{},{};{},{}\".format(source[1], source[0], destination[1], destination[0])\n", + " url = \"http://router.project-osrm.org/route/v1/driving/\"\n", + " r = requests.get(url + loc) \n", + "\n", + " res = r.json() \n", + " routes = polyline.decode(res['routes'][0]['geometry'])\n", + "\n", + " folium.PolyLine(\n", + " routes,\n", + " weight=5,\n", + " color='blue',\n", + " opacity=0.6\n", + " ).add_to(m)\n", + "\n", + " return m\n", + "get_map(lat_lon_coords)" + ] + }, + { + "cell_type": "markdown", + "id": "2bac6512", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + }, + "vscode": { + "interpreter": { + "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/python/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb new file mode 100644 index 0000000..759bb9e --- /dev/null +++ b/notebooks/routing/python/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb @@ -0,0 +1,599 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "2cb694f7", + "metadata": {}, + "outputs": [], + "source": [ + "from cuopt import routing\n", + "from cuopt import distance_engine\n", + "import cudf\n", + "import numpy as np\n", + "import pandas as pd" + ] + }, + { + "cell_type": "markdown", + "id": "9326712e", + "metadata": {}, + "source": [ + "# Intra-factory Transport\n", + "## Capacitated Pickup and Delivery Problem with Time Windows" + ] + }, + { + "cell_type": "markdown", + "id": "382afbd9", + "metadata": {}, + "source": [ + "Factory automation allows companies to raise the quality and consistency of manufacturing processes while also allowing human workers to focus on safer, less repetitive tasks that have higher cognitive and creative demands.\n", + "\n", + "In this scenario we have a set of intra-factory transport orders to move products at various stages in the assembly process from one processing station to another. Each station represents a particular type of manufacturing process and a given product may need to visit each processing station more than once. Multiple autonomous mobile robots (AMRs) with a fixed capacity will execute pickup and delivery orders between target locations, all with corresponding time_windows." + ] + }, + { + "cell_type": "markdown", + "id": "c3bc4ad4", + "metadata": {}, + "source": [ + "### Problem Details:\n", + "- 4 Locations each with an associated demand\n", + " - 1 Start Location for AMRs\n", + "\n", + " - 3 Process Stations\n", + "\n", + "- 3 AMRs with associated capacity" + ] + }, + { + "cell_type": "markdown", + "id": "e6090764", + "metadata": {}, + "source": [ + "- Hours of operation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d12f05d", + "metadata": {}, + "outputs": [], + "source": [ + "factory_open_time = 0\n", + "factory_close_time = 100" + ] + }, + { + "cell_type": "markdown", + "id": "e67a05ed", + "metadata": {}, + "source": [ + "![waypoint_graph.png not found](./notebook_utils/images/waypoint_graph.png \"Waypoint Graph\")" + ] + }, + { + "cell_type": "markdown", + "id": "d90ba90d", + "metadata": {}, + "source": [ + "### Waypoint Graph" + ] + }, + { + "cell_type": "markdown", + "id": "6febdb57", + "metadata": {}, + "source": [ + "#### Compressed Sparse Row (CSR) representation of above weighted waypoint graph.\n", + "For details on the CSR encoding of the above graph see the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c824c99", + "metadata": {}, + "outputs": [], + "source": [ + "offsets = np.array([0, 1, 3, 7, 9, 11, 13, 15, 17, 20, 22])\n", + "edges = np.array([2, 2, 4, 0, 1, 3, 5, 2, 6, 1, 7, 2, 8, 3, 9, 4, 8, 5, 7, 9, 6, 8])\n", + "weights = np.array([2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2])" + ] + }, + { + "cell_type": "markdown", + "id": "dbfcfa33", + "metadata": {}, + "source": [ + "#### Select specific waypoints in the graph as target locations\n", + "In this case we would like the AMRs to begin from waypoint 0 and service locations 4, 5, and 6." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e08f664", + "metadata": {}, + "outputs": [], + "source": [ + "target_locations = np.array([0, 4, 5, 6])" + ] + }, + { + "cell_type": "markdown", + "id": "7af883ad", + "metadata": {}, + "source": [ + "### Cost Matrix" + ] + }, + { + "cell_type": "markdown", + "id": "52bdc1d0", + "metadata": {}, + "source": [ + "#### Use cuOpt to calculate the corresponding cost matrix\n", + "Here we will be using a single cost matrix representing time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9975bf1a", + "metadata": {}, + "outputs": [], + "source": [ + "waypoint_graph = distance_engine.WaypointMatrix(\n", + " offsets,\n", + " edges,\n", + " weights\n", + ")\n", + "time_matrix = waypoint_graph.compute_cost_matrix(target_locations)\n", + "target_map = {v:k for k, v in enumerate(target_locations)}\n", + "index_map = {k:v for k, v in enumerate(target_locations)}\n", + "print(f\"Waypoint graph node to time matrix index mapping \\n{target_map}\\n\")\n", + "print(time_matrix)" + ] + }, + { + "cell_type": "markdown", + "id": "4ed911ff", + "metadata": {}, + "source": [ + "### Transport Orders" + ] + }, + { + "cell_type": "markdown", + "id": "4265c03a", + "metadata": {}, + "source": [ + "Setup Transport Order Data" + ] + }, + { + "cell_type": "markdown", + "id": "d7d7536d", + "metadata": {}, + "source": [ + "The transport orders dictate the movement of parts from one area of the factory to another. In this example nodes 4, 5, and 6 represent the processing stations that parts must travel between and deliveries to node 0 represent the movement of parts off the factory floor." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72b715c7", + "metadata": {}, + "outputs": [], + "source": [ + "transport_order_data = cudf.DataFrame({\n", + " \"pickup_location\": [4, 5, 6, 6, 5, 4],\n", + " \"delivery_location\": [5, 6, 0, 5, 4, 0],\n", + " \"order_demand\": [1, 1, 1, 1, 1, 1],\n", + " \"earliest_pickup\": [0, 0, 0, 0, 0, 0],\n", + " \"latest_pickup\": [10, 20, 30, 10, 20, 30],\n", + " \"pickup_service_time\": [2, 2, 2, 2, 2, 2],\n", + " \"earliest_delivery\": [0, 0, 0, 0, 0, 0],\n", + " \"latest_delivery\": [45, 45, 45, 45, 45, 45],\n", + " \"delivery_serivice_time\":[2, 2, 2, 2, 2, 2]\n", + "})\n", + "transport_order_data" + ] + }, + { + "cell_type": "markdown", + "id": "f2aaf28a", + "metadata": {}, + "source": [ + "### AMR Data" + ] + }, + { + "cell_type": "markdown", + "id": "a4e5e749", + "metadata": {}, + "source": [ + "Set up AMR fleet data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e17e899", + "metadata": {}, + "outputs": [], + "source": [ + "n_robots = 2\n", + "robot_data = {\n", + " \"robot_ids\": [i for i in range(n_robots)],\n", + " \"carrying_capacity\":[2, 2]\n", + "}\n", + "robot_data = cudf.DataFrame(robot_data).set_index('robot_ids')\n", + "robot_data" + ] + }, + { + "cell_type": "markdown", + "id": "31db9053", + "metadata": {}, + "source": [ + "### cuOpt DataModel View" + ] + }, + { + "cell_type": "markdown", + "id": "731fdcbe", + "metadata": {}, + "source": [ + "Setup the routing.DataModel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e765325", + "metadata": {}, + "outputs": [], + "source": [ + "n_locations = len(time_matrix)\n", + "n_vehicles = len(robot_data)\n", + "\n", + "# a pickup order and a delivery order are distinct with additional pad for the depot with 0 demand\n", + "n_orders = len(transport_order_data) * 2\n", + "\n", + "data_model = routing.DataModel(n_locations, n_vehicles, n_orders)\n", + "data_model.add_cost_matrix(time_matrix)" + ] + }, + { + "cell_type": "markdown", + "id": "7f8f10e8", + "metadata": {}, + "source": [ + "\n", + "#### Set the per order demand\n", + "\n", + "From the perspective of the cuOpt solver_settings, each distinct transaction (pickup order or delivery order) are treated separately with demand for pickup denoted as positive and the corresponding delivery treated as negative demand." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c936b137", + "metadata": {}, + "outputs": [], + "source": [ + "# This is the number of parts that needs to be moved\n", + "raw_demand = transport_order_data[\"order_demand\"]\n", + "\n", + "# When dropping off parts we want to remove one unit of demand from the robot\n", + "drop_off_demand = raw_demand * -1\n", + "\n", + "# Create pickup and delivery demand\n", + "order_demand = cudf.concat([raw_demand, drop_off_demand], ignore_index=True)\n", + "\n", + "order_demand" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87c2d9f8", + "metadata": {}, + "outputs": [], + "source": [ + "# add the capacity dimension\n", + "data_model.add_capacity_dimension(\"demand\", order_demand, robot_data['carrying_capacity'])" + ] + }, + { + "cell_type": "markdown", + "id": "48706e31", + "metadata": {}, + "source": [ + "#### Setting Order locations" + ] + }, + { + "cell_type": "markdown", + "id": "281bcd93", + "metadata": {}, + "source": [ + "set the order locations and pickup and delivery pairs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d325f4b", + "metadata": {}, + "outputs": [], + "source": [ + "pickup_order_locations = cudf.Series([target_map[loc] for loc in transport_order_data['pickup_location'].to_arrow().to_pylist()])\n", + "delivery_order_locations = cudf.Series([target_map[loc] for loc in transport_order_data['delivery_location'].to_arrow().to_pylist()])\n", + "order_locations = cudf.concat([pickup_order_locations, delivery_order_locations], ignore_index=True)\n", + "\n", + "print(order_locations)\n", + "\n", + "# add order locations\n", + "data_model.set_order_locations(order_locations)" + ] + }, + { + "cell_type": "markdown", + "id": "9389060b", + "metadata": {}, + "source": [ + "#### Mapping pickups to deliveries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "064978ca", + "metadata": {}, + "outputs": [], + "source": [ + "# IMPORTANT NOTE : pickup and delivery pairs are indexed into the order locations array.\n", + "npair_orders = int(len(order_locations)/2)\n", + "pickup_orders = cudf.Series([i for i in range(npair_orders)])\n", + "delivery_orders = cudf.Series([i + npair_orders for i in range(npair_orders)])\n", + "# add pickup and delivery pairs.\n", + "data_model.set_pickup_delivery_pairs(pickup_orders, delivery_orders)" + ] + }, + { + "cell_type": "markdown", + "id": "f8b35777", + "metadata": {}, + "source": [ + "#### Precedence Constraints" + ] + }, + { + "cell_type": "markdown", + "id": "2c4b288e", + "metadata": {}, + "source": [ + "We have decided to model the deliveries to index 0 as removing items from the factory floor. In some cases it may be necessary which operations are complete prior to exiting. Here we set precedence constraints on specific deliveries which must occur before parts can exit the factory floor" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "116ce6ca", + "metadata": {}, + "outputs": [], + "source": [ + "data_model.add_order_precedence(8, cudf.Series([6,7]))\n", + "data_model.add_order_precedence(11, cudf.Series([9,10]))" + ] + }, + { + "cell_type": "markdown", + "id": "ef21d42d", + "metadata": {}, + "source": [ + "#### Time Windows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3f328e3", + "metadata": {}, + "outputs": [], + "source": [ + "# create earliest times\n", + "vehicle_earliest_time = cudf.Series([factory_open_time] * n_vehicles)\n", + "order_time_window_earliest = cudf.concat([transport_order_data[\"earliest_pickup\"], transport_order_data[\"earliest_delivery\"]], ignore_index=True)\n", + "\n", + "# create latest times\n", + "vehicle_latest_time = cudf.Series([factory_close_time] * n_vehicles)\n", + "order_time_window_latest = cudf.concat([transport_order_data[\"latest_pickup\"], transport_order_data[\"latest_delivery\"]], ignore_index=True)\n", + "\n", + "# create service times\n", + "order_service_time = cudf.concat([transport_order_data[\"pickup_service_time\"], transport_order_data[\"delivery_serivice_time\"]], ignore_index=True)\n", + "\n", + "# add time window constraints\n", + "data_model.set_order_time_windows(order_time_window_earliest, order_time_window_latest)\n", + "data_model.set_order_service_times(order_service_time)\n", + "data_model.set_vehicle_time_windows(vehicle_earliest_time, vehicle_latest_time)" + ] + }, + { + "cell_type": "markdown", + "id": "b0d06888", + "metadata": {}, + "source": [ + "### CuOpt SolverSettings" + ] + }, + { + "cell_type": "markdown", + "id": "e3e08235", + "metadata": {}, + "source": [ + "Set up routing.SolverSettings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a6babc11", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "solver_settings = routing.SolverSettings()\n", + "\n", + "# set number of climbers that will try to search for an optimal routes in parallel\n", + "solver_settings.set_number_of_climbers(128)\n", + "\n", + "# solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", + "solver_settings.set_time_limit(0.05)" + ] + }, + { + "cell_type": "markdown", + "id": "854e9519", + "metadata": {}, + "source": [ + "### Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28a05ace", + "metadata": {}, + "outputs": [], + "source": [ + "routing_solution = routing.Solve(data_model, solver_settings)\n", + "if routing_solution.get_status() == 0:\n", + " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", + " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", + " print(routing_solution.route)\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" + ] + }, + { + "cell_type": "markdown", + "id": "4f6c5067", + "metadata": {}, + "source": [ + "#### Converting solution to waypoint graph" + ] + }, + { + "cell_type": "markdown", + "id": "1dbba138", + "metadata": {}, + "source": [ + "Because we maintained the mapping between cost matrix indices and locations in the waypoint graph we can now convert our solution to reference the nodes in the waypoint graph corresponding to the selected target locations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e0d98709", + "metadata": {}, + "outputs": [], + "source": [ + "target_loc_route = [index_map[loc] for loc in routing_solution.route['location'].to_arrow().to_pylist()]\n", + "routing_solution.route['order_array_index'] = routing_solution.route['route']\n", + "routing_solution.route['route'] = target_loc_route\n", + "print(routing_solution.route)" + ] + }, + { + "cell_type": "markdown", + "id": "bba4accd", + "metadata": {}, + "source": [ + "#### Convert routes from target location based routes to waypoint level routes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c13cfbf3", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "unique_robot_ids = routing_solution.route['truck_id'].unique()\n", + "all_routes = routing_solution.get_route()\n", + "\n", + "for robot in unique_robot_ids.to_arrow().to_pylist():\n", + " route = all_routes[all_routes['truck_id']==robot]\n", + " unique_target_locs = all_routes[all_routes['truck_id']==robot]['route'].unique().to_numpy()\n", + " \n", + " waypoint_route = waypoint_graph.compute_waypoint_sequence(unique_target_locs, route)\n", + " print(f\"Target location level route for robot {robot}:\\n{all_routes[all_routes['truck_id']==robot]['route']}\\n\\n\")\n", + " print(f\"Waypoint level route for robot {robot}:\\n{waypoint_route}\\n\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "4cb94aa7", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/python/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb new file mode 100644 index 0000000..d893a1a --- /dev/null +++ b/notebooks/routing/python/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb @@ -0,0 +1,368 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "e67c5c1d", + "metadata": {}, + "outputs": [], + "source": [ + "from cuopt import routing\n", + "from cuopt.routing import utils\n", + "import cudf\n", + "import pandas as pd" + ] + }, + { + "cell_type": "markdown", + "id": "ba50d71a", + "metadata": {}, + "source": [ + "# Daily Deliveries\n", + "## Capacitated Vehicle Routing Problem (CVRP)" + ] + }, + { + "cell_type": "markdown", + "id": "3ec34cd8", + "metadata": {}, + "source": [ + "Micro fulfillment centers allow retailers to move predictable, high volume products closer to the end consumer allowing for lower costs and shorter overall delivery times.\n", + "\n", + "In this scenario we have a number of same-day delivery orders that we would like to process for a given area from a given micro fulfillment center. We have the requisite number of delivery vehicles and enough time to deliver all packages over the course of a single day. Each delivery vehicle has a maximum capacity of orders it can carry and we are looking for the route assignment that minimizes the total distance driven by all vehicles." + ] + }, + { + "cell_type": "markdown", + "id": "4fc9ef31", + "metadata": {}, + "source": [ + "### Problem Details:\n", + "- 8 Locations each with an associated demand\n", + " - 1 MFC \n", + " - demand: [0]\n", + " - 7 Delivery Locations\n", + " - demand: [4, 4, 2, 2, 1, 2, 1]\n", + " \n", + "\n", + "- 3 Delivery vehicles each with an associated capacity\n", + " - 2 trucks\n", + " - capacity: [8, 8]\n", + " - 1 van\n", + " - capacity: [4]" + ] + }, + { + "cell_type": "markdown", + "id": "ed3c2736", + "metadata": {}, + "source": [ + "Below we visualize the delivery locations with respect to the MFC. The cost from all locations to all other locations (a cost matrix) will be required for optimization. To see an example of cost matrix generation from map data or a waypoint graph, refer to the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook. For the purpose of this simple example we will omit the cost matrix calculation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "01b12b30", + "metadata": {}, + "outputs": [], + "source": [ + "location_names = [ \"MFC\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\" ]\n", + "location_coordinates = [ [4, 4], [1, 3], [8, 1], [2, 1], [6, 7], [0, 2], [7, 6], [5, 3] ]\n", + "location_coordinates_df = pd.DataFrame(location_coordinates, columns=['xcord', 'ycord'], index=location_names)\n", + "utils.gen_plot(location_coordinates_df).show()" + ] + }, + { + "cell_type": "markdown", + "id": "42ba94fb", + "metadata": {}, + "source": [ + "### Cost Matrix" + ] + }, + { + "cell_type": "markdown", + "id": "82edd816", + "metadata": {}, + "source": [ + "The cost matrix dictates the cost of travel between locations of interest. The cost itself can be anything relevant to the user. In this case we simply use distance as our cost.\n", + "\n", + "Here is the cost(distance) matrix corresponding to the above locations:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfa64aee", + "metadata": {}, + "outputs": [], + "source": [ + "distance_matrix = [\n", + " [0.0, 3.1, 5.0, 3.6, 3.6, 4.5, 3.6, 1.4],\n", + " [3.1, 0.0, 7.3, 2.2, 6.4, 1.4, 6.7, 4.0],\n", + " [5.0, 7.3, 0.0, 6.0, 6.3, 8.1, 5.1, 3.6],\n", + " [3.6, 2.2, 6.0, 0.0, 7.2, 2.2, 7.1, 3.6],\n", + " [3.6, 6.4, 6.3, 7.2, 0.0, 7.8, 1.4, 4.1],\n", + " [4.5, 1.4, 8.1, 2.2, 7.8, 0.0, 8.1, 5.1],\n", + " [3.6, 6.7, 5.1, 7.1, 1.4, 8.1, 0.0, 3.6],\n", + " [1.4, 4.0, 3.6, 3.6, 4.1, 5.1, 3.6, 0.0]\n", + "]\n", + "\n", + "# Create a dataframe of this matrix\n", + "distance_matrix = cudf.DataFrame(distance_matrix, \n", + " index=location_coordinates_df.index, \n", + " columns=location_coordinates_df.index)\n", + "distance_matrix" + ] + }, + { + "cell_type": "markdown", + "id": "161b18aa", + "metadata": {}, + "source": [ + " ### Demand and Capacity" + ] + }, + { + "cell_type": "markdown", + "id": "3b038198", + "metadata": {}, + "source": [ + "Set up the demand for each location and the capacity for each vehicle" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9cb56810", + "metadata": {}, + "outputs": [], + "source": [ + "# \"MFC\" \"A\" \"B\" \"C\" \"D\" \"E\" \"F\" \"G\"\n", + "location_demand = [ 0, 4, 4, 2, 2, 1, 2, 1]\n", + "\n", + "\n", + "# Vehicle 0 Vehicle 1 Vehicle 2\n", + "vehicle_capacity = [ 8, 8, 4 ]" + ] + }, + { + "cell_type": "markdown", + "id": "9312c733", + "metadata": {}, + "source": [ + "### cuOpt DataModel View" + ] + }, + { + "cell_type": "markdown", + "id": "dd9932de", + "metadata": {}, + "source": [ + "Set up the cuOpt DataModel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a02105ba", + "metadata": {}, + "outputs": [], + "source": [ + "n_locations = len(location_demand)\n", + "n_vehicles = len(vehicle_capacity)\n", + "\n", + "data_model = routing.DataModel(n_locations, n_vehicles)\n", + "\n", + "# set the cost matrix\n", + "data_model.add_cost_matrix(distance_matrix)\n", + "\n", + "# add a capacity dimension for the deliveries\n", + "data_model.add_capacity_dimension(\n", + " \"deliveries\",\n", + " cudf.Series(location_demand),\n", + " cudf.Series(vehicle_capacity)\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "bc3d347a", + "metadata": {}, + "source": [ + "### CuOpt SolverSettings" + ] + }, + { + "cell_type": "markdown", + "id": "32f0dafd", + "metadata": {}, + "source": [ + "Set up cuOpt SolverSettings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd600ffa", + "metadata": {}, + "outputs": [], + "source": [ + "solver_settings = routing.SolverSettings()\n", + "\n", + "# set number of climbers that will try to search for an optimal routes in parallel\n", + "solver_settings.set_number_of_climbers(128)\n", + "\n", + "# solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", + "solver_settings.set_time_limit(0.01)\n" + ] + }, + { + "cell_type": "markdown", + "id": "e6bf223a", + "metadata": {}, + "source": [ + "### Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4141fd5", + "metadata": {}, + "outputs": [], + "source": [ + "routing_solution = routing.Solve(data_model, solver_settings)\n", + "if routing_solution.get_status() == 0:\n", + " print(\"Cost for the routing in distance: \", routing_solution.final_cost)\n", + " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", + " utils.show_vehicle_routes(routing_solution.route, location_names)\n", + " routing_solution.route\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8618e29a", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "vehicle_colors = [\"red\", \"green\", \"blue\"]\n", + "utils.map_vehicle_routes(location_coordinates_df, routing_solution.route, vehicle_colors).show()" + ] + }, + { + "cell_type": "markdown", + "id": "37ccafc5", + "metadata": {}, + "source": [ + "### Additional Constraints \n", + "##### Minimum Vehicles" + ] + }, + { + "cell_type": "markdown", + "id": "c560394e", + "metadata": {}, + "source": [ + "cuOpt has found a solution that does not require all available vehicles because the combined capacity of the two larger vehicles (16) is equal to total location demand (16). In some cases, this is a great solution as it gives the option to save on the costs associated with additional vehicles. In other cases there is value to assigning all available resources. In the latter case we can require that cuOpt use all 3 available vehicles and re-solve the problem with this constraint." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fab4aebb", + "metadata": {}, + "outputs": [], + "source": [ + "data_model.set_min_vehicles(n_vehicles)\n", + "\n", + "routing_solution = routing.Solve(data_model, solver_settings)\n", + "if routing_solution.get_status() == 0:\n", + " print(\"Cost for the routing in distance: \", routing_solution.final_cost)\n", + " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", + " utils.show_vehicle_routes(routing_solution.route, location_names)\n", + " routing_solution.route\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb069a02", + "metadata": {}, + "outputs": [], + "source": [ + "routing_solution.route" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "adb83802", + "metadata": {}, + "outputs": [], + "source": [ + "utils.map_vehicle_routes(location_coordinates_df, routing_solution.route, vehicle_colors).show()" + ] + }, + { + "cell_type": "markdown", + "id": "a25c9d04", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/python/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb new file mode 100644 index 0000000..d3935f3 --- /dev/null +++ b/notebooks/routing/python/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb @@ -0,0 +1,651 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "65705fab", + "metadata": {}, + "outputs": [], + "source": [ + "from cuopt import routing\n", + "from cuopt.routing import utils\n", + "import cudf\n", + "import pandas as pd" + ] + }, + { + "cell_type": "markdown", + "id": "fbb36201", + "metadata": {}, + "source": [ + "# Priority Routing\n", + "## Capacitated Vehicle Routing Problem with Soft Time Windows (CVRPSTW)" + ] + }, + { + "cell_type": "markdown", + "id": "bffb5f0c", + "metadata": {}, + "source": [ + "Loyalty (or Preferred) customer programs help companies to reward repeat customers and enhance their overall business offering. While the best possible customer service is always the goal, loyalty programs provide a mechanism for reinforcing the relationship with the customers that drive business revenue.\n", + "\n", + "In this scenario we have a set of deliveries with target time windows for delivery that do not represent a feasible solution given the delivery vehicles that are available. We would still like to deliver all the packages even if some of them are a little behind schedule. However, we would like to prioritize the deliveries of customers in our loyalty program to minimize the delay these customers experience.\n", + "\n", + "We also want to optimize according to a business defined cost objective that is a combination of business relevant metrics. To track time window constraints we will pass a time matrix as a constraint checking \"secondary matrix\".\n" + ] + }, + { + "cell_type": "markdown", + "id": "cb33a971", + "metadata": {}, + "source": [ + "### Problem Details:\n", + "- 8 Locations each with an associated demand\n", + " - 1 Distribution Center \n", + " - distribution center demand: [0]\n", + " - hours of operation: [0,24]\n", + " - 7 Service Locations\n", + " - demand for deliveries: [1, 1, 1, 1, 1, 1, 1]\n", + " - delivery time windows: [[9,10],[9,10],[9,10],[10,11],[10,11],[10,11],[9,10]]\n", + " - service location service times: [ 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25]\n", + " - loyalty program member: [1, 0, 0, 0, 1, 0, 1]\n", + "\n", + "- 3 Delivery vehicles each with an associated capacity\n", + " - 3 delivery vehicles\n", + " - capacity for deliveries: [3, 3, 3]" + ] + }, + { + "cell_type": "markdown", + "id": "baa93a42", + "metadata": {}, + "source": [ + "Below we visualize the delivery locations with respect to the distribution center. The cost from all locations to all other locations (a cost matrix) will be required for optimization. To see an example of cost matrix generation from map data or a waypoint graph, refer to the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook. For the purpose of this simple example we will omit the cost matrix calculation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e747d30d", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "location_names = [ \"DC\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\" ]\n", + "location_coordinates = [ [4, 4], [1, 3], [8, 1], [2, 1], [6, 7], [0, 2], [7, 6], [5, 3] ]\n", + "location_coordinates_df = pd.DataFrame(location_coordinates, columns=['xcord', 'ycord'], index=location_names)\n", + "utils.gen_plot(location_coordinates_df).show()" + ] + }, + { + "cell_type": "markdown", + "id": "1307fc95", + "metadata": {}, + "source": [ + "### Cost Matrix : Primary" + ] + }, + { + "cell_type": "markdown", + "id": "27525bf2", + "metadata": {}, + "source": [ + "The cost matrix dictates the cost of travel between locations of interest. The cost itself can be anything relevant to the user. In this case we are using a business defined cost objective as a primary cost matrix and a secondary time matrix to verify our time based constraints. \n", + "\n", + "Here is the cost(business metric) matrix corresponding to the locations above:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef01144e", + "metadata": {}, + "outputs": [], + "source": [ + "business_metric_cost_matrix = [\n", + " [0.0, 3.1, 5.0, 3.6, 3.6, 4.5, 3.6, 1.4],\n", + " [3.1, 0.0, 7.3, 2.2, 6.4, 1.4, 6.7, 4.0],\n", + " [5.0, 7.3, 0.0, 6.0, 6.3, 8.1, 5.1, 3.6],\n", + " [3.6, 2.2, 6.0, 0.0, 7.2, 2.2, 7.1, 3.6],\n", + " [3.6, 6.4, 6.3, 7.2, 0.0, 7.8, 1.4, 4.1],\n", + " [4.5, 1.4, 8.1, 2.2, 7.8, 0.0, 8.1, 5.1],\n", + " [3.6, 6.7, 5.1, 7.1, 1.4, 8.1, 0.0, 3.6],\n", + " [1.4, 4.0, 3.6, 3.6, 4.1, 5.1, 3.6, 0.0]\n", + "]\n", + "\n", + "# Create a dataframe of this matrix\n", + "business_metric_cost_matrix = cudf.DataFrame(business_metric_cost_matrix, \n", + " index=location_coordinates_df.index, \n", + " columns=location_coordinates_df.index)\n", + "business_metric_cost_matrix" + ] + }, + { + "cell_type": "markdown", + "id": "9671d772", + "metadata": {}, + "source": [ + "### Cost Matrix : Secondary" + ] + }, + { + "cell_type": "markdown", + "id": "d226f72b", + "metadata": {}, + "source": [ + "Here is the constraint checking (time) secondary matrix:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "145a2560", + "metadata": {}, + "outputs": [], + "source": [ + "constraint_checking_time_matrix = [\n", + " [0.00, 0.39, 0.63, 0.45, 0.45, 0.55, 0.45, 0.18 ],\n", + " [0.39, 0.00, 0.90, 0.28, 0.80, 0.18, 0.84, 0.50 ],\n", + " [0.63, 0.90, 0.00, 0.75, 0.79, 1.00, 0.64, 0.45 ],\n", + " [0.45, 0.28, 0.75, 0.00, 0.90, 0.28, 0.88, 0.45 ],\n", + " [0.45, 0.80, 0.79, 0.90, 0.00, 0.96, 0.18, 0.51 ],\n", + " [0.55, 0.18, 1.00, 0.28, 0.96, 0.00, 1.00, 0.64 ],\n", + " [0.45, 0.84, 0.64, 0.88, 0.18, 1.00, 0.00, 0.45 ],\n", + " [0.18, 0.50, 0.45, 0.45, 0.51, 0.64, 0.45, 0.00 ]\n", + "]\n", + "\n", + "# Create a dataframe of this matrix\n", + "constraint_checking_time_matrix = cudf.DataFrame(constraint_checking_time_matrix, \n", + " index=location_coordinates_df.index, \n", + " columns=location_coordinates_df.index)\n", + "constraint_checking_time_matrix" + ] + }, + { + "cell_type": "markdown", + "id": "cbd80cd3", + "metadata": {}, + "source": [ + "### Deliveries" + ] + }, + { + "cell_type": "markdown", + "id": "54d6af91", + "metadata": {}, + "source": [ + "Setup the delivery data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28e33246", + "metadata": {}, + "outputs": [], + "source": [ + "delivery_location_data = {\n", + " \"location_ids\": location_names,\n", + " \"delivery_demand\": [0, 1, 1, 1, 1, 1, 1, 1 ],\n", + " \"location_earliest_time\": [5, 9, 9, 9, 10, 10, 10, 9 ],\n", + " \"location_latest_time\": [20, 10, 10, 10, 11, 11, 11, 10],\n", + " \"required_service_time\": [0, 1, 1, 1, 1, 1, 1, 1 ],\n", + " \"loyalty_member\": [0, 0, 1, 0, 1, 0, 1, 0 ]\n", + "}\n", + "delivery_location_data = cudf.DataFrame(delivery_location_data).set_index('location_ids')\n", + "delivery_location_data" + ] + }, + { + "cell_type": "markdown", + "id": "6bace17e", + "metadata": {}, + "source": [ + "### Vehicles" + ] + }, + { + "cell_type": "markdown", + "id": "49cb13c9", + "metadata": {}, + "source": [ + "Setup delivery vehicle data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4931236", + "metadata": {}, + "outputs": [], + "source": [ + "n_vehicles = 3\n", + "vehicle_data = {\n", + " \"vehicle_ids\": [i for i in range(n_vehicles)],\n", + " \"delivery_capacity\":[3, 3, 3]\n", + "}\n", + "vehicle_data = cudf.DataFrame(vehicle_data).set_index('vehicle_ids')\n", + "vehicle_data" + ] + }, + { + "cell_type": "markdown", + "id": "e5738d68", + "metadata": {}, + "source": [ + "### cuOpt DataModel View" + ] + }, + { + "cell_type": "markdown", + "id": "fa84f4d7", + "metadata": {}, + "source": [ + "Setup the routing.DataModel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0bcf99c", + "metadata": {}, + "outputs": [], + "source": [ + "n_locations = len(delivery_location_data)\n", + "n_vehicles = len(vehicle_data)\n", + "\n", + "data_model = routing.DataModel(n_locations, n_vehicles)\n", + "\n", + "# set the primary cost matrix\n", + "data_model.add_cost_matrix(business_metric_cost_matrix)\n", + "\n", + "# set the secondary constraint checking time matrix\n", + "data_model.add_transit_time_matrix(constraint_checking_time_matrix)\n", + "\n", + "# add a capacity dimension for deliveries\n", + "data_model.add_capacity_dimension(\n", + " \"deliveries\",\n", + " cudf.Series(delivery_location_data[\"delivery_demand\"]),\n", + " cudf.Series(vehicle_data[\"delivery_capacity\"])\n", + ")\n", + "\n", + "# add time windows and service time for the locations\n", + "data_model.set_order_time_windows(\n", + " delivery_location_data[\"location_earliest_time\"],\n", + " delivery_location_data[\"location_latest_time\"]\n", + ")\n", + "data_model.set_order_service_times(\n", + " delivery_location_data[\"required_service_time\"]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "82f4dd75", + "metadata": {}, + "source": [ + "### CuOpt SolverSettings" + ] + }, + { + "cell_type": "markdown", + "id": "a606dd07", + "metadata": {}, + "source": [ + "Set up routing.SolverSettings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0f811c5", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "solver_settings = routing.SolverSettings()\n", + "\n", + "# set number of climbers that will try to search for an optimal routes in parallel\n", + "solver_settings.set_number_of_climbers(128)\n", + "\n", + "# solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", + "solver_settings.set_time_limit(0.05)" + ] + }, + { + "cell_type": "markdown", + "id": "1d2997dd", + "metadata": {}, + "source": [ + "### Attempted Solution" + ] + }, + { + "cell_type": "markdown", + "id": "284aaabd", + "metadata": {}, + "source": [ + "We can attempt to solve this problem as stated but as previously discussed it is not feasible within the specified target time windows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a63b509b", + "metadata": {}, + "outputs": [], + "source": [ + "routing_solution = routing.Solve(data_model, solver_settings)\n", + "if routing_solution.get_status() == 0:\n", + " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", + " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", + " utils.show_vehicle_routes(routing_solution.route, location_names)\n", + " routing_solution.route\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" + ] + }, + { + "cell_type": "markdown", + "id": "3f863e49", + "metadata": {}, + "source": [ + "cuOpt is unable to find a feasible solution. As previously discussed we would like to allow the deliveries to exceed the latest time windows by using soft time windows" + ] + }, + { + "cell_type": "markdown", + "id": "900028af", + "metadata": {}, + "source": [ + "### Initial Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8f6e8a2c", + "metadata": {}, + "outputs": [], + "source": [ + "solver_settings.set_solution_scope(routing.Scope.SOFT_TW)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "098c0061", + "metadata": {}, + "outputs": [], + "source": [ + "routing_solution = routing.Solve(data_model, solver_settings)\n", + "if routing_solution.get_status() == 0:\n", + " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", + " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", + " utils.show_vehicle_routes(routing_solution.route, location_names)\n", + " routing_solution.route\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" + ] + }, + { + "cell_type": "markdown", + "id": "42445bf0", + "metadata": {}, + "source": [ + "This works but if we look at the violations of latest arrival times we can see that some of our loyalty program customers are experiencing significant delivery delays. \n", + "**Note** Positive value in the delay column represents how late the delivery was compared to the latest target time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1845a27", + "metadata": {}, + "outputs": [], + "source": [ + "solution_data = routing_solution.route\n", + "solution_data['route'] = [location_names[i] for i in routing_solution.route['route'].to_arrow().to_pylist()]\n", + "solution_data = routing_solution.route.set_index('route')\n", + "solution_data = solution_data.join(delivery_location_data[\"location_latest_time\"])\n", + "solution_data = solution_data.join(delivery_location_data[\"loyalty_member\"])\n", + "solution_data[\"delay\"] = solution_data[\"arrival_stamp\"] - solution_data[\"location_latest_time\"]\n", + "solution_data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "984c2b7c", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "priority_delay = solution_data[(solution_data['delay'] > 0) & (solution_data['loyalty_member'] == 1)]\n", + "total_priority_delay = priority_delay['delay'].sum()\n", + "print(f\"Total delay of priority orders is {total_priority_delay}\")" + ] + }, + { + "cell_type": "markdown", + "id": "4672f2be", + "metadata": {}, + "source": [ + "### Improved Solution" + ] + }, + { + "cell_type": "markdown", + "id": "ccb8970b", + "metadata": {}, + "source": [ + "##### Introducing Penalty" + ] + }, + { + "cell_type": "markdown", + "id": "e81c2d40", + "metadata": {}, + "source": [ + "We can address this issue by assessing a large penalty for delivering late to loyalty members. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ed8275da", + "metadata": {}, + "outputs": [], + "source": [ + "delivery_location_data['penalty'] = delivery_location_data[\"loyalty_member\"]*100\n", + "delivery_location_data" + ] + }, + { + "cell_type": "markdown", + "id": "2f58006b", + "metadata": {}, + "source": [ + "Recreate the DataModel, adding penalty to the time windows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "100986d9", + "metadata": {}, + "outputs": [], + "source": [ + "data_model_with_penalty = routing.DataModel(n_locations, n_vehicles)\n", + "\n", + "# set the primary cost matrix\n", + "data_model_with_penalty.add_cost_matrix(business_metric_cost_matrix)\n", + "\n", + "# set the secondary constraint checking time matrix\n", + "data_model_with_penalty.add_transit_time_matrix(constraint_checking_time_matrix)\n", + "\n", + "# add a capacity dimension for deliveries\n", + "data_model_with_penalty.add_capacity_dimension(\n", + " \"deliveries\",\n", + " cudf.Series(delivery_location_data[\"delivery_demand\"]),\n", + " cudf.Series(vehicle_data[\"delivery_capacity\"])\n", + ")\n", + "\n", + "# add time windows and service time and penalty for the locations\n", + "data_model_with_penalty.set_order_time_windows(\n", + " delivery_location_data[\"location_earliest_time\"],\n", + " delivery_location_data[\"location_latest_time\"]\n", + ")\n", + "data_model.set_order_service_times(\n", + " delivery_location_data[\"required_service_time\"]\n", + ")\n", + "data_model.set_order_penalties(\n", + " delivery_location_data[\"penalty\"]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "e6e1eda1", + "metadata": {}, + "source": [ + "Setup another solver_settings instance" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fff016f4", + "metadata": {}, + "outputs": [], + "source": [ + "solver_settings = routing.SolverSettings()\n", + "\n", + "# set number of climbers that will try to search for an optimal routes in parallel\n", + "solver_settings.set_number_of_climbers(128)\n", + "\n", + "# solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", + "solver_settings.set_time_limit(0.05)\n", + "\n", + "# allow for soft time windows\n", + "solver_settings.set_solution_scope(routing.Scope.SOFT_TW)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35d19881", + "metadata": {}, + "outputs": [], + "source": [ + "routing_solution = routing.Solve(data_model_with_penalty, solver_settings)\n", + "if routing_solution.get_status() == 0:\n", + " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", + " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", + " utils.show_vehicle_routes(routing_solution.route, location_names)\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" + ] + }, + { + "cell_type": "markdown", + "id": "3ce0e0da", + "metadata": {}, + "source": [ + "**Note**: The new solution decreases the delay seen by priority customers as seen below" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a9c2d01b", + "metadata": {}, + "outputs": [], + "source": [ + "solution_data_priority = routing_solution.route\n", + "solution_data_priority['route'] = [location_names[i] for i in routing_solution.route['route'].to_arrow().to_pylist()]\n", + "solution_data_priority = routing_solution.route.set_index('route')\n", + "solution_data_priority = solution_data_priority.join(delivery_location_data[\"location_latest_time\"])\n", + "solution_data_priority = solution_data_priority.join(delivery_location_data[\"loyalty_member\"])\n", + "solution_data_priority[\"delay\"] = solution_data_priority[\"arrival_stamp\"] - solution_data_priority[\"location_latest_time\"]\n", + "solution_data_priority" + ] + }, + { + "cell_type": "markdown", + "id": "7c9d265a", + "metadata": {}, + "source": [ + "### Reduced Delay for Priority Orders" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8887d9a9", + "metadata": {}, + "outputs": [], + "source": [ + "priority_delay_penalty = solution_data_priority[(solution_data_priority['delay'] > 0) & (solution_data_priority['loyalty_member'] == 1)]\n", + "total_priority_delay_penalty = priority_delay_penalty['delay'].sum()\n", + "print(f\"Total delay of priority orders is now {total_priority_delay_penalty}\")\n", + "print(f\"Reduced the total delay to loyalty customers by {total_priority_delay - total_priority_delay_penalty}.\")" + ] + }, + { + "cell_type": "markdown", + "id": "30589911", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/python/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb new file mode 100644 index 0000000..32d009c --- /dev/null +++ b/notebooks/routing/python/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb @@ -0,0 +1,380 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a05e01aa", + "metadata": {}, + "source": [ + "
\n", + "\n", + "# Skip notebook test\n", + "\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "afc57ab3", + "metadata": {}, + "outputs": [], + "source": [ + "import cudf\n", + "from cuopt import routing\n", + "import numpy as np\n", + "import os\n", + "from cuopt.routing import utils\n", + "from scipy.spatial import distance" + ] + }, + { + "cell_type": "markdown", + "id": "0d500386", + "metadata": {}, + "source": [ + "# Benchmark Gehring & Homberger\n", + "## Capacitated Vehicle Routing Problem with Time Windows (CVRPTW)" + ] + }, + { + "cell_type": "markdown", + "id": "30e63d74", + "metadata": {}, + "source": [ + "While other notebooks such as [cvrptw_service_team_routing.ipynb](cvrptw_service_team_routing.ipynb) focus on the cuOpt API and high level problem modeling, here we focus on performance.\n", + "\n", + "cuOpt offers a unique benefit over other solver_settingss, specifically, time to solution. In addition to achieving world class accuracy, cuOpt also produces these solutions in a time frame that allows for re-optimization in dynamic environments and rapid iteration over possible problem configurations.\n", + "\n", + "Here we are demonstrating this performance on a large popular academic [dataset by Gehing & Homberger](https://www.sintef.no/projectweb/top/vrptw/homberger-benchmark/). These problems are well studied and used as the basis for comparison for VRP research and product offerings. The particular instance we will test with is from the group of largest (1000 location) problems. Each problem instance has an associated best known solution, the one we will measure against is shown below" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65860d5d", + "metadata": {}, + "outputs": [], + "source": [ + "homberger_1000_file = 'notebook_utils/data/C1_10_1.TXT'\n", + "\n", + "best_known_solution = {\n", + " \"n_vehicles\": 100,\n", + " \"cost\": 42478.95\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "af25d3f9", + "metadata": {}, + "source": [ + "### Problem Data\n", + "The data for this problem instance are provided via text file. cuOpt has a utility function available specifically for the Gehring & Homberger benchmark which converts the problem into the components required by cuOpt." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd6089b5", + "metadata": {}, + "outputs": [], + "source": [ + "orders, vehicle_capacity, n_vehicles = utils.create_from_file(homberger_1000_file)\n", + "\n", + "print(\"Number of locations : \", orders[\"demand\"].shape[0]-1)\n", + "print(\"Number of vehicles available : \", n_vehicles)\n", + "print(\"Capacity of each vehicle : \", vehicle_capacity)\n", + "print(\"\\nInitial Orders information\")\n", + "print(orders)" + ] + }, + { + "cell_type": "markdown", + "id": "ba4eb34d", + "metadata": {}, + "source": [ + "### Cost Matrix" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0cc3ced9", + "metadata": {}, + "outputs": [], + "source": [ + "coords = list(zip(orders['xcord'].to_arrow().to_pylist(),\n", + " orders['ycord'].to_arrow().to_pylist()))\n", + "\n", + "cost_matrix = cudf.DataFrame(distance.cdist(coords, coords, 'euclidean')).astype(np.float32)\n", + "print(f\"Shape of cost matrix: {cost_matrix.shape}\")" + ] + }, + { + "cell_type": "markdown", + "id": "1262221d", + "metadata": {}, + "source": [ + "### cuOpt DataModel View" + ] + }, + { + "cell_type": "markdown", + "id": "892ad25f", + "metadata": {}, + "source": [ + "Setup the routing.DataModel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "017f7783", + "metadata": {}, + "outputs": [], + "source": [ + "n_locations = len(cost_matrix)\n", + "\n", + "data_model = routing.DataModel(n_locations, n_vehicles)\n", + "data_model.add_cost_matrix(cost_matrix)\n", + "\n", + "capacity = cudf.Series([vehicle_capacity] * n_vehicles)\n", + "data_model.add_capacity_dimension(\"demand\", orders['demand'], capacity)\n", + "\n", + "data_model.set_order_time_windows(orders['earliest_time'], orders['latest_time'])\n", + "data_model.set_order_service_times(orders['service_time'])" + ] + }, + { + "cell_type": "markdown", + "id": "e4f9a455", + "metadata": {}, + "source": [ + "### CuOpt SolverSettings" + ] + }, + { + "cell_type": "markdown", + "id": "0097b2a6", + "metadata": {}, + "source": [ + "Set up routing.SolverSettings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3eddb994", + "metadata": {}, + "outputs": [], + "source": [ + "solver_settings = routing.SolverSettings()\n", + "\n", + "# set number of climbers that will try to search for an optimal routes in parallel\n", + "solver_settings.set_number_of_climbers(2048)" + ] + }, + { + "cell_type": "markdown", + "id": "50e77fb4", + "metadata": {}, + "source": [ + "### Solution" + ] + }, + { + "cell_type": "markdown", + "id": "dd5fc419", + "metadata": {}, + "source": [ + "Here we will examine the quality of the solution we increase the time budget provided to cuOpt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0a0934d", + "metadata": {}, + "outputs": [], + "source": [ + "def solve_problem(data_model, solver_settings, problem_size):\n", + " routing_solution = routing.Solve(data_model, solver_settings)\n", + " if routing_solution.get_status() == 0:\n", + " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", + " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", + " utils.show_vehicle_routes(routing_solution.route, [\"Depot\"]+[str(i) for i in range(1, problem_size+1)])\n", + " else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())\n", + " \n", + " return(routing_solution.vehicle_count, routing_solution.final_cost)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8880748", + "metadata": {}, + "outputs": [], + "source": [ + "def solution_eval(vehicles, cost, best_known_solution):\n", + " \n", + " print(f\"- cuOpt provides a solution using {vehicles} vehicles\")\n", + " print(f\"- This represents {vehicles - best_known_solution['n_vehicles']} more than the best known solution\")\n", + " print(f\"- Vehicle Percent Difference {(vehicles/best_known_solution['n_vehicles'] - 1)*100}% \\n\\n\")\n", + " print(f\"- In addition cuOpt provides a solution cost of {cost}\") \n", + " print(f\"- Best known solution cost is {best_known_solution['cost']}\")\n", + " print(f\"- Cost Percent Difference {(cost/best_known_solution['cost'] - 1)*100}%\")" + ] + }, + { + "cell_type": "markdown", + "id": "0941d56f", + "metadata": {}, + "source": [ + "**1 Second Time Limit**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70f12ffa", + "metadata": {}, + "outputs": [], + "source": [ + "solver_settings.set_time_limit(1)\n", + "vehicles, cost = solve_problem(data_model, solver_settings, len(cost_matrix))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a2453b1b", + "metadata": {}, + "outputs": [], + "source": [ + "# Evaluation:\n", + "solution_eval(vehicles, cost, best_known_solution)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bed97098", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "04ef0c21", + "metadata": {}, + "source": [ + "**10 Second Time Limit**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3934d8de", + "metadata": {}, + "outputs": [], + "source": [ + "solver_settings.set_time_limit(10)\n", + "vehicles, cost = solve_problem(data_model, solver_settings, len(cost_matrix))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ab5e6d1", + "metadata": {}, + "outputs": [], + "source": [ + "# Evaluation:\n", + "solution_eval(vehicles, cost, best_known_solution)" + ] + }, + { + "cell_type": "markdown", + "id": "a9a1b855", + "metadata": {}, + "source": [ + "**20 Second Time Limit**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e0c38643", + "metadata": {}, + "outputs": [], + "source": [ + "solver_settings.set_time_limit(20)\n", + "vehicles, cost = solve_problem(data_model, solver_settings, len(cost_matrix))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff80118b", + "metadata": {}, + "outputs": [], + "source": [ + "# Evaluation:\n", + "solution_eval(vehicles, cost, best_known_solution)" + ] + }, + { + "cell_type": "markdown", + "id": "251e8e38", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/python/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb new file mode 100644 index 0000000..886b2d1 --- /dev/null +++ b/notebooks/routing/python/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb @@ -0,0 +1,398 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "b2cba47f", + "metadata": {}, + "outputs": [], + "source": [ + "from cuopt import routing\n", + "from cuopt.routing import utils\n", + "import cudf\n", + "import pandas as pd" + ] + }, + { + "cell_type": "markdown", + "id": "371f38f1", + "metadata": {}, + "source": [ + "# Service Team Routing\n", + "## Capacitated Vehicle Routing Problem with Time Windows (CVRPTW)" + ] + }, + { + "cell_type": "markdown", + "id": "25e90f86", + "metadata": {}, + "source": [ + "The ability of service providers to set service time windows allows for easier and more dependable coordination between the service provider and their customers, while increasing overall customer satisfaction.\n", + "\n", + "In this scenario we have a number of service order locations with associated time windows and service times (time on-site to complete service). Each technician has an associated availability, ability to complete certain types of service, and a maximum number of service appointments per day." + ] + }, + { + "cell_type": "markdown", + "id": "63093d54", + "metadata": {}, + "source": [ + "### Problem Details:\n", + "- 8 Locations each with an associated demand\n", + " - 1 Headquarters \n", + " - service type 1 demand: [0]\n", + " - service type 2 demand: [1]\n", + " - headquarters hours of operation: [5,20]\n", + " - 7 Service Locations\n", + " - service type 1 demand: [1, 1, 1, 0, 0, 0, 0]\n", + " - service type 2 demand: [0, 0, 1, 1, 1, 1, 1]\n", + " - service locations time windows: [[9,12],[9,12],[11,14],[13,16],[13,16],[13,16],[13,16]]\n", + " - service location service times: [ 1, 1, 1.5, 0.5, 0.5, 0.5]\n", + "\n", + "- 3 Delivery vehicles each with an associated capacity\n", + " - 3 service technicians\n", + " - capacity for service type 1: [2, 1, 0]\n", + " - capacity for service type 2: [0, 1, 4]\n", + " - technician availability [[9,17], [12,15], [9,17]]\n" + ] + }, + { + "cell_type": "markdown", + "id": "baeeee39", + "metadata": {}, + "source": [ + "Below we visualize the service locations with respect to the service company headquarters. The cost from all locations to all other locations (a cost matrix) will be required for optimization. To see an example of cost matrix generation from map data or a waypoint graph, refer to the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook. For the purpose of this simple example we will omit the cost matrix calculation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8a0847d", + "metadata": {}, + "outputs": [], + "source": [ + "location_names = [ \"Headquarters\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\" ]\n", + "location_coordinates = [ [4, 4], [1, 3], [8, 1], [2, 1], [6, 7], [0, 2], [7, 6], [5, 3] ]\n", + "location_coordinates_df = pd.DataFrame(location_coordinates, columns=['xcord', 'ycord'], index=location_names)\n", + "utils.gen_plot(location_coordinates_df).show()" + ] + }, + { + "cell_type": "markdown", + "id": "ff1d68f2", + "metadata": {}, + "source": [ + "### Cost Matrix" + ] + }, + { + "cell_type": "markdown", + "id": "210a57e9", + "metadata": {}, + "source": [ + "The cost matrix dictates the cost of travel between locations of interest. The cost itself can be anything relevant to the user. In this case we are constraining time window constraints. When constraining time windows for locations or vehicles it is assumed (if only a single cost matrix is provided) that it represents time. \n", + "\n", + "Here is the cost(time) matrix corresponding to the locations above:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1054c7e3", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "time_matrix = [\n", + " [0.00, 0.31, 0.50, 0.36, 0.36, 0.44, 0.36, 0.14],\n", + " [0.31, 0.00, 0.72, 0.22, 0.64, 0.14, 0.67, 0.40],\n", + " [0.50, 0.72, 0.00, 0.60, 0.63, 0.80, 0.51, 0.36],\n", + " [0.36, 0.22, 0.60, 0.00, 0.72, 0.22, 0.70, 0.36],\n", + " [0.36, 0.64, 0.63, 0.72, 0.00, 0.77, 0.14, 0.41],\n", + " [0.44, 0.14, 0.80, 0.22, 0.77, 0.00, 0.80, 0.51],\n", + " [0.36, 0.67, 0.51, 0.70, 0.14, 0.80, 0.00, 0.36],\n", + " [0.14, 0.40, 0.36, 0.36, 0.41, 0.51, 0.36, 0.00]\n", + "]\n", + "\n", + "# Create a dataframe of this matrix\n", + "time_matrix = cudf.DataFrame(time_matrix, \n", + " index=location_coordinates_df.index, \n", + " columns=location_coordinates_df.index)\n", + "time_matrix" + ] + }, + { + "cell_type": "markdown", + "id": "3397b254", + "metadata": {}, + "source": [ + "### Service Locations" + ] + }, + { + "cell_type": "markdown", + "id": "ce7b7af7", + "metadata": {}, + "source": [ + "Setup the service location data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dc98afdf", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "service_location_data = {\n", + " \"location_ids\": location_names,\n", + " \"service_type1_demand\": [0, 1, 1, 1, 0, 0, 0, 0],\n", + " \"service_type2_demand\": [0, 0, 0, 1, 1, 1, 1, 1],\n", + " \"location_earliest_time\": [5, 9, 9, 11, 13, 13, 13, 13],\n", + " \"location_latest_time\": [20, 12, 12, 14, 16, 16, 16,16],\n", + " \"required_service_time\": [0, 1, 1, 1.5, 0.5, 0.5, 0.5, 0.5]\n", + "}\n", + "service_location_data = cudf.DataFrame(service_location_data).set_index('location_ids')\n", + "service_location_data" + ] + }, + { + "cell_type": "markdown", + "id": "cd27971f", + "metadata": {}, + "source": [ + "### Vehicles" + ] + }, + { + "cell_type": "markdown", + "id": "d66df281", + "metadata": {}, + "source": [ + "Setup vehicle/technician data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "248e1add", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "n_vehicles = 3\n", + "vehicle_data = {\n", + " \"vehicle_ids\": [i for i in range(n_vehicles)],\n", + " \"capacity_service_type1\":[2, 1, 0],\n", + " \"capacity_service_type2\":[0, 1, 4],\n", + " \"vehicle_availability_earliest\":[9, 11, 9],\n", + " \"vehicle_availability_latest\":[17, 15, 17]\n", + "}\n", + "vehicle_data = cudf.DataFrame(vehicle_data).set_index('vehicle_ids')\n", + "vehicle_data" + ] + }, + { + "cell_type": "markdown", + "id": "8c70e5b3", + "metadata": {}, + "source": [ + "### cuOpt DataModel View" + ] + }, + { + "cell_type": "markdown", + "id": "96c89955", + "metadata": {}, + "source": [ + "Setup the routing.DataModel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b28f01c", + "metadata": {}, + "outputs": [], + "source": [ + "n_locations = len(service_location_data)\n", + "n_vehicles = len(vehicle_data)\n", + "\n", + "data_model = routing.DataModel(n_locations, n_vehicles)\n", + "\n", + "# set the cost matrix\n", + "data_model.add_cost_matrix(time_matrix)\n", + "\n", + "# add a capacity dimension for service type 1\n", + "data_model.add_capacity_dimension(\n", + " \"service_type1\",\n", + " cudf.Series(service_location_data[\"service_type1_demand\"]),\n", + " cudf.Series(vehicle_data[\"capacity_service_type1\"])\n", + ")\n", + "# add a capacity dimension for service type 2\n", + "data_model.add_capacity_dimension(\n", + " \"service_type2\",\n", + " cudf.Series(service_location_data[\"service_type2_demand\"]),\n", + " cudf.Series(vehicle_data[\"capacity_service_type2\"])\n", + ")\n", + "\n", + "# add time windows and service time for the locations\n", + "data_model.set_order_time_windows(\n", + " service_location_data[\"location_earliest_time\"],\n", + " service_location_data[\"location_latest_time\"]\n", + ")\n", + "data_model.set_order_service_times(\n", + " service_location_data[\"required_service_time\"]\n", + ")\n", + "\n", + "# add time windows for vehicle availability\n", + "data_model.set_vehicle_time_windows(\n", + " vehicle_data[\"vehicle_availability_earliest\"], \n", + " vehicle_data[\"vehicle_availability_latest\"]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "be4c7654", + "metadata": {}, + "source": [ + "### CuOpt SolverSettings" + ] + }, + { + "cell_type": "markdown", + "id": "a5c8ffc8", + "metadata": {}, + "source": [ + "Setup routing.SolverSettings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd528c7e", + "metadata": {}, + "outputs": [], + "source": [ + "solver_settings = routing.SolverSettings()\n", + "\n", + "# set number of climbers that will try to search for an optimal routes in parallel\n", + "solver_settings.set_number_of_climbers(128)\n", + "\n", + "# solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", + "solver_settings.set_time_limit(0.05)" + ] + }, + { + "cell_type": "markdown", + "id": "c1994f6a", + "metadata": {}, + "source": [ + "### Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d243bdef", + "metadata": {}, + "outputs": [], + "source": [ + "routing_solution = routing.Solve(data_model, solver_settings)\n", + "if routing_solution.get_status() == 0:\n", + " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", + " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", + " utils.show_vehicle_routes(routing_solution.route, location_names)\n", + " routing_solution.route\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" + ] + }, + { + "cell_type": "markdown", + "id": "68b89b87", + "metadata": {}, + "source": [ + "**Notice** that this solution leverages the fact that vehicle 1 is the only vehicle with the ability to perform both service type 1 and service type 2. In addition, vehicle 0 and vehicle 2 also serve the locations they are suited to service and minimize the time taken along these routes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "617cdd8e", + "metadata": {}, + "outputs": [], + "source": [ + "vehicle_colors = [\"red\", \"green\", \"blue\"]\n", + "utils.map_vehicle_routes(location_coordinates_df, routing_solution.route, vehicle_colors).show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff30f074", + "metadata": {}, + "outputs": [], + "source": [ + "routing_solution.route" + ] + }, + { + "cell_type": "markdown", + "id": "9237ef66", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/python/.ipynb_checkpoints/pdptw_mixed_fleet-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/pdptw_mixed_fleet-checkpoint.ipynb new file mode 100644 index 0000000..6705007 --- /dev/null +++ b/notebooks/routing/python/.ipynb_checkpoints/pdptw_mixed_fleet-checkpoint.ipynb @@ -0,0 +1,458 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "3631e3f7", + "metadata": {}, + "outputs": [], + "source": [ + "from cuopt import routing\n", + "import cudf" + ] + }, + { + "cell_type": "markdown", + "id": "9326712e", + "metadata": {}, + "source": [ + "# Heterogenous Fleet Routing\n", + "## Pickup and Delivery Problem with Time Windows using Heterogenous Fleet of Vehicles" + ] + }, + { + "cell_type": "markdown", + "id": "382afbd9", + "metadata": {}, + "source": [ + "In scenarios such as food delivery, the delivery fleet may consist of various types of vehicles, for example bikes, and cars, and each type of vehicle has own advantages and limitations. For example, in crowded streets of NYC, it might be faster to reach a nearby destination on bike compared to car, while it is much faster with car in suburban areas. Service provides can improve customer satisfaction, reduce costs, and increase earning opportunity for drivers, using various types of vehicles depending on the geography of the service area. " + ] + }, + { + "cell_type": "markdown", + "id": "c3bc4ad4", + "metadata": {}, + "source": [ + "### Problem Details:\n", + "- 5 customer orders each with an associated demand, merchant, and time windows \n", + "- 2 merchants \n", + "- 3 vehicles \n", + " - 2 bikes \n", + " - 1 car\n", + "- 7 locations in total (5 customers + 2 merchants)" + ] + }, + { + "cell_type": "markdown", + "id": "7af883ad", + "metadata": {}, + "source": [ + "### Cost Matrix" + ] + }, + { + "cell_type": "markdown", + "id": "52bdc1d0", + "metadata": {}, + "source": [ + "#### Define cost matrix for each vehicle type" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9975bf1a", + "metadata": {}, + "outputs": [], + "source": [ + "# locations 0, 1 correspond to merchant \n", + "# locations 2 to 6 correspond to customers\n", + "n_locations = 7\n", + "time_matrix_car = cudf.DataFrame([\n", + " [0., 6., 5., 6., 6., 7., 4.], \n", + " [6., 0., 3., 8., 7., 3., 2.], \n", + " [5., 3., 0., 4., 11., 6., 4.], \n", + " [6., 8., 4., 0., 12., 11., 10.], \n", + " [6., 7., 11., 12., 0., 7., 4.],\n", + " [7., 3., 6., 11., 7., 0., 3.], \n", + " [4., 2., 4., 10., 4., 3., 0.]])\n", + "\n", + "time_matrix_bike = cudf.DataFrame([\n", + " [0., 15., 10., 9., 10., 21., 6.],\n", + " [15., 0., 6., 15., 12., 5., 4.], \n", + " [10., 6., 0., 9., 20, 12., 9.],\n", + " [9., 15., 9., 0., 20., 22., 20.],\n", + " [10.,12., 20., 20., 0., 15., 8.], \n", + " [21., 5., 12., 22., 15., 0., 8.], \n", + " [6., 4., 9., 20., 8., 8., 0.]])\n", + "\n", + "print('time matrix for bike:: \\n', time_matrix_bike, '\\n')\n", + "print('time matrix for car:: \\n', time_matrix_car, '\\n')" + ] + }, + { + "cell_type": "markdown", + "id": "f2aaf28a", + "metadata": {}, + "source": [ + "### Fleet Data" + ] + }, + { + "cell_type": "markdown", + "id": "a4e5e749", + "metadata": {}, + "source": [ + "Set up mixed fleet data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e17e899", + "metadata": {}, + "outputs": [], + "source": [ + "n_vehicles = 3\n", + "\n", + "# type 0 corresponds to bike and type 1 corresponds to car\n", + "vehicle_types = cudf.Series([0, 0, 1])\n", + "\n", + "# bikes can carry two units of goods while car can carry 5 units of goods\n", + "vehicle_capacity = cudf.Series([2, 2, 5])\n", + "\n", + "print(n_vehicles)\n", + "print(vehicle_types)\n", + "print(vehicle_capacity)" + ] + }, + { + "cell_type": "markdown", + "id": "4ed911ff", + "metadata": {}, + "source": [ + "### Customer Orders" + ] + }, + { + "cell_type": "markdown", + "id": "4265c03a", + "metadata": {}, + "source": [ + "Setup Customer Order Data" + ] + }, + { + "cell_type": "markdown", + "id": "d7d7536d", + "metadata": {}, + "source": [ + "The customer order data contains the information of merchant, the amount of goods, and the time window for the delivery." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72b715c7", + "metadata": {}, + "outputs": [], + "source": [ + "customer_order_data = cudf.DataFrame({\n", + " \"pickup_location\": [0, 0, 1, 1, 1],\n", + " \"delivery_location\": [2, 3, 4, 5, 6],\n", + " \"order_demand\": [1, 2, 2, 1, 2],\n", + " \"earliest_pickup\": [0, 0, 0, 0, 0],\n", + " \"latest_pickup\": [30, 120, 60, 120, 45],\n", + " \"pickup_service_time\": [10, 10, 10, 10, 10],\n", + " \"earliest_delivery\": [0, 0, 0, 0, 0],\n", + " \"latest_delivery\": [30, 120, 60, 120, 45],\n", + " \"delivery_serivice_time\":[10, 10, 10, 10, 10]\n", + "})\n", + "customer_order_data" + ] + }, + { + "cell_type": "markdown", + "id": "31db9053", + "metadata": {}, + "source": [ + "### cuOpt routing DataModel" + ] + }, + { + "cell_type": "markdown", + "id": "731fdcbe", + "metadata": {}, + "source": [ + "#### Initialize routing.DataModel object" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e765325", + "metadata": {}, + "outputs": [], + "source": [ + "# a pickup order and a delivery order are distinct\n", + "n_orders = len(customer_order_data) * 2\n", + "\n", + "data_model = routing.DataModel(n_locations, n_vehicles, n_orders)" + ] + }, + { + "cell_type": "markdown", + "id": "bc27baca", + "metadata": {}, + "source": [ + "#### Set Vehicle types and corresponding cost matrices" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a4804c2", + "metadata": {}, + "outputs": [], + "source": [ + "# set matrices associated with each vehicle type\n", + "data_model.set_vehicle_types(vehicle_types)\n", + "\n", + "data_model.add_cost_matrix(time_matrix_bike, 0)\n", + "data_model.add_cost_matrix(time_matrix_car, 1)" + ] + }, + { + "cell_type": "markdown", + "id": "48706e31", + "metadata": {}, + "source": [ + "#### Setting Order locations\n", + "From the cuOpt solver_settings perspective, each distinct transaction (pickup order or delivery order) is treated separately. The locations for each order is specified using order locations. The first entry in order locations array is always reserved for the notion of depot for the problem. So for a total n orders, the order location array is of size 2n+1. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d325f4b", + "metadata": {}, + "outputs": [], + "source": [ + "pickup_order_locations = customer_order_data['pickup_location'] \n", + "delivery_order_locations = customer_order_data['delivery_location']\n", + "order_locations = cudf.concat([pickup_order_locations, delivery_order_locations], ignore_index=True)\n", + "\n", + "print(order_locations)\n", + "\n", + "# add order locations\n", + "data_model.set_order_locations(order_locations)" + ] + }, + { + "cell_type": "markdown", + "id": "9389060b", + "metadata": {}, + "source": [ + "#### Mapping pickups to deliveries\n", + "Order locations do not provide information regarding the type of order (i.e, pickup or delivery). This information is provided to solver by setting two arrays pickup_orders and delivery_orders. The entries of these arrays corresponding the order numbers in exanded list described above. \n", + "\n", + "For a pair order i, pickup_orders[i] and delivery_orders[i] correspond to order index in 2n total orders. Furthermore, order_locations[pickup_orders[i]] and order_locations[delivery_orders[i]] indicate the pickup location and delivery location of order i. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "064978ca", + "metadata": {}, + "outputs": [], + "source": [ + "npair_orders = int(len(order_locations)/2)\n", + "pickup_orders = cudf.Series([i for i in range(npair_orders)])\n", + "delivery_orders = cudf.Series([i + npair_orders for i in range(npair_orders)])\n", + "\n", + "# add pickup and delivery pairs.\n", + "data_model.set_pickup_delivery_pairs(pickup_orders, delivery_orders)" + ] + }, + { + "cell_type": "markdown", + "id": "7f8f10e8", + "metadata": {}, + "source": [ + "\n", + "#### Set the per order demand\n", + "\n", + "From the perspective of the cuOpt solver, each distinct transaction (pickup order or delivery order) are treated separately with demand for pickup denoted as positive and the corresponding delivery treated as negative demand." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c936b137", + "metadata": {}, + "outputs": [], + "source": [ + "# This is the number of goods that need to be delivered\n", + "raw_demand = customer_order_data[\"order_demand\"]\n", + "\n", + "# When dropping off goods, remove one unit of demand from the vehicle\n", + "drop_off_demand = raw_demand * -1\n", + "\n", + "# Create pickup and delivery demand\n", + "order_demand = cudf.concat([raw_demand, drop_off_demand], ignore_index=True)\n", + "\n", + "order_demand\n", + "\n", + "# add the capacity dimension\n", + "data_model.add_capacity_dimension(\"demand\", order_demand, vehicle_capacity)" + ] + }, + { + "cell_type": "markdown", + "id": "ef21d42d", + "metadata": {}, + "source": [ + "#### Time Windows\n", + "\n", + "Create per order time windows similar to demand. Set earliest time and service time of depot to be zero and the latest time to be a large value so that all orders are fulfilled. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3f328e3", + "metadata": {}, + "outputs": [], + "source": [ + "# create earliest times\n", + "order_time_window_earliest = cudf.concat([customer_order_data[\"earliest_pickup\"], customer_order_data[\"earliest_delivery\"]], ignore_index=True)\n", + "\n", + "# create latest times\n", + "order_time_window_latest = cudf.concat([customer_order_data[\"latest_pickup\"], customer_order_data[\"latest_delivery\"]], ignore_index=True)\n", + "\n", + "# create service times\n", + "order_service_time = cudf.concat([customer_order_data[\"pickup_service_time\"], customer_order_data[\"delivery_serivice_time\"]], ignore_index=True)\n", + "\n", + "# add time window constraints\n", + "data_model.set_order_time_windows(order_time_window_earliest, order_time_window_latest)\n", + "data_model.set_order_service_times(order_service_time)\n", + "\n", + "# set time windows for the fleet\n", + "vehicle_earliest_time = cudf.Series([0] * n_vehicles)\n", + "vehicle_latest_time = cudf.Series([1000] * n_vehicles)\n", + "\n", + "data_model.set_vehicle_time_windows(vehicle_earliest_time, vehicle_latest_time)" + ] + }, + { + "cell_type": "markdown", + "id": "b0d06888", + "metadata": {}, + "source": [ + "### CuOpt SolverSettings" + ] + }, + { + "cell_type": "markdown", + "id": "e3e08235", + "metadata": {}, + "source": [ + "Set up routing.SolverSettings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a6babc11", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "solver_settings = routing.SolverSettings()\n", + "\n", + "# solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", + "solver_settings.set_time_limit(0.05)" + ] + }, + { + "cell_type": "markdown", + "id": "854e9519", + "metadata": {}, + "source": [ + "### Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28a05ace", + "metadata": {}, + "outputs": [], + "source": [ + "routing_solution = routing.Solve(data_model, solver_settings)\n", + "if routing_solution.get_status() == 0:\n", + " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", + " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", + " print(routing_solution.route)\n", + "else:\n", + " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" + ] + }, + { + "cell_type": "markdown", + "id": "93309efc", + "metadata": {}, + "source": [ + "_____\n", + "\n", + "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "#### SPDX-License-Identifier: MIT\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a\n", + "copy of this software and associated documentation files (the \"Software\"),\n", + "to deal in the Software without restriction, including without limitation\n", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", + "and/or sell copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following conditions:\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", + "DEALINGS IN THE SOFTWARE.\n", + "\n", + "---" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/routing/python/README.md b/notebooks/routing/python/README.md index 0535443..056cdda 100644 --- a/notebooks/routing/python/README.md +++ b/notebooks/routing/python/README.md @@ -1,42 +1,40 @@ # cuOpt Notebooks -A collection of Jupyter Notebooks that outline how cuOpt python API can be used to solve a wide variety of routing problems - -These notebooks require a running NVIDIA cuOpt container availble from [NGC](https://catalog.ngc.nvidia.com/) +Contains a collection of Jupyter Notebooks that outline how cuOpt python API can be used to solve a wide variety of problems ## Summary Each notebook represents an example use case for NVIDIA cuOpt. All notebooks demonstrate high level problem modeling leveraging the cuOpt DataModel and SolverSettings. In addition, each notebook covers additional cuOpt features listed below alongside notebook descriptions -- **[cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) :** A notebook demonstrating how to build a cost matrix for various problem types +- **cost_matrix_creation.ipynb :** A notebook demonstrating how to build a cost matrix for various problem types - *Additional Features:* - WaypointMatrix - Visualization -- **[cvrp_daily_deliveries.ipynb](cvrp_daily_deliveries.ipynb) :** A notebook demonstrating a simple delivery use case +- **cvrp_daily_deliveries.ipynb :** A notebook demonstrating a simple delivery use case - *Additional Features:* - Min Vehicles Constraint -- **[cvrptw_service_team_routing.ipynb](cvrptw_service_team_routing.ipynb) :** A notebook demonstrating service team routing using technicians with varied availability and skillset. +- **cvrptw_service_team_routing.ipynb :** A notebook demonstrating service team routing using technicians with varied availability and skillset. - *Additional Features:* - Multiple Capacity (and demand) Dimensions - Vehicle Time Windows -- **[cvrpstw_priority_routing.ipynb](cvrpstw_priority_routing.ipynb) :** A notebook demonstrating routing of mixed priority orders +- **cvrpstw_priority_routing.ipynb :** A notebook demonstrating routing of mixed priority orders - *Additional Features:* - Secondary Cost Matrix - Soft Time Windows - Penalties -- **[cpdptw_intra-factory_transport.ipynb](cpdptw_intra-factory_transport.ipynb) :** A notebook demonstrating intra-factory routing modeled as a pickup and delivery problem +- **cpdptw_intra-factory_transport.ipynb :** A notebook demonstrating intra-factory routing modeled as a pickup and delivery problem - *Additional Features:* - Pickup and Deliver - Order Locations - Precedence Constraints - WaypointMatrix -- **[cvrptw_benchmark_gehring_homberger.ipynb](cvrptw_benchmark_gehring_homberger.ipynb) :** A notebook demonstrating a benchmark run using a large academic problem instance. +- **cvrptw_benchmark_gehring_homberger.ipynb :** A notebook demonstrating a benchmark run using a large academic problem instance. -- **[pdptw_mixed_fleet.ipynb](pdptw_mixed_fleet.ipynb) :** A notebook demonstrating heterogenous fleet modeling for a pickup and delivery problem +- **pdptw_mixed_fleet.ipynb :** A notebook demonstrating heterogenous fleet modeling for a pickup and delivery problem - *Additional Features:* - Pickup and Deliver - Order Locations \ No newline at end of file diff --git a/notebooks/routing/python/cost_matrix_creation.ipynb b/notebooks/routing/python/cost_matrix_creation.ipynb index d435c62..478eda5 100644 --- a/notebooks/routing/python/cost_matrix_creation.ipynb +++ b/notebooks/routing/python/cost_matrix_creation.ipynb @@ -383,7 +383,7 @@ }, { "cell_type": "markdown", - "id": "02184bfe", + "id": "2bac6512", "metadata": {}, "source": [ "_____\n", @@ -429,7 +429,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" }, "vscode": { "interpreter": { diff --git a/notebooks/routing/python/cpdptw_intra-factory_transport.ipynb b/notebooks/routing/python/cpdptw_intra-factory_transport.ipynb index e595486..759bb9e 100644 --- a/notebooks/routing/python/cpdptw_intra-factory_transport.ipynb +++ b/notebooks/routing/python/cpdptw_intra-factory_transport.ipynb @@ -419,7 +419,8 @@ "order_service_time = cudf.concat([transport_order_data[\"pickup_service_time\"], transport_order_data[\"delivery_serivice_time\"]], ignore_index=True)\n", "\n", "# add time window constraints\n", - "data_model.set_order_time_windows(order_time_window_earliest, order_time_window_latest, order_service_time)\n", + "data_model.set_order_time_windows(order_time_window_earliest, order_time_window_latest)\n", + "data_model.set_order_service_times(order_service_time)\n", "data_model.set_vehicle_time_windows(vehicle_earliest_time, vehicle_latest_time)" ] }, @@ -541,7 +542,7 @@ }, { "cell_type": "markdown", - "id": "34b4221c", + "id": "4cb94aa7", "metadata": {}, "source": [ "_____\n", @@ -590,7 +591,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" } }, "nbformat": 4, diff --git a/notebooks/routing/python/cvrp_daily_deliveries.ipynb b/notebooks/routing/python/cvrp_daily_deliveries.ipynb index 40fe6ea..d893a1a 100644 --- a/notebooks/routing/python/cvrp_daily_deliveries.ipynb +++ b/notebooks/routing/python/cvrp_daily_deliveries.ipynb @@ -314,7 +314,7 @@ }, { "cell_type": "markdown", - "id": "07a30d4b", + "id": "a25c9d04", "metadata": {}, "source": [ "_____\n", @@ -360,7 +360,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" } }, "nbformat": 4, diff --git a/notebooks/routing/python/cvrpstw_priority_routing.ipynb b/notebooks/routing/python/cvrpstw_priority_routing.ipynb index 7e99c5c..d3935f3 100644 --- a/notebooks/routing/python/cvrpstw_priority_routing.ipynb +++ b/notebooks/routing/python/cvrpstw_priority_routing.ipynb @@ -273,7 +273,9 @@ "# add time windows and service time for the locations\n", "data_model.set_order_time_windows(\n", " delivery_location_data[\"location_earliest_time\"],\n", - " delivery_location_data[\"location_latest_time\"], \n", + " delivery_location_data[\"location_latest_time\"]\n", + ")\n", + "data_model.set_order_service_times(\n", " delivery_location_data[\"required_service_time\"]\n", ")" ] @@ -492,11 +494,15 @@ " cudf.Series(vehicle_data[\"delivery_capacity\"])\n", ")\n", "\n", - "# add time windows and service time for the locations\n", + "# add time windows and service time and penalty for the locations\n", "data_model_with_penalty.set_order_time_windows(\n", " delivery_location_data[\"location_earliest_time\"],\n", - " delivery_location_data[\"location_latest_time\"], \n", - " delivery_location_data[\"required_service_time\"],\n", + " delivery_location_data[\"location_latest_time\"]\n", + ")\n", + "data_model.set_order_service_times(\n", + " delivery_location_data[\"required_service_time\"]\n", + ")\n", + "data_model.set_order_penalties(\n", " delivery_location_data[\"penalty\"]\n", ")" ] @@ -591,7 +597,7 @@ }, { "cell_type": "markdown", - "id": "3f675726", + "id": "30589911", "metadata": {}, "source": [ "_____\n", @@ -637,7 +643,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" } }, "nbformat": 4, diff --git a/notebooks/routing/python/cvrptw_benchmark_gehring_homberger.ipynb b/notebooks/routing/python/cvrptw_benchmark_gehring_homberger.ipynb index d54385c..32d009c 100644 --- a/notebooks/routing/python/cvrptw_benchmark_gehring_homberger.ipynb +++ b/notebooks/routing/python/cvrptw_benchmark_gehring_homberger.ipynb @@ -141,7 +141,8 @@ "capacity = cudf.Series([vehicle_capacity] * n_vehicles)\n", "data_model.add_capacity_dimension(\"demand\", orders['demand'], capacity)\n", "\n", - "data_model.set_order_time_windows(orders['earliest_time'], orders['latest_time'], orders['service_time'])" + "data_model.set_order_time_windows(orders['earliest_time'], orders['latest_time'])\n", + "data_model.set_order_service_times(orders['service_time'])" ] }, { @@ -325,7 +326,7 @@ }, { "cell_type": "markdown", - "id": "e55b584b", + "id": "251e8e38", "metadata": {}, "source": [ "_____\n", @@ -371,7 +372,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" } }, "nbformat": 4, diff --git a/notebooks/routing/python/cvrptw_service_team_routing.ipynb b/notebooks/routing/python/cvrptw_service_team_routing.ipynb index ff3c490..886b2d1 100644 --- a/notebooks/routing/python/cvrptw_service_team_routing.ipynb +++ b/notebooks/routing/python/cvrptw_service_team_routing.ipynb @@ -243,7 +243,9 @@ "# add time windows and service time for the locations\n", "data_model.set_order_time_windows(\n", " service_location_data[\"location_earliest_time\"],\n", - " service_location_data[\"location_latest_time\"], \n", + " service_location_data[\"location_latest_time\"]\n", + ")\n", + "data_model.set_order_service_times(\n", " service_location_data[\"required_service_time\"]\n", ")\n", "\n", @@ -342,7 +344,7 @@ }, { "cell_type": "markdown", - "id": "9c4feb62", + "id": "9237ef66", "metadata": {}, "source": [ "_____\n", @@ -388,7 +390,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" } }, "nbformat": 4, diff --git a/notebooks/routing/python/notebook_utils/notebook_helpers.py b/notebooks/routing/python/notebook_utils/notebook_helpers.py index 402a287..65b3205 100644 --- a/notebooks/routing/python/notebook_utils/notebook_helpers.py +++ b/notebooks/routing/python/notebook_utils/notebook_helpers.py @@ -1,30 +1,12 @@ -# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: MIT -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - +# Copyright (c) 2022, NVIDIA CORPORATION. +# CONFIDENTIAL, provided under NDA. import matplotlib.pyplot as plt import matplotlib.font_manager as fm # Used to plot the Co-ordinates + def gen_plot(df): plt.figure(figsize=(11, 11)) plt.scatter( diff --git a/notebooks/routing/python/pdptw_mixed_fleet.ipynb b/notebooks/routing/python/pdptw_mixed_fleet.ipynb index b127bd0..6705007 100644 --- a/notebooks/routing/python/pdptw_mixed_fleet.ipynb +++ b/notebooks/routing/python/pdptw_mixed_fleet.ipynb @@ -334,7 +334,8 @@ "order_service_time = cudf.concat([customer_order_data[\"pickup_service_time\"], customer_order_data[\"delivery_serivice_time\"]], ignore_index=True)\n", "\n", "# add time window constraints\n", - "data_model.set_order_time_windows(order_time_window_earliest, order_time_window_latest, order_service_time)\n", + "data_model.set_order_time_windows(order_time_window_earliest, order_time_window_latest)\n", + "data_model.set_order_service_times(order_service_time)\n", "\n", "# set time windows for the fleet\n", "vehicle_earliest_time = cudf.Series([0] * n_vehicles)\n", @@ -400,7 +401,7 @@ }, { "cell_type": "markdown", - "id": "37b22720", + "id": "93309efc", "metadata": {}, "source": [ "_____\n", @@ -449,7 +450,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.15" } }, "nbformat": 4, From 4828f534044c4c9d58c8e197b19f8edc033bd8d6 Mon Sep 17 00:00:00 2001 From: iroy Date: Tue, 6 Dec 2022 15:50:30 -0600 Subject: [PATCH 2/5] remove checkpoints --- .../cost_matrix_creation-checkpoint.ipynb | 442 --------- .../cpdptw-reoptmization-checkpoint.ipynb | 878 ------------------ ...w_intra-factory_transport-checkpoint.ipynb | 610 ------------ .../cvrp_daily_deliveries-checkpoint.ipynb | 452 --------- .../cvrpstw_priority_routing-checkpoint.ipynb | 548 ----------- ...nchmark_gehring_homberger-checkpoint.ipynb | 462 --------- ...rptw_service_team_routing-checkpoint.ipynb | 457 --------- .../cost_matrix_creation-checkpoint.ipynb | 442 --------- ...w_intra-factory_transport-checkpoint.ipynb | 599 ------------ .../cvrp_daily_deliveries-checkpoint.ipynb | 368 -------- .../cvrpstw_priority_routing-checkpoint.ipynb | 651 ------------- ...nchmark_gehring_homberger-checkpoint.ipynb | 380 -------- ...rptw_service_team_routing-checkpoint.ipynb | 398 -------- .../pdptw_mixed_fleet-checkpoint.ipynb | 458 --------- 14 files changed, 7145 deletions(-) delete mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb delete mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cpdptw-reoptmization-checkpoint.ipynb delete mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb delete mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb delete mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb delete mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb delete mode 100644 notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb delete mode 100644 notebooks/routing/python/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb delete mode 100644 notebooks/routing/python/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb delete mode 100644 notebooks/routing/python/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb delete mode 100644 notebooks/routing/python/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb delete mode 100644 notebooks/routing/python/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb delete mode 100644 notebooks/routing/python/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb delete mode 100644 notebooks/routing/python/.ipynb_checkpoints/pdptw_mixed_fleet-checkpoint.ipynb diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb deleted file mode 100644 index f1fa5f3..0000000 --- a/notebooks/routing/microservice/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb +++ /dev/null @@ -1,442 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "dfba40d6", - "metadata": {}, - "outputs": [], - "source": [ - "from cuopt import routing\n", - "from cuopt import distance_engine\n", - "import cudf\n", - "from scipy.spatial import distance\n", - "import numpy as np\n", - "import requests\n", - "import polyline\n", - "import folium\n", - "import json" - ] - }, - { - "cell_type": "markdown", - "id": "cd4716e9", - "metadata": {}, - "source": [ - "# Cost Matrix Calculation" - ] - }, - { - "cell_type": "markdown", - "id": "bdff7c68", - "metadata": {}, - "source": [ - "The cost matrix represents the user defined cost of traversing from one state/location in the optimization problem to another. This matrix is what cuOpt uses to assess the quality of a given solution as it seeks to minimize the total cost.\n", - "\n", - "The cost matrix is a square matrix of dimension equal to the number of locations in a given problem. In the example below we see an illustration of one such matrix." - ] - }, - { - "cell_type": "markdown", - "id": "991cad72", - "metadata": {}, - "source": [ - "\"cost_matrix.png" - ] - }, - { - "cell_type": "markdown", - "id": "ed85b5b1", - "metadata": {}, - "source": [ - "Additionally:\n", - "- The cost of going from a location to itself (e.g Cost(A,A)) is typically 0 \n", - "- Cost(A,B) need not be equal to Cost(B,A)" - ] - }, - { - "cell_type": "markdown", - "id": "2fe0488b", - "metadata": {}, - "source": [ - "## Simple Metric" - ] - }, - { - "cell_type": "markdown", - "id": "667af128", - "metadata": {}, - "source": [ - "In some simple cases a cost matrix can be generated from a list of points according to a user defined metric (e.g. Euclidean, Manhattan, etc.)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "01c4a7de", - "metadata": {}, - "outputs": [], - "source": [ - "points = cudf.DataFrame({\"x_coord\": [1, 1, 2, 3], \"y_coord\":[3, 1, 4, 1]})\n", - "points" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b52d43bb", - "metadata": {}, - "outputs": [], - "source": [ - "cost_matrix = distance.cdist(points.to_pandas().values, points.to_pandas().values, \"euclidean\")\n", - "print(f\"Simple Metric Cost Matrix:\\n\\n{cost_matrix}\")" - ] - }, - { - "cell_type": "markdown", - "id": "eb39ea0f", - "metadata": {}, - "source": [ - "## Weighted Waypoint Graph" - ] - }, - { - "cell_type": "markdown", - "id": "a72d76a3", - "metadata": {}, - "source": [ - "In cases where a unique environment needs to be described such as in the case of factories or warehouses it can be useful to define a waypoint graph that defines the cost of travel between adjacent accessible points in the environment.\n", - "\n", - "cuOpt has built in functionality to compute a cost matrix based on key target locations within a given waypoint graph. In the graph below we model 10 distinct waypoints. The target locations are 0, 4, 5, and 6." - ] - }, - { - "cell_type": "markdown", - "id": "d8afe1d6", - "metadata": {}, - "source": [ - "\"waypoint_graph.png" - ] - }, - { - "cell_type": "markdown", - "id": "fadc253b", - "metadata": {}, - "source": [ - "#### Graph Description\n", - "A simple description of each node, it's outgoing edges and corresponding weights" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f8bf07e3", - "metadata": {}, - "outputs": [], - "source": [ - "graph = {\n", - " 0:{\n", - " \"edges\":[2], \n", - " \"weights\":[2]},\n", - " 1:{\n", - " \"edges\":[2, 4], \n", - " \"weights\":[2, 2]},\n", - " 2:{\n", - " \"edges\":[0, 1, 3, 5], \n", - " \"weights\":[2, 2, 2, 2]},\n", - " 3:{\n", - " \"edges\":[2, 6], \n", - " \"weights\":[2, 2]},\n", - " 4:{\n", - " \"edges\":[1, 7], \n", - " \"weights\":[2, 1]},\n", - " 5:{\n", - " \"edges\":[2, 8], \n", - " \"weights\":[2, 1]},\n", - " 6:{\n", - " \"edges\":[3, 9], \n", - " \"weights\":[2, 1]},\n", - " 7:{\n", - " \"edges\":[4, 8], \n", - " \"weights\":[1, 2]},\n", - " 8:{\n", - " \"edges\":[5, 7, 9], \n", - " \"weights\":[1, 2, 2]},\n", - " 9:{\n", - " \"edges\":[6, 8], \n", - " \"weights\":[1, 2]}\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "54d51f36", - "metadata": {}, - "source": [ - "#### Convert to CSR\n", - "cuOpt requires that the graph be in compressed sparse row (CSR) format. Here we define a simple function that converts our graph to CSR." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ace5c271", - "metadata": {}, - "outputs": [], - "source": [ - "def convert_to_csr(graph):\n", - " num_nodes = len(graph)\n", - " \n", - " offsets = []\n", - " edges = []\n", - " weights = []\n", - " \n", - " cur_offset = 0\n", - " for node in range(num_nodes):\n", - " offsets.append(cur_offset)\n", - " cur_offset += len(graph[node][\"edges\"])\n", - " \n", - " edges = edges + graph[node][\"edges\"]\n", - " weights = weights + graph[node][\"weights\"]\n", - " \n", - " offsets.append(cur_offset)\n", - " \n", - " return np.array(offsets), np.array(edges), np.array(weights)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fed80f3a", - "metadata": {}, - "outputs": [], - "source": [ - "offsets, edges, weights = convert_to_csr(graph)\n", - "print(f\"offsets = {list(offsets)}\")\n", - "print(f\"edges = {list(edges)}\")\n", - "print(f\"weights = {list(weights)}\")" - ] - }, - { - "cell_type": "markdown", - "id": "17f18f56", - "metadata": {}, - "source": [ - "#### Define desired target locations and calculate the cost matrix " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "45ddc548", - "metadata": {}, - "outputs": [], - "source": [ - "target_locations = np.array([0, 4, 5, 6])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "475edfd9", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "waypoint_graph = distance_engine.WaypointMatrix(\n", - " offsets,\n", - " edges,\n", - " weights\n", - ")\n", - "cost_matrix = waypoint_graph.compute_cost_matrix(target_locations)\n", - "target_map = {k:v for k, v in enumerate(target_locations)}\n", - "\n", - "print(f\"Index <-> Waypoint Mapping: \\n{target_map}\\n\\n Waypoint Graph Cost Matrix: \\n{cost_matrix}\")" - ] - }, - { - "cell_type": "markdown", - "id": "cc7349c1", - "metadata": {}, - "source": [ - "## Map Based" - ] - }, - { - "cell_type": "markdown", - "id": "c1ea0b6e", - "metadata": {}, - "source": [ - "When dealing with problems in shipping and logistics, road distance and/or time is often used as a cost metric. In these cases there are a number of tools available to calculate drive distance and/or time. One such tool is the [Open Source Routing Machine](http://project-osrm.org/)(OSRM). In the below example we create a cost matrix using OSRM from a list of lat/lon coordinates." - ] - }, - { - "cell_type": "markdown", - "id": "0c6a374a", - "metadata": {}, - "source": [ - "#### Define Points of Interest" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3671b3e9", - "metadata": {}, - "outputs": [], - "source": [ - "lat_lon_coords = [\n", - " [33.698206, -117.851364],\n", - " [33.672260, -117.838925], \n", - " [33.721003, -117.864121], \n", - " [33.695563, -117.824500]\n", - "] " - ] - }, - { - "cell_type": "markdown", - "id": "2bf8d58a", - "metadata": {}, - "source": [ - "#### Create Distance Matrix via OSRM" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0b1e0a74", - "metadata": {}, - "outputs": [], - "source": [ - "locations=\"\"\n", - "for loc in lat_lon_coords:\n", - " locations = locations + \"{},{};\".format(loc[1], loc[0])\n", - "r = requests.get(\"http://router.project-osrm.org/table/v1/driving/\"+ locations[:-1])\n", - "\n", - "routes = json.loads(r.content)\n", - "cols = [str(i) for i in lat_lon_coords]\n", - "cost_matrix = cudf.DataFrame(routes['durations'], columns = cols, index= cols)\n", - "print(f\"Cost Matrix via OSRM:\\n\")\n", - "cost_matrix" - ] - }, - { - "cell_type": "markdown", - "id": "3f6e69fc", - "metadata": {}, - "source": [ - "#### Map Visualization" - ] - }, - { - "cell_type": "markdown", - "id": "d71a3260", - "metadata": {}, - "source": [ - "Visualization can be a helpful tool for understanding and communication. Here we demonstrate a sample visualization implementation showing the routes represented by the cost matrix above." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "15508e51", - "metadata": {}, - "outputs": [], - "source": [ - "def get_map(my_lat_longs):\n", - " m = folium.Map(location=[33.7, -117.83], #[52.52, 13.41],\n", - " zoom_start=13)\n", - " folium.Marker(\n", - " location=[my_lat_longs[0][0],my_lat_longs[0][1]] ,\n", - " icon=folium.Icon(icon='play', color='red')\n", - " ).add_to(m)\n", - " for loc in my_lat_longs[1:]:\n", - " folium.Marker(\n", - " location=[loc[0], loc[1]],\n", - " icon=folium.Icon(icon='stop', color='green')\n", - " ).add_to(m)\n", - " \n", - " for src_idx in range(len(lat_lon_coords)):\n", - " for dst_idx in range(len(lat_lon_coords)):\n", - " if src_idx == dst_idx:\n", - " break\n", - " source = lat_lon_coords[src_idx]\n", - " destination = lat_lon_coords[dst_idx]\n", - " loc = \"{},{};{},{}\".format(source[1], source[0], destination[1], destination[0])\n", - " url = \"http://router.project-osrm.org/route/v1/driving/\"\n", - " r = requests.get(url + loc) \n", - "\n", - " res = r.json() \n", - " routes = polyline.decode(res['routes'][0]['geometry'])\n", - "\n", - " folium.PolyLine(\n", - " routes,\n", - " weight=5,\n", - " color='blue',\n", - " opacity=0.6\n", - " ).add_to(m)\n", - "\n", - " return m\n", - "get_map(lat_lon_coords)" - ] - }, - { - "cell_type": "markdown", - "id": "745930d2", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - }, - "vscode": { - "interpreter": { - "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cpdptw-reoptmization-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cpdptw-reoptmization-checkpoint.ipynb deleted file mode 100644 index 385d6a1..0000000 --- a/notebooks/routing/microservice/.ipynb_checkpoints/cpdptw-reoptmization-checkpoint.ipynb +++ /dev/null @@ -1,878 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "f2a6732c", - "metadata": {}, - "outputs": [], - "source": [ - "import requests\n", - "import pandas as pd\n", - "import notebook_utils.notebook_helpers as utils" - ] - }, - { - "cell_type": "markdown", - "id": "30be751d", - "metadata": {}, - "source": [ - "# Re-optimization\n", - "\n", - "## Capacitated Pickup and Delivery Problem with Time Windows\n" - ] - }, - { - "cell_type": "markdown", - "id": "afa87cd0", - "metadata": {}, - "source": [ - "In a factory set-up, routing needs to be optimized as and when a new set of tasks/requests are dispatched, this would help re-route robots to any other new task which got dispatched on the floor. This would require knowledge of all the robot's statuses like current location, tasks yet to done, and many other factors. \n", - "\n", - "In this scenario, we have 4 robots which are in a factory and serves 5 locations/nodes in the factory. Factory would be receiving new tasks of pickup and delivery for 3 different time stamps, and this would trigger rerouting to efficiently complete all the tasks.\n", - "\n", - "### Problem Details:\n", - "\n", - "- 5 locations\n", - "- 4 vehicles/robots\n", - " - capacity: [3, 3, 3, 3]\n", - " - vehicle start location : [0, 0, 0, 0]\n", - " - vehicle end location: [0, 0, 0, 0]\n", - " \n", - "At each time stamp, a set of tasks gets added:\n", - "\n", - "- Time Stamp: 0 time unit\n", - " - tasks : [1, 2, 3, 4, 3, 2, 1, 3]\n", - "- Time Stamp: 4 time units\n", - " - tasks : [4, 2, 3, 1]\n", - "- Time Stamp: 6 time units\n", - " - tasks : [2, 3]\n", - " \n", - "**API Reference**: [cuOpt Server Documentation](https://docs.nvidia.com/cuopt/serv_api.html)" - ] - }, - { - "cell_type": "markdown", - "id": "8686c3cb", - "metadata": {}, - "source": [ - "### Assumptions\n", - "- Fleet size and capacity would remain same\n", - "- Vehicles en route to service a particular task will finish that task first\n", - "- The original optimized plan is going according to the plan, so we can determine the finished tasks just based on the time stamp\n", - "- The problem is pickup and delivery only\n", - "- There is only one capacity demand dimension" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5ff2a1a1", - "metadata": {}, - "outputs": [], - "source": [ - "# Provide URL information of the server\n", - "ip = \"0.0.0.0\"\n", - "port = \"5000\"\n", - "url = \"http://\" + ip + \":\" + port + \"/cuopt/\"\n", - "\n", - "# Test server health\n", - "assert requests.get(url + \"health\").status_code == 200" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9bbbf3f0", - "metadata": {}, - "outputs": [], - "source": [ - " # Processes input data, communicates data to cuOpt server and returns Optimized routes\n", - "def get_optimized_route(data, url, update=False):\n", - " \n", - " data_params = {\"return_data_state\": False}\n", - " \n", - "\n", - " # Set/Update cost matrix\n", - " \n", - " if \"cost_matrices\" in data:\n", - " if not update:\n", - " resp = requests.post(\n", - " url + \"set_cost_matrix\", params=data_params, json={\"cost_matrix\": data[\"cost_matrices\"]}\n", - " )\n", - " else:\n", - " resp = requests.put(\n", - " url + \"update_cost_matrix\", params=data_params, json={\"cost_matrix\": data[\"cost_matrices\"]}\n", - " )\n", - " assert resp.status_code == 200\n", - " \n", - " \n", - " # Set/Update Transit time matrix \n", - " \n", - " if \"transit_time_matrices\" in data:\n", - " if not update:\n", - " resp = requests.post(\n", - " url + \"set_travel_time_matrix\", params=data_params, json={\"cost_matrix\": data[\"transit_time_matrices\"]}\n", - " )\n", - " else:\n", - " resp = requests.put(\n", - " url + \"update_travel_time_matrix\", params=data_params, json={\"cost_matrix\": data[\"transit_time_matrices\"]}\n", - " )\n", - " assert resp.status_code == 200\n", - " \n", - " \n", - " # Set/Update Task data\n", - " \n", - " task_data = {}\n", - " if \"task_locations\" in data:\n", - " task_data[\"task_locations\"] = data[\"task_locations\"]\n", - " if \"pickup_indices\" in data and \"delivery_indices\" in data:\n", - " task_data[\"pickup_and_delivery_pairs\"] = [\n", - " [pickup_idx, delivery_idx] for pickup_idx, delivery_idx in zip(\n", - " data[\"pickup_indices\"], data[\"delivery_indices\"]\n", - " )\n", - " ]\n", - " elif \"pickup_indices\" in data or \"delivery_indices\" in data:\n", - " raise ValueError(\"Pick indices or Delivery indices are missing, both should be provided\")\n", - " \n", - " if \"task_earliest_time\" in data and \"task_latest_time\" in data and \"task_service_time\" in data:\n", - " task_data[\"task_time_windows\"] = [\n", - " [earliest, latest] for earliest, latest in zip(data[\"task_earliest_time\"], data[\"task_latest_time\"])\n", - " ]\n", - " task_data[\"service_times\"] = data[\"task_service_time\"]\n", - " elif \"task_earliest_time\" in data or \"task_latest_time\" in data or \"task_service_time\" in data:\n", - " raise ValueError(\"Earliest, Latest and Service time should be provided, one or more are missing\")\n", - " \n", - " if \"demand\" in data:\n", - " task_data[\"demand\"] = data[\"demand\"]\n", - " \n", - " if \"order_vehicle_match\" in data:\n", - " task_data[\"order_vehicle_match\"] = data[\"order_vehicle_match\"]\n", - " \n", - " if len(task_data) > 0:\n", - " resp = requests.post(url + \"set_task_data\", json=task_data)\n", - " assert resp.status_code == 200\n", - "\n", - " \n", - " # Set/Update Fleet data\n", - " \n", - " fleet_data = {}\n", - " \n", - " if \"vehicle_locations\" in data:\n", - " fleet_data[\"vehicle_locations\"] = data[\"vehicle_locations\"]\n", - " \n", - " if \"capacity\" in data:\n", - " fleet_data[\"capacities\"] = data[\"capacity\"]\n", - " \n", - " if \"vehicle_earliest\" in data and \"vehicle_latest\" in data:\n", - " fleet_data[\"vehicle_time_windows\"] = [\n", - " [earliest, latest] for earliest, latest in zip(data[\"vehicle_earliest\"], data[\"vehicle_latest\"])\n", - " ]\n", - " elif \"vehicle_earliest\" in data or \"vehicle_latest\" in data:\n", - " raise ValueError(\"vehicle_earliest and vehicle_latest both should be provided, one of them is missing\")\n", - " \n", - " if len(fleet_data) > 0:\n", - " if not update:\n", - " resp = requests.post(url + \"set_fleet_data\", json=fleet_data)\n", - " else:\n", - " resp = requests.put(url + \"update_fleet_data\", json=fleet_data)\n", - " assert resp.status_code == 200\n", - " \n", - " # Set Solver settings\n", - " \n", - " solver_config = {\n", - " \"time_limit\": 0.01\n", - " }\n", - " resp = requests.post(url + \"set_solver_config\", json=solver_config) \n", - " assert resp.status_code == 200 \n", - " \n", - " # Get optimized route\n", - " \n", - " solver_response = requests.get(url + \"get_optimized_routes\")\n", - " assert solver_response.status_code == 200\n", - " \n", - " return solver_response.json()[\"response\"][\"solver_response\"]" - ] - }, - { - "cell_type": "markdown", - "id": "0df9f137", - "metadata": {}, - "source": [ - " ### Following function accumulates status of tasks and vehicles\n", - " \n", - " - Collect status of tasks [Completed, Picked-up but not yet Delivered, Yet to Pick-up]\n", - " - Along with that update vehicle locations, vehicle earliest times at a given time.\n", - " - It is also required to keep tabs on tasks which have been picked up by particular vehicle, to ensure that\n", - " that task is fulfilled by the same vehicle." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "266f5a6b", - "metadata": {}, - "outputs": [], - "source": [ - "def collect_current_status(previous_data, optimized_route_data, reroute_from_time):\n", - " \n", - " transit_times = previous_data[\"transit_time_matrices\"][0]\n", - " task_locations = previous_data[\"task_locations\"]\n", - " vehicle_start_locations = [val[0] for val in previous_data[\"vehicle_locations\"]]\n", - " vehicle_return_locations = [val[1] for val in previous_data[\"vehicle_locations\"]]\n", - " \n", - " # Create a mapping between pickup and delivery\n", - " pickup_of_delivery = {\n", - " previous_data[\"delivery_indices\"][i]: previous_data[\"pickup_indices\"][i]\n", - " for i in range(len(previous_data[\"pickup_indices\"]))\n", - " }\n", - " \n", - " # Update vehicle earliest if needs to be changed to current time\n", - " vehicle_earliest = [max(earliest, reroute_from_time) for earliest in previous_data[\"vehicle_earliest\"]]\n", - " \n", - " # Collect completed and partial set of tasks, so we can add partialy completed tasks back\n", - " completed_tasks = []\n", - " picked_up_but_not_delivered = {}\n", - " picked_up_task_to_vehicle = {}\n", - " \n", - " for veh_id, veh_data in optimized_route_data[\"vehicle_data\"].items():\n", - " route_len = len(veh_data['route'])\n", - " task_len = len(veh_data[\"task_id\"])\n", - " vehicle_id = int(veh_id)\n", - " \n", - " # In this case, all the tasks are already completed, or waiting on last task service time\n", - " if veh_data['arrival_stamp'][-1] <= reroute_from_time:\n", - " intra_task_id = task_len\n", - " else:\n", - " try:\n", - " # Look for a task that is yet to be completed\n", - " intra_task_id, time = next(\n", - " (i, el)\n", - " for i, el in enumerate(veh_data['arrival_stamp'])\n", - " if el > reroute_from_time\n", - " )\n", - " except StopIteration:\n", - " # In case none of the tasks are completed\n", - " intra_task_id = 0\n", - " time = max(vehicle_earliest[vehicle_id], reroute_from_time)\n", - "\n", - " # All the tasks are completed and vehicle is on the way to return location or already reached\n", - "\n", - " picked_up_but_not_delivered[vehicle_id] = []\n", - " \n", - " # There are tasks that are still pending\n", - " if intra_task_id < task_len:\n", - " last_task = veh_data[\"task_id\"][intra_task_id]\n", - " \n", - " # Update vehicle start location\n", - " vehicle_start_locations[int(vehicle_id)] = task_locations[last_task]\n", - " \n", - " # Update vehicle earliest\n", - " vehicle_earliest[int(vehicle_id)] = min(\n", - " max(time, reroute_from_time), previous_data[\"vehicle_latest\"][vehicle_id]\n", - " )\n", - " \n", - " for j in range(0, intra_task_id):\n", - " task = veh_data[\"task_id\"][j]\n", - " if task in previous_data[\"pickup_indices\"]:\n", - " picked_up_but_not_delivered[vehicle_id].append(task)\n", - " picked_up_task_to_vehicle[task] = vehicle_id\n", - " else:\n", - " # Moves any delivered pick-up tasks to completed.\n", - " corresponding_pickup = pickup_of_delivery[task]\n", - " picked_up_but_not_delivered[vehicle_id].remove(\n", - " corresponding_pickup\n", - " )\n", - " completed_tasks.append(corresponding_pickup)\n", - " completed_tasks.append(task)\n", - " picked_up_task_to_vehicle.pop(corresponding_pickup)\n", - " else:\n", - " completed_tasks.extend(veh_data[\"task_id\"])\n", - " # In this case vehicle is at last location about to finish the task,\n", - " # so vehicle start location would last task location and accordingly the earliest vehicle time as well\n", - " if (veh_data['arrival_stamp'][-1] == reroute_from_time) and (veh_data['arrival_stamp'][-1]+previous_data[\"task_service_time\"][veh_data[\"task_id\"][-1]] >= reroute_from_time):\n", - " vehicle_start_locations[vehicle_id] = task_locations[veh_data[\"task_id\"][-1]]\n", - " vehicle_earliest[vehicle_id] = veh_data['arrival_stamp'][-1] + previous_data[\"task_service_time\"][veh_data[\"task_id\"][-1]] \n", - " else:\n", - " # In this case vehicle completed last task and may be enroute to vehicle return location or might have reached.\n", - " end_time = (\n", - " veh_data['arrival_stamp'][-1] + previous_data[\"task_service_time\"][veh_data[\"task_id\"][-1]] + transit_times[task_locations[veh_data[\"task_id\"][-1]]][vehicle_return_locations[vehicle_id]]\n", - " )\n", - " time = max(end_time, reroute_from_time)\n", - " print(\"For vehicle ID updating\", vehicle_id)\n", - " vehicle_start_locations[vehicle_id] = vehicle_return_locations[vehicle_id]\n", - " vehicle_earliest[vehicle_id] = min(time, previous_data[\"vehicle_earliest\"][vehicle_id])\n", - " \n", - " return (\n", - " vehicle_earliest, vehicle_start_locations,\n", - " vehicle_return_locations, completed_tasks,\n", - " picked_up_but_not_delivered, picked_up_task_to_vehicle)" - ] - }, - { - "cell_type": "markdown", - "id": "179691ce", - "metadata": {}, - "source": [ - "### Redefine old data at current time\n", - "- Unfinished task data gets moved up the task locations\n", - "- This changes task id which affects pick-up and delivery indices, so they get remapped\n", - "- Similarly update the earliest time appropriately, since this might change with current time" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "99804c87", - "metadata": {}, - "outputs": [], - "source": [ - "def redefine_old_data_to_current_time(\n", - " previous_data, vehicle_earliest, vehicle_start_locations,\n", - " completed_tasks, picked_up_task_to_vehicle, reroute_from_time\n", - "):\n", - " data = {\n", - " \"task_locations\" : [],\n", - " \"pickup_indices\" : [],\n", - " \"delivery_indices\" : [],\n", - " \"task_earliest_time\" : [],\n", - " \"task_latest_time\" : [],\n", - " \"task_service_time\" : [],\n", - " # assume that fleet is not changing\n", - " \"vehicle_capacity\" : previous_data[\"capacity\"],\n", - " \"task_demand\" : [[]*len(previous_data[\"capacity\"])],\n", - " }\n", - " \n", - " # If there are unfinished tasks (picked up but not delivered yet) at\n", - " # the time of re-routing, create new pickup tasks from the vehicle\n", - " # locations at time zero (i.e. re-routing start time)\n", - " \n", - " cnt = 0\n", - " # Mapping new task Id to old task Id and old task Id to new task Id\n", - " new_task_to_old_task = {}\n", - " old_task_to_new_task = {}\n", - " \n", - " ntasks = len(previous_data[\"task_locations\"])\n", - " \n", - " for task_id in range(0, ntasks):\n", - " if completed_tasks.count(task_id) == 0:\n", - " new_task_to_old_task[cnt] = task_id\n", - " old_task_to_new_task[task_id] = cnt\n", - " cnt = cnt + 1\n", - " \n", - " # Convert pickup and delivery indices to new numbering\n", - " for i in range(len(previous_data[\"pickup_indices\"])):\n", - " pickup = previous_data[\"pickup_indices\"][i]\n", - " delivery = previous_data[\"delivery_indices\"][i]\n", - " if pickup not in completed_tasks:\n", - " new_pickup = old_task_to_new_task[pickup]\n", - " new_delivery = old_task_to_new_task[delivery]\n", - " data[\"pickup_indices\"].append(new_pickup)\n", - " data[\"delivery_indices\"].append(new_delivery)\n", - " \n", - " # Convert task to vehicle to new numbering\n", - " new_picked_up_task_to_vehicle = [\n", - " {\n", - " \"order_id\" :old_task_to_new_task[pickup],\n", - " \"vehicle_ids\" :[vehicle]\n", - " }\n", - " for pickup, vehicle in picked_up_task_to_vehicle.items()\n", - " ]\n", - " \n", - " new_picked_up_task_list = [val[\"order_id\"] for val in new_picked_up_task_to_vehicle]\n", - " \n", - " # Extract the task info of incomplete tasks\n", - " for i in range(0, len(new_task_to_old_task)):\n", - " old_id = new_task_to_old_task[i]\n", - " is_pickup_task = i in data[\"pickup_indices\"]\n", - "\n", - " # If this task is already picked up, make sure that in the new problem\n", - " # it is picked up at time zero\n", - " if is_pickup_task and i in new_picked_up_task_list:\n", - " vehicle = picked_up_task_to_vehicle[old_id]\n", - " new_loc = vehicle_start_locations[vehicle]\n", - " pickup_time = vehicle_earliest[vehicle]\n", - " data[\"task_locations\"].append(new_loc)\n", - " data[\"task_earliest_time\"].append(pickup_time)\n", - " data[\"task_latest_time\"].append(pickup_time)\n", - " data[\"task_service_time\"].append(0)\n", - " else:\n", - " data[\"task_locations\"].append(previous_data[\"task_locations\"][old_id])\n", - " data[\"task_earliest_time\"].append(max(previous_data[\"task_earliest_time\"][old_id], reroute_from_time))\n", - " data[\"task_latest_time\"].append(previous_data[\"task_latest_time\"][old_id])\n", - " data[\"task_service_time\"].append(previous_data[\"task_service_time\"][old_id])\n", - "\n", - " for idx, demands in enumerate(previous_data[\"demand\"]):\n", - " # assume that fleet is not changing\n", - " demand = demands[old_id]\n", - " data[\"task_demand\"][idx].append(demand)\n", - " \n", - " data[\"order_vehicle_match\"] = new_picked_up_task_to_vehicle\n", - " \n", - " return data, new_task_to_old_task, old_task_to_new_task" - ] - }, - { - "cell_type": "markdown", - "id": "6b6146ef", - "metadata": {}, - "source": [ - "### Reconstructs data adding pending tasks with new tasks taking current env status" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "67284da4", - "metadata": {}, - "outputs": [], - "source": [ - "def reconstruct_task_data(\n", - " previous_data, optimized_route_data, reroute_from_time, new_task_data,\n", - "): \n", - " # Approach:\n", - " # ---------\n", - " # 1. Using the optimized route and the time of re-optimization, we figure\n", - " # out which tasks are fulfilled (picked up and delivered), partially\n", - " # fulfilled (picked up but not delivered), and not initiated\n", - " # 2. We remove the tasks that are fulfilled while keeping the tasks that\n", - " # are not initiated. For the partially fulfilled tasks, we create dummy\n", - " # pickup tasks at vehicle start locations\n", - " \n", - " if new_task_data is not None:\n", - " expected_entries_in_new_task_data = [\n", - " \"task_locations\",\n", - " \"task_earliest_time\",\n", - " \"task_latest_time\",\n", - " \"task_service_time\",\n", - " \"pickup_indices\",\n", - " \"delivery_indices\",\n", - " \"demand\",\n", - " ]\n", - " for entry in expected_entries_in_new_task_data:\n", - " if entry not in new_task_data:\n", - " raise ValueError(f\"{entry} is missing in new task data\")\n", - "\n", - " for entry, value in new_task_data.items():\n", - " if entry not in expected_entries_in_new_task_data:\n", - " raise NotImplementedError(\n", - " f\"{entry} is not implemented for re-optimization\"\n", - " )\n", - " \n", - " if \"cost_matrices\" in new_task_data:\n", - " if not len(new_task_data[\"cost_matrices\"][0]) == len(previous_data[\"cost_matrices\"][0]):\n", - " raise ValueError(\"Shape of cost matrices is not matching\")\n", - " \n", - " if \"transit_time_matrices\" in new_task_data:\n", - " if not len(new_task_data[\"transit_time_matrices\"][0]) == len(previous_data[\"transit_time_matrices\"][0]):\n", - " raise ValueError(\"Shape of transit time matrices is not matching\")\n", - " \n", - " \n", - " vehicle_num = len(previous_data[\"vehicle_locations\"])\n", - " n_locations = len(previous_data[\"cost_matrices\"][0])\n", - " task_locations = previous_data[\"task_locations\"]\n", - " \n", - " # - Collect status of tasks [Completed, Picked-up note yet Delivered, Yet to Pick-up]\n", - " # - Along with that update vehicle locations, vehicle earliest times at given time.\n", - " # - It is also required to keep tabs on tasks which have been picked up by particular vehicle, to ensure that\n", - " # that task is fulfilled by same vehicle.\n", - " (\n", - " vehicle_earliest, vehicle_start_locations,\n", - " vehicle_return_locations, completed_tasks,\n", - " picked_up_but_not_delivered, picked_up_task_to_vehicle\n", - " ) = collect_current_status(previous_data, optimized_route_data, reroute_from_time)\n", - " \n", - " \n", - " # Map old task as new tasks with given time, in this the old task gets new ids since they are moved\n", - " # upfront in task locations removing completed tasks.\n", - " restructured_data, new_task_to_old_task, old_task_to_new_task = redefine_old_data_to_current_time(\n", - " previous_data, vehicle_earliest, vehicle_start_locations,\n", - " completed_tasks, picked_up_task_to_vehicle, reroute_from_time\n", - " )\n", - " \n", - " n_leftover_tasks = len(restructured_data[\"task_locations\"])\n", - " \n", - " # Append new task data to pending tasks details\n", - " \n", - " restructured_data[\"task_locations\"].extend(new_task_data[\"task_locations\"])\n", - " restructured_data[\"task_earliest_time\"].extend(new_task_data[\"task_earliest_time\"])\n", - " restructured_data[\"task_latest_time\"].extend(new_task_data[\"task_latest_time\"])\n", - " restructured_data[\"task_service_time\"].extend(new_task_data[\"task_service_time\"])\n", - "\n", - " # new task data consists of indices with respect to new task data\n", - " adjusted_pickup_indices = [\n", - " id + n_leftover_tasks for id in new_task_data[\"pickup_indices\"]\n", - " ]\n", - " adjusted_delivery_indices = [\n", - " id + n_leftover_tasks for id in new_task_data[\"delivery_indices\"]\n", - " ]\n", - " restructured_data[\"pickup_indices\"].extend(adjusted_pickup_indices)\n", - " restructured_data[\"delivery_indices\"].extend(adjusted_delivery_indices)\n", - " \n", - " restructured_data[\"task_demand\"] = [\n", - " old_demand + new_demand for old_demand, new_demand in zip(\n", - " restructured_data[\"task_demand\"], new_task_data[\"demand\"]\n", - " )\n", - " ]\n", - " \n", - " restructured_data[\"vehicle_locations\"] = [\n", - " [start, ret] for start, ret in zip(vehicle_start_locations, vehicle_return_locations)\n", - " ]\n", - " \n", - " # Form the output data\n", - " \n", - " reconstructed_data = {\n", - " \"cost_matrices\" : (\n", - " new_task_data[\"cost_matrices\"] if \"cost_matrices\" in new_task_data else previous_data[\"cost_matrices\"]\n", - " ),\n", - " \"transit_time_matrices\" : (\n", - " new_task_data[\"transit_time_matrices\"] if \"transit_time_matrices\" in new_task_data else previous_data[\"transit_time_matrices\"]\n", - " ),\n", - " \"task_locations\": restructured_data[\"task_locations\"],\n", - " \"pickup_indices\": restructured_data[\"pickup_indices\"], \n", - " \"delivery_indices\": restructured_data[\"delivery_indices\"],\n", - " \"task_earliest_time\": restructured_data[\"task_earliest_time\"],\n", - " \"task_latest_time\": restructured_data[\"task_latest_time\"],\n", - " \"task_service_time\": restructured_data[\"task_service_time\"],\n", - " \"demand\": restructured_data[\"task_demand\"],\n", - " \"capacity\": restructured_data[\"vehicle_capacity\"],\n", - " \"vehicle_locations\": restructured_data[\"vehicle_locations\"],\n", - " \"vehicle_earliest\": vehicle_earliest,\n", - " \"vehicle_latest\": previous_data[\"vehicle_latest\"],\n", - " \"order_vehicle_match\": restructured_data[\"order_vehicle_match\"]\n", - " }\n", - " \n", - " return reconstructed_data, completed_tasks" - ] - }, - { - "cell_type": "markdown", - "id": "b4574c43", - "metadata": {}, - "source": [ - "## Reoptimization across different time stamps\n", - "________________________________________________\n" - ] - }, - { - "cell_type": "markdown", - "id": "871910af", - "metadata": {}, - "source": [ - "## Time Stamp : 0 time unit\n", - "- Solver gets first set of tasks to plan for their optimized routes\n", - "- Vehicles/Robots all start from their start locations" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6c734805", - "metadata": {}, - "outputs": [], - "source": [ - "# Input data - 1\n", - "\n", - "input_data_1 = {}\n", - "\n", - "input_data_1[\"cost_matrices\"] = {0:[\n", - " [0, 1, 2, 3, 4],\n", - " [1, 0, 2, 3, 4],\n", - " [2, 3, 0, 4, 1],\n", - " [3, 4, 1, 0, 2],\n", - " [4, 1, 2, 3, 0]\n", - "]}\n", - "\n", - "input_data_1[\"transit_time_matrices\"] = {0:[\n", - " [0, 1, 1, 1, 1],\n", - " [1, 0, 1, 1, 1],\n", - " [1, 1, 0, 1, 1],\n", - " [1, 1, 1, 0, 1],\n", - " [1, 1, 1, 1, 0]\n", - "]}\n", - " \n", - "# Task data\n", - "input_data_1[\"task_locations\"] = [1, 2, 3, 4, 3, 2, 1, 3]\n", - "input_data_1[\"pickup_indices\"] = [0, 2, 4, 6]\n", - "input_data_1[\"delivery_indices\"] = [1, 3, 5, 7]\n", - "input_data_1[\"task_earliest_time\"] = [0, 2, 1, 2, 0, 0, 2, 1]\n", - "input_data_1[\"task_latest_time\"] = [5, 4, 5, 5, 7, 7, 10, 9]\n", - "input_data_1[\"task_service_time\"] = [1, 1, 1, 1, 1, 1, 1, 1]\n", - "input_data_1[\"demand\"] = [[1, -1, 1, -1, 1, -1, 1, -1]]\n", - "\n", - "# Vehicle data\n", - "input_data_1[\"vehicle_locations\"] = [[0, 0]] * 4 \n", - "input_data_1[\"vehicle_earliest\"] = [0.0, 0.0, 2.0, 2.0]\n", - "input_data_1[\"vehicle_latest\"] = [8.0, 8.0, 15.0, 15.0]\n", - "input_data_1[\"capacity\"] = [[3, 3, 3, 3]]\n", - "\n", - "# Solve for optimized route\n", - "resp = get_optimized_route(input_data_1, url)" - ] - }, - { - "cell_type": "markdown", - "id": "fb5b6c58", - "metadata": {}, - "source": [ - "#### Optimized route" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f15cf585", - "metadata": {}, - "outputs": [], - "source": [ - "utils.print_vehicle_data(resp)" - ] - }, - { - "cell_type": "markdown", - "id": "84e00e2d", - "metadata": {}, - "source": [ - "## Time Stamp: 4 time units\n", - "- 4 New tasks gets as added\n", - "- it's a pair of pick-up and delivery tasks\n", - "- As per observation of the result above, at time stamp 4, only 2 pair of pick-up and delivery is completed, 2 are still pendng" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8148bfb8", - "metadata": {}, - "outputs": [], - "source": [ - "# time stamnp\n", - "current_time = 4\n", - "\n", - "# new set of tasks\n", - "\n", - "new_task_data = {}\n", - "new_task_data[\"task_locations\"] = [4, 2, 3, 1]\n", - "new_task_data[\"pickup_indices\"] = [0, 2]\n", - "new_task_data[\"delivery_indices\"] = [1, 3]\n", - "new_task_data[\"task_earliest_time\"] = [3, 4, 8, 9]\n", - "new_task_data[\"task_latest_time\"] = [8, 9, 10, 10]\n", - "new_task_data[\"task_service_time\"] = [1, 1, 1, 1]\n", - "new_task_data[\"demand\"] = [[1, -1, 1, -1]]" - ] - }, - { - "cell_type": "markdown", - "id": "304a79cf", - "metadata": {}, - "source": [ - "#### Reconstruct task data adding pending tasks and new tasks" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "07dcb09a", - "metadata": {}, - "outputs": [], - "source": [ - "updated_task_data, completed_tasks = reconstruct_task_data(input_data_1, resp, current_time, new_task_data)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dc8e3a7f", - "metadata": {}, - "outputs": [], - "source": [ - "utils.print_data(updated_task_data, completed_tasks)" - ] - }, - { - "cell_type": "markdown", - "id": "ca58dea3", - "metadata": {}, - "source": [ - "#### Analyzing updated task data\n", - "\n", - "- It can be observed that now the updated task data has 4 more tasks along with 4 new tasks, which makes it 4 sets of pickup and delivery\n", - "- The vehicle earliest, vehicle start locations have been updated with current location of the robots\n", - "- The previous task which were picked-up but not yet delivered are assigned to the same vehicles which had picked them up through task to vehicle match\n", - "- All the vehicle earliest is changed to current time if they were less than that" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ff1d4edd", - "metadata": {}, - "outputs": [], - "source": [ - "resp = get_optimized_route(updated_task_data, url, update=True)" - ] - }, - { - "cell_type": "markdown", - "id": "4707d474", - "metadata": {}, - "source": [ - "#### Optimized route" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b1ab4768", - "metadata": {}, - "outputs": [], - "source": [ - "utils.print_vehicle_data(resp)" - ] - }, - { - "cell_type": "markdown", - "id": "76a5a3ed", - "metadata": {}, - "source": [ - "## Time Stamp: 6 time units\n", - "- 2 New tasks gets as added\n", - "- it's a pick-up and delivery\n", - "- As per observation of the result above, at time stamp 6, only 2 pair of pick-up and delivery is completed, 2 are still pendng and now one more pickup and delivery is added" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9107e6b1", - "metadata": {}, - "outputs": [], - "source": [ - "# time stamnp\n", - "current_time = 6\n", - "\n", - "# new set of tasks\n", - "\n", - "new_task_data = {}\n", - "new_task_data[\"task_locations\"] = [2, 3]\n", - "new_task_data[\"pickup_indices\"] = [0]\n", - "new_task_data[\"delivery_indices\"] = [1]\n", - "new_task_data[\"task_earliest_time\"] = [10, 9]\n", - "new_task_data[\"task_latest_time\"] = [12, 14]\n", - "new_task_data[\"task_service_time\"] = [1, 1]\n", - "new_task_data[\"demand\"] = [[1, -1]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "89a14294", - "metadata": {}, - "outputs": [], - "source": [ - "updated_task_data, completed_tasks = reconstruct_task_data(updated_task_data, resp, current_time, new_task_data)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "10c59203", - "metadata": {}, - "outputs": [], - "source": [ - "utils.print_data(updated_task_data, completed_tasks)" - ] - }, - { - "cell_type": "markdown", - "id": "6a964990", - "metadata": {}, - "source": [ - "#### Analyzing updated task data\n", - "- Out of 8 tasks, 4 are completed and 4 are pending and we are adding 2 more, at the end we have 3 pair of pick-up and delivery\n", - "- Vehicle start locations have changed to location 2 and 3 for vehicles 2 and 3.\n", - "- Only task 0 is bound to a vehicle sice it already picked-up before" - ] - }, - { - "cell_type": "markdown", - "id": "87786df2", - "metadata": {}, - "source": [ - "#### Optimized route" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "783e555f", - "metadata": {}, - "outputs": [], - "source": [ - "resp = get_optimized_route(updated_task_data, url, update=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "04cc5332", - "metadata": {}, - "outputs": [], - "source": [ - "utils.print_vehicle_data(resp)" - ] - }, - { - "cell_type": "markdown", - "id": "f989f957", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb deleted file mode 100644 index 471d852..0000000 --- a/notebooks/routing/microservice/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb +++ /dev/null @@ -1,610 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "3631e3f7", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import requests\n", - "import notebook_utils.notebook_helpers as utils" - ] - }, - { - "cell_type": "markdown", - "id": "9326712e", - "metadata": {}, - "source": [ - "# Intra-factory Transport\n", - "## Capacitated Pickup and Delivery Problem with Time Windows" - ] - }, - { - "cell_type": "markdown", - "id": "382afbd9", - "metadata": {}, - "source": [ - "Factory automation allows companies to raise the quality and consistency of manufacturing processes while also allowing human workers to focus on safer, less repetitive tasks that have higher cognitive and creative demands.\n", - "\n", - "In this scenario we have a set of intra-factory transport orders to move products at various stages in the assembly process from one processing station to another. Each station represents a particular type of manufacturing process and a given product may need to visit each processing station more than once. Multiple autonomous mobile robots (AMRs) with a fixed capacity will execute pickup and delivery orders between target locations, all with corresponding time_windows." - ] - }, - { - "cell_type": "markdown", - "id": "c3bc4ad4", - "metadata": {}, - "source": [ - "### Problem Details:\n", - "- 4 Locations each with an associated demand\n", - " - 1 Start Location for AMRs\n", - "\n", - " - 3 Process Stations\n", - "\n", - "- 3 AMRs with associated capacity" - ] - }, - { - "cell_type": "markdown", - "id": "e6090764", - "metadata": {}, - "source": [ - "- Hours of operation" - ] - }, - { - "cell_type": "markdown", - "id": "24cff46b", - "metadata": {}, - "source": [ - "**API Reference**: [cuOpt Server Documentation](https://docs.nvidia.com/cuopt/serv_api.html)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5d12f05d", - "metadata": {}, - "outputs": [], - "source": [ - "factory_open_time = 0\n", - "factory_close_time = 100" - ] - }, - { - "cell_type": "markdown", - "id": "32c76994", - "metadata": {}, - "source": [ - "### Setup the cuOpt server and test the health of the server\n", - "\n", - "**NOTE**: Please update **ip** and **port** on which the server is running." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2c1316c4", - "metadata": {}, - "outputs": [], - "source": [ - "ip = \"0.0.0.0\"\n", - "port = \"5000\"\n", - "url = \"http://\" + ip + \":\" + port + \"/cuopt/\"\n", - "\n", - "# Test server health\n", - "assert requests.get(url + \"health\").status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "e67a05ed", - "metadata": {}, - "source": [ - "![waypoint_graph.png not found](./notebook_utils/images/waypoint_graph.png \"Waypoint Graph\")" - ] - }, - { - "cell_type": "markdown", - "id": "c89d0f91", - "metadata": {}, - "source": [ - "### Set location names" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0828c9c7", - "metadata": {}, - "outputs": [], - "source": [ - "location_names = [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"]" - ] - }, - { - "cell_type": "markdown", - "id": "d90ba90d", - "metadata": {}, - "source": [ - "### Waypoint Graph" - ] - }, - { - "cell_type": "markdown", - "id": "6febdb57", - "metadata": {}, - "source": [ - "#### Compressed Sparse Row (CSR) representation of above weighted waypoint graph.\n", - "For details on the CSR encoding of the above graph see the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2c824c99", - "metadata": {}, - "outputs": [], - "source": [ - "offsets = [0, 1, 3, 7, 9, 11, 13, 15, 17, 20, 22]\n", - "edges = [2, 2, 4, 0, 1, 3, 5, 2, 6, 1, 7, 2, 8, 3, 9, 4, 8, 5, 7, 9, 6, 8]\n", - "weights = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2]" - ] - }, - { - "cell_type": "markdown", - "id": "dbfcfa33", - "metadata": {}, - "source": [ - "#### Select specific waypoints in the graph as target locations\n", - "In this case we would like the AMRs to begin from waypoint 0 and service locations 4, 5, and 6." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4e08f664", - "metadata": {}, - "outputs": [], - "source": [ - "# Setup service locations\n", - "target_locations = [0, 4, 5, 6]" - ] - }, - { - "cell_type": "markdown", - "id": "4f11fdb2", - "metadata": {}, - "source": [ - "### Transport Orders\n", - "Setup Transport Order Data\n", - "\n", - "The transport orders dictate the movement of parts from one area of the factory to another. In this example nodes 4, 5, and 6 represent the processing stations that parts must travel between and deliveries to node 0 represent the movement of parts off the factory floor." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "12c3c595", - "metadata": {}, - "outputs": [], - "source": [ - "transport_order_data = pd.DataFrame({\n", - " \"pickup_location\": [4, 5, 6, 6, 5, 4],\n", - " \"delivery_location\": [5, 6, 0, 5, 4, 0],\n", - " \"order_demand\": [1, 1, 1, 1, 1, 1],\n", - " \"earliest_pickup\": [0, 0, 0, 0, 0, 0],\n", - " \"latest_pickup\": [10, 20, 30, 10, 20, 30],\n", - " \"pickup_service_time\": [2, 2, 2, 2, 2, 2],\n", - " \"earliest_delivery\": [0, 0, 0, 0, 0, 0],\n", - " \"latest_delivery\": [45, 45, 45, 45, 45, 45],\n", - " \"delivery_service_time\": [2, 2, 2, 2, 2, 2]\n", - "})\n", - "transport_order_data" - ] - }, - { - "cell_type": "markdown", - "id": "7af883ad", - "metadata": {}, - "source": [ - "### Set Waypoint Graph" - ] - }, - { - "cell_type": "markdown", - "id": "52bdc1d0", - "metadata": {}, - "source": [ - "cuOpt will use this waypoint graph along with task locations and vehicle locations to determine cost matrix internally from one location to another. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9975bf1a", - "metadata": {}, - "outputs": [], - "source": [ - "graph_data = {\n", - " \"edges\": edges,\n", - " \"offsets\": offsets,\n", - " \"weights\": weights,\n", - "}\n", - "cost_waypoint_graph_data = {'waypoint_graph': {0: graph_data}}\n", - "response_set = requests.post(\n", - " url + \"set_cost_waypoint_graph\", json=cost_waypoint_graph_data\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "1644ce90", - "metadata": {}, - "source": [ - "### Set Order/Task data\n" - ] - }, - { - "cell_type": "markdown", - "id": "449ec36b", - "metadata": {}, - "source": [ - "#### Process Order locations\n", - "\n", - "Order locations, pickup and delivery pairs are processed and created to be digested bu cuOpt" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1440c936", - "metadata": {}, - "outputs": [], - "source": [ - "task_data = {}\n", - "\n", - "pickup_order_locations = transport_order_data['pickup_location']\n", - "delivery_order_locations = transport_order_data['delivery_location']\n", - "order_locations = pd.concat([pickup_order_locations, delivery_order_locations], ignore_index=True)\n", - "\n", - "# Set the task locations in the task data json datastructure\n", - "task_data[\"task_locations\"] = order_locations.to_list()\n", - "print(order_locations)" - ] - }, - { - "cell_type": "markdown", - "id": "a0c838b6", - "metadata": {}, - "source": [ - "\n", - "#### Process demand data\n", - "\n", - "From the perspective of the cuOpt solver_settings, each distinct transaction (pickup order or delivery order) are treated separately with demand for pickup denoted as positive and the corresponding delivery treated as negative demand." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "86be7280", - "metadata": {}, - "outputs": [], - "source": [ - "# This is the number of parts that needs to be moved\n", - "raw_demand = transport_order_data[\"order_demand\"]\n", - "\n", - "# When dropping off parts we want to remove one unit of demand from the robot\n", - "drop_off_demand = raw_demand * -1\n", - "\n", - "# Create pickup and delivery demand\n", - "order_demand = pd.concat([raw_demand, drop_off_demand], ignore_index=True)\n", - "\n", - "# Add demand to the task data\n", - "task_data[\"demand\"] = [order_demand.to_list()]\n", - "print(order_demand)" - ] - }, - { - "cell_type": "markdown", - "id": "a1f03f4c", - "metadata": {}, - "source": [ - "#### Process task time windows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2b1a0b1d", - "metadata": {}, - "outputs": [], - "source": [ - "# create earliest times\n", - "order_time_window_earliest = pd.concat([transport_order_data[\"earliest_pickup\"], transport_order_data[\"earliest_delivery\"]], ignore_index=True)\n", - "\n", - "# create latest times\n", - "order_time_window_latest = pd.concat([transport_order_data[\"latest_pickup\"], transport_order_data[\"latest_delivery\"]], ignore_index=True)\n", - "\n", - "# create service times\n", - "order_service_time = pd.concat([transport_order_data[\"pickup_service_time\"],transport_order_data[\"delivery_service_time\"]], ignore_index=True)\n", - "\n", - "# add time window constraints\n", - "task_data[\"task_time_windows\"] = list(zip(order_time_window_earliest.to_list(),\n", - " order_time_window_latest.to_list()))\n", - "task_data[\"service_times\"] = order_service_time.to_list()" - ] - }, - { - "cell_type": "markdown", - "id": "200614ab", - "metadata": {}, - "source": [ - "#### Mapping pickups to deliveries" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "32272d7b", - "metadata": {}, - "outputs": [], - "source": [ - "# IMPORTANT NOTE : pickup and delivery pairs are indexed into the order locations array.\n", - "npair_orders = int(len(order_locations)/2)\n", - "pickup_order_ids = pd.Series([i for i in range(npair_orders)])\n", - "delivery_order_ids = pd.Series([i + npair_orders for i in range(npair_orders)])\n", - "\n", - "# add pickup and delivery pairs.\n", - "task_data[\"pickup_and_delivery_pairs\"] = list(zip(pickup_order_ids.to_list(),\n", - " delivery_order_ids.to_list()))" - ] - }, - { - "cell_type": "markdown", - "id": "15c673de", - "metadata": {}, - "source": [ - "#### Precedence Constraints\n", - "\n", - "We have decided to model the deliveries to index 0 as removing items from the factory floor. In some cases it may be necessary which operations are complete prior to exiting. Here we set precedence constraints on specific deliveries which must occur before parts can exit the factory floor" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "731fb876", - "metadata": {}, - "outputs": [], - "source": [ - "precedences = [\n", - " {\n", - " \"order_id\": 8,\n", - " \"preceding_orders\": [6, 7],\n", - " },\n", - " {\n", - " \"order_id\": 11,\n", - " \"preceding_orders\": [9, 10],\n", - " },\n", - "]\n", - "task_data[\"precedences\"] = precedences" - ] - }, - { - "cell_type": "markdown", - "id": "4ef964ca", - "metadata": {}, - "source": [ - "#### Dispatch the task data to the server" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f9aa8c8f", - "metadata": {}, - "outputs": [], - "source": [ - "# Send the task data to the server\n", - "response_set = requests.post(\n", - " url + \"set_task_data\", json=task_data\n", - ")\n", - "\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "31db9053", - "metadata": {}, - "source": [ - "### Set AMR data" - ] - }, - { - "cell_type": "markdown", - "id": "731fdcbe", - "metadata": {}, - "source": [ - "Accumulate AMR fleet data such as its start and end locations, capacity, break/charging times and other details that relate to a vehicle." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2e765325", - "metadata": {}, - "outputs": [], - "source": [ - "n_robots = 2\n", - "robot_data = {}\n", - "\n", - "# Add start and end locations for AMRs, assuming all AMRs start and end at location 0.\n", - "robot_data[\"vehicle_locations\"] = [[0, 0]] * n_robots\n", - "\n", - "# Add carrying capacity for AMRs, assuming all robots have capacity of 2,\n", - "# means, they can carry at the max two items at any point\n", - "robot_data[\"capacities\"] = [[2] * n_robots]\n", - "\n", - "robot_data[\"vehicle_time_windows\"] = [[factory_open_time, factory_close_time]] * n_robots\n", - "\n", - "# Dispatch the fleet data to the server\n", - "response_set = requests.post(\n", - " url + \"set_fleet_data\", json=robot_data\n", - ")\n", - "\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "b0d06888", - "metadata": {}, - "source": [ - "### Set Solver Settings" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a6babc11", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "# Setup the solver settings json datastructure\n", - "solver_settings = {\n", - " # solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", - " \"time_limit\": 0.05,\n", - " # set number of climbers that will try to search for an optimal routes in parallel\n", - " \"number_of_climbers\": 128,\n", - "}\n", - "\n", - "\n", - "# dispatch the solver settings to the server\n", - "response_set = requests.post(\n", - " url + \"set_solver_config\", json=solver_settings\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "854e9519", - "metadata": {}, - "source": [ - "### Get optimized route" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "28a05ace", - "metadata": {}, - "outputs": [], - "source": [ - "solver_response = requests.get(url + \"get_optimized_routes\")\n", - "\n", - "assert solver_response.status_code == 200\n", - "\n", - "# Process returned data\n", - "solver_resp = solver_response.json()[\"response\"][\"solver_response\"]\n", - "\n", - "if solver_resp[\"status\"] == 0:\n", - " print(\"Cost for the routing in time: \", solver_resp[\"solution_cost\"])\n", - " print(\"Vehicle count to complete routing: \", solver_resp[\"num_vehicles\"])\n", - " utils.show_vehicle_routes(solver_resp, location_names)\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_resp[\"status\"])" - ] - }, - { - "cell_type": "markdown", - "id": "bba4accd", - "metadata": {}, - "source": [ - "#### Waypoint level routes for AMRs" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c13cfbf3", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "solver_resp_df = utils.get_solution_df(solver_resp)\n", - "unique_robot_ids = solver_resp_df['truck_id'].unique()\n", - "all_routes = solver_resp_df\n", - "\n", - "for robot in unique_robot_ids:\n", - " route = all_routes[all_routes['truck_id']==robot]\n", - " unique_target_locs = all_routes[all_routes['truck_id']==robot]['route'].unique()\n", - " \n", - " print(f\"Waypoint level route for robot {robot}:\\n{all_routes[all_routes['truck_id']==robot]['route']}\\n\\n\")" - ] - }, - { - "cell_type": "markdown", - "id": "2668327f", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - }, - "vscode": { - "interpreter": { - "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb deleted file mode 100644 index 04699f0..0000000 --- a/notebooks/routing/microservice/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb +++ /dev/null @@ -1,452 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "e67c5c1d", - "metadata": {}, - "outputs": [], - "source": [ - "import requests\n", - "import pandas as pd\n", - "import notebook_utils.notebook_helpers as utils" - ] - }, - { - "cell_type": "markdown", - "id": "ba50d71a", - "metadata": {}, - "source": [ - "# Daily Deliveries\n", - "## Capacitated Vehicle Routing Problem (CVRP)" - ] - }, - { - "cell_type": "markdown", - "id": "3ec34cd8", - "metadata": {}, - "source": [ - "Micro fulfillment centers allow retailers to move predictable, high volume products closer to the end consumer allowing for lower costs and shorter overall delivery times.\n", - "\n", - "In this scenario we have a number of same-day delivery orders that we would like to process for a given area from a given micro fulfillment center. We have the requisite number of delivery vehicles and enough time to deliver all packages over the course of a single day. Each delivery vehicle has a maximum capacity of orders it can carry and we are looking for the route assignment that minimizes the total distance driven by all vehicles." - ] - }, - { - "cell_type": "markdown", - "id": "4fc9ef31", - "metadata": {}, - "source": [ - "### Problem Details:\n", - "- 8 Locations each with an associated demand\n", - " - 1 MFC \n", - " - demand: [0]\n", - " - 7 Delivery Locations\n", - " - demand: [4, 4, 2, 2, 1, 2, 1]\n", - " \n", - "\n", - "- 3 Delivery vehicles each with an associated capacity\n", - " - 2 trucks\n", - " - capacity: [8, 8]\n", - " - 1 van\n", - " - capacity: [4]\n", - " \n", - "**API Reference**: [cuOpt Server Documentation](https://docs.nvidia.com/cuopt/serv_api.html)" - ] - }, - { - "cell_type": "markdown", - "id": "ed3c2736", - "metadata": {}, - "source": [ - "Below we visualize the delivery locations with respect to the MFC. The cost from all locations to all other locations (a cost matrix) will be required for optimization. To see an example of cost matrix generation from map data or a waypoint graph, refer to the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook. For the purpose of this simple example we will omit the cost matrix calculation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "01b12b30", - "metadata": {}, - "outputs": [], - "source": [ - "location_names = [ \"MFC\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\" ]\n", - "location_coordinates = [ [4, 4], [1, 3], [8, 1], [2, 1], [6, 7], [0, 2], [7, 6], [5, 3] ]\n", - "location_coordinates_df = pd.DataFrame(location_coordinates, columns=['xcord', 'ycord'], index=location_names)\n", - "utils.gen_plot(location_coordinates_df).show()" - ] - }, - { - "cell_type": "markdown", - "id": "42ba94fb", - "metadata": {}, - "source": [ - "### Cost Matrix" - ] - }, - { - "cell_type": "markdown", - "id": "82edd816", - "metadata": {}, - "source": [ - "The cost matrix dictates the cost of travel between locations of interest. The cost itself can be anything relevant to the user. In this case we simply use distance as our cost.\n", - "\n", - "Here is the cost(distance) matrix corresponding to the above locations:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bfa64aee", - "metadata": {}, - "outputs": [], - "source": [ - "distance_matrix = [\n", - " [0.0, 3.1, 5.0, 3.6, 3.6, 4.5, 3.6, 1.4],\n", - " [3.1, 0.0, 7.3, 2.2, 6.4, 1.4, 6.7, 4.0],\n", - " [5.0, 7.3, 0.0, 6.0, 6.3, 8.1, 5.1, 3.6],\n", - " [3.6, 2.2, 6.0, 0.0, 7.2, 2.2, 7.1, 3.6],\n", - " [3.6, 6.4, 6.3, 7.2, 0.0, 7.8, 1.4, 4.1],\n", - " [4.5, 1.4, 8.1, 2.2, 7.8, 0.0, 8.1, 5.1],\n", - " [3.6, 6.7, 5.1, 7.1, 1.4, 8.1, 0.0, 3.6],\n", - " [1.4, 4.0, 3.6, 3.6, 4.1, 5.1, 3.6, 0.0]\n", - "]\n", - "\n", - "distance_matrix" - ] - }, - { - "cell_type": "markdown", - "id": "161b18aa", - "metadata": {}, - "source": [ - " ### Demand and Capacity" - ] - }, - { - "cell_type": "markdown", - "id": "3b038198", - "metadata": {}, - "source": [ - "Set up the demand for each location and the capacity for each vehicle" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9cb56810", - "metadata": {}, - "outputs": [], - "source": [ - "location_ids = [i+1 for i in range(len(location_names)-1)] # exclude the fulfillment center from task data\n", - "# \"A\" \"B\" \"C\" \"D\" \"E\" \"F\" \"G\"\n", - "location_demand = [ 4, 4, 2, 2, 1, 2, 1]\n", - "# Vehicle 0 Vehicle 1 Vehicle 2\n", - "vehicle_capacity = [ 8, 8, 4 ]\n", - "# Vehicle 0 loc, Vehicel 1 loc, Vehicle 2 loc\n", - "vehicle_locs = [ [0, 0 ], [0, 0], [0, 0] ]\n", - "n_vehicles = len(vehicle_locs)" - ] - }, - { - "cell_type": "markdown", - "id": "65ae9e05", - "metadata": {}, - "source": [ - "# Setup the cuOpt server\n", - "\n", - "**NOTE**: Please update **ip** and **port** on which the server is running." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "65505db8", - "metadata": {}, - "outputs": [], - "source": [ - "ip = \"0.0.0.0\"\n", - "port = \"5000\"\n", - "url = \"http://\" + ip + \":\" + port + \"/cuopt/\"\n", - "\n", - "# Test server health\n", - "assert requests.get(url + \"health\").status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "9312c733", - "metadata": {}, - "source": [ - "### Set Cost Matrix\n", - "\n", - "Dispatch cost matrix to the server" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a02105ba", - "metadata": {}, - "outputs": [], - "source": [ - "data_params = {\"return_data_state\": False}\n", - "\n", - "cost_data = {\"cost_matrix\": {0: distance_matrix}}\n", - "\n", - "# Set the cost matrix\n", - "response_set = requests.post(\n", - " url + \"set_cost_matrix\", params=data_params, json=cost_data\n", - ")\n", - "assert response_set.status_code == 200\n" - ] - }, - { - "cell_type": "markdown", - "id": "3139d541", - "metadata": {}, - "source": [ - "### Set Task Data\n", - "\n", - "Dispatch task data to the server" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2b930156", - "metadata": {}, - "outputs": [], - "source": [ - "task_data = {\n", - " \"task_locations\": location_ids,\n", - " \"demand\": [location_demand],\n", - "}\n", - "response_set = requests.post(url + \"set_task_data\", json=task_data)\n", - "assert response_set.status_code == 200\n" - ] - }, - { - "cell_type": "markdown", - "id": "ef924325", - "metadata": {}, - "source": [ - "### Set Vehicle Data\n", - "\n", - "Dispatch vehicle data to the server" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ee8859c8", - "metadata": {}, - "outputs": [], - "source": [ - "fleet_data = {\n", - " \"vehicle_locations\": vehicle_locs,\n", - " \"capacities\": [vehicle_capacity],\n", - "}\n", - "response_set = requests.post(url + \"set_fleet_data\", json=fleet_data)\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "bc3d347a", - "metadata": {}, - "source": [ - "### Set Solver Settings\n", - "\n", - "\n", - "Dispatch solver settings to the server" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bd600ffa", - "metadata": {}, - "outputs": [], - "source": [ - "solver_config = {\n", - " \"time_limit\": 0.01,\n", - " \"number_of_climbers\": 128,\n", - "}\n", - "response_set = requests.post(url + \"set_solver_config\", json=solver_config)\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "e6bf223a", - "metadata": {}, - "source": [ - "### Get Optimized Routes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b4141fd5", - "metadata": {}, - "outputs": [], - "source": [ - "# Solve the problem\n", - "solver_response = requests.get(url + \"get_optimized_routes\")\n", - "\n", - "assert solver_response.status_code == 200\n", - "\n", - "# Process returned data\n", - "solver_resp = solver_response.json()[\"response\"][\"solver_response\"]\n", - "\n", - "if solver_resp[\"status\"] == 0:\n", - " print(\"Cost for the routing in distance: \", solver_resp[\"solution_cost\"])\n", - " print(\"Vehicle count to complete routing: \", solver_resp[\"num_vehicles\"])\n", - " utils.show_vehicle_routes(solver_resp, location_names)\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_resp[\"status\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8618e29a", - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "vehicle_colors = [\"red\", \"green\", \"blue\"]\n", - "utils.map_vehicle_routes(location_coordinates_df, solver_resp, vehicle_colors).show()" - ] - }, - { - "cell_type": "markdown", - "id": "37ccafc5", - "metadata": {}, - "source": [ - "### Additional Constraints \n", - "##### Minimum Vehicles" - ] - }, - { - "cell_type": "markdown", - "id": "c560394e", - "metadata": {}, - "source": [ - "cuOpt has found a solution that does not require all available vehicles because the combined capacity of the two larger vehicles (16) is equal to total location demand (16). In some cases, this is a great solution as it gives the option to save on the costs associated with additional vehicles. In other cases there is value to assigning all available resources. In the latter case we can require that cuOpt use all 3 available vehicles and re-solve the problem with this constraint." - ] - }, - { - "cell_type": "markdown", - "id": "a646ca3a", - "metadata": {}, - "source": [ - "**Update the existing solver configuration in server and re-optimize**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fab4aebb", - "metadata": {}, - "outputs": [], - "source": [ - "# Set the minimum vehicles through solver config\n", - "fleet_data = {\n", - " \"min_vehicles\": n_vehicles,\n", - "}\n", - "response_set = requests.put(url + \"update_fleet_data\", json=fleet_data)\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e7637f20", - "metadata": {}, - "outputs": [], - "source": [ - "# Re-Solve the problem\n", - "solver_response = requests.get(url + \"get_optimized_routes\")\n", - "\n", - "assert response_set.status_code == 200\n", - "\n", - "solver_resp = solver_response.json()[\"response\"][\"solver_response\"]\n", - "\n", - "if solver_resp[\"status\"] == 0:\n", - " print(\"Cost for the routing in distance: \", solver_resp[\"solution_cost\"])\n", - " print(\"Vehicle count to complete routing: \", solver_resp[\"num_vehicles\"])\n", - " utils.show_vehicle_routes(solver_resp, location_names)\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_resp[\"status\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "adb83802", - "metadata": {}, - "outputs": [], - "source": [ - "utils.map_vehicle_routes(location_coordinates_df, solver_resp, vehicle_colors).show()" - ] - }, - { - "cell_type": "markdown", - "id": "97b987b2", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - }, - "vscode": { - "interpreter": { - "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb deleted file mode 100644 index 0ddb6a7..0000000 --- a/notebooks/routing/microservice/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb +++ /dev/null @@ -1,548 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "65705fab", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import requests\n", - "import notebook_utils.notebook_helpers as utils" - ] - }, - { - "cell_type": "markdown", - "id": "fbb36201", - "metadata": {}, - "source": [ - "# Priority Routing\n", - "## Capacitated Vehicle Routing Problem with Soft Time Windows (CVRPSTW)" - ] - }, - { - "cell_type": "markdown", - "id": "bffb5f0c", - "metadata": {}, - "source": [ - "Loyalty (or Preferred) customer programs help companies to reward repeat customers and enhance their overall business offering. While the best possible customer service is always the goal, loyalty programs provide a mechanism for reinforcing the relationship with the customers that drive business revenue.\n", - "\n", - "In this scenario we have a set of deliveries with target time windows for delivery that do not represent a feasible solution given the delivery vehicles that are available. We would still like to deliver all the packages even if some of them are a little behind schedule. However, we would like to prioritize the deliveries of customers in our loyalty program to minimize the delay these customers experience.\n", - "\n", - "We also want to optimize according to a business defined cost objective that is a combination of business relevant metrics. To track time window constraints we will pass a time matrix as a constraint checking \"secondary matrix\".\n" - ] - }, - { - "cell_type": "markdown", - "id": "cb33a971", - "metadata": {}, - "source": [ - "### Problem Details:\n", - "- 8 Locations each with an associated demand\n", - " - 1 Distribution Center \n", - " - distribution center demand: [0]\n", - " - hours of operation: [0,24]\n", - " - 7 Service Locations\n", - " - demand for deliveries: [1, 1, 1, 1, 1, 1, 1]\n", - " - delivery time windows: [[9,10],[9,10],[9,10],[10,11],[10,11],[10,11],[9,10]]\n", - " - service location service times: [ 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25]\n", - " - loyalty program member: [1, 0, 0, 0, 1, 0, 1]\n", - "\n", - "- 3 Delivery vehicles each with an associated capacity\n", - " - 3 delivery vehicles\n", - " - capacity for deliveries: [3, 3, 3]\n", - " \n", - "**API Reference**: [cuOpt Server Documentation](https://docs.nvidia.com/cuopt/serv_api.html)" - ] - }, - { - "cell_type": "markdown", - "id": "baa93a42", - "metadata": {}, - "source": [ - "Below we visualize the delivery locations with respect to the distribution center. The cost from all locations to all other locations (a cost matrix) will be required for optimization. To see an example of cost matrix generation from map data or a waypoint graph, refer to the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook. For the purpose of this simple example we will omit the cost matrix calculation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e747d30d", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "location_names = [ \"DC\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\" ]\n", - "location_coordinates = [ [4, 4], [1, 3], [8, 1], [2, 1], [6, 7], [0, 2], [7, 6], [5, 3] ]\n", - "location_coordinates_df = pd.DataFrame(location_coordinates, columns=['xcord', 'ycord'], index=location_names)\n", - "utils.gen_plot(location_coordinates_df).show()" - ] - }, - { - "cell_type": "markdown", - "id": "413aeb52", - "metadata": {}, - "source": [ - "## Setup the cuOpt server and test its health\n", - "\n", - "**NOTE**: Please update **ip** and **port** on which the server is running." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5b5b9102", - "metadata": {}, - "outputs": [], - "source": [ - "ip = \"0.0.0.0\"\n", - "port = \"5000\"\n", - "url = \"http://\" + ip + \":\" + port + \"/cuopt/\"\n", - "\n", - "# Test server health\n", - "assert requests.get(url + \"health\").status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "1307fc95", - "metadata": {}, - "source": [ - "### Cost Matrix : Primary" - ] - }, - { - "cell_type": "markdown", - "id": "27525bf2", - "metadata": {}, - "source": [ - "The cost matrix dictates the cost of travel between locations of interest. The cost itself can be anything relevant to the user. In this case we are using a business defined cost objective as a primary cost matrix and a secondary time matrix to verify our time based constraints. \n", - "\n", - "Here is the cost(business metric) matrix corresponding to the locations above:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ef01144e", - "metadata": {}, - "outputs": [], - "source": [ - "business_metric_cost_matrix = [\n", - " [0.0, 3.1, 5.0, 3.6, 3.6, 4.5, 3.6, 1.4],\n", - " [3.1, 0.0, 7.3, 2.2, 6.4, 1.4, 6.7, 4.0],\n", - " [5.0, 7.3, 0.0, 6.0, 6.3, 8.1, 5.1, 3.6],\n", - " [3.6, 2.2, 6.0, 0.0, 7.2, 2.2, 7.1, 3.6],\n", - " [3.6, 6.4, 6.3, 7.2, 0.0, 7.8, 1.4, 4.1],\n", - " [4.5, 1.4, 8.1, 2.2, 7.8, 0.0, 8.1, 5.1],\n", - " [3.6, 6.7, 5.1, 7.1, 1.4, 8.1, 0.0, 3.6],\n", - " [1.4, 4.0, 3.6, 3.6, 4.1, 5.1, 3.6, 0.0]\n", - "]" - ] - }, - { - "cell_type": "markdown", - "id": "9671d772", - "metadata": {}, - "source": [ - "### Cost Matrix : Secondary" - ] - }, - { - "cell_type": "markdown", - "id": "d226f72b", - "metadata": {}, - "source": [ - "Here is the constraint checking (time) secondary matrix:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "145a2560", - "metadata": {}, - "outputs": [], - "source": [ - "constraint_checking_time_matrix = [\n", - " [0.00, 0.39, 0.63, 0.45, 0.45, 0.55, 0.45, 0.18 ],\n", - " [0.39, 0.00, 0.90, 0.28, 0.80, 0.18, 0.84, 0.50 ],\n", - " [0.63, 0.90, 0.00, 0.75, 0.79, 1.00, 0.64, 0.45 ],\n", - " [0.45, 0.28, 0.75, 0.00, 0.90, 0.28, 0.88, 0.45 ],\n", - " [0.45, 0.80, 0.79, 0.90, 0.00, 0.96, 0.18, 0.51 ],\n", - " [0.55, 0.18, 1.00, 0.28, 0.96, 0.00, 1.00, 0.64 ],\n", - " [0.45, 0.84, 0.64, 0.88, 0.18, 1.00, 0.00, 0.45 ],\n", - " [0.18, 0.50, 0.45, 0.45, 0.51, 0.64, 0.45, 0.00 ]\n", - "]" - ] - }, - { - "cell_type": "markdown", - "id": "cbd80cd3", - "metadata": {}, - "source": [ - "### Deliveries" - ] - }, - { - "cell_type": "markdown", - "id": "54d6af91", - "metadata": {}, - "source": [ - "Setup the delivery data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "28e33246", - "metadata": {}, - "outputs": [], - "source": [ - "delivery_location_data = {\n", - " \"location_ids\": [i+1 for i in range(len(location_names)-1)], # designate zeroth location as start and return points for fleet\n", - " \"delivery_demand\": [1, 1, 1, 1, 1, 1, 1 ],\n", - " \"location_earliest_time\": [9, 9, 9, 10, 10, 10, 9 ],\n", - " \"location_latest_time\": [10, 10, 10, 11, 11, 11, 10],\n", - " \"required_service_time\": [1, 1, 1, 1, 1, 1, 1 ],\n", - " \"loyalty_member\": [0, 1, 0, 1, 0, 1, 0 ]\n", - "}\n", - "print(delivery_location_data)" - ] - }, - { - "cell_type": "markdown", - "id": "dd268248", - "metadata": {}, - "source": [ - "### Set Cost Matrix\n", - "\n", - "Dispatch cost matrix to server" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "14b41584", - "metadata": {}, - "outputs": [], - "source": [ - "data_params = {\"return_data_state\": False}\n", - "cost_data = {\"cost_matrix\": {0: business_metric_cost_matrix}}\n", - "response_set = requests.post(\n", - " url + \"set_cost_matrix\", params=data_params, json=cost_data\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "8181e6f1", - "metadata": {}, - "source": [ - "### Set Secondary Cost Matrix\n", - "\n", - "Dispatch secondary cost matrix to server" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a603ed5c", - "metadata": {}, - "outputs": [], - "source": [ - "# set the secondary constraint checking time matrix\n", - "time_data = {\"cost_matrix\": {0: constraint_checking_time_matrix}}\n", - "response_set = requests.post(\n", - " url + \"set_travel_time_matrix\", params=data_params, json=time_data\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "6bace17e", - "metadata": {}, - "source": [ - "### Set Vehicle Data\n", - "Dispatch vehicle data to server" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f4931236", - "metadata": {}, - "outputs": [], - "source": [ - "n_vehicles = 3\n", - "vehicle_capacity = 3 # As per problem statement, all vehicles have capacities of 3\n", - "\n", - "# Build the fleet data\n", - "fleet_data = {\n", - " # Vehicle start and end at location 0, since 0 is distribution center\n", - " \"vehicle_locations\": [[0,0]] * n_vehicles,\n", - " \"capacities\": [[vehicle_capacity] * n_vehicles],\n", - " \"vehicle_time_windows\": [[5, 20]] * n_vehicles\n", - "}\n", - "\n", - "# Dispatch the fleet data request to the server\n", - "response_set = requests.post(url + \"set_fleet_data\", json=fleet_data)\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "e5738d68", - "metadata": {}, - "source": [ - "### Set Task Data\n", - "Dispatch task data to server" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c0bcf99c", - "metadata": {}, - "outputs": [], - "source": [ - "# Build the task data\n", - "task_data = {\n", - " \"task_locations\": delivery_location_data[\"location_ids\"],\n", - " \"demand\": [delivery_location_data[\"delivery_demand\"]],\n", - "}\n", - "\n", - "# add time window constraints and service time for the locations\n", - "task_data[\"task_time_windows\"] = list(zip(delivery_location_data[\"location_earliest_time\"],\n", - " delivery_location_data[\"location_latest_time\"]))\n", - "\n", - "task_data[\"service_times\"] = delivery_location_data[\"required_service_time\"]\n", - "\n", - "# Dispatch the task request to the server\n", - "response_set = requests.post(url + \"set_task_data\", json=task_data)\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "82f4dd75", - "metadata": {}, - "source": [ - "### Set CuOpt Solver Configuration" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d0f811c5", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "# Setup the solver settings json datastructure\n", - "solver_settings = {\n", - " # solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", - " \"time_limit\": 0.05,\n", - " # set number of climbers that will try to search for an optimal routes in parallel\n", - " \"number_of_climbers\": 128,\n", - "}\n", - "\n", - "# dispatch the solver settings to the server\n", - "response_set = requests.post(\n", - " url + \"set_solver_config\", json=solver_settings\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "1d2997dd", - "metadata": {}, - "source": [ - "### Attempt to obtain optimized routes\n", - "We can attempt to solve this problem as stated, but as previously discussed it is not feasible within the specified target time windows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a63b509b", - "metadata": {}, - "outputs": [], - "source": [ - "solver_response = requests.get(url + \"get_optimized_routes\")\n", - "\n", - "if solver_response.status_code == 200:\n", - " print(\"Cost for the routing in time: \", solver_response[\"solution_cost\"])\n", - " print(\"Vehicle count to complete routing: \", solver_response[\"num_vehicles\"])\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_response.status_code)" - ] - }, - { - "cell_type": "markdown", - "id": "3f863e49", - "metadata": {}, - "source": [ - "cuOpt is unable to find a feasible solution. As previously discussed we would like to allow the deliveries to exceed the latest time windows by using soft time windows" - ] - }, - { - "cell_type": "markdown", - "id": "900028af", - "metadata": {}, - "source": [ - "### Initial Solution\n", - "\n", - "With soft time window option, we can relax time window constraints along with penality to come up with a solution but at a additional cost. \n", - "\n", - "#### Update solver configuration to use Soft Time windows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8f6e8a2c", - "metadata": {}, - "outputs": [], - "source": [ - "solver_settings[\"solution_scope\"] = 1\n", - "# Update the solver settings to the server\n", - "response_set = requests.put(\n", - " url + \"update_solver_config\", json=solver_settings\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "65bcae7d", - "metadata": {}, - "source": [ - "#### Add Penalty\n", - "\n", - "With this, we can prioritize order/customers by providing higher penalties to such jobs compared to others." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "720d9692", - "metadata": {}, - "outputs": [], - "source": [ - "delivery_location_data['penalty'] = [x * 100 for x in delivery_location_data[\"loyalty_member\"]]\n", - "delivery_location_data\n", - "\n", - "\n", - "task_data = {\n", - " \"penalties\": delivery_location_data[\"penalty\"]\n", - "}\n", - "\n", - "# Update the task request to the server\n", - "response_set = requests.put(url + \"update_task_data\", json=task_data)\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "1eb424b0", - "metadata": {}, - "source": [ - "#### Re-optimize " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "098c0061", - "metadata": {}, - "outputs": [], - "source": [ - "solver_response = requests.get(url + \"get_optimized_routes\")\n", - "solver_resp = solver_response.json()[\"response\"][\"solver_response\"]\n", - "\n", - "if solver_resp[\"status\"] == 0: \n", - " solver_resp_df = utils.get_solution_df(solver_resp)\n", - " print(\"Cost for the routing in time: \", solver_resp[\"solution_cost\"])\n", - " print(\"Vehicle count to complete routing: \", solver_resp[\"num_vehicles\"])\n", - " print(solver_resp_df)\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_resp[\"status\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d1845a27", - "metadata": {}, - "outputs": [], - "source": [ - "solution_data_priority = utils.get_solution_df(solver_resp)\n", - "solution_data_priority['route'] = [location_names[i] for i in solution_data_priority['route'].to_list()]\n", - "solution_data_priority = solution_data_priority.set_index('route')\n", - "solution_data_priority" - ] - }, - { - "cell_type": "markdown", - "id": "38bc3217", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - }, - "vscode": { - "interpreter": { - "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb deleted file mode 100644 index 47215c3..0000000 --- a/notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb +++ /dev/null @@ -1,462 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a05e01aa", - "metadata": {}, - "source": [ - "
\n", - "\n", - "# Skip notebook test\n", - "\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "afc57ab3", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import numpy as np\n", - "import requests\n", - "from scipy.spatial import distance\n", - "import notebook_utils.notebook_helpers as utils" - ] - }, - { - "cell_type": "markdown", - "id": "0d500386", - "metadata": {}, - "source": [ - "# Benchmark Gehring & Homberger\n", - "## Capacitated Vehicle Routing Problem with Time Windows (CVRPTW)" - ] - }, - { - "cell_type": "markdown", - "id": "30e63d74", - "metadata": {}, - "source": [ - "While other notebooks such as [cvrptw_service_team_routing.ipynb](cvrptw_service_team_routing.ipynb) focus on the cuOpt API and high level problem modeling, here we focus on performance.\n", - "\n", - "cuOpt offers a unique benefit over other solver_settingss, specifically, time to solution. In addition to achieving world class accuracy, cuOpt also produces these solutions in a time frame that allows for re-optimization in dynamic environments and rapid iteration over possible problem configurations.\n", - "\n", - "Here we are demonstrating this performance on a large popular academic [dataset by Gehing & Homberger](https://www.sintef.no/projectweb/top/vrptw/homberger-benchmark/). These problems are well studied and used as the basis for comparison for VRP research and product offerings. The particular instance we will test with is from the group of largest (1000 location) problems. Each problem instance has an associated best known solution, the one we will measure against is shown below\n", - "\n", - "**API Reference**: [cuOpt Server Documentation](https://docs.nvidia.com/cuopt/serv_api.html)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "65860d5d", - "metadata": {}, - "outputs": [], - "source": [ - "homberger_1000_file = 'notebook_utils/data/C1_10_1.TXT'\n", - "\n", - "best_known_solution = {\n", - " \"n_vehicles\": 100,\n", - " \"cost\": 42478.95\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "af25d3f9", - "metadata": {}, - "source": [ - "### Problem Data\n", - "The data for this problem instance are provided via text file. cuOpt has a utility function available specifically for the Gehring & Homberger benchmark which converts the problem into the components required by cuOpt." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fd6089b5", - "metadata": {}, - "outputs": [], - "source": [ - "orders, vehicle_capacity, n_vehicles = utils.create_from_file(homberger_1000_file)\n", - "n_locations = orders[\"demand\"].shape[0]-1\n", - "print(\"Number of locations : \", n_locations)\n", - "print(\"Number of vehicles available : \", n_vehicles)\n", - "print(\"Capacity of each vehicle : \", vehicle_capacity)\n", - "print(\"\\nInitial Orders information\")\n", - "print(orders)" - ] - }, - { - "cell_type": "markdown", - "id": "4890f027", - "metadata": {}, - "source": [ - "### Setup the cuOpt server and test the health of the server\n", - "\n", - "**NOTE**: Please update **ip** and **port** on which the server is running." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d57690c9", - "metadata": {}, - "outputs": [], - "source": [ - "ip = \"0.0.0.0\"\n", - "port = \"5000\"\n", - "url = \"http://\" + ip + \":\" + port + \"/cuopt/\"\n", - "\n", - "# Test the health of the cuOpt server\n", - "assert requests.get(url + \"health\").status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "ba4eb34d", - "metadata": {}, - "source": [ - "### Cost Matrix" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0cc3ced9", - "metadata": {}, - "outputs": [], - "source": [ - "coords = list(zip(orders['xcord'].to_list(),\n", - " orders['ycord'].to_list()))\n", - "\n", - "cost_matrix = pd.DataFrame(distance.cdist(coords, coords, 'euclidean')).astype(np.float32)\n", - "print(f\"Shape of cost matrix: {cost_matrix.shape}\")" - ] - }, - { - "cell_type": "markdown", - "id": "c938e463", - "metadata": {}, - "source": [ - "### Set Cost Matrix" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8493081f", - "metadata": {}, - "outputs": [], - "source": [ - "data_params = {\"return_data_state\": False}\n", - "cost_data = {\"cost_matrix\": {0: cost_matrix.values.tolist()}}\n", - "response_set = requests.post(\n", - " url + \"set_cost_matrix\", params=data_params, json=cost_data\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "9ad17098", - "metadata": {}, - "source": [ - "### Set Fleet Data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "45f8aa47", - "metadata": {}, - "outputs": [], - "source": [ - "# Set the fleet data\n", - "vehicle_locations = [[0, 0]] * n_vehicles\n", - "fleet_data = {\n", - " \"vehicle_locations\": vehicle_locations,\n", - " \"capacities\": [[vehicle_capacity] * n_vehicles],\n", - "}\n", - "\n", - "# Dispatch the fleet data to the cuOpt server\n", - "response_set = requests.post(\n", - " url + \"set_fleet_data\", json=fleet_data\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "800db055", - "metadata": {}, - "source": [ - "### Set Task Data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "638df884", - "metadata": {}, - "outputs": [], - "source": [ - "# Set the task data\n", - "task_data = {\n", - " \"task_locations\": orders['vertex'].values.tolist(),\n", - " \"task_time_windows\": list(zip(orders['earliest_time'].values.tolist(),\n", - " orders['latest_time'].values.tolist())),\n", - " \"service_times\": orders['service_time'].values.tolist(),\n", - " \"demand\": [orders['demand'].values.tolist()],\n", - "}\n", - "\n", - "# Dispatch the task data to the cuOpt server\n", - "response_set = requests.post(\n", - " url + \"set_task_data\", json=task_data\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "e4f9a455", - "metadata": {}, - "source": [ - "### Set Solver configuration" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3eddb994", - "metadata": {}, - "outputs": [], - "source": [ - "solver_settings = {\n", - " \"time_limit\": 0.5,\n", - " \"number_of_climbers\": 2048,\n", - "}\n", - "# set number of climbers that will try to search for an optimal routes in parallel\n", - "response_set = requests.post(\n", - " url + \"set_solver_config\", json=solver_settings\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "8944c315", - "metadata": {}, - "source": [ - "### Helper functions to solve and process the output" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5382727c", - "metadata": {}, - "outputs": [], - "source": [ - "# Here we will examine the quality of the solution we increase the time budget provided to cuOpt\n", - "def solve_problem(problem_size):\n", - " solver_response = requests.get(url + \"get_optimized_routes\")\n", - " solver_resp = solver_response.json()[\"response\"][\"solver_response\"]\n", - " if solver_resp[\"status\"] == 0:\n", - " print(\"Cost for the routing in time: \", solver_resp[\"solution_cost\"])\n", - " print(\"Vehicle count to complete routing: \", solver_resp[\"num_vehicles\"])\n", - " utils.show_vehicle_routes(solver_resp, [\"Depot\"]+[str(i) for i in range(1, problem_size+1)])\n", - " else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_resp[\"status\"])\n", - " \n", - " return(solver_resp[\"num_vehicles\"], solver_resp[\"solution_cost\"])\n", - "\n", - "def solution_eval(vehicles, cost, best_known_solution):\n", - " \n", - " print(f\"- cuOpt provides a solution using {vehicles} vehicles\")\n", - " print(f\"- This represents {vehicles - best_known_solution['n_vehicles']} more than the best known solution\")\n", - " print(f\"- Vehicle Percent Difference {(vehicles/best_known_solution['n_vehicles'] - 1)*100}% \\n\\n\")\n", - " print(f\"- In addition cuOpt provides a solution cost of {cost}\") \n", - " print(f\"- Best known solution cost is {best_known_solution['cost']}\")\n", - " print(f\"- Cost Percent Difference {(cost/best_known_solution['cost'] - 1)*100}%\")" - ] - }, - { - "cell_type": "markdown", - "id": "24afe2f5", - "metadata": {}, - "source": [ - "### Get Optimized Results\n", - "\n", - "Update solver config and test different run-time " - ] - }, - { - "cell_type": "markdown", - "id": "0941d56f", - "metadata": {}, - "source": [ - "**1 Second Time Limit**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "70f12ffa", - "metadata": {}, - "outputs": [], - "source": [ - "solver_settings[\"time_limit\"] = 1\n", - "# update the time limit for solving the problem\n", - "response_set = requests.put(\n", - " url + \"update_solver_config\", json=solver_settings\n", - ")\n", - "assert response_set.status_code == 200\n", - "# re-solve the problem with time limit equals 1\n", - "vehicles, cost = solve_problem(len(cost_matrix))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a2453b1b", - "metadata": {}, - "outputs": [], - "source": [ - "# Evaluation:\n", - "solution_eval(vehicles, cost, best_known_solution)" - ] - }, - { - "cell_type": "markdown", - "id": "04ef0c21", - "metadata": {}, - "source": [ - "**10 Second Time Limit**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3934d8de", - "metadata": {}, - "outputs": [], - "source": [ - "solver_settings[\"time_limit\"] = 10\n", - "# update the time limit for solving the problem\n", - "response_set = requests.put(\n", - " url + \"update_solver_config\", json=solver_settings\n", - ")\n", - "assert response_set.status_code == 200\n", - "# re-solve the problem with time limit equals ten\n", - "vehicles, cost = solve_problem(len(cost_matrix))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7ab5e6d1", - "metadata": {}, - "outputs": [], - "source": [ - "# Evaluation:\n", - "solution_eval(vehicles, cost, best_known_solution)" - ] - }, - { - "cell_type": "markdown", - "id": "a9a1b855", - "metadata": {}, - "source": [ - "**20 Second Time Limit**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e0c38643", - "metadata": {}, - "outputs": [], - "source": [ - "solver_settings[\"time_limit\"] = 20\n", - "# update the time limit for solving the problem\n", - "response_set = requests.put(\n", - " url + \"update_solver_config\", json=solver_settings\n", - ")\n", - "assert response_set.status_code == 200\n", - "# re-solve the problem with time limit equals twenty\n", - "vehicles, cost = solve_problem(len(cost_matrix))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ff80118b", - "metadata": {}, - "outputs": [], - "source": [ - "# Evaluation:\n", - "solution_eval(vehicles, cost, best_known_solution)" - ] - }, - { - "cell_type": "markdown", - "id": "dc94ab34", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - }, - "vscode": { - "interpreter": { - "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb b/notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb deleted file mode 100644 index 7aa159b..0000000 --- a/notebooks/routing/microservice/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb +++ /dev/null @@ -1,457 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "b2cba47f", - "metadata": {}, - "outputs": [], - "source": [ - "import requests\n", - "import pandas as pd\n", - "import notebook_utils.notebook_helpers as utils" - ] - }, - { - "cell_type": "markdown", - "id": "371f38f1", - "metadata": {}, - "source": [ - "# Service Team Routing\n", - "## Capacitated Vehicle Routing Problem with Time Windows (CVRPTW)" - ] - }, - { - "cell_type": "markdown", - "id": "25e90f86", - "metadata": {}, - "source": [ - "The ability of service providers to set service time windows allows for easier and more dependable coordination between the service provider and their customers, while increasing overall customer satisfaction.\n", - "\n", - "In this scenario we have a number of service order locations with associated time windows and service times (time on-site to complete service). Each technician has an associated availability, ability to complete certain types of service, and a maximum number of service appointments per day." - ] - }, - { - "cell_type": "markdown", - "id": "63093d54", - "metadata": {}, - "source": [ - "### Problem Details:\n", - "- 8 Locations each with an associated demand\n", - " - 1 Headquarters \n", - " - service type 1 demand: [0]\n", - " - service type 2 demand: [1]\n", - " - headquarters hours of operation: [5,20]\n", - " - 7 Service Locations\n", - " - service type 1 demand: [1, 1, 1, 0, 0, 0, 0]\n", - " - service type 2 demand: [0, 0, 1, 1, 1, 1, 1]\n", - " - service locations time windows: [[9,12],[9,12],[11,14],[13,16],[13,16],[13,16],[13,16]]\n", - " - service location service times: [ 1, 1, 1.5, 0.5, 0.5, 0.5]\n", - "\n", - "- 3 Delivery vehicles each with an associated capacity\n", - " - 3 service technicians\n", - " - capacity for service type 1: [2, 1, 0]\n", - " - capacity for service type 2: [0, 1, 4]\n", - " - technician availability [[9,17], [12,15], [9,17]]\n", - " \n", - "**API Reference**: [cuOpt Server Documentation](https://docs.nvidia.com/cuopt/serv_api.html)" - ] - }, - { - "cell_type": "markdown", - "id": "baeeee39", - "metadata": {}, - "source": [ - "Below we visualize the service locations with respect to the service company headquarters. The cost from all locations to all other locations (a cost matrix) will be required for optimization. To see an example of cost matrix generation from map data or a waypoint graph, refer to the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook. For the purpose of this simple example we will omit the cost matrix calculation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c8a0847d", - "metadata": {}, - "outputs": [], - "source": [ - "location_names = [ \"Headquarters\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\" ]\n", - "location_coordinates = [ [4, 4], [1, 3], [8, 1], [2, 1], [6, 7], [0, 2], [7, 6], [5, 3] ]\n", - "location_coordinates_df = pd.DataFrame(location_coordinates, columns=['xcord', 'ycord'], index=location_names)\n", - "utils.gen_plot(location_coordinates_df).show()" - ] - }, - { - "cell_type": "markdown", - "id": "ff1d68f2", - "metadata": {}, - "source": [ - "### Cost Matrix" - ] - }, - { - "cell_type": "markdown", - "id": "210a57e9", - "metadata": {}, - "source": [ - "The cost matrix dictates the cost of travel between locations of interest. The cost itself can be anything relevant to the user. In this case we are constraining time window constraints. When constraining time windows for locations or vehicles it is assumed (if only a single cost matrix is provided) that it represents time. \n", - "\n", - "Here is the cost(time) matrix corresponding to the locations above:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1054c7e3", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "time_matrix = [\n", - " [0.00, 0.31, 0.50, 0.36, 0.36, 0.44, 0.36, 0.14],\n", - " [0.31, 0.00, 0.72, 0.22, 0.64, 0.14, 0.67, 0.40],\n", - " [0.50, 0.72, 0.00, 0.60, 0.63, 0.80, 0.51, 0.36],\n", - " [0.36, 0.22, 0.60, 0.00, 0.72, 0.22, 0.70, 0.36],\n", - " [0.36, 0.64, 0.63, 0.72, 0.00, 0.77, 0.14, 0.41],\n", - " [0.44, 0.14, 0.80, 0.22, 0.77, 0.00, 0.80, 0.51],\n", - " [0.36, 0.67, 0.51, 0.70, 0.14, 0.80, 0.00, 0.36],\n", - " [0.14, 0.40, 0.36, 0.36, 0.41, 0.51, 0.36, 0.00]\n", - "]\n", - "\n", - "# Create a dataframe of this matrix\n", - "time_matrix_df = pd.DataFrame(time_matrix, \n", - " index=location_coordinates_df.index, \n", - " columns=location_coordinates_df.index)\n", - "time_matrix_df" - ] - }, - { - "cell_type": "markdown", - "id": "3397b254", - "metadata": {}, - "source": [ - "### Service Locations" - ] - }, - { - "cell_type": "markdown", - "id": "ce7b7af7", - "metadata": {}, - "source": [ - "Setup the service location data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dc98afdf", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "# exclude head quarters from service location names\n", - "service_location_ids = [1, 2, 3, 4, 5, 6, 7]\n", - "service_location_names = [location_names[i] for i in service_location_ids]\n", - "service_location_data = {\n", - " \"service_location_names\": service_location_names,\n", - " \"service_location_ids\": service_location_ids,\n", - " \"service_type1_demand\": [1, 1, 1, 0, 0, 0, 0],\n", - " \"service_type2_demand\": [0, 0, 1, 1, 1, 1, 1],\n", - " \"location_earliest_time\": [9, 9, 11, 13, 13, 13, 13],\n", - " \"location_latest_time\": [12, 12, 14, 16, 16, 16,16],\n", - " \"required_service_time\": [1, 1, 1.5, 0.5, 0.5, 0.5, 0.5]\n", - "}\n", - "service_location_data_df = pd.DataFrame(service_location_data).set_index('service_location_names')\n", - "service_location_data_df" - ] - }, - { - "cell_type": "markdown", - "id": "cd27971f", - "metadata": {}, - "source": [ - "### Vehicles" - ] - }, - { - "cell_type": "markdown", - "id": "d66df281", - "metadata": {}, - "source": [ - "Setup vehicle/technician data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "248e1add", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "n_vehicles = 3\n", - "vehicle_data = {\n", - " \"vehicle_ids\": [i for i in range(n_vehicles)],\n", - " \"capacity_service_type1\":[2, 1, 0],\n", - " \"capacity_service_type2\":[0, 1, 4],\n", - " \"vehicle_availability_earliest\":[9, 11, 9],\n", - " \"vehicle_availability_latest\":[17, 15, 17]\n", - "}\n", - "vehicle_data_df = pd.DataFrame(vehicle_data).set_index('vehicle_ids')\n", - "vehicle_data_df" - ] - }, - { - "cell_type": "markdown", - "id": "40cc2676", - "metadata": {}, - "source": [ - "# Setup the cuOpt server and test its health\n", - "\n", - "**NOTE**: Please update **ip** and **port** on which the server is running." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cf2a003f", - "metadata": {}, - "outputs": [], - "source": [ - "ip = \"0.0.0.0\"\n", - "port = \"5000\"\n", - "url = \"http://\" + ip + \":\" + port + \"/cuopt/\"\n", - "\n", - "# Test health\n", - "assert requests.get(url + \"health\").status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "8c70e5b3", - "metadata": {}, - "source": [ - "### Set Cost Matrix" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "15214631", - "metadata": {}, - "outputs": [], - "source": [ - "# set the cost matrix\n", - "data_params = {\"return_data_state\": False}\n", - "cost_data = {\"cost_matrix\": {0: time_matrix}}\n", - "response_set = requests.post(\n", - " url + \"set_cost_matrix\", params=data_params, json=cost_data\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "6cb27e14", - "metadata": {}, - "source": [ - "### Set Fleet Data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9b07f8ad", - "metadata": {}, - "outputs": [], - "source": [ - "# Build the vehicle data\n", - "fleet_data = {\n", - " \"vehicle_locations\": [[0,0]] * n_vehicles,\n", - " \"capacities\": [vehicle_data[\"capacity_service_type1\"], vehicle_data[\"capacity_service_type2\"]],\n", - "}\n", - "\n", - "# add time windows for vehicle availability\n", - "fleet_data[\"vehicle_time_windows\"] = list(zip(vehicle_data['vehicle_availability_earliest'],\n", - " vehicle_data['vehicle_availability_latest']))\n", - "\n", - "# Dispatch the vehicle data to the cuOpt server\n", - "response_set = requests.post(\n", - " url + \"set_fleet_data\", json=fleet_data\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "d6fce836", - "metadata": {}, - "source": [ - "### Set Task Data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2b28f01c", - "metadata": {}, - "outputs": [], - "source": [ - "n_vehicles = len(vehicle_data_df)\n", - "\n", - "# Build the task data\n", - "task_data = {\n", - " \"task_locations\": service_location_ids,\n", - " # demand for service type 1 and service type 2\n", - " \"demand\": [service_location_data[\"service_type1_demand\"], service_location_data[\"service_type2_demand\"]],\n", - "}\n", - "\n", - "# add time window constraints and service time for the service locations\n", - "task_data[\"task_time_windows\"] = list(zip(service_location_data[\"location_earliest_time\"],\n", - " service_location_data[\"location_latest_time\"]))\n", - "task_data[\"service_times\"] = service_location_data[\"required_service_time\"]\n", - "\n", - "# Dispatch the task data to the cuOpt server\n", - "response_set = requests.post(\n", - " url + \"set_task_data\", json=task_data\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "be4c7654", - "metadata": {}, - "source": [ - "### Set Solver Configuration" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fd528c7e", - "metadata": {}, - "outputs": [], - "source": [ - "# Set the time limit \n", - "# Set number of climbers that will try to search for an optimal routes in parallel\n", - "solver_settings = {\n", - " \"time_limit\": 0.05,\n", - " \"number_of_climbers\": 128,\n", - "}\n", - "# Dispatch the solver settings to the cuOpt server\n", - "response_set = requests.post(\n", - " url + \"set_solver_config\", json=solver_settings\n", - ")\n", - "assert response_set.status_code == 200" - ] - }, - { - "cell_type": "markdown", - "id": "c1994f6a", - "metadata": {}, - "source": [ - "### Get Optimized Routes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d243bdef", - "metadata": {}, - "outputs": [], - "source": [ - "# Solve the problem\n", - "solver_response = requests.get(url + \"get_optimized_routes\")\n", - "\n", - "assert solver_response.status_code == 200\n", - "\n", - "# Process the solver results\n", - "solver_resp = solver_response.json()[\"response\"][\"solver_response\"]\n", - "\n", - "if solver_resp[\"status\"] == 0:\n", - " print(\"Cost for the routing in distance: \", solver_resp[\"solution_cost\"])\n", - " print(\"Vehicle count to complete routing: \", solver_resp[\"num_vehicles\"])\n", - " utils.show_vehicle_routes(solver_resp, location_names)\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", solver_resp[\"status\"])" - ] - }, - { - "cell_type": "markdown", - "id": "68b89b87", - "metadata": {}, - "source": [ - "**Notice** that this solution leverages the fact that vehicle 1 is the only vehicle with the ability to perform both service type 1 and service type 2. In addition, vehicle 0 and vehicle 2 also serve the locations they are suited to service and minimize the time taken along these routes." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "617cdd8e", - "metadata": {}, - "outputs": [], - "source": [ - "vehicle_colors = [\"red\", \"green\", \"blue\"]\n", - "utils.map_vehicle_routes(location_coordinates_df, solver_resp, vehicle_colors).show()" - ] - }, - { - "cell_type": "markdown", - "id": "47eb4eed", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - }, - "vscode": { - "interpreter": { - "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/routing/python/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb deleted file mode 100644 index 478eda5..0000000 --- a/notebooks/routing/python/.ipynb_checkpoints/cost_matrix_creation-checkpoint.ipynb +++ /dev/null @@ -1,442 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "dfba40d6", - "metadata": {}, - "outputs": [], - "source": [ - "from cuopt import routing\n", - "from cuopt import distance_engine\n", - "import cudf\n", - "from scipy.spatial import distance\n", - "import numpy as np\n", - "import requests\n", - "import polyline\n", - "import folium\n", - "import json" - ] - }, - { - "cell_type": "markdown", - "id": "cd4716e9", - "metadata": {}, - "source": [ - "# Cost Matrix Calculation" - ] - }, - { - "cell_type": "markdown", - "id": "bdff7c68", - "metadata": {}, - "source": [ - "The cost matrix represents the user defined cost of traversing from one state/location in the optimization problem to another. This matrix is what cuOpt uses to assess the quality of a given solution as it seeks to minimize the total cost.\n", - "\n", - "The cost matrix is a square matrix of dimension equal to the number of locations in a given problem. In the example below we see an illustration of one such matrix." - ] - }, - { - "cell_type": "markdown", - "id": "991cad72", - "metadata": {}, - "source": [ - "\"cost_matrix.png" - ] - }, - { - "cell_type": "markdown", - "id": "ed85b5b1", - "metadata": {}, - "source": [ - "Additionally:\n", - "- The cost of going from a location to itself (e.g Cost(A,A)) is typically 0 \n", - "- Cost(A,B) need not be equal to Cost(B,A)" - ] - }, - { - "cell_type": "markdown", - "id": "2fe0488b", - "metadata": {}, - "source": [ - "## Simple Metric" - ] - }, - { - "cell_type": "markdown", - "id": "667af128", - "metadata": {}, - "source": [ - "In some simple cases a cost matrix can be generated from a list of points according to a user defined metric (e.g. Euclidean, Manhattan, etc.)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "01c4a7de", - "metadata": {}, - "outputs": [], - "source": [ - "points = cudf.DataFrame({\"x_coord\": [1, 1, 2, 3], \"y_coord\":[3, 1, 4, 1]})\n", - "points" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b52d43bb", - "metadata": {}, - "outputs": [], - "source": [ - "cost_matrix = distance.cdist(points.to_pandas().values, points.to_pandas().values, \"euclidean\")\n", - "print(f\"Simple Metric Cost Matrix:\\n\\n{cost_matrix}\")" - ] - }, - { - "cell_type": "markdown", - "id": "eb39ea0f", - "metadata": {}, - "source": [ - "## Weighted Waypoint Graph" - ] - }, - { - "cell_type": "markdown", - "id": "a72d76a3", - "metadata": {}, - "source": [ - "In cases where a unique environment needs to be described such as in the case of factories or warehouses it can be useful to define a waypoint graph that defines the cost of travel between adjacent accessible points in the environment.\n", - "\n", - "cuOpt has built in functionality to compute a cost matrix based on key target locations within a given waypoint graph. In the graph below we model 10 distinct waypoints. The target locations are 0, 4, 5, and 6." - ] - }, - { - "cell_type": "markdown", - "id": "d8afe1d6", - "metadata": {}, - "source": [ - "\"waypoint_graph.png" - ] - }, - { - "cell_type": "markdown", - "id": "fadc253b", - "metadata": {}, - "source": [ - "#### Graph Description\n", - "A simple description of each node, it's outgoing edges and corresponding weights" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f8bf07e3", - "metadata": {}, - "outputs": [], - "source": [ - "graph = {\n", - " 0:{\n", - " \"edges\":[2], \n", - " \"weights\":[2]},\n", - " 1:{\n", - " \"edges\":[2, 4], \n", - " \"weights\":[2, 2]},\n", - " 2:{\n", - " \"edges\":[0, 1, 3, 5], \n", - " \"weights\":[2, 2, 2, 2]},\n", - " 3:{\n", - " \"edges\":[2, 6], \n", - " \"weights\":[2, 2]},\n", - " 4:{\n", - " \"edges\":[1, 7], \n", - " \"weights\":[2, 1]},\n", - " 5:{\n", - " \"edges\":[2, 8], \n", - " \"weights\":[2, 1]},\n", - " 6:{\n", - " \"edges\":[3, 9], \n", - " \"weights\":[2, 1]},\n", - " 7:{\n", - " \"edges\":[4, 8], \n", - " \"weights\":[1, 2]},\n", - " 8:{\n", - " \"edges\":[5, 7, 9], \n", - " \"weights\":[1, 2, 2]},\n", - " 9:{\n", - " \"edges\":[6, 8], \n", - " \"weights\":[1, 2]}\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "54d51f36", - "metadata": {}, - "source": [ - "#### Convert to CSR\n", - "cuOpt requires that the graph be in compressed sparse row (CSR) format. Here we define a simple function that converts our graph to CSR." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ace5c271", - "metadata": {}, - "outputs": [], - "source": [ - "def convert_to_csr(graph):\n", - " num_nodes = len(graph)\n", - " \n", - " offsets = []\n", - " edges = []\n", - " weights = []\n", - " \n", - " cur_offset = 0\n", - " for node in range(num_nodes):\n", - " offsets.append(cur_offset)\n", - " cur_offset += len(graph[node][\"edges\"])\n", - " \n", - " edges = edges + graph[node][\"edges\"]\n", - " weights = weights + graph[node][\"weights\"]\n", - " \n", - " offsets.append(cur_offset)\n", - " \n", - " return np.array(offsets), np.array(edges), np.array(weights)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fed80f3a", - "metadata": {}, - "outputs": [], - "source": [ - "offsets, edges, weights = convert_to_csr(graph)\n", - "print(f\"offsets = {list(offsets)}\")\n", - "print(f\"edges = {list(edges)}\")\n", - "print(f\"weights = {list(weights)}\")" - ] - }, - { - "cell_type": "markdown", - "id": "17f18f56", - "metadata": {}, - "source": [ - "#### Define desired target locations and calculate the cost matrix " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "45ddc548", - "metadata": {}, - "outputs": [], - "source": [ - "target_locations = np.array([0, 4, 5, 6])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "475edfd9", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "waypoint_graph = distance_engine.WaypointMatrix(\n", - " offsets,\n", - " edges,\n", - " weights\n", - ")\n", - "cost_matrix = waypoint_graph.compute_cost_matrix(target_locations)\n", - "target_map = {k:v for k, v in enumerate(target_locations)}\n", - "\n", - "print(f\"Index <-> Waypoint Mapping: \\n{target_map}\\n\\n Waypoint Graph Cost Matrix: \\n{cost_matrix}\")" - ] - }, - { - "cell_type": "markdown", - "id": "cc7349c1", - "metadata": {}, - "source": [ - "## Map Based" - ] - }, - { - "cell_type": "markdown", - "id": "c1ea0b6e", - "metadata": {}, - "source": [ - "When dealing with problems in shipping and logistics, road distance and/or time is often used as a cost metric. In these cases there are a number of tools available to calculate drive distance and/or time. One such tool is the [Open Source Routing Machine](http://project-osrm.org/)(OSRM). In the below example we create a cost matrix using OSRM from a list of lat/lon coordinates." - ] - }, - { - "cell_type": "markdown", - "id": "0c6a374a", - "metadata": {}, - "source": [ - "#### Define Points of Interest" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3671b3e9", - "metadata": {}, - "outputs": [], - "source": [ - "lat_lon_coords = [\n", - " [33.698206, -117.851364],\n", - " [33.672260, -117.838925], \n", - " [33.721003, -117.864121], \n", - " [33.695563, -117.824500]\n", - "] " - ] - }, - { - "cell_type": "markdown", - "id": "2bf8d58a", - "metadata": {}, - "source": [ - "#### Create Distance Matrix via OSRM" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0b1e0a74", - "metadata": {}, - "outputs": [], - "source": [ - "locations=\"\"\n", - "for loc in lat_lon_coords:\n", - " locations = locations + \"{},{};\".format(loc[1], loc[0])\n", - "r = requests.get(\"http://router.project-osrm.org/table/v1/driving/\"+ locations[:-1])\n", - "\n", - "routes = json.loads(r.content)\n", - "cols = [str(i) for i in lat_lon_coords]\n", - "cost_matrix = cudf.DataFrame(routes['durations'], columns = cols, index= cols)\n", - "print(f\"Cost Matrix via OSRM:\\n\")\n", - "cost_matrix" - ] - }, - { - "cell_type": "markdown", - "id": "3f6e69fc", - "metadata": {}, - "source": [ - "#### Map Visualization" - ] - }, - { - "cell_type": "markdown", - "id": "d71a3260", - "metadata": {}, - "source": [ - "Visualization can be a helpful tool for understanding and communication. Here we demonstrate a sample visualization implementation showing the routes represented by the cost matrix above." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "15508e51", - "metadata": {}, - "outputs": [], - "source": [ - "def get_map(my_lat_longs):\n", - " m = folium.Map(location=[33.7, -117.83], #[52.52, 13.41],\n", - " zoom_start=13)\n", - " folium.Marker(\n", - " location=[my_lat_longs[0][0],my_lat_longs[0][1]] ,\n", - " icon=folium.Icon(icon='play', color='red')\n", - " ).add_to(m)\n", - " for loc in my_lat_longs[1:]:\n", - " folium.Marker(\n", - " location=[loc[0], loc[1]],\n", - " icon=folium.Icon(icon='stop', color='green')\n", - " ).add_to(m)\n", - " \n", - " for src_idx in range(len(lat_lon_coords)):\n", - " for dst_idx in range(len(lat_lon_coords)):\n", - " if src_idx == dst_idx:\n", - " break\n", - " source = lat_lon_coords[src_idx]\n", - " destination = lat_lon_coords[dst_idx]\n", - " loc = \"{},{};{},{}\".format(source[1], source[0], destination[1], destination[0])\n", - " url = \"http://router.project-osrm.org/route/v1/driving/\"\n", - " r = requests.get(url + loc) \n", - "\n", - " res = r.json() \n", - " routes = polyline.decode(res['routes'][0]['geometry'])\n", - "\n", - " folium.PolyLine(\n", - " routes,\n", - " weight=5,\n", - " color='blue',\n", - " opacity=0.6\n", - " ).add_to(m)\n", - "\n", - " return m\n", - "get_map(lat_lon_coords)" - ] - }, - { - "cell_type": "markdown", - "id": "2bac6512", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - }, - "vscode": { - "interpreter": { - "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/routing/python/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb deleted file mode 100644 index 759bb9e..0000000 --- a/notebooks/routing/python/.ipynb_checkpoints/cpdptw_intra-factory_transport-checkpoint.ipynb +++ /dev/null @@ -1,599 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "2cb694f7", - "metadata": {}, - "outputs": [], - "source": [ - "from cuopt import routing\n", - "from cuopt import distance_engine\n", - "import cudf\n", - "import numpy as np\n", - "import pandas as pd" - ] - }, - { - "cell_type": "markdown", - "id": "9326712e", - "metadata": {}, - "source": [ - "# Intra-factory Transport\n", - "## Capacitated Pickup and Delivery Problem with Time Windows" - ] - }, - { - "cell_type": "markdown", - "id": "382afbd9", - "metadata": {}, - "source": [ - "Factory automation allows companies to raise the quality and consistency of manufacturing processes while also allowing human workers to focus on safer, less repetitive tasks that have higher cognitive and creative demands.\n", - "\n", - "In this scenario we have a set of intra-factory transport orders to move products at various stages in the assembly process from one processing station to another. Each station represents a particular type of manufacturing process and a given product may need to visit each processing station more than once. Multiple autonomous mobile robots (AMRs) with a fixed capacity will execute pickup and delivery orders between target locations, all with corresponding time_windows." - ] - }, - { - "cell_type": "markdown", - "id": "c3bc4ad4", - "metadata": {}, - "source": [ - "### Problem Details:\n", - "- 4 Locations each with an associated demand\n", - " - 1 Start Location for AMRs\n", - "\n", - " - 3 Process Stations\n", - "\n", - "- 3 AMRs with associated capacity" - ] - }, - { - "cell_type": "markdown", - "id": "e6090764", - "metadata": {}, - "source": [ - "- Hours of operation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5d12f05d", - "metadata": {}, - "outputs": [], - "source": [ - "factory_open_time = 0\n", - "factory_close_time = 100" - ] - }, - { - "cell_type": "markdown", - "id": "e67a05ed", - "metadata": {}, - "source": [ - "![waypoint_graph.png not found](./notebook_utils/images/waypoint_graph.png \"Waypoint Graph\")" - ] - }, - { - "cell_type": "markdown", - "id": "d90ba90d", - "metadata": {}, - "source": [ - "### Waypoint Graph" - ] - }, - { - "cell_type": "markdown", - "id": "6febdb57", - "metadata": {}, - "source": [ - "#### Compressed Sparse Row (CSR) representation of above weighted waypoint graph.\n", - "For details on the CSR encoding of the above graph see the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2c824c99", - "metadata": {}, - "outputs": [], - "source": [ - "offsets = np.array([0, 1, 3, 7, 9, 11, 13, 15, 17, 20, 22])\n", - "edges = np.array([2, 2, 4, 0, 1, 3, 5, 2, 6, 1, 7, 2, 8, 3, 9, 4, 8, 5, 7, 9, 6, 8])\n", - "weights = np.array([2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2])" - ] - }, - { - "cell_type": "markdown", - "id": "dbfcfa33", - "metadata": {}, - "source": [ - "#### Select specific waypoints in the graph as target locations\n", - "In this case we would like the AMRs to begin from waypoint 0 and service locations 4, 5, and 6." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4e08f664", - "metadata": {}, - "outputs": [], - "source": [ - "target_locations = np.array([0, 4, 5, 6])" - ] - }, - { - "cell_type": "markdown", - "id": "7af883ad", - "metadata": {}, - "source": [ - "### Cost Matrix" - ] - }, - { - "cell_type": "markdown", - "id": "52bdc1d0", - "metadata": {}, - "source": [ - "#### Use cuOpt to calculate the corresponding cost matrix\n", - "Here we will be using a single cost matrix representing time." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9975bf1a", - "metadata": {}, - "outputs": [], - "source": [ - "waypoint_graph = distance_engine.WaypointMatrix(\n", - " offsets,\n", - " edges,\n", - " weights\n", - ")\n", - "time_matrix = waypoint_graph.compute_cost_matrix(target_locations)\n", - "target_map = {v:k for k, v in enumerate(target_locations)}\n", - "index_map = {k:v for k, v in enumerate(target_locations)}\n", - "print(f\"Waypoint graph node to time matrix index mapping \\n{target_map}\\n\")\n", - "print(time_matrix)" - ] - }, - { - "cell_type": "markdown", - "id": "4ed911ff", - "metadata": {}, - "source": [ - "### Transport Orders" - ] - }, - { - "cell_type": "markdown", - "id": "4265c03a", - "metadata": {}, - "source": [ - "Setup Transport Order Data" - ] - }, - { - "cell_type": "markdown", - "id": "d7d7536d", - "metadata": {}, - "source": [ - "The transport orders dictate the movement of parts from one area of the factory to another. In this example nodes 4, 5, and 6 represent the processing stations that parts must travel between and deliveries to node 0 represent the movement of parts off the factory floor." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "72b715c7", - "metadata": {}, - "outputs": [], - "source": [ - "transport_order_data = cudf.DataFrame({\n", - " \"pickup_location\": [4, 5, 6, 6, 5, 4],\n", - " \"delivery_location\": [5, 6, 0, 5, 4, 0],\n", - " \"order_demand\": [1, 1, 1, 1, 1, 1],\n", - " \"earliest_pickup\": [0, 0, 0, 0, 0, 0],\n", - " \"latest_pickup\": [10, 20, 30, 10, 20, 30],\n", - " \"pickup_service_time\": [2, 2, 2, 2, 2, 2],\n", - " \"earliest_delivery\": [0, 0, 0, 0, 0, 0],\n", - " \"latest_delivery\": [45, 45, 45, 45, 45, 45],\n", - " \"delivery_serivice_time\":[2, 2, 2, 2, 2, 2]\n", - "})\n", - "transport_order_data" - ] - }, - { - "cell_type": "markdown", - "id": "f2aaf28a", - "metadata": {}, - "source": [ - "### AMR Data" - ] - }, - { - "cell_type": "markdown", - "id": "a4e5e749", - "metadata": {}, - "source": [ - "Set up AMR fleet data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9e17e899", - "metadata": {}, - "outputs": [], - "source": [ - "n_robots = 2\n", - "robot_data = {\n", - " \"robot_ids\": [i for i in range(n_robots)],\n", - " \"carrying_capacity\":[2, 2]\n", - "}\n", - "robot_data = cudf.DataFrame(robot_data).set_index('robot_ids')\n", - "robot_data" - ] - }, - { - "cell_type": "markdown", - "id": "31db9053", - "metadata": {}, - "source": [ - "### cuOpt DataModel View" - ] - }, - { - "cell_type": "markdown", - "id": "731fdcbe", - "metadata": {}, - "source": [ - "Setup the routing.DataModel." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2e765325", - "metadata": {}, - "outputs": [], - "source": [ - "n_locations = len(time_matrix)\n", - "n_vehicles = len(robot_data)\n", - "\n", - "# a pickup order and a delivery order are distinct with additional pad for the depot with 0 demand\n", - "n_orders = len(transport_order_data) * 2\n", - "\n", - "data_model = routing.DataModel(n_locations, n_vehicles, n_orders)\n", - "data_model.add_cost_matrix(time_matrix)" - ] - }, - { - "cell_type": "markdown", - "id": "7f8f10e8", - "metadata": {}, - "source": [ - "\n", - "#### Set the per order demand\n", - "\n", - "From the perspective of the cuOpt solver_settings, each distinct transaction (pickup order or delivery order) are treated separately with demand for pickup denoted as positive and the corresponding delivery treated as negative demand." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c936b137", - "metadata": {}, - "outputs": [], - "source": [ - "# This is the number of parts that needs to be moved\n", - "raw_demand = transport_order_data[\"order_demand\"]\n", - "\n", - "# When dropping off parts we want to remove one unit of demand from the robot\n", - "drop_off_demand = raw_demand * -1\n", - "\n", - "# Create pickup and delivery demand\n", - "order_demand = cudf.concat([raw_demand, drop_off_demand], ignore_index=True)\n", - "\n", - "order_demand" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "87c2d9f8", - "metadata": {}, - "outputs": [], - "source": [ - "# add the capacity dimension\n", - "data_model.add_capacity_dimension(\"demand\", order_demand, robot_data['carrying_capacity'])" - ] - }, - { - "cell_type": "markdown", - "id": "48706e31", - "metadata": {}, - "source": [ - "#### Setting Order locations" - ] - }, - { - "cell_type": "markdown", - "id": "281bcd93", - "metadata": {}, - "source": [ - "set the order locations and pickup and delivery pairs." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1d325f4b", - "metadata": {}, - "outputs": [], - "source": [ - "pickup_order_locations = cudf.Series([target_map[loc] for loc in transport_order_data['pickup_location'].to_arrow().to_pylist()])\n", - "delivery_order_locations = cudf.Series([target_map[loc] for loc in transport_order_data['delivery_location'].to_arrow().to_pylist()])\n", - "order_locations = cudf.concat([pickup_order_locations, delivery_order_locations], ignore_index=True)\n", - "\n", - "print(order_locations)\n", - "\n", - "# add order locations\n", - "data_model.set_order_locations(order_locations)" - ] - }, - { - "cell_type": "markdown", - "id": "9389060b", - "metadata": {}, - "source": [ - "#### Mapping pickups to deliveries" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "064978ca", - "metadata": {}, - "outputs": [], - "source": [ - "# IMPORTANT NOTE : pickup and delivery pairs are indexed into the order locations array.\n", - "npair_orders = int(len(order_locations)/2)\n", - "pickup_orders = cudf.Series([i for i in range(npair_orders)])\n", - "delivery_orders = cudf.Series([i + npair_orders for i in range(npair_orders)])\n", - "# add pickup and delivery pairs.\n", - "data_model.set_pickup_delivery_pairs(pickup_orders, delivery_orders)" - ] - }, - { - "cell_type": "markdown", - "id": "f8b35777", - "metadata": {}, - "source": [ - "#### Precedence Constraints" - ] - }, - { - "cell_type": "markdown", - "id": "2c4b288e", - "metadata": {}, - "source": [ - "We have decided to model the deliveries to index 0 as removing items from the factory floor. In some cases it may be necessary which operations are complete prior to exiting. Here we set precedence constraints on specific deliveries which must occur before parts can exit the factory floor" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "116ce6ca", - "metadata": {}, - "outputs": [], - "source": [ - "data_model.add_order_precedence(8, cudf.Series([6,7]))\n", - "data_model.add_order_precedence(11, cudf.Series([9,10]))" - ] - }, - { - "cell_type": "markdown", - "id": "ef21d42d", - "metadata": {}, - "source": [ - "#### Time Windows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b3f328e3", - "metadata": {}, - "outputs": [], - "source": [ - "# create earliest times\n", - "vehicle_earliest_time = cudf.Series([factory_open_time] * n_vehicles)\n", - "order_time_window_earliest = cudf.concat([transport_order_data[\"earliest_pickup\"], transport_order_data[\"earliest_delivery\"]], ignore_index=True)\n", - "\n", - "# create latest times\n", - "vehicle_latest_time = cudf.Series([factory_close_time] * n_vehicles)\n", - "order_time_window_latest = cudf.concat([transport_order_data[\"latest_pickup\"], transport_order_data[\"latest_delivery\"]], ignore_index=True)\n", - "\n", - "# create service times\n", - "order_service_time = cudf.concat([transport_order_data[\"pickup_service_time\"], transport_order_data[\"delivery_serivice_time\"]], ignore_index=True)\n", - "\n", - "# add time window constraints\n", - "data_model.set_order_time_windows(order_time_window_earliest, order_time_window_latest)\n", - "data_model.set_order_service_times(order_service_time)\n", - "data_model.set_vehicle_time_windows(vehicle_earliest_time, vehicle_latest_time)" - ] - }, - { - "cell_type": "markdown", - "id": "b0d06888", - "metadata": {}, - "source": [ - "### CuOpt SolverSettings" - ] - }, - { - "cell_type": "markdown", - "id": "e3e08235", - "metadata": {}, - "source": [ - "Set up routing.SolverSettings." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a6babc11", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "solver_settings = routing.SolverSettings()\n", - "\n", - "# set number of climbers that will try to search for an optimal routes in parallel\n", - "solver_settings.set_number_of_climbers(128)\n", - "\n", - "# solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", - "solver_settings.set_time_limit(0.05)" - ] - }, - { - "cell_type": "markdown", - "id": "854e9519", - "metadata": {}, - "source": [ - "### Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "28a05ace", - "metadata": {}, - "outputs": [], - "source": [ - "routing_solution = routing.Solve(data_model, solver_settings)\n", - "if routing_solution.get_status() == 0:\n", - " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", - " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", - " print(routing_solution.route)\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" - ] - }, - { - "cell_type": "markdown", - "id": "4f6c5067", - "metadata": {}, - "source": [ - "#### Converting solution to waypoint graph" - ] - }, - { - "cell_type": "markdown", - "id": "1dbba138", - "metadata": {}, - "source": [ - "Because we maintained the mapping between cost matrix indices and locations in the waypoint graph we can now convert our solution to reference the nodes in the waypoint graph corresponding to the selected target locations." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e0d98709", - "metadata": {}, - "outputs": [], - "source": [ - "target_loc_route = [index_map[loc] for loc in routing_solution.route['location'].to_arrow().to_pylist()]\n", - "routing_solution.route['order_array_index'] = routing_solution.route['route']\n", - "routing_solution.route['route'] = target_loc_route\n", - "print(routing_solution.route)" - ] - }, - { - "cell_type": "markdown", - "id": "bba4accd", - "metadata": {}, - "source": [ - "#### Convert routes from target location based routes to waypoint level routes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c13cfbf3", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "unique_robot_ids = routing_solution.route['truck_id'].unique()\n", - "all_routes = routing_solution.get_route()\n", - "\n", - "for robot in unique_robot_ids.to_arrow().to_pylist():\n", - " route = all_routes[all_routes['truck_id']==robot]\n", - " unique_target_locs = all_routes[all_routes['truck_id']==robot]['route'].unique().to_numpy()\n", - " \n", - " waypoint_route = waypoint_graph.compute_waypoint_sequence(unique_target_locs, route)\n", - " print(f\"Target location level route for robot {robot}:\\n{all_routes[all_routes['truck_id']==robot]['route']}\\n\\n\")\n", - " print(f\"Waypoint level route for robot {robot}:\\n{waypoint_route}\\n\\n\")" - ] - }, - { - "cell_type": "markdown", - "id": "4cb94aa7", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "interpreter": { - "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/routing/python/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb deleted file mode 100644 index d893a1a..0000000 --- a/notebooks/routing/python/.ipynb_checkpoints/cvrp_daily_deliveries-checkpoint.ipynb +++ /dev/null @@ -1,368 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "e67c5c1d", - "metadata": {}, - "outputs": [], - "source": [ - "from cuopt import routing\n", - "from cuopt.routing import utils\n", - "import cudf\n", - "import pandas as pd" - ] - }, - { - "cell_type": "markdown", - "id": "ba50d71a", - "metadata": {}, - "source": [ - "# Daily Deliveries\n", - "## Capacitated Vehicle Routing Problem (CVRP)" - ] - }, - { - "cell_type": "markdown", - "id": "3ec34cd8", - "metadata": {}, - "source": [ - "Micro fulfillment centers allow retailers to move predictable, high volume products closer to the end consumer allowing for lower costs and shorter overall delivery times.\n", - "\n", - "In this scenario we have a number of same-day delivery orders that we would like to process for a given area from a given micro fulfillment center. We have the requisite number of delivery vehicles and enough time to deliver all packages over the course of a single day. Each delivery vehicle has a maximum capacity of orders it can carry and we are looking for the route assignment that minimizes the total distance driven by all vehicles." - ] - }, - { - "cell_type": "markdown", - "id": "4fc9ef31", - "metadata": {}, - "source": [ - "### Problem Details:\n", - "- 8 Locations each with an associated demand\n", - " - 1 MFC \n", - " - demand: [0]\n", - " - 7 Delivery Locations\n", - " - demand: [4, 4, 2, 2, 1, 2, 1]\n", - " \n", - "\n", - "- 3 Delivery vehicles each with an associated capacity\n", - " - 2 trucks\n", - " - capacity: [8, 8]\n", - " - 1 van\n", - " - capacity: [4]" - ] - }, - { - "cell_type": "markdown", - "id": "ed3c2736", - "metadata": {}, - "source": [ - "Below we visualize the delivery locations with respect to the MFC. The cost from all locations to all other locations (a cost matrix) will be required for optimization. To see an example of cost matrix generation from map data or a waypoint graph, refer to the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook. For the purpose of this simple example we will omit the cost matrix calculation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "01b12b30", - "metadata": {}, - "outputs": [], - "source": [ - "location_names = [ \"MFC\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\" ]\n", - "location_coordinates = [ [4, 4], [1, 3], [8, 1], [2, 1], [6, 7], [0, 2], [7, 6], [5, 3] ]\n", - "location_coordinates_df = pd.DataFrame(location_coordinates, columns=['xcord', 'ycord'], index=location_names)\n", - "utils.gen_plot(location_coordinates_df).show()" - ] - }, - { - "cell_type": "markdown", - "id": "42ba94fb", - "metadata": {}, - "source": [ - "### Cost Matrix" - ] - }, - { - "cell_type": "markdown", - "id": "82edd816", - "metadata": {}, - "source": [ - "The cost matrix dictates the cost of travel between locations of interest. The cost itself can be anything relevant to the user. In this case we simply use distance as our cost.\n", - "\n", - "Here is the cost(distance) matrix corresponding to the above locations:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bfa64aee", - "metadata": {}, - "outputs": [], - "source": [ - "distance_matrix = [\n", - " [0.0, 3.1, 5.0, 3.6, 3.6, 4.5, 3.6, 1.4],\n", - " [3.1, 0.0, 7.3, 2.2, 6.4, 1.4, 6.7, 4.0],\n", - " [5.0, 7.3, 0.0, 6.0, 6.3, 8.1, 5.1, 3.6],\n", - " [3.6, 2.2, 6.0, 0.0, 7.2, 2.2, 7.1, 3.6],\n", - " [3.6, 6.4, 6.3, 7.2, 0.0, 7.8, 1.4, 4.1],\n", - " [4.5, 1.4, 8.1, 2.2, 7.8, 0.0, 8.1, 5.1],\n", - " [3.6, 6.7, 5.1, 7.1, 1.4, 8.1, 0.0, 3.6],\n", - " [1.4, 4.0, 3.6, 3.6, 4.1, 5.1, 3.6, 0.0]\n", - "]\n", - "\n", - "# Create a dataframe of this matrix\n", - "distance_matrix = cudf.DataFrame(distance_matrix, \n", - " index=location_coordinates_df.index, \n", - " columns=location_coordinates_df.index)\n", - "distance_matrix" - ] - }, - { - "cell_type": "markdown", - "id": "161b18aa", - "metadata": {}, - "source": [ - " ### Demand and Capacity" - ] - }, - { - "cell_type": "markdown", - "id": "3b038198", - "metadata": {}, - "source": [ - "Set up the demand for each location and the capacity for each vehicle" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9cb56810", - "metadata": {}, - "outputs": [], - "source": [ - "# \"MFC\" \"A\" \"B\" \"C\" \"D\" \"E\" \"F\" \"G\"\n", - "location_demand = [ 0, 4, 4, 2, 2, 1, 2, 1]\n", - "\n", - "\n", - "# Vehicle 0 Vehicle 1 Vehicle 2\n", - "vehicle_capacity = [ 8, 8, 4 ]" - ] - }, - { - "cell_type": "markdown", - "id": "9312c733", - "metadata": {}, - "source": [ - "### cuOpt DataModel View" - ] - }, - { - "cell_type": "markdown", - "id": "dd9932de", - "metadata": {}, - "source": [ - "Set up the cuOpt DataModel." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a02105ba", - "metadata": {}, - "outputs": [], - "source": [ - "n_locations = len(location_demand)\n", - "n_vehicles = len(vehicle_capacity)\n", - "\n", - "data_model = routing.DataModel(n_locations, n_vehicles)\n", - "\n", - "# set the cost matrix\n", - "data_model.add_cost_matrix(distance_matrix)\n", - "\n", - "# add a capacity dimension for the deliveries\n", - "data_model.add_capacity_dimension(\n", - " \"deliveries\",\n", - " cudf.Series(location_demand),\n", - " cudf.Series(vehicle_capacity)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "bc3d347a", - "metadata": {}, - "source": [ - "### CuOpt SolverSettings" - ] - }, - { - "cell_type": "markdown", - "id": "32f0dafd", - "metadata": {}, - "source": [ - "Set up cuOpt SolverSettings." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bd600ffa", - "metadata": {}, - "outputs": [], - "source": [ - "solver_settings = routing.SolverSettings()\n", - "\n", - "# set number of climbers that will try to search for an optimal routes in parallel\n", - "solver_settings.set_number_of_climbers(128)\n", - "\n", - "# solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", - "solver_settings.set_time_limit(0.01)\n" - ] - }, - { - "cell_type": "markdown", - "id": "e6bf223a", - "metadata": {}, - "source": [ - "### Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b4141fd5", - "metadata": {}, - "outputs": [], - "source": [ - "routing_solution = routing.Solve(data_model, solver_settings)\n", - "if routing_solution.get_status() == 0:\n", - " print(\"Cost for the routing in distance: \", routing_solution.final_cost)\n", - " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", - " utils.show_vehicle_routes(routing_solution.route, location_names)\n", - " routing_solution.route\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8618e29a", - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "vehicle_colors = [\"red\", \"green\", \"blue\"]\n", - "utils.map_vehicle_routes(location_coordinates_df, routing_solution.route, vehicle_colors).show()" - ] - }, - { - "cell_type": "markdown", - "id": "37ccafc5", - "metadata": {}, - "source": [ - "### Additional Constraints \n", - "##### Minimum Vehicles" - ] - }, - { - "cell_type": "markdown", - "id": "c560394e", - "metadata": {}, - "source": [ - "cuOpt has found a solution that does not require all available vehicles because the combined capacity of the two larger vehicles (16) is equal to total location demand (16). In some cases, this is a great solution as it gives the option to save on the costs associated with additional vehicles. In other cases there is value to assigning all available resources. In the latter case we can require that cuOpt use all 3 available vehicles and re-solve the problem with this constraint." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fab4aebb", - "metadata": {}, - "outputs": [], - "source": [ - "data_model.set_min_vehicles(n_vehicles)\n", - "\n", - "routing_solution = routing.Solve(data_model, solver_settings)\n", - "if routing_solution.get_status() == 0:\n", - " print(\"Cost for the routing in distance: \", routing_solution.final_cost)\n", - " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", - " utils.show_vehicle_routes(routing_solution.route, location_names)\n", - " routing_solution.route\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cb069a02", - "metadata": {}, - "outputs": [], - "source": [ - "routing_solution.route" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "adb83802", - "metadata": {}, - "outputs": [], - "source": [ - "utils.map_vehicle_routes(location_coordinates_df, routing_solution.route, vehicle_colors).show()" - ] - }, - { - "cell_type": "markdown", - "id": "a25c9d04", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/routing/python/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb deleted file mode 100644 index d3935f3..0000000 --- a/notebooks/routing/python/.ipynb_checkpoints/cvrpstw_priority_routing-checkpoint.ipynb +++ /dev/null @@ -1,651 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "65705fab", - "metadata": {}, - "outputs": [], - "source": [ - "from cuopt import routing\n", - "from cuopt.routing import utils\n", - "import cudf\n", - "import pandas as pd" - ] - }, - { - "cell_type": "markdown", - "id": "fbb36201", - "metadata": {}, - "source": [ - "# Priority Routing\n", - "## Capacitated Vehicle Routing Problem with Soft Time Windows (CVRPSTW)" - ] - }, - { - "cell_type": "markdown", - "id": "bffb5f0c", - "metadata": {}, - "source": [ - "Loyalty (or Preferred) customer programs help companies to reward repeat customers and enhance their overall business offering. While the best possible customer service is always the goal, loyalty programs provide a mechanism for reinforcing the relationship with the customers that drive business revenue.\n", - "\n", - "In this scenario we have a set of deliveries with target time windows for delivery that do not represent a feasible solution given the delivery vehicles that are available. We would still like to deliver all the packages even if some of them are a little behind schedule. However, we would like to prioritize the deliveries of customers in our loyalty program to minimize the delay these customers experience.\n", - "\n", - "We also want to optimize according to a business defined cost objective that is a combination of business relevant metrics. To track time window constraints we will pass a time matrix as a constraint checking \"secondary matrix\".\n" - ] - }, - { - "cell_type": "markdown", - "id": "cb33a971", - "metadata": {}, - "source": [ - "### Problem Details:\n", - "- 8 Locations each with an associated demand\n", - " - 1 Distribution Center \n", - " - distribution center demand: [0]\n", - " - hours of operation: [0,24]\n", - " - 7 Service Locations\n", - " - demand for deliveries: [1, 1, 1, 1, 1, 1, 1]\n", - " - delivery time windows: [[9,10],[9,10],[9,10],[10,11],[10,11],[10,11],[9,10]]\n", - " - service location service times: [ 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25]\n", - " - loyalty program member: [1, 0, 0, 0, 1, 0, 1]\n", - "\n", - "- 3 Delivery vehicles each with an associated capacity\n", - " - 3 delivery vehicles\n", - " - capacity for deliveries: [3, 3, 3]" - ] - }, - { - "cell_type": "markdown", - "id": "baa93a42", - "metadata": {}, - "source": [ - "Below we visualize the delivery locations with respect to the distribution center. The cost from all locations to all other locations (a cost matrix) will be required for optimization. To see an example of cost matrix generation from map data or a waypoint graph, refer to the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook. For the purpose of this simple example we will omit the cost matrix calculation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e747d30d", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "location_names = [ \"DC\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\" ]\n", - "location_coordinates = [ [4, 4], [1, 3], [8, 1], [2, 1], [6, 7], [0, 2], [7, 6], [5, 3] ]\n", - "location_coordinates_df = pd.DataFrame(location_coordinates, columns=['xcord', 'ycord'], index=location_names)\n", - "utils.gen_plot(location_coordinates_df).show()" - ] - }, - { - "cell_type": "markdown", - "id": "1307fc95", - "metadata": {}, - "source": [ - "### Cost Matrix : Primary" - ] - }, - { - "cell_type": "markdown", - "id": "27525bf2", - "metadata": {}, - "source": [ - "The cost matrix dictates the cost of travel between locations of interest. The cost itself can be anything relevant to the user. In this case we are using a business defined cost objective as a primary cost matrix and a secondary time matrix to verify our time based constraints. \n", - "\n", - "Here is the cost(business metric) matrix corresponding to the locations above:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ef01144e", - "metadata": {}, - "outputs": [], - "source": [ - "business_metric_cost_matrix = [\n", - " [0.0, 3.1, 5.0, 3.6, 3.6, 4.5, 3.6, 1.4],\n", - " [3.1, 0.0, 7.3, 2.2, 6.4, 1.4, 6.7, 4.0],\n", - " [5.0, 7.3, 0.0, 6.0, 6.3, 8.1, 5.1, 3.6],\n", - " [3.6, 2.2, 6.0, 0.0, 7.2, 2.2, 7.1, 3.6],\n", - " [3.6, 6.4, 6.3, 7.2, 0.0, 7.8, 1.4, 4.1],\n", - " [4.5, 1.4, 8.1, 2.2, 7.8, 0.0, 8.1, 5.1],\n", - " [3.6, 6.7, 5.1, 7.1, 1.4, 8.1, 0.0, 3.6],\n", - " [1.4, 4.0, 3.6, 3.6, 4.1, 5.1, 3.6, 0.0]\n", - "]\n", - "\n", - "# Create a dataframe of this matrix\n", - "business_metric_cost_matrix = cudf.DataFrame(business_metric_cost_matrix, \n", - " index=location_coordinates_df.index, \n", - " columns=location_coordinates_df.index)\n", - "business_metric_cost_matrix" - ] - }, - { - "cell_type": "markdown", - "id": "9671d772", - "metadata": {}, - "source": [ - "### Cost Matrix : Secondary" - ] - }, - { - "cell_type": "markdown", - "id": "d226f72b", - "metadata": {}, - "source": [ - "Here is the constraint checking (time) secondary matrix:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "145a2560", - "metadata": {}, - "outputs": [], - "source": [ - "constraint_checking_time_matrix = [\n", - " [0.00, 0.39, 0.63, 0.45, 0.45, 0.55, 0.45, 0.18 ],\n", - " [0.39, 0.00, 0.90, 0.28, 0.80, 0.18, 0.84, 0.50 ],\n", - " [0.63, 0.90, 0.00, 0.75, 0.79, 1.00, 0.64, 0.45 ],\n", - " [0.45, 0.28, 0.75, 0.00, 0.90, 0.28, 0.88, 0.45 ],\n", - " [0.45, 0.80, 0.79, 0.90, 0.00, 0.96, 0.18, 0.51 ],\n", - " [0.55, 0.18, 1.00, 0.28, 0.96, 0.00, 1.00, 0.64 ],\n", - " [0.45, 0.84, 0.64, 0.88, 0.18, 1.00, 0.00, 0.45 ],\n", - " [0.18, 0.50, 0.45, 0.45, 0.51, 0.64, 0.45, 0.00 ]\n", - "]\n", - "\n", - "# Create a dataframe of this matrix\n", - "constraint_checking_time_matrix = cudf.DataFrame(constraint_checking_time_matrix, \n", - " index=location_coordinates_df.index, \n", - " columns=location_coordinates_df.index)\n", - "constraint_checking_time_matrix" - ] - }, - { - "cell_type": "markdown", - "id": "cbd80cd3", - "metadata": {}, - "source": [ - "### Deliveries" - ] - }, - { - "cell_type": "markdown", - "id": "54d6af91", - "metadata": {}, - "source": [ - "Setup the delivery data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "28e33246", - "metadata": {}, - "outputs": [], - "source": [ - "delivery_location_data = {\n", - " \"location_ids\": location_names,\n", - " \"delivery_demand\": [0, 1, 1, 1, 1, 1, 1, 1 ],\n", - " \"location_earliest_time\": [5, 9, 9, 9, 10, 10, 10, 9 ],\n", - " \"location_latest_time\": [20, 10, 10, 10, 11, 11, 11, 10],\n", - " \"required_service_time\": [0, 1, 1, 1, 1, 1, 1, 1 ],\n", - " \"loyalty_member\": [0, 0, 1, 0, 1, 0, 1, 0 ]\n", - "}\n", - "delivery_location_data = cudf.DataFrame(delivery_location_data).set_index('location_ids')\n", - "delivery_location_data" - ] - }, - { - "cell_type": "markdown", - "id": "6bace17e", - "metadata": {}, - "source": [ - "### Vehicles" - ] - }, - { - "cell_type": "markdown", - "id": "49cb13c9", - "metadata": {}, - "source": [ - "Setup delivery vehicle data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f4931236", - "metadata": {}, - "outputs": [], - "source": [ - "n_vehicles = 3\n", - "vehicle_data = {\n", - " \"vehicle_ids\": [i for i in range(n_vehicles)],\n", - " \"delivery_capacity\":[3, 3, 3]\n", - "}\n", - "vehicle_data = cudf.DataFrame(vehicle_data).set_index('vehicle_ids')\n", - "vehicle_data" - ] - }, - { - "cell_type": "markdown", - "id": "e5738d68", - "metadata": {}, - "source": [ - "### cuOpt DataModel View" - ] - }, - { - "cell_type": "markdown", - "id": "fa84f4d7", - "metadata": {}, - "source": [ - "Setup the routing.DataModel." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c0bcf99c", - "metadata": {}, - "outputs": [], - "source": [ - "n_locations = len(delivery_location_data)\n", - "n_vehicles = len(vehicle_data)\n", - "\n", - "data_model = routing.DataModel(n_locations, n_vehicles)\n", - "\n", - "# set the primary cost matrix\n", - "data_model.add_cost_matrix(business_metric_cost_matrix)\n", - "\n", - "# set the secondary constraint checking time matrix\n", - "data_model.add_transit_time_matrix(constraint_checking_time_matrix)\n", - "\n", - "# add a capacity dimension for deliveries\n", - "data_model.add_capacity_dimension(\n", - " \"deliveries\",\n", - " cudf.Series(delivery_location_data[\"delivery_demand\"]),\n", - " cudf.Series(vehicle_data[\"delivery_capacity\"])\n", - ")\n", - "\n", - "# add time windows and service time for the locations\n", - "data_model.set_order_time_windows(\n", - " delivery_location_data[\"location_earliest_time\"],\n", - " delivery_location_data[\"location_latest_time\"]\n", - ")\n", - "data_model.set_order_service_times(\n", - " delivery_location_data[\"required_service_time\"]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "82f4dd75", - "metadata": {}, - "source": [ - "### CuOpt SolverSettings" - ] - }, - { - "cell_type": "markdown", - "id": "a606dd07", - "metadata": {}, - "source": [ - "Set up routing.SolverSettings." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d0f811c5", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "solver_settings = routing.SolverSettings()\n", - "\n", - "# set number of climbers that will try to search for an optimal routes in parallel\n", - "solver_settings.set_number_of_climbers(128)\n", - "\n", - "# solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", - "solver_settings.set_time_limit(0.05)" - ] - }, - { - "cell_type": "markdown", - "id": "1d2997dd", - "metadata": {}, - "source": [ - "### Attempted Solution" - ] - }, - { - "cell_type": "markdown", - "id": "284aaabd", - "metadata": {}, - "source": [ - "We can attempt to solve this problem as stated but as previously discussed it is not feasible within the specified target time windows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a63b509b", - "metadata": {}, - "outputs": [], - "source": [ - "routing_solution = routing.Solve(data_model, solver_settings)\n", - "if routing_solution.get_status() == 0:\n", - " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", - " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", - " utils.show_vehicle_routes(routing_solution.route, location_names)\n", - " routing_solution.route\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" - ] - }, - { - "cell_type": "markdown", - "id": "3f863e49", - "metadata": {}, - "source": [ - "cuOpt is unable to find a feasible solution. As previously discussed we would like to allow the deliveries to exceed the latest time windows by using soft time windows" - ] - }, - { - "cell_type": "markdown", - "id": "900028af", - "metadata": {}, - "source": [ - "### Initial Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8f6e8a2c", - "metadata": {}, - "outputs": [], - "source": [ - "solver_settings.set_solution_scope(routing.Scope.SOFT_TW)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "098c0061", - "metadata": {}, - "outputs": [], - "source": [ - "routing_solution = routing.Solve(data_model, solver_settings)\n", - "if routing_solution.get_status() == 0:\n", - " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", - " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", - " utils.show_vehicle_routes(routing_solution.route, location_names)\n", - " routing_solution.route\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" - ] - }, - { - "cell_type": "markdown", - "id": "42445bf0", - "metadata": {}, - "source": [ - "This works but if we look at the violations of latest arrival times we can see that some of our loyalty program customers are experiencing significant delivery delays. \n", - "**Note** Positive value in the delay column represents how late the delivery was compared to the latest target time." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d1845a27", - "metadata": {}, - "outputs": [], - "source": [ - "solution_data = routing_solution.route\n", - "solution_data['route'] = [location_names[i] for i in routing_solution.route['route'].to_arrow().to_pylist()]\n", - "solution_data = routing_solution.route.set_index('route')\n", - "solution_data = solution_data.join(delivery_location_data[\"location_latest_time\"])\n", - "solution_data = solution_data.join(delivery_location_data[\"loyalty_member\"])\n", - "solution_data[\"delay\"] = solution_data[\"arrival_stamp\"] - solution_data[\"location_latest_time\"]\n", - "solution_data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "984c2b7c", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "priority_delay = solution_data[(solution_data['delay'] > 0) & (solution_data['loyalty_member'] == 1)]\n", - "total_priority_delay = priority_delay['delay'].sum()\n", - "print(f\"Total delay of priority orders is {total_priority_delay}\")" - ] - }, - { - "cell_type": "markdown", - "id": "4672f2be", - "metadata": {}, - "source": [ - "### Improved Solution" - ] - }, - { - "cell_type": "markdown", - "id": "ccb8970b", - "metadata": {}, - "source": [ - "##### Introducing Penalty" - ] - }, - { - "cell_type": "markdown", - "id": "e81c2d40", - "metadata": {}, - "source": [ - "We can address this issue by assessing a large penalty for delivering late to loyalty members. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ed8275da", - "metadata": {}, - "outputs": [], - "source": [ - "delivery_location_data['penalty'] = delivery_location_data[\"loyalty_member\"]*100\n", - "delivery_location_data" - ] - }, - { - "cell_type": "markdown", - "id": "2f58006b", - "metadata": {}, - "source": [ - "Recreate the DataModel, adding penalty to the time windows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "100986d9", - "metadata": {}, - "outputs": [], - "source": [ - "data_model_with_penalty = routing.DataModel(n_locations, n_vehicles)\n", - "\n", - "# set the primary cost matrix\n", - "data_model_with_penalty.add_cost_matrix(business_metric_cost_matrix)\n", - "\n", - "# set the secondary constraint checking time matrix\n", - "data_model_with_penalty.add_transit_time_matrix(constraint_checking_time_matrix)\n", - "\n", - "# add a capacity dimension for deliveries\n", - "data_model_with_penalty.add_capacity_dimension(\n", - " \"deliveries\",\n", - " cudf.Series(delivery_location_data[\"delivery_demand\"]),\n", - " cudf.Series(vehicle_data[\"delivery_capacity\"])\n", - ")\n", - "\n", - "# add time windows and service time and penalty for the locations\n", - "data_model_with_penalty.set_order_time_windows(\n", - " delivery_location_data[\"location_earliest_time\"],\n", - " delivery_location_data[\"location_latest_time\"]\n", - ")\n", - "data_model.set_order_service_times(\n", - " delivery_location_data[\"required_service_time\"]\n", - ")\n", - "data_model.set_order_penalties(\n", - " delivery_location_data[\"penalty\"]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "e6e1eda1", - "metadata": {}, - "source": [ - "Setup another solver_settings instance" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fff016f4", - "metadata": {}, - "outputs": [], - "source": [ - "solver_settings = routing.SolverSettings()\n", - "\n", - "# set number of climbers that will try to search for an optimal routes in parallel\n", - "solver_settings.set_number_of_climbers(128)\n", - "\n", - "# solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", - "solver_settings.set_time_limit(0.05)\n", - "\n", - "# allow for soft time windows\n", - "solver_settings.set_solution_scope(routing.Scope.SOFT_TW)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "35d19881", - "metadata": {}, - "outputs": [], - "source": [ - "routing_solution = routing.Solve(data_model_with_penalty, solver_settings)\n", - "if routing_solution.get_status() == 0:\n", - " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", - " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", - " utils.show_vehicle_routes(routing_solution.route, location_names)\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" - ] - }, - { - "cell_type": "markdown", - "id": "3ce0e0da", - "metadata": {}, - "source": [ - "**Note**: The new solution decreases the delay seen by priority customers as seen below" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a9c2d01b", - "metadata": {}, - "outputs": [], - "source": [ - "solution_data_priority = routing_solution.route\n", - "solution_data_priority['route'] = [location_names[i] for i in routing_solution.route['route'].to_arrow().to_pylist()]\n", - "solution_data_priority = routing_solution.route.set_index('route')\n", - "solution_data_priority = solution_data_priority.join(delivery_location_data[\"location_latest_time\"])\n", - "solution_data_priority = solution_data_priority.join(delivery_location_data[\"loyalty_member\"])\n", - "solution_data_priority[\"delay\"] = solution_data_priority[\"arrival_stamp\"] - solution_data_priority[\"location_latest_time\"]\n", - "solution_data_priority" - ] - }, - { - "cell_type": "markdown", - "id": "7c9d265a", - "metadata": {}, - "source": [ - "### Reduced Delay for Priority Orders" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8887d9a9", - "metadata": {}, - "outputs": [], - "source": [ - "priority_delay_penalty = solution_data_priority[(solution_data_priority['delay'] > 0) & (solution_data_priority['loyalty_member'] == 1)]\n", - "total_priority_delay_penalty = priority_delay_penalty['delay'].sum()\n", - "print(f\"Total delay of priority orders is now {total_priority_delay_penalty}\")\n", - "print(f\"Reduced the total delay to loyalty customers by {total_priority_delay - total_priority_delay_penalty}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "30589911", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/routing/python/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb deleted file mode 100644 index 32d009c..0000000 --- a/notebooks/routing/python/.ipynb_checkpoints/cvrptw_benchmark_gehring_homberger-checkpoint.ipynb +++ /dev/null @@ -1,380 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a05e01aa", - "metadata": {}, - "source": [ - "
\n", - "\n", - "# Skip notebook test\n", - "\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "afc57ab3", - "metadata": {}, - "outputs": [], - "source": [ - "import cudf\n", - "from cuopt import routing\n", - "import numpy as np\n", - "import os\n", - "from cuopt.routing import utils\n", - "from scipy.spatial import distance" - ] - }, - { - "cell_type": "markdown", - "id": "0d500386", - "metadata": {}, - "source": [ - "# Benchmark Gehring & Homberger\n", - "## Capacitated Vehicle Routing Problem with Time Windows (CVRPTW)" - ] - }, - { - "cell_type": "markdown", - "id": "30e63d74", - "metadata": {}, - "source": [ - "While other notebooks such as [cvrptw_service_team_routing.ipynb](cvrptw_service_team_routing.ipynb) focus on the cuOpt API and high level problem modeling, here we focus on performance.\n", - "\n", - "cuOpt offers a unique benefit over other solver_settingss, specifically, time to solution. In addition to achieving world class accuracy, cuOpt also produces these solutions in a time frame that allows for re-optimization in dynamic environments and rapid iteration over possible problem configurations.\n", - "\n", - "Here we are demonstrating this performance on a large popular academic [dataset by Gehing & Homberger](https://www.sintef.no/projectweb/top/vrptw/homberger-benchmark/). These problems are well studied and used as the basis for comparison for VRP research and product offerings. The particular instance we will test with is from the group of largest (1000 location) problems. Each problem instance has an associated best known solution, the one we will measure against is shown below" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "65860d5d", - "metadata": {}, - "outputs": [], - "source": [ - "homberger_1000_file = 'notebook_utils/data/C1_10_1.TXT'\n", - "\n", - "best_known_solution = {\n", - " \"n_vehicles\": 100,\n", - " \"cost\": 42478.95\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "af25d3f9", - "metadata": {}, - "source": [ - "### Problem Data\n", - "The data for this problem instance are provided via text file. cuOpt has a utility function available specifically for the Gehring & Homberger benchmark which converts the problem into the components required by cuOpt." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fd6089b5", - "metadata": {}, - "outputs": [], - "source": [ - "orders, vehicle_capacity, n_vehicles = utils.create_from_file(homberger_1000_file)\n", - "\n", - "print(\"Number of locations : \", orders[\"demand\"].shape[0]-1)\n", - "print(\"Number of vehicles available : \", n_vehicles)\n", - "print(\"Capacity of each vehicle : \", vehicle_capacity)\n", - "print(\"\\nInitial Orders information\")\n", - "print(orders)" - ] - }, - { - "cell_type": "markdown", - "id": "ba4eb34d", - "metadata": {}, - "source": [ - "### Cost Matrix" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0cc3ced9", - "metadata": {}, - "outputs": [], - "source": [ - "coords = list(zip(orders['xcord'].to_arrow().to_pylist(),\n", - " orders['ycord'].to_arrow().to_pylist()))\n", - "\n", - "cost_matrix = cudf.DataFrame(distance.cdist(coords, coords, 'euclidean')).astype(np.float32)\n", - "print(f\"Shape of cost matrix: {cost_matrix.shape}\")" - ] - }, - { - "cell_type": "markdown", - "id": "1262221d", - "metadata": {}, - "source": [ - "### cuOpt DataModel View" - ] - }, - { - "cell_type": "markdown", - "id": "892ad25f", - "metadata": {}, - "source": [ - "Setup the routing.DataModel." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "017f7783", - "metadata": {}, - "outputs": [], - "source": [ - "n_locations = len(cost_matrix)\n", - "\n", - "data_model = routing.DataModel(n_locations, n_vehicles)\n", - "data_model.add_cost_matrix(cost_matrix)\n", - "\n", - "capacity = cudf.Series([vehicle_capacity] * n_vehicles)\n", - "data_model.add_capacity_dimension(\"demand\", orders['demand'], capacity)\n", - "\n", - "data_model.set_order_time_windows(orders['earliest_time'], orders['latest_time'])\n", - "data_model.set_order_service_times(orders['service_time'])" - ] - }, - { - "cell_type": "markdown", - "id": "e4f9a455", - "metadata": {}, - "source": [ - "### CuOpt SolverSettings" - ] - }, - { - "cell_type": "markdown", - "id": "0097b2a6", - "metadata": {}, - "source": [ - "Set up routing.SolverSettings." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3eddb994", - "metadata": {}, - "outputs": [], - "source": [ - "solver_settings = routing.SolverSettings()\n", - "\n", - "# set number of climbers that will try to search for an optimal routes in parallel\n", - "solver_settings.set_number_of_climbers(2048)" - ] - }, - { - "cell_type": "markdown", - "id": "50e77fb4", - "metadata": {}, - "source": [ - "### Solution" - ] - }, - { - "cell_type": "markdown", - "id": "dd5fc419", - "metadata": {}, - "source": [ - "Here we will examine the quality of the solution we increase the time budget provided to cuOpt" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f0a0934d", - "metadata": {}, - "outputs": [], - "source": [ - "def solve_problem(data_model, solver_settings, problem_size):\n", - " routing_solution = routing.Solve(data_model, solver_settings)\n", - " if routing_solution.get_status() == 0:\n", - " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", - " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", - " utils.show_vehicle_routes(routing_solution.route, [\"Depot\"]+[str(i) for i in range(1, problem_size+1)])\n", - " else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())\n", - " \n", - " return(routing_solution.vehicle_count, routing_solution.final_cost)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b8880748", - "metadata": {}, - "outputs": [], - "source": [ - "def solution_eval(vehicles, cost, best_known_solution):\n", - " \n", - " print(f\"- cuOpt provides a solution using {vehicles} vehicles\")\n", - " print(f\"- This represents {vehicles - best_known_solution['n_vehicles']} more than the best known solution\")\n", - " print(f\"- Vehicle Percent Difference {(vehicles/best_known_solution['n_vehicles'] - 1)*100}% \\n\\n\")\n", - " print(f\"- In addition cuOpt provides a solution cost of {cost}\") \n", - " print(f\"- Best known solution cost is {best_known_solution['cost']}\")\n", - " print(f\"- Cost Percent Difference {(cost/best_known_solution['cost'] - 1)*100}%\")" - ] - }, - { - "cell_type": "markdown", - "id": "0941d56f", - "metadata": {}, - "source": [ - "**1 Second Time Limit**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "70f12ffa", - "metadata": {}, - "outputs": [], - "source": [ - "solver_settings.set_time_limit(1)\n", - "vehicles, cost = solve_problem(data_model, solver_settings, len(cost_matrix))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a2453b1b", - "metadata": {}, - "outputs": [], - "source": [ - "# Evaluation:\n", - "solution_eval(vehicles, cost, best_known_solution)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bed97098", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "04ef0c21", - "metadata": {}, - "source": [ - "**10 Second Time Limit**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3934d8de", - "metadata": {}, - "outputs": [], - "source": [ - "solver_settings.set_time_limit(10)\n", - "vehicles, cost = solve_problem(data_model, solver_settings, len(cost_matrix))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7ab5e6d1", - "metadata": {}, - "outputs": [], - "source": [ - "# Evaluation:\n", - "solution_eval(vehicles, cost, best_known_solution)" - ] - }, - { - "cell_type": "markdown", - "id": "a9a1b855", - "metadata": {}, - "source": [ - "**20 Second Time Limit**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e0c38643", - "metadata": {}, - "outputs": [], - "source": [ - "solver_settings.set_time_limit(20)\n", - "vehicles, cost = solve_problem(data_model, solver_settings, len(cost_matrix))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ff80118b", - "metadata": {}, - "outputs": [], - "source": [ - "# Evaluation:\n", - "solution_eval(vehicles, cost, best_known_solution)" - ] - }, - { - "cell_type": "markdown", - "id": "251e8e38", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/routing/python/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb deleted file mode 100644 index 886b2d1..0000000 --- a/notebooks/routing/python/.ipynb_checkpoints/cvrptw_service_team_routing-checkpoint.ipynb +++ /dev/null @@ -1,398 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "b2cba47f", - "metadata": {}, - "outputs": [], - "source": [ - "from cuopt import routing\n", - "from cuopt.routing import utils\n", - "import cudf\n", - "import pandas as pd" - ] - }, - { - "cell_type": "markdown", - "id": "371f38f1", - "metadata": {}, - "source": [ - "# Service Team Routing\n", - "## Capacitated Vehicle Routing Problem with Time Windows (CVRPTW)" - ] - }, - { - "cell_type": "markdown", - "id": "25e90f86", - "metadata": {}, - "source": [ - "The ability of service providers to set service time windows allows for easier and more dependable coordination between the service provider and their customers, while increasing overall customer satisfaction.\n", - "\n", - "In this scenario we have a number of service order locations with associated time windows and service times (time on-site to complete service). Each technician has an associated availability, ability to complete certain types of service, and a maximum number of service appointments per day." - ] - }, - { - "cell_type": "markdown", - "id": "63093d54", - "metadata": {}, - "source": [ - "### Problem Details:\n", - "- 8 Locations each with an associated demand\n", - " - 1 Headquarters \n", - " - service type 1 demand: [0]\n", - " - service type 2 demand: [1]\n", - " - headquarters hours of operation: [5,20]\n", - " - 7 Service Locations\n", - " - service type 1 demand: [1, 1, 1, 0, 0, 0, 0]\n", - " - service type 2 demand: [0, 0, 1, 1, 1, 1, 1]\n", - " - service locations time windows: [[9,12],[9,12],[11,14],[13,16],[13,16],[13,16],[13,16]]\n", - " - service location service times: [ 1, 1, 1.5, 0.5, 0.5, 0.5]\n", - "\n", - "- 3 Delivery vehicles each with an associated capacity\n", - " - 3 service technicians\n", - " - capacity for service type 1: [2, 1, 0]\n", - " - capacity for service type 2: [0, 1, 4]\n", - " - technician availability [[9,17], [12,15], [9,17]]\n" - ] - }, - { - "cell_type": "markdown", - "id": "baeeee39", - "metadata": {}, - "source": [ - "Below we visualize the service locations with respect to the service company headquarters. The cost from all locations to all other locations (a cost matrix) will be required for optimization. To see an example of cost matrix generation from map data or a waypoint graph, refer to the [cost_matrix_creation.ipynb](cost_matrix_creation.ipynb) notebook. For the purpose of this simple example we will omit the cost matrix calculation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c8a0847d", - "metadata": {}, - "outputs": [], - "source": [ - "location_names = [ \"Headquarters\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\" ]\n", - "location_coordinates = [ [4, 4], [1, 3], [8, 1], [2, 1], [6, 7], [0, 2], [7, 6], [5, 3] ]\n", - "location_coordinates_df = pd.DataFrame(location_coordinates, columns=['xcord', 'ycord'], index=location_names)\n", - "utils.gen_plot(location_coordinates_df).show()" - ] - }, - { - "cell_type": "markdown", - "id": "ff1d68f2", - "metadata": {}, - "source": [ - "### Cost Matrix" - ] - }, - { - "cell_type": "markdown", - "id": "210a57e9", - "metadata": {}, - "source": [ - "The cost matrix dictates the cost of travel between locations of interest. The cost itself can be anything relevant to the user. In this case we are constraining time window constraints. When constraining time windows for locations or vehicles it is assumed (if only a single cost matrix is provided) that it represents time. \n", - "\n", - "Here is the cost(time) matrix corresponding to the locations above:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1054c7e3", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "time_matrix = [\n", - " [0.00, 0.31, 0.50, 0.36, 0.36, 0.44, 0.36, 0.14],\n", - " [0.31, 0.00, 0.72, 0.22, 0.64, 0.14, 0.67, 0.40],\n", - " [0.50, 0.72, 0.00, 0.60, 0.63, 0.80, 0.51, 0.36],\n", - " [0.36, 0.22, 0.60, 0.00, 0.72, 0.22, 0.70, 0.36],\n", - " [0.36, 0.64, 0.63, 0.72, 0.00, 0.77, 0.14, 0.41],\n", - " [0.44, 0.14, 0.80, 0.22, 0.77, 0.00, 0.80, 0.51],\n", - " [0.36, 0.67, 0.51, 0.70, 0.14, 0.80, 0.00, 0.36],\n", - " [0.14, 0.40, 0.36, 0.36, 0.41, 0.51, 0.36, 0.00]\n", - "]\n", - "\n", - "# Create a dataframe of this matrix\n", - "time_matrix = cudf.DataFrame(time_matrix, \n", - " index=location_coordinates_df.index, \n", - " columns=location_coordinates_df.index)\n", - "time_matrix" - ] - }, - { - "cell_type": "markdown", - "id": "3397b254", - "metadata": {}, - "source": [ - "### Service Locations" - ] - }, - { - "cell_type": "markdown", - "id": "ce7b7af7", - "metadata": {}, - "source": [ - "Setup the service location data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dc98afdf", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "service_location_data = {\n", - " \"location_ids\": location_names,\n", - " \"service_type1_demand\": [0, 1, 1, 1, 0, 0, 0, 0],\n", - " \"service_type2_demand\": [0, 0, 0, 1, 1, 1, 1, 1],\n", - " \"location_earliest_time\": [5, 9, 9, 11, 13, 13, 13, 13],\n", - " \"location_latest_time\": [20, 12, 12, 14, 16, 16, 16,16],\n", - " \"required_service_time\": [0, 1, 1, 1.5, 0.5, 0.5, 0.5, 0.5]\n", - "}\n", - "service_location_data = cudf.DataFrame(service_location_data).set_index('location_ids')\n", - "service_location_data" - ] - }, - { - "cell_type": "markdown", - "id": "cd27971f", - "metadata": {}, - "source": [ - "### Vehicles" - ] - }, - { - "cell_type": "markdown", - "id": "d66df281", - "metadata": {}, - "source": [ - "Setup vehicle/technician data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "248e1add", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "n_vehicles = 3\n", - "vehicle_data = {\n", - " \"vehicle_ids\": [i for i in range(n_vehicles)],\n", - " \"capacity_service_type1\":[2, 1, 0],\n", - " \"capacity_service_type2\":[0, 1, 4],\n", - " \"vehicle_availability_earliest\":[9, 11, 9],\n", - " \"vehicle_availability_latest\":[17, 15, 17]\n", - "}\n", - "vehicle_data = cudf.DataFrame(vehicle_data).set_index('vehicle_ids')\n", - "vehicle_data" - ] - }, - { - "cell_type": "markdown", - "id": "8c70e5b3", - "metadata": {}, - "source": [ - "### cuOpt DataModel View" - ] - }, - { - "cell_type": "markdown", - "id": "96c89955", - "metadata": {}, - "source": [ - "Setup the routing.DataModel." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2b28f01c", - "metadata": {}, - "outputs": [], - "source": [ - "n_locations = len(service_location_data)\n", - "n_vehicles = len(vehicle_data)\n", - "\n", - "data_model = routing.DataModel(n_locations, n_vehicles)\n", - "\n", - "# set the cost matrix\n", - "data_model.add_cost_matrix(time_matrix)\n", - "\n", - "# add a capacity dimension for service type 1\n", - "data_model.add_capacity_dimension(\n", - " \"service_type1\",\n", - " cudf.Series(service_location_data[\"service_type1_demand\"]),\n", - " cudf.Series(vehicle_data[\"capacity_service_type1\"])\n", - ")\n", - "# add a capacity dimension for service type 2\n", - "data_model.add_capacity_dimension(\n", - " \"service_type2\",\n", - " cudf.Series(service_location_data[\"service_type2_demand\"]),\n", - " cudf.Series(vehicle_data[\"capacity_service_type2\"])\n", - ")\n", - "\n", - "# add time windows and service time for the locations\n", - "data_model.set_order_time_windows(\n", - " service_location_data[\"location_earliest_time\"],\n", - " service_location_data[\"location_latest_time\"]\n", - ")\n", - "data_model.set_order_service_times(\n", - " service_location_data[\"required_service_time\"]\n", - ")\n", - "\n", - "# add time windows for vehicle availability\n", - "data_model.set_vehicle_time_windows(\n", - " vehicle_data[\"vehicle_availability_earliest\"], \n", - " vehicle_data[\"vehicle_availability_latest\"]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "be4c7654", - "metadata": {}, - "source": [ - "### CuOpt SolverSettings" - ] - }, - { - "cell_type": "markdown", - "id": "a5c8ffc8", - "metadata": {}, - "source": [ - "Setup routing.SolverSettings." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fd528c7e", - "metadata": {}, - "outputs": [], - "source": [ - "solver_settings = routing.SolverSettings()\n", - "\n", - "# set number of climbers that will try to search for an optimal routes in parallel\n", - "solver_settings.set_number_of_climbers(128)\n", - "\n", - "# solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", - "solver_settings.set_time_limit(0.05)" - ] - }, - { - "cell_type": "markdown", - "id": "c1994f6a", - "metadata": {}, - "source": [ - "### Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d243bdef", - "metadata": {}, - "outputs": [], - "source": [ - "routing_solution = routing.Solve(data_model, solver_settings)\n", - "if routing_solution.get_status() == 0:\n", - " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", - " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", - " utils.show_vehicle_routes(routing_solution.route, location_names)\n", - " routing_solution.route\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" - ] - }, - { - "cell_type": "markdown", - "id": "68b89b87", - "metadata": {}, - "source": [ - "**Notice** that this solution leverages the fact that vehicle 1 is the only vehicle with the ability to perform both service type 1 and service type 2. In addition, vehicle 0 and vehicle 2 also serve the locations they are suited to service and minimize the time taken along these routes." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "617cdd8e", - "metadata": {}, - "outputs": [], - "source": [ - "vehicle_colors = [\"red\", \"green\", \"blue\"]\n", - "utils.map_vehicle_routes(location_coordinates_df, routing_solution.route, vehicle_colors).show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ff30f074", - "metadata": {}, - "outputs": [], - "source": [ - "routing_solution.route" - ] - }, - { - "cell_type": "markdown", - "id": "9237ef66", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/routing/python/.ipynb_checkpoints/pdptw_mixed_fleet-checkpoint.ipynb b/notebooks/routing/python/.ipynb_checkpoints/pdptw_mixed_fleet-checkpoint.ipynb deleted file mode 100644 index 6705007..0000000 --- a/notebooks/routing/python/.ipynb_checkpoints/pdptw_mixed_fleet-checkpoint.ipynb +++ /dev/null @@ -1,458 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "3631e3f7", - "metadata": {}, - "outputs": [], - "source": [ - "from cuopt import routing\n", - "import cudf" - ] - }, - { - "cell_type": "markdown", - "id": "9326712e", - "metadata": {}, - "source": [ - "# Heterogenous Fleet Routing\n", - "## Pickup and Delivery Problem with Time Windows using Heterogenous Fleet of Vehicles" - ] - }, - { - "cell_type": "markdown", - "id": "382afbd9", - "metadata": {}, - "source": [ - "In scenarios such as food delivery, the delivery fleet may consist of various types of vehicles, for example bikes, and cars, and each type of vehicle has own advantages and limitations. For example, in crowded streets of NYC, it might be faster to reach a nearby destination on bike compared to car, while it is much faster with car in suburban areas. Service provides can improve customer satisfaction, reduce costs, and increase earning opportunity for drivers, using various types of vehicles depending on the geography of the service area. " - ] - }, - { - "cell_type": "markdown", - "id": "c3bc4ad4", - "metadata": {}, - "source": [ - "### Problem Details:\n", - "- 5 customer orders each with an associated demand, merchant, and time windows \n", - "- 2 merchants \n", - "- 3 vehicles \n", - " - 2 bikes \n", - " - 1 car\n", - "- 7 locations in total (5 customers + 2 merchants)" - ] - }, - { - "cell_type": "markdown", - "id": "7af883ad", - "metadata": {}, - "source": [ - "### Cost Matrix" - ] - }, - { - "cell_type": "markdown", - "id": "52bdc1d0", - "metadata": {}, - "source": [ - "#### Define cost matrix for each vehicle type" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9975bf1a", - "metadata": {}, - "outputs": [], - "source": [ - "# locations 0, 1 correspond to merchant \n", - "# locations 2 to 6 correspond to customers\n", - "n_locations = 7\n", - "time_matrix_car = cudf.DataFrame([\n", - " [0., 6., 5., 6., 6., 7., 4.], \n", - " [6., 0., 3., 8., 7., 3., 2.], \n", - " [5., 3., 0., 4., 11., 6., 4.], \n", - " [6., 8., 4., 0., 12., 11., 10.], \n", - " [6., 7., 11., 12., 0., 7., 4.],\n", - " [7., 3., 6., 11., 7., 0., 3.], \n", - " [4., 2., 4., 10., 4., 3., 0.]])\n", - "\n", - "time_matrix_bike = cudf.DataFrame([\n", - " [0., 15., 10., 9., 10., 21., 6.],\n", - " [15., 0., 6., 15., 12., 5., 4.], \n", - " [10., 6., 0., 9., 20, 12., 9.],\n", - " [9., 15., 9., 0., 20., 22., 20.],\n", - " [10.,12., 20., 20., 0., 15., 8.], \n", - " [21., 5., 12., 22., 15., 0., 8.], \n", - " [6., 4., 9., 20., 8., 8., 0.]])\n", - "\n", - "print('time matrix for bike:: \\n', time_matrix_bike, '\\n')\n", - "print('time matrix for car:: \\n', time_matrix_car, '\\n')" - ] - }, - { - "cell_type": "markdown", - "id": "f2aaf28a", - "metadata": {}, - "source": [ - "### Fleet Data" - ] - }, - { - "cell_type": "markdown", - "id": "a4e5e749", - "metadata": {}, - "source": [ - "Set up mixed fleet data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9e17e899", - "metadata": {}, - "outputs": [], - "source": [ - "n_vehicles = 3\n", - "\n", - "# type 0 corresponds to bike and type 1 corresponds to car\n", - "vehicle_types = cudf.Series([0, 0, 1])\n", - "\n", - "# bikes can carry two units of goods while car can carry 5 units of goods\n", - "vehicle_capacity = cudf.Series([2, 2, 5])\n", - "\n", - "print(n_vehicles)\n", - "print(vehicle_types)\n", - "print(vehicle_capacity)" - ] - }, - { - "cell_type": "markdown", - "id": "4ed911ff", - "metadata": {}, - "source": [ - "### Customer Orders" - ] - }, - { - "cell_type": "markdown", - "id": "4265c03a", - "metadata": {}, - "source": [ - "Setup Customer Order Data" - ] - }, - { - "cell_type": "markdown", - "id": "d7d7536d", - "metadata": {}, - "source": [ - "The customer order data contains the information of merchant, the amount of goods, and the time window for the delivery." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "72b715c7", - "metadata": {}, - "outputs": [], - "source": [ - "customer_order_data = cudf.DataFrame({\n", - " \"pickup_location\": [0, 0, 1, 1, 1],\n", - " \"delivery_location\": [2, 3, 4, 5, 6],\n", - " \"order_demand\": [1, 2, 2, 1, 2],\n", - " \"earliest_pickup\": [0, 0, 0, 0, 0],\n", - " \"latest_pickup\": [30, 120, 60, 120, 45],\n", - " \"pickup_service_time\": [10, 10, 10, 10, 10],\n", - " \"earliest_delivery\": [0, 0, 0, 0, 0],\n", - " \"latest_delivery\": [30, 120, 60, 120, 45],\n", - " \"delivery_serivice_time\":[10, 10, 10, 10, 10]\n", - "})\n", - "customer_order_data" - ] - }, - { - "cell_type": "markdown", - "id": "31db9053", - "metadata": {}, - "source": [ - "### cuOpt routing DataModel" - ] - }, - { - "cell_type": "markdown", - "id": "731fdcbe", - "metadata": {}, - "source": [ - "#### Initialize routing.DataModel object" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2e765325", - "metadata": {}, - "outputs": [], - "source": [ - "# a pickup order and a delivery order are distinct\n", - "n_orders = len(customer_order_data) * 2\n", - "\n", - "data_model = routing.DataModel(n_locations, n_vehicles, n_orders)" - ] - }, - { - "cell_type": "markdown", - "id": "bc27baca", - "metadata": {}, - "source": [ - "#### Set Vehicle types and corresponding cost matrices" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7a4804c2", - "metadata": {}, - "outputs": [], - "source": [ - "# set matrices associated with each vehicle type\n", - "data_model.set_vehicle_types(vehicle_types)\n", - "\n", - "data_model.add_cost_matrix(time_matrix_bike, 0)\n", - "data_model.add_cost_matrix(time_matrix_car, 1)" - ] - }, - { - "cell_type": "markdown", - "id": "48706e31", - "metadata": {}, - "source": [ - "#### Setting Order locations\n", - "From the cuOpt solver_settings perspective, each distinct transaction (pickup order or delivery order) is treated separately. The locations for each order is specified using order locations. The first entry in order locations array is always reserved for the notion of depot for the problem. So for a total n orders, the order location array is of size 2n+1. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1d325f4b", - "metadata": {}, - "outputs": [], - "source": [ - "pickup_order_locations = customer_order_data['pickup_location'] \n", - "delivery_order_locations = customer_order_data['delivery_location']\n", - "order_locations = cudf.concat([pickup_order_locations, delivery_order_locations], ignore_index=True)\n", - "\n", - "print(order_locations)\n", - "\n", - "# add order locations\n", - "data_model.set_order_locations(order_locations)" - ] - }, - { - "cell_type": "markdown", - "id": "9389060b", - "metadata": {}, - "source": [ - "#### Mapping pickups to deliveries\n", - "Order locations do not provide information regarding the type of order (i.e, pickup or delivery). This information is provided to solver by setting two arrays pickup_orders and delivery_orders. The entries of these arrays corresponding the order numbers in exanded list described above. \n", - "\n", - "For a pair order i, pickup_orders[i] and delivery_orders[i] correspond to order index in 2n total orders. Furthermore, order_locations[pickup_orders[i]] and order_locations[delivery_orders[i]] indicate the pickup location and delivery location of order i. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "064978ca", - "metadata": {}, - "outputs": [], - "source": [ - "npair_orders = int(len(order_locations)/2)\n", - "pickup_orders = cudf.Series([i for i in range(npair_orders)])\n", - "delivery_orders = cudf.Series([i + npair_orders for i in range(npair_orders)])\n", - "\n", - "# add pickup and delivery pairs.\n", - "data_model.set_pickup_delivery_pairs(pickup_orders, delivery_orders)" - ] - }, - { - "cell_type": "markdown", - "id": "7f8f10e8", - "metadata": {}, - "source": [ - "\n", - "#### Set the per order demand\n", - "\n", - "From the perspective of the cuOpt solver, each distinct transaction (pickup order or delivery order) are treated separately with demand for pickup denoted as positive and the corresponding delivery treated as negative demand." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c936b137", - "metadata": {}, - "outputs": [], - "source": [ - "# This is the number of goods that need to be delivered\n", - "raw_demand = customer_order_data[\"order_demand\"]\n", - "\n", - "# When dropping off goods, remove one unit of demand from the vehicle\n", - "drop_off_demand = raw_demand * -1\n", - "\n", - "# Create pickup and delivery demand\n", - "order_demand = cudf.concat([raw_demand, drop_off_demand], ignore_index=True)\n", - "\n", - "order_demand\n", - "\n", - "# add the capacity dimension\n", - "data_model.add_capacity_dimension(\"demand\", order_demand, vehicle_capacity)" - ] - }, - { - "cell_type": "markdown", - "id": "ef21d42d", - "metadata": {}, - "source": [ - "#### Time Windows\n", - "\n", - "Create per order time windows similar to demand. Set earliest time and service time of depot to be zero and the latest time to be a large value so that all orders are fulfilled. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b3f328e3", - "metadata": {}, - "outputs": [], - "source": [ - "# create earliest times\n", - "order_time_window_earliest = cudf.concat([customer_order_data[\"earliest_pickup\"], customer_order_data[\"earliest_delivery\"]], ignore_index=True)\n", - "\n", - "# create latest times\n", - "order_time_window_latest = cudf.concat([customer_order_data[\"latest_pickup\"], customer_order_data[\"latest_delivery\"]], ignore_index=True)\n", - "\n", - "# create service times\n", - "order_service_time = cudf.concat([customer_order_data[\"pickup_service_time\"], customer_order_data[\"delivery_serivice_time\"]], ignore_index=True)\n", - "\n", - "# add time window constraints\n", - "data_model.set_order_time_windows(order_time_window_earliest, order_time_window_latest)\n", - "data_model.set_order_service_times(order_service_time)\n", - "\n", - "# set time windows for the fleet\n", - "vehicle_earliest_time = cudf.Series([0] * n_vehicles)\n", - "vehicle_latest_time = cudf.Series([1000] * n_vehicles)\n", - "\n", - "data_model.set_vehicle_time_windows(vehicle_earliest_time, vehicle_latest_time)" - ] - }, - { - "cell_type": "markdown", - "id": "b0d06888", - "metadata": {}, - "source": [ - "### CuOpt SolverSettings" - ] - }, - { - "cell_type": "markdown", - "id": "e3e08235", - "metadata": {}, - "source": [ - "Set up routing.SolverSettings." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a6babc11", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "solver_settings = routing.SolverSettings()\n", - "\n", - "# solver_settings will run for given time limit. Larger and/or more complex problems may require more time.\n", - "solver_settings.set_time_limit(0.05)" - ] - }, - { - "cell_type": "markdown", - "id": "854e9519", - "metadata": {}, - "source": [ - "### Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "28a05ace", - "metadata": {}, - "outputs": [], - "source": [ - "routing_solution = routing.Solve(data_model, solver_settings)\n", - "if routing_solution.get_status() == 0:\n", - " print(\"Cost for the routing in time: \", routing_solution.final_cost)\n", - " print(\"Vehicle count to complete routing: \", routing_solution.vehicle_count)\n", - " print(routing_solution.route)\n", - "else:\n", - " print(\"NVIDIA cuOpt Failed to find a solution with status : \", routing_solution.get_status())" - ] - }, - { - "cell_type": "markdown", - "id": "93309efc", - "metadata": {}, - "source": [ - "_____\n", - "\n", - "#### SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", - "\n", - "#### SPDX-License-Identifier: MIT\n", - "\n", - "Permission is hereby granted, free of charge, to any person obtaining a\n", - "copy of this software and associated documentation files (the \"Software\"),\n", - "to deal in the Software without restriction, including without limitation\n", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n", - "and/or sell copies of the Software, and to permit persons to whom the\n", - "Software is furnished to do so, subject to the following conditions:\n", - "The above copyright notice and this permission notice shall be included in\n", - "all copies or substantial portions of the Software.\n", - "\n", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n", - "DEALINGS IN THE SOFTWARE.\n", - "\n", - "---" - ] - } - ], - "metadata": { - "interpreter": { - "hash": "0f29e496949dc4ef652a1afa2d601ce2913fc84758b70efb060a954cb0e2d83f" - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.15" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 320ee25ef3f654b1a8e50a3c1d8621f8e2bd24e1 Mon Sep 17 00:00:00 2001 From: Iroy30 <41401566+Iroy30@users.noreply.github.com> Date: Tue, 6 Dec 2022 15:54:35 -0600 Subject: [PATCH 3/5] Update notebook_helpers.py --- .../notebook_utils/notebook_helpers.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/notebooks/routing/microservice/notebook_utils/notebook_helpers.py b/notebooks/routing/microservice/notebook_utils/notebook_helpers.py index b244934..e9667ab 100644 --- a/notebooks/routing/microservice/notebook_utils/notebook_helpers.py +++ b/notebooks/routing/microservice/notebook_utils/notebook_helpers.py @@ -1,5 +1,23 @@ -# Copyright (c) 2022, NVIDIA CORPORATION. -# CONFIDENTIAL, provided under NDA. +# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: MIT +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. import matplotlib.pyplot as plt import matplotlib.font_manager as fm From 1dbf43a4f47e8192dbce690ea923101a0c493544 Mon Sep 17 00:00:00 2001 From: Iroy30 <41401566+Iroy30@users.noreply.github.com> Date: Tue, 6 Dec 2022 15:55:06 -0600 Subject: [PATCH 4/5] Update notebook_helpers.py --- .../python/notebook_utils/notebook_helpers.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/notebooks/routing/python/notebook_utils/notebook_helpers.py b/notebooks/routing/python/notebook_utils/notebook_helpers.py index 65b3205..011a04d 100644 --- a/notebooks/routing/python/notebook_utils/notebook_helpers.py +++ b/notebooks/routing/python/notebook_utils/notebook_helpers.py @@ -1,5 +1,23 @@ -# Copyright (c) 2022, NVIDIA CORPORATION. -# CONFIDENTIAL, provided under NDA. +# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: MIT +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. import matplotlib.pyplot as plt import matplotlib.font_manager as fm From 177e1ef0e71e74501c4546804621c7a6556c8d3a Mon Sep 17 00:00:00 2001 From: iroy Date: Wed, 14 Dec 2022 11:13:26 -0600 Subject: [PATCH 5/5] update 22.10.1 to 22.12 --- cloud-scripts/README.md | 2 +- cloud-scripts/build-cuopt-server.sh | 6 +++--- cloud-scripts/scripts/cuopt-helm.sh | 2 +- docker-utilities/utilities/Dockerfile | 2 +- docker-utilities/utilities/run_cuopt.sh | 2 +- docker-utilities/utilities/run_server.sh | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cloud-scripts/README.md b/cloud-scripts/README.md index 14e3e84..b6c2e90 100644 --- a/cloud-scripts/README.md +++ b/cloud-scripts/README.md @@ -348,7 +348,7 @@ There are two ways to repair this: $ export SERVER_TYPE=both # or jupyter, or api $ helm list -n cuopt-server NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION - nvidia-cuopt-chart cuopt-server 1 2022-09-19 14:11:45.677689124 +0000 UTC deployed cuopt-22.10.1 22.10.1 + nvidia-cuopt-chart cuopt-server 1 2022-09-19 14:11:45.677689124 +0000 UTC deployed cuopt-22.12.0 22.12.0 $ helm uninstall nvidia-cuopt-chart -n cuopt-server $ scripts/cuopt-helm.sh $ scripts/wait-cuopt.sh diff --git a/cloud-scripts/build-cuopt-server.sh b/cloud-scripts/build-cuopt-server.sh index 1d0b14f..d76b31a 100755 --- a/cloud-scripts/build-cuopt-server.sh +++ b/cloud-scripts/build-cuopt-server.sh @@ -33,10 +33,10 @@ fi if command -v helm &> /dev/null; then echo helm installed, checking API_KEY - rm -f /tmp/cuopt-22.10.1.tgz - helm fetch https://helm.ngc.nvidia.com/nvidia/cuopt/charts/cuopt-22.10.1.tgz --username='$oauthtoken' --password=$API_KEY -d /tmp + rm -f /tmp/cuopt-22.12.0.tgz + helm fetch https://helm.ngc.nvidia.com/nvidia/cuopt/charts/cuopt-22.12.0.tgz --username='$oauthtoken' --password=$API_KEY -d /tmp if [ "$?" -eq 0 ]; then - rm -f /tmp/cuopt-22.10.1.tgz + rm -f /tmp/cuopt-22.12.0.tgz echo API_KEY is valid else echo Failed to download cuopt helm chart, API_KEY is invalid. Please try again with a valid NGC api key. diff --git a/cloud-scripts/scripts/cuopt-helm.sh b/cloud-scripts/scripts/cuopt-helm.sh index 57a1197..232e60f 100755 --- a/cloud-scripts/scripts/cuopt-helm.sh +++ b/cloud-scripts/scripts/cuopt-helm.sh @@ -24,7 +24,7 @@ NAMESPACE=cuopt-server kubectl create namespace $NAMESPACE -helm fetch https://helm.ngc.nvidia.com/nvidia/cuopt/charts/cuopt-22.10.1.tgz --username='$oauthtoken' --password=$API_KEY --untar +helm fetch https://helm.ngc.nvidia.com/nvidia/cuopt/charts/cuopt-22.12.0.tgz --username='$oauthtoken' --password=$API_KEY --untar case $SERVER_TYPE in "jupyter") diff --git a/docker-utilities/utilities/Dockerfile b/docker-utilities/utilities/Dockerfile index c254721..171249a 100644 --- a/docker-utilities/utilities/Dockerfile +++ b/docker-utilities/utilities/Dockerfile @@ -1,4 +1,4 @@ -from nvcr.io/nvidia/cuopt/cuopt:22.10.01 +from nvcr.io/nvidia/cuopt/cuopt:22.12 COPY requirements.txt . RUN conda run -n cuopt conda install --file requirements.txt -y diff --git a/docker-utilities/utilities/run_cuopt.sh b/docker-utilities/utilities/run_cuopt.sh index 79eb6ec..536765f 100755 --- a/docker-utilities/utilities/run_cuopt.sh +++ b/docker-utilities/utilities/run_cuopt.sh @@ -25,7 +25,7 @@ detached=False detached_log= jupyterserver=True requirements= -image=nvcr.io/nvidia/cuopt/cuopt:22.10.01 +image=nvcr.io/nvidia/cuopt/cuopt:22.12 localdir= have_realpath=$(command -v realpath) diff --git a/docker-utilities/utilities/run_server.sh b/docker-utilities/utilities/run_server.sh index 8eb1a1c..f5bb9de 100755 --- a/docker-utilities/utilities/run_server.sh +++ b/docker-utilities/utilities/run_server.sh @@ -21,7 +21,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -image=nvcr.io/nvidia/cuopt/cuopt:22.10.01 +image=nvcr.io/nvidia/cuopt/cuopt:22.12 detached=False detached_log=