269 lines
11 KiB
Plaintext
269 lines
11 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "google",
|
|
"metadata": {},
|
|
"source": [
|
|
"##### Copyright 2025 Google LLC."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "apache",
|
|
"metadata": {},
|
|
"source": [
|
|
"Licensed under the Apache License, Version 2.0 (the \"License\");\n",
|
|
"you may not use this file except in compliance with the License.\n",
|
|
"You may obtain a copy of the License at\n",
|
|
"\n",
|
|
" http://www.apache.org/licenses/LICENSE-2.0\n",
|
|
"\n",
|
|
"Unless required by applicable law or agreed to in writing, software\n",
|
|
"distributed under the License is distributed on an \"AS IS\" BASIS,\n",
|
|
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
|
|
"See the License for the specific language governing permissions and\n",
|
|
"limitations under the License.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "basename",
|
|
"metadata": {},
|
|
"source": [
|
|
"# vrp_breaks_from_start"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "link",
|
|
"metadata": {},
|
|
"source": [
|
|
"<table align=\"left\">\n",
|
|
"<td>\n",
|
|
"<a href=\"https://colab.research.google.com/github/google/or-tools/blob/main/examples/notebook/constraint_solver/vrp_breaks_from_start.ipynb\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/colab_32px.png\"/>Run in Google Colab</a>\n",
|
|
"</td>\n",
|
|
"<td>\n",
|
|
"<a href=\"https://github.com/google/or-tools/blob/main/ortools/constraint_solver/samples/vrp_breaks_from_start.py\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/github_32px.png\"/>View source on GitHub</a>\n",
|
|
"</td>\n",
|
|
"</table>"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "doc",
|
|
"metadata": {},
|
|
"source": [
|
|
"First, you must install [ortools](https://pypi.org/project/ortools/) package in this colab."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "install",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%pip install ortools"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "description",
|
|
"metadata": {},
|
|
"source": [
|
|
"Vehicles Routing Problem (VRP) with breaks relative to the vehicle start time.\n",
|
|
"\n",
|
|
"Each vehicles start at T:15min, T:30min, T:45min and T:60min respectively.\n",
|
|
"\n",
|
|
"Each vehicle must perform a break lasting 5 minutes,\n",
|
|
"starting between 25 and 45 minutes after route start.\n",
|
|
"e.g. vehicle 2 starting a T:45min must start a 5min breaks\n",
|
|
"between [45+25,45+45] i.e. in the range [70, 90].\n",
|
|
"\n",
|
|
"Durations are in minutes.\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "code",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from ortools.constraint_solver import routing_enums_pb2\n",
|
|
"from ortools.constraint_solver import pywrapcp\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"def create_data_model():\n",
|
|
" \"\"\"Stores the data for the problem.\"\"\"\n",
|
|
" data = {}\n",
|
|
" data[\"num_vehicles\"] = 4\n",
|
|
" data[\"depot\"] = 0\n",
|
|
" data[\"time_matrix\"] = [\n",
|
|
" [0, 27, 38, 34, 29, 13, 25, 9, 15, 9, 26, 25, 19, 17, 23, 38, 33],\n",
|
|
" [27, 0, 34, 15, 9, 25, 36, 17, 34, 37, 54, 29, 24, 33, 50, 43, 60],\n",
|
|
" [38, 34, 0, 49, 43, 25, 13, 40, 23, 37, 20, 63, 58, 56, 39, 77, 37],\n",
|
|
" [34, 15, 49, 0, 5, 32, 43, 25, 42, 44, 61, 25, 31, 41, 58, 28, 67],\n",
|
|
" [29, 9, 43, 5, 0, 26, 38, 19, 36, 38, 55, 20, 25, 35, 52, 33, 62],\n",
|
|
" [13, 25, 25, 32, 26, 0, 11, 15, 9, 12, 29, 38, 33, 31, 25, 52, 35],\n",
|
|
" [25, 36, 13, 43, 38, 11, 0, 26, 9, 23, 17, 50, 44, 42, 25, 63, 24],\n",
|
|
" [9, 17, 40, 25, 19, 15, 26, 0, 17, 19, 36, 23, 17, 16, 33, 37, 42],\n",
|
|
" [15, 34, 23, 42, 36, 9, 9, 17, 0, 13, 19, 40, 34, 33, 16, 54, 25],\n",
|
|
" [9, 37, 37, 44, 38, 12, 23, 19, 13, 0, 17, 26, 21, 19, 13, 40, 23],\n",
|
|
" [26, 54, 20, 61, 55, 29, 17, 36, 19, 17, 0, 43, 38, 36, 19, 57, 17],\n",
|
|
" [25, 29, 63, 25, 20, 38, 50, 23, 40, 26, 43, 0, 5, 15, 32, 13, 42],\n",
|
|
" [19, 24, 58, 31, 25, 33, 44, 17, 34, 21, 38, 5, 0, 9, 26, 19, 36],\n",
|
|
" [17, 33, 56, 41, 35, 31, 42, 16, 33, 19, 36, 15, 9, 0, 17, 21, 26],\n",
|
|
" [23, 50, 39, 58, 52, 25, 25, 33, 16, 13, 19, 32, 26, 17, 0, 38, 9],\n",
|
|
" [38, 43, 77, 28, 33, 52, 63, 37, 54, 40, 57, 13, 19, 21, 38, 0, 39],\n",
|
|
" [33, 60, 37, 67, 62, 35, 24, 42, 25, 23, 17, 42, 36, 26, 9, 39, 0],\n",
|
|
" ]\n",
|
|
" # 15 min of service time\n",
|
|
" data[\"service_time\"] = [15] * len(data[\"time_matrix\"])\n",
|
|
" data[\"service_time\"][data[\"depot\"]] = 0\n",
|
|
" assert len(data[\"time_matrix\"]) == len(data[\"service_time\"])\n",
|
|
" return data\n",
|
|
"\n",
|
|
"\n",
|
|
"def print_solution(manager, routing, solution):\n",
|
|
" \"\"\"Prints solution on console.\"\"\"\n",
|
|
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
|
|
"\n",
|
|
" print(\"Breaks:\")\n",
|
|
" intervals = solution.IntervalVarContainer()\n",
|
|
" for i in range(intervals.Size()):\n",
|
|
" brk = intervals.Element(i)\n",
|
|
" if brk.PerformedValue() == 1:\n",
|
|
" print(\n",
|
|
" f\"{brk.Var().Name()}: \"\n",
|
|
" + f\"Start({brk.StartValue()}) Duration({brk.DurationValue()})\"\n",
|
|
" )\n",
|
|
" else:\n",
|
|
" print(f\"{brk.Var().Name()}: Unperformed\")\n",
|
|
"\n",
|
|
" time_dimension = routing.GetDimensionOrDie(\"Time\")\n",
|
|
" total_time = 0\n",
|
|
" for vehicle_id in range(manager.GetNumberOfVehicles()):\n",
|
|
" if not routing.IsVehicleUsed(solution, vehicle_id):\n",
|
|
" continue\n",
|
|
" index = routing.Start(vehicle_id)\n",
|
|
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
|
|
" while not routing.IsEnd(index):\n",
|
|
" time_var = time_dimension.CumulVar(index)\n",
|
|
" if routing.IsStart(index):\n",
|
|
" start_time = solution.Value(time_var)\n",
|
|
" plan_output += f\"{manager.IndexToNode(index)} \"\n",
|
|
" plan_output += f\"Time({solution.Value(time_var)}) -> \"\n",
|
|
" index = solution.Value(routing.NextVar(index))\n",
|
|
" time_var = time_dimension.CumulVar(index)\n",
|
|
" plan_output += f\"{manager.IndexToNode(index)} \"\n",
|
|
" plan_output += f\"Time({solution.Value(time_var)})\"\n",
|
|
" print(plan_output)\n",
|
|
" route_time = solution.Value(time_var) - start_time\n",
|
|
" print(f\"Time of the route: {route_time}min\\n\")\n",
|
|
" total_time += route_time\n",
|
|
" print(f\"Total time of all routes: {total_time}min\")\n",
|
|
"\n",
|
|
"\n",
|
|
"def main():\n",
|
|
" \"\"\"Solve the VRP with time windows.\"\"\"\n",
|
|
" # Instantiate the data problem.\n",
|
|
" data = create_data_model()\n",
|
|
"\n",
|
|
" # Create the routing index manager.\n",
|
|
" manager = pywrapcp.RoutingIndexManager(\n",
|
|
" len(data[\"time_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
|
|
" )\n",
|
|
"\n",
|
|
" # Create Routing Model.\n",
|
|
" routing = pywrapcp.RoutingModel(manager)\n",
|
|
"\n",
|
|
" # Create and register a transit callback.\n",
|
|
" def time_callback(from_index, to_index):\n",
|
|
" \"\"\"Returns the travel time between the two nodes.\"\"\"\n",
|
|
" # Convert from routing variable Index to time matrix NodeIndex.\n",
|
|
" from_node = manager.IndexToNode(from_index)\n",
|
|
" to_node = manager.IndexToNode(to_index)\n",
|
|
" return data[\"time_matrix\"][from_node][to_node]\n",
|
|
"\n",
|
|
" transit_callback_index = routing.RegisterTransitCallback(time_callback)\n",
|
|
"\n",
|
|
" # Define cost of each arc.\n",
|
|
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
|
|
"\n",
|
|
" # Add Time Windows constraint.\n",
|
|
" time = \"Time\"\n",
|
|
" routing.AddDimension(\n",
|
|
" transit_callback_index,\n",
|
|
" 10, # need optional waiting time to place break\n",
|
|
" 180, # maximum time per vehicle\n",
|
|
" False, # Don't force start cumul to zero.\n",
|
|
" time,\n",
|
|
" )\n",
|
|
" time_dimension = routing.GetDimensionOrDie(time)\n",
|
|
" time_dimension.SetGlobalSpanCostCoefficient(10)\n",
|
|
"\n",
|
|
" # Each vehicle start with a 15min delay\n",
|
|
" for vehicle_id in range(manager.GetNumberOfVehicles()):\n",
|
|
" index = routing.Start(vehicle_id)\n",
|
|
" time_dimension.CumulVar(index).SetValue((vehicle_id + 1) * 15)\n",
|
|
"\n",
|
|
" # Add breaks\n",
|
|
" # warning: Need a pre-travel array using the solver's index order.\n",
|
|
" node_visit_transit = [0] * routing.Size()\n",
|
|
" for index in range(routing.Size()):\n",
|
|
" node = manager.IndexToNode(index)\n",
|
|
" node_visit_transit[index] = data[\"service_time\"][node]\n",
|
|
"\n",
|
|
" # Add a break lasting 5 minutes, start between 25 and 45 minutes after route start\n",
|
|
" for v in range(manager.GetNumberOfVehicles()):\n",
|
|
" start_var = time_dimension.CumulVar(routing.Start(v))\n",
|
|
" break_start = routing.solver().Sum([routing.solver().IntVar(25, 45), start_var])\n",
|
|
"\n",
|
|
" break_intervals = [\n",
|
|
" routing.solver().FixedDurationIntervalVar(\n",
|
|
" break_start, 5, f\"Break for vehicle {v}\"\n",
|
|
" )\n",
|
|
" ]\n",
|
|
" time_dimension.SetBreakIntervalsOfVehicle(\n",
|
|
" break_intervals, v, node_visit_transit\n",
|
|
" )\n",
|
|
"\n",
|
|
" # Setting first solution heuristic.\n",
|
|
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
|
|
" search_parameters.first_solution_strategy = (\n",
|
|
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
|
|
" )\n",
|
|
" search_parameters.local_search_metaheuristic = (\n",
|
|
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
|
|
" )\n",
|
|
" # search_parameters.log_search = True\n",
|
|
" search_parameters.time_limit.FromSeconds(2)\n",
|
|
"\n",
|
|
" # Solve the problem.\n",
|
|
" solution = routing.SolveWithParameters(search_parameters)\n",
|
|
"\n",
|
|
" # Print solution on console.\n",
|
|
" if solution:\n",
|
|
" print_solution(manager, routing, solution)\n",
|
|
" else:\n",
|
|
" print(\"No solution found !\")\n",
|
|
"\n",
|
|
"\n",
|
|
"main()\n",
|
|
"\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"language_info": {
|
|
"name": "python"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|