├── 4th_model_run_FINAL
├── output.xlsx
└── Screenshot_Gantt_Chart.png
├── 2nd_model_run
├── Screenshot_Gantt_Chart.png
└── Model_Result.html
├── README.md
├── 3rd_model_run
└── Model_3b_non-green-green.ipynb
└── index.html
/4th_model_run_FINAL/output.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marco-cheung/airport-gate-assignment-problem/HEAD/4th_model_run_FINAL/output.xlsx
--------------------------------------------------------------------------------
/2nd_model_run/Screenshot_Gantt_Chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marco-cheung/airport-gate-assignment-problem/HEAD/2nd_model_run/Screenshot_Gantt_Chart.png
--------------------------------------------------------------------------------
/4th_model_run_FINAL/Screenshot_Gantt_Chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marco-cheung/airport-gate-assignment-problem/HEAD/4th_model_run_FINAL/Screenshot_Gantt_Chart.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Background
2 | The stand allocation for flight arrivals and departures is an important task for airport operators at major airports. For example, as almost all COVID-19-related travel restrictions to Hong Kong has been lifted, it comes to a challenge for the airfield management to decide the minimum number of required parking stands, including frontal stands and remote stands, that can serve all scheduled passenger flights based on a given data of upcoming monthly flight schedule.
3 |
4 | In case the number of flights exceeds the capacity of gate/stands parking, i.e., overflow towards remote stand parking, then the airport operators need to evaluate how many additional remote stands, which are not equipped with passenger-boarding bridge”, are required so as to facilitate planning of airside shuttle bus schedule for in advance.
5 |
6 | # Model Selection
7 | In this project, the **Mixed-Integer Linear Programming (MILP)** is applied to solve this problem. In our case, we want the model to find the feasible solution for the objective function subject to a set of constraints. A decision variable is a parameter that the model solver can adjust in its best effort to minimize the cost of objective function. Simply put, we want to run the model to assign stand j for each flight turn i, on the condition that the setting of stand allocation rules can be satisfied.
8 |
9 | Some constraints are so-called “hard-rules” for which that the model must satisfy, whereas some are “soft-rules” such as the preference of assigning flights to frontal stands instead of remote stands as much as possible.
10 |
11 | In case there is no feasible solutions, it implies that the resource input of parking stands may not enough to serve the simulated scenario of aircraft operation. At this time, we will adjust input of remote stands and then re-run the model until the optimal solution can be found.
12 |
13 | # Constraints
14 | - Each parking stand can only handle specific aircraft size
15 |
16 | - Each flight turn must be assigned to a compatible parking stand
17 |
18 | - On grounds of airfield safety operation, at least 25 minutes interval (“time gap”) in-between slot of each aircraft occupied in a parking stand. For each parking stand, therefore, no flight-to-gate assignment exercise shall be made 12.5 minutes before the scheduled time of arrival (STA) of arriving flights until 12.5 minutes after the scheduled time of departure (STD) of departing flights
19 |
20 | - Aircraft with over 6 hours of ground time (i.e. STA - STD) will be towed away from the frontal stand
21 |
22 | - If the pair of flights do not correspond to the same zone (i.e. Green-Orange or Orange-Green), then towing protocol will also be applied
23 |
24 | - For towing plane, add additional 90 mins on top of ATD to cater for boarding, departing the plane and the physical process of towing. Besides, the aircraft needs to tow back to the compatible stand to prepare for embarking 1 hour prior to its STD
25 |
26 | - Mainland China (CN) flight shall be allocated to “Green zone” (exclusively defined by a set of parking stands), where remote stands #N141 - N145 are reserved for handling departure flights in Green Zone)
27 |
28 | - Non-mainland flight shall be allocated to “Orange zone” only
29 |
30 | # Assumptions
31 | 1) The first arriving passenger flights were assumed to land in an empty airport, i.e., no parking stands were pre-occupied at the first beginning.
32 | 2) The flight schedule data is simulated as close as some days in Nov-22 flight schedule of Hong Kong International Airport (HKIA). (Due to data privacy concern, the raw data of seasonal schedule data would keep it confidential)
33 | 3) Our model only serves one objective in this study, despite there may have multiple objectives to consider by Airport Authority during stand allocation, such as assigning flights close to airline service counters, maintaining some degree of stand allocation patterns for regular flights from time to time, etc.
34 |
35 | # Terminology
36 | **Turns**: a pair of arrival and departure flights with the same aircraft.
37 | **STA**: Scheduled Arrival Time.
38 | **STD**: Scheduled Departure Time.
39 | **Ground Time**: the planned time period for which an aircraft will occupy a particular stand, i.e. time between STA and STD.
40 | **Frontal Stand**: a set of parking stands equipped with “passenger boarding bridge” for connection with the terminal building.
41 | **Remote Stand**: an aircraft stand that is not airbridge-served, therefore requires require a shuttle bus service.
42 | **Towing Stand**: a set of parking stand for serving towing plane.
43 | **Green Zone**: Mainland flight shall be allocated to “Green” zone only.
44 | **Orange Zone**: Non-Mainland flight shall be allocated to “Orange” zone only.
45 | **Towing**: The turns followed by the towing protocol will be broken up as three turns as each of them is assigned to a different stand for parking: (1) disembarking in a frontal or remote stand; (2) temporary parking in a towing stand; (3) tow back to a frontal or remote stand for embarking 1 hour before STD.
46 |
47 |
48 | # Model result
49 | The model was built with **four versions**. Why? Because after running the model for the first time, it could not find a feasible solution that satisfy the **hard-rule of 25-minutes interval** in-between each turn in the parking stand.
50 |
51 | So for **2nd model version**, the model found out that the existing resource of parking stand input can only satisfy the constraints by setting **14-minutes as buffer time at most**. However, this is not what the final answer that the airport management was looking for.
52 |
53 | For the **3rd model version**, a feasible solution could be reached when the flight schedule related to "Green Zone" (the zone exclusively designed for handling mainland China flights) are removed. In other words, it appears that the issue lied in **the question of input stand resources in "Green Zone"**.
54 |
55 | In our **final model version**, three more remote stands (namely W121-123) were added for "Green Zone", holding all other original constraints constant. The final result of stand allocation of turns is presented in **Gantt Chart for data visualization**:
56 | https://marco-cheung.github.io/airport-gate-assignment-problem
57 |
58 |
59 |
60 | *Thanks to @Ilia Karmanov's Github Repo for coding inspiration.
61 |
--------------------------------------------------------------------------------
/3rd_model_run/Model_3b_non-green-green.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Mixed Integer Linear Programming (MILP) Model for solving airport gate assignment problem"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "### **Part 1 - Flight-to-Gate Assignment Rules**\n",
15 | "### **Part 2 - Model formulation and result**\n",
16 | "### **Part 3 - Visualization of gate assignment result using Gantt Chart**"
17 | ]
18 | },
19 | {
20 | "cell_type": "markdown",
21 | "metadata": {
22 | "tags": []
23 | },
24 | "source": [
25 | "#### *Part 1*\n",
26 | "#### Install and import required libraries"
27 | ]
28 | },
29 | {
30 | "cell_type": "code",
31 | "execution_count": 1,
32 | "metadata": {
33 | "tags": []
34 | },
35 | "outputs": [
36 | {
37 | "name": "stdout",
38 | "output_type": "stream",
39 | "text": [
40 | "Collecting pulp\n",
41 | " Using cached PuLP-2.6.0-py3-none-any.whl (14.2 MB)\n",
42 | "Installing collected packages: pulp\n",
43 | "Successfully installed pulp-2.6.0\n",
44 | "Collecting openpyxl\n",
45 | " Using cached openpyxl-3.0.10-py2.py3-none-any.whl (242 kB)\n",
46 | "Collecting et-xmlfile\n",
47 | " Using cached et_xmlfile-1.1.0-py3-none-any.whl (4.7 kB)\n",
48 | "Installing collected packages: et-xmlfile, openpyxl\n",
49 | "Successfully installed et-xmlfile-1.1.0 openpyxl-3.0.10\n",
50 | "Requirement already satisfied: plotly in /home/jupyter/.local/lib/python3.7/site-packages (5.8.0)\n",
51 | "Requirement already satisfied: tenacity>=6.2.0 in /opt/conda/lib/python3.7/site-packages (from plotly) (8.0.1)\n"
52 | ]
53 | }
54 | ],
55 | "source": [
56 | "!pip install pulp #open source Linear programming (LP) modeler\n",
57 | "!pip install openpyxl\n",
58 | "!pip install plotly --user\n",
59 | "\n",
60 | "import pandas as pd\n",
61 | "import numpy as np\n",
62 | "from pulp import *"
63 | ]
64 | },
65 | {
66 | "cell_type": "markdown",
67 | "metadata": {},
68 | "source": [
69 | "#### Each flight turn can only assign to a compatible parking stand"
70 | ]
71 | },
72 | {
73 | "cell_type": "code",
74 | "execution_count": 2,
75 | "metadata": {},
76 | "outputs": [],
77 | "source": [
78 | "#import hard-rule such that the type of the aircraft need to match with stand size constraints\n",
79 | "aircraft_allowed_stands = pd.read_excel(\"ac_types_allowed_stands.xlsx\", converters={'aircraft_code':str}, sheet_name = 'Stands for Boarding')"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": 3,
85 | "metadata": {},
86 | "outputs": [
87 | {
88 | "data": {
89 | "text/html": [
90 | "
\n",
91 | "\n",
104 | "
\n",
105 | " \n",
106 | " \n",
107 | " | \n",
108 | " aircraft_code | \n",
109 | " allowed_stand | \n",
110 | " code_ref | \n",
111 | " served_zone | \n",
112 | "
\n",
113 | " \n",
114 | " \n",
115 | " \n",
116 | " | 0 | \n",
117 | " 388 | \n",
118 | " N5 | \n",
119 | " IATA_code | \n",
120 | " Green | \n",
121 | "
\n",
122 | " \n",
123 | " | 1 | \n",
124 | " 351 | \n",
125 | " N6 | \n",
126 | " IATA_code | \n",
127 | " Green | \n",
128 | "
\n",
129 | " \n",
130 | " | 2 | \n",
131 | " 351 | \n",
132 | " N7 | \n",
133 | " IATA_code | \n",
134 | " Green | \n",
135 | "
\n",
136 | " \n",
137 | " | 3 | \n",
138 | " 351 | \n",
139 | " N9 | \n",
140 | " IATA_code | \n",
141 | " Green | \n",
142 | "
\n",
143 | " \n",
144 | " | 4 | \n",
145 | " 351 | \n",
146 | " N141 | \n",
147 | " IATA_code | \n",
148 | " Green | \n",
149 | "
\n",
150 | " \n",
151 | " | ... | \n",
152 | " ... | \n",
153 | " ... | \n",
154 | " ... | \n",
155 | " ... | \n",
156 | "
\n",
157 | " \n",
158 | " | 6627 | \n",
159 | " 787 | \n",
160 | " S111 | \n",
161 | " AODB_code | \n",
162 | " towing | \n",
163 | "
\n",
164 | " \n",
165 | " | 6628 | \n",
166 | " 777 | \n",
167 | " S111 | \n",
168 | " AODB_code | \n",
169 | " towing | \n",
170 | "
\n",
171 | " \n",
172 | " | 6629 | \n",
173 | " 748 | \n",
174 | " S111 | \n",
175 | " AODB_code | \n",
176 | " towing | \n",
177 | "
\n",
178 | " \n",
179 | " | 6630 | \n",
180 | " 747 | \n",
181 | " S111 | \n",
182 | " AODB_code | \n",
183 | " towing | \n",
184 | "
\n",
185 | " \n",
186 | " | 6631 | \n",
187 | " 757 | \n",
188 | " S111 | \n",
189 | " AODB_code | \n",
190 | " towing | \n",
191 | "
\n",
192 | " \n",
193 | "
\n",
194 | "
6632 rows × 4 columns
\n",
195 | "
"
196 | ],
197 | "text/plain": [
198 | " aircraft_code allowed_stand code_ref served_zone\n",
199 | "0 388 N5 IATA_code Green\n",
200 | "1 351 N6 IATA_code Green\n",
201 | "2 351 N7 IATA_code Green\n",
202 | "3 351 N9 IATA_code Green\n",
203 | "4 351 N141 IATA_code Green\n",
204 | "... ... ... ... ...\n",
205 | "6627 787 S111 AODB_code towing\n",
206 | "6628 777 S111 AODB_code towing\n",
207 | "6629 748 S111 AODB_code towing\n",
208 | "6630 747 S111 AODB_code towing\n",
209 | "6631 757 S111 AODB_code towing\n",
210 | "\n",
211 | "[6632 rows x 4 columns]"
212 | ]
213 | },
214 | "execution_count": 3,
215 | "metadata": {},
216 | "output_type": "execute_result"
217 | }
218 | ],
219 | "source": [
220 | "#import list of stands available for towing\n",
221 | "towing_stands = pd.read_excel(\"ac_types_allowed_stands.xlsx\", converters={'aircraft_code':str}, sheet_name = 'Stands for Towing')\n",
222 | "stands_for_towing = towing_stands['allowed_stand'].to_list()\n",
223 | "\n",
224 | "aircraft_allowed_stands = aircraft_allowed_stands.append(towing_stands, ignore_index = True)\n",
225 | "aircraft_allowed_stands"
226 | ]
227 | },
228 | {
229 | "cell_type": "markdown",
230 | "metadata": {},
231 | "source": [
232 | "#### Due to measures of segregated handling of passengers from different places at the airport, the authority has been handling flights from/to places outside Mainland only in \"Orange Zone\". For flights from/to Mainland, they will only be allowed to park in \"Green Zone\""
233 | ]
234 | },
235 | {
236 | "cell_type": "code",
237 | "execution_count": 4,
238 | "metadata": {},
239 | "outputs": [],
240 | "source": [
241 | "#Orange Zone: arrival_aircraft_allowed_stands\n",
242 | "oz_arrival_aircraft_allowed_stands = aircraft_allowed_stands.loc[aircraft_allowed_stands['served_zone'].isin([\"Orange\",\"Green_Orange\"])]\n",
243 | "oz_arrival_stand_list = list(oz_arrival_aircraft_allowed_stands['allowed_stand'].unique())\n",
244 | "\n",
245 | "#Orange Zone: departure_aircraft_allowed_stands, excl. S1-S4 where = \"Green_Orange\"\n",
246 | "oz_departure_aircraft_allowed_stands = aircraft_allowed_stands.loc[aircraft_allowed_stands['served_zone'] == \"Orange\"]\n",
247 | "oz_departure_stand_list = list(oz_departure_aircraft_allowed_stands['allowed_stand'].unique())\n",
248 | "\n",
249 | "#Green Zone: arrival_aircraft_allowed_stands\n",
250 | "#N5-N9; (Remote) N141-N145\n",
251 | "gz_arrival_aircraft_allowed_stands = aircraft_allowed_stands.loc[aircraft_allowed_stands['served_zone'] == \"Green\"]\n",
252 | "gz_arrival_stand_list = list(gz_arrival_aircraft_allowed_stands['allowed_stand'].unique())\n",
253 | "\n",
254 | "#Green Zone: departure_aircraft_allowed_stands\n",
255 | "#N5-N9; (Remote) N141-N145; S1-S4\n",
256 | "gz_departure_aircraft_allowed_stands = aircraft_allowed_stands.loc[aircraft_allowed_stands['served_zone'].isin([\"Green\",\"Green_Orange\"])]\n",
257 | "gz_departure_stand_list = list(gz_departure_aircraft_allowed_stands['allowed_stand'].unique())"
258 | ]
259 | },
260 | {
261 | "cell_type": "markdown",
262 | "metadata": {},
263 | "source": [
264 | "#### Import flight schedule raw data"
265 | ]
266 | },
267 | {
268 | "cell_type": "code",
269 | "execution_count": 5,
270 | "metadata": {},
271 | "outputs": [],
272 | "source": [
273 | "#import flight schedule data\n",
274 | "turns = pd.read_excel(\"20Nov (Linked Flight).xlsx\", converters={'Actyp':str})\n",
275 | "\n",
276 | "#Add unique id for each flight record\n",
277 | "turns.insert(0, 'turn_no', range(1, 1 + len(turns)))"
278 | ]
279 | },
280 | {
281 | "cell_type": "code",
282 | "execution_count": 6,
283 | "metadata": {},
284 | "outputs": [
285 | {
286 | "data": {
287 | "text/html": [
288 | "\n",
289 | "\n",
302 | "
\n",
303 | " \n",
304 | " \n",
305 | " | \n",
306 | " turn_no | \n",
307 | " OpeName | \n",
308 | " Airl.Desig | \n",
309 | " A Fltno | \n",
310 | " Arr Time | \n",
311 | " D Fltno | \n",
312 | " Dep Time | \n",
313 | " Actyp | \n",
314 | " Seats | \n",
315 | " Serv.type | \n",
316 | " ... | \n",
317 | " Next | \n",
318 | " Dest | \n",
319 | " Next City Served | \n",
320 | " Next Country | \n",
321 | " Arrival Zone | \n",
322 | " Departure Zone | \n",
323 | " Zone Pair | \n",
324 | " arrival_zone | \n",
325 | " departure_zone | \n",
326 | " from_to_same_zone | \n",
327 | "
\n",
328 | " \n",
329 | " \n",
330 | " \n",
331 | " | 0 | \n",
332 | " 1 | \n",
333 | " Sichuan Airlines | \n",
334 | " 3U | \n",
335 | " 3959 | \n",
336 | " 2022-11-20 17:00:00 | \n",
337 | " 3960 | \n",
338 | " 2022-11-20 18:30:00 | \n",
339 | " 330 | \n",
340 | " 305 | \n",
341 | " J | \n",
342 | " ... | \n",
343 | " CTU | \n",
344 | " CTU | \n",
345 | " Chengdu | \n",
346 | " CN | \n",
347 | " Green | \n",
348 | " Green | \n",
349 | " G-G | \n",
350 | " Green | \n",
351 | " Green | \n",
352 | " 1 | \n",
353 | "
\n",
354 | " \n",
355 | " | 1 | \n",
356 | " 2 | \n",
357 | " Fly Gangwon | \n",
358 | " 4V | \n",
359 | " 241 | \n",
360 | " 2022-11-20 00:50:00 | \n",
361 | " 242 | \n",
362 | " 2022-11-20 02:05:00 | \n",
363 | " 738 | \n",
364 | " 186 | \n",
365 | " J | \n",
366 | " ... | \n",
367 | " YNY | \n",
368 | " YNY | \n",
369 | " Yangyang | \n",
370 | " KR | \n",
371 | " Orange | \n",
372 | " Orange | \n",
373 | " O-O | \n",
374 | " Orange | \n",
375 | " Orange | \n",
376 | " 1 | \n",
377 | "
\n",
378 | " \n",
379 | " | 2 | \n",
380 | " 3 | \n",
381 | " Cebu Pacific | \n",
382 | " 5J | \n",
383 | " 272 | \n",
384 | " 2022-11-20 08:10:00 | \n",
385 | " 273 | \n",
386 | " 2022-11-20 09:25:00 | \n",
387 | " 333 | \n",
388 | " 436 | \n",
389 | " J | \n",
390 | " ... | \n",
391 | " MNL | \n",
392 | " MNL | \n",
393 | " Manila | \n",
394 | " PH | \n",
395 | " Orange | \n",
396 | " Orange | \n",
397 | " O-O | \n",
398 | " Orange | \n",
399 | " Orange | \n",
400 | " 1 | \n",
401 | "
\n",
402 | " \n",
403 | " | 3 | \n",
404 | " 4 | \n",
405 | " Cebu Pacific | \n",
406 | " 5J | \n",
407 | " 110 | \n",
408 | " 2022-11-20 09:45:00 | \n",
409 | " 111 | \n",
410 | " 2022-11-20 11:00:00 | \n",
411 | " 339 | \n",
412 | " 459 | \n",
413 | " J | \n",
414 | " ... | \n",
415 | " MNL | \n",
416 | " MNL | \n",
417 | " Manila | \n",
418 | " PH | \n",
419 | " Orange | \n",
420 | " Orange | \n",
421 | " O-O | \n",
422 | " Orange | \n",
423 | " Orange | \n",
424 | " 1 | \n",
425 | "
\n",
426 | " \n",
427 | " | 4 | \n",
428 | " 5 | \n",
429 | " Cebu Pacific | \n",
430 | " 5J | \n",
431 | " 112 | \n",
432 | " 2022-11-20 18:00:00 | \n",
433 | " 113 | \n",
434 | " 2022-11-20 19:15:00 | \n",
435 | " 32Q | \n",
436 | " 236 | \n",
437 | " J | \n",
438 | " ... | \n",
439 | " MNL | \n",
440 | " MNL | \n",
441 | " Manila | \n",
442 | " PH | \n",
443 | " Orange | \n",
444 | " Orange | \n",
445 | " O-O | \n",
446 | " Orange | \n",
447 | " Orange | \n",
448 | " 1 | \n",
449 | "
\n",
450 | " \n",
451 | "
\n",
452 | "
5 rows × 24 columns
\n",
453 | "
"
454 | ],
455 | "text/plain": [
456 | " turn_no OpeName Airl.Desig A Fltno Arr Time D Fltno \\\n",
457 | "0 1 Sichuan Airlines 3U 3959 2022-11-20 17:00:00 3960 \n",
458 | "1 2 Fly Gangwon 4V 241 2022-11-20 00:50:00 242 \n",
459 | "2 3 Cebu Pacific 5J 272 2022-11-20 08:10:00 273 \n",
460 | "3 4 Cebu Pacific 5J 110 2022-11-20 09:45:00 111 \n",
461 | "4 5 Cebu Pacific 5J 112 2022-11-20 18:00:00 113 \n",
462 | "\n",
463 | " Dep Time Actyp Seats Serv.type ... Next Dest Next City Served \\\n",
464 | "0 2022-11-20 18:30:00 330 305 J ... CTU CTU Chengdu \n",
465 | "1 2022-11-20 02:05:00 738 186 J ... YNY YNY Yangyang \n",
466 | "2 2022-11-20 09:25:00 333 436 J ... MNL MNL Manila \n",
467 | "3 2022-11-20 11:00:00 339 459 J ... MNL MNL Manila \n",
468 | "4 2022-11-20 19:15:00 32Q 236 J ... MNL MNL Manila \n",
469 | "\n",
470 | " Next Country Arrival Zone Departure Zone Zone Pair arrival_zone \\\n",
471 | "0 CN Green Green G-G Green \n",
472 | "1 KR Orange Orange O-O Orange \n",
473 | "2 PH Orange Orange O-O Orange \n",
474 | "3 PH Orange Orange O-O Orange \n",
475 | "4 PH Orange Orange O-O Orange \n",
476 | "\n",
477 | " departure_zone from_to_same_zone \n",
478 | "0 Green 1 \n",
479 | "1 Orange 1 \n",
480 | "2 Orange 1 \n",
481 | "3 Orange 1 \n",
482 | "4 Orange 1 \n",
483 | "\n",
484 | "[5 rows x 24 columns]"
485 | ]
486 | },
487 | "execution_count": 6,
488 | "metadata": {},
489 | "output_type": "execute_result"
490 | }
491 | ],
492 | "source": [
493 | "#Identify Zone Group at the times of arrival and departure\n",
494 | "turns['arrival_zone'] = turns['Last Country'].apply(lambda x: 'Green' if x=='CN' else 'Orange')\n",
495 | "turns['departure_zone'] = turns['Next Country'].apply(lambda x: 'Green' if x=='CN' else 'Orange')\n",
496 | "turns['from_to_same_zone'] = np.where((turns['arrival_zone'] == turns['departure_zone']), 1, 0)\n",
497 | "turns.head()"
498 | ]
499 | },
500 | {
501 | "cell_type": "markdown",
502 | "metadata": {},
503 | "source": [
504 | "#### Towing protocol will be applied for the following cases:\n",
505 | "#### 1) Over 6 hours of ground time will be towed away from the stand\n",
506 | "#### 2) The pair of flights do not correspond to the same zone (i.e. Green-Orange or Orange-Green)"
507 | ]
508 | },
509 | {
510 | "cell_type": "code",
511 | "execution_count": 7,
512 | "metadata": {},
513 | "outputs": [],
514 | "source": [
515 | "towing_threshold_hours = 6\n",
516 | "\n",
517 | "# #Towing plane indicator\n",
518 | "turns['ground_time'] = (turns['Dep Time'] - turns['Arr Time']).astype('timedelta64[h]')\n",
519 | "turns['towing_required'] = np.where((turns['ground_time'] > towing_threshold_hours) | (turns['from_to_same_zone'] == 0), 1, 0)\n",
520 | "\n",
521 | "# Filter on eligible turns for towing and duplicate\n",
522 | "eligible_for_tow = turns[turns['towing_required'] ==1]\n",
523 | "eligible_for_tow = eligible_for_tow.append(eligible_for_tow, ignore_index=True)\n",
524 | "\n",
525 | "# Add back to original\n",
526 | "turns = turns.append(eligible_for_tow, ignore_index=True)\n",
527 | "\n",
528 | "# Turn_part\n",
529 | "turns['turn_part'] = turns.groupby('turn_no')['turn_no'].rank(method='first')\n",
530 | "\n",
531 | "# Turn_part = 0 if no tows\n",
532 | "cnt_parts = turns.groupby(\"turn_no\")[\"turn_no\"].transform('count')\n",
533 | "turns.loc[cnt_parts==1, \"turn_part\"] = 0\n",
534 | "\n",
535 | "# Sort\n",
536 | "turns.sort_values(['turn_no', 'turn_part'], inplace=True)"
537 | ]
538 | },
539 | {
540 | "cell_type": "markdown",
541 | "metadata": {},
542 | "source": [
543 | "#### For towing plane, add additional 90 mins on top of ATD to cater for boarding, departing the plane and the physical process of towing. Besides, the aircraft needs to tow back to the compatible stand to prepare for embarking 1 hour prior to its STD"
544 | ]
545 | },
546 | {
547 | "cell_type": "code",
548 | "execution_count": 8,
549 | "metadata": {},
550 | "outputs": [],
551 | "source": [
552 | "# Part 1\n",
553 | "turns.loc[turns.turn_part==1, \"Dep Time\"] = turns['Arr Time'] + pd.offsets.Minute(90)\n",
554 | "# Part 2\n",
555 | "turns.loc[turns.turn_part==2, \"Arr Time\"] = turns['Arr Time'] + pd.offsets.Minute(90)\n",
556 | "turns.loc[turns.turn_part==2, \"Dep Time\"] = turns['Dep Time'] - pd.offsets.Minute(60)\n",
557 | "\n",
558 | "# Part 3\n",
559 | "turns.loc[turns.turn_part==3, \"Arr Time\"] = turns['Dep Time'] - pd.offsets.Hour(1)"
560 | ]
561 | },
562 | {
563 | "cell_type": "code",
564 | "execution_count": 9,
565 | "metadata": {},
566 | "outputs": [],
567 | "source": [
568 | "# Create new-id and reset columns\n",
569 | "turns.reset_index(drop=True, inplace=True)\n",
570 | "turns.rename(columns={'turn_no': 'original_turn'}, inplace=True)\n",
571 | "turns['turn_no'] = turns.index"
572 | ]
573 | },
574 | {
575 | "cell_type": "markdown",
576 | "metadata": {},
577 | "source": [
578 | "#### Exclude turns whose zone pair is \"Green-Green\" (i.e. Origin-Destination is Mainland China region)"
579 | ]
580 | },
581 | {
582 | "cell_type": "code",
583 | "execution_count": 10,
584 | "metadata": {},
585 | "outputs": [],
586 | "source": [
587 | "turns = turns.loc[turns['Zone Pair']!=\"G-G\"].reset_index(drop=True)\n",
588 | "turns['turn_no'] = turns.index"
589 | ]
590 | },
591 | {
592 | "cell_type": "markdown",
593 | "metadata": {},
594 | "source": [
595 | "#### On grounds of airfield safety operation, at least 25 minutes interval (“time gap”) in-between slot of each aircraft occupied in a parking stand. For each parking stand, therefore, no flight-to-gate assignment exercise shall be made 12.5 minutes before the scheduled time of arrival (STA) of arriving flights until 12.5 minutes after the scheduled time of departure (STD) of departing flights"
596 | ]
597 | },
598 | {
599 | "cell_type": "code",
600 | "execution_count": 11,
601 | "metadata": {},
602 | "outputs": [],
603 | "source": [
604 | "#Set 25 mins as a buffer time of stand allocation slot in-between each turn in a parking stand\n",
605 | "#From 12.5 minutes before the scheduled time of arrival (STA) until 12.5 minutes after the scheduled time of departure (STD)\n",
606 | "min_buffer_threshold = 750\n",
607 | "\n",
608 | "turns['occupied_stand_from'] = turns['Arr Time']\n",
609 | "turns['occupied_stand_to'] = turns['Dep Time']\n",
610 | "\n",
611 | "turns.loc[turns.turn_part!=2, \"occupied_stand_from\"] = turns['occupied_stand_from'] - pd.offsets.Second(min_buffer_threshold)\n",
612 | "turns.loc[turns.turn_part!=2, \"occupied_stand_to\"] = turns['occupied_stand_to'] + pd.offsets.Second(min_buffer_threshold)"
613 | ]
614 | },
615 | {
616 | "cell_type": "code",
617 | "execution_count": 12,
618 | "metadata": {},
619 | "outputs": [],
620 | "source": [
621 | "stand_list = pd.Series(aircraft_allowed_stands['allowed_stand']).unique()\n",
622 | "turn_list = turns['turn_no'].to_numpy()"
623 | ]
624 | },
625 | {
626 | "cell_type": "markdown",
627 | "metadata": {},
628 | "source": [
629 | "#### Create a dictionary to store values of compatible stand values for each turn"
630 | ]
631 | },
632 | {
633 | "cell_type": "code",
634 | "execution_count": 13,
635 | "metadata": {},
636 | "outputs": [],
637 | "source": [
638 | "compatible_stands = {}\n",
639 | "for idx, row in turns.iterrows():\n",
640 | " gates_lst = aircraft_allowed_stands[aircraft_allowed_stands.aircraft_code == row.Actyp].allowed_stand.to_numpy()\n",
641 | " for g in gates_lst:\n",
642 | " if (row['arrival_zone'] == 'Green') and (row['turn_part'] in (0,1)):\n",
643 | " gates_lst = [ele for ele in gates_lst if ele in gz_arrival_stand_list]\n",
644 | " \n",
645 | " if (row['arrival_zone'] == 'Orange') and (row['turn_part'] in (0,1)):\n",
646 | " gates_lst = [ele for ele in gates_lst if ele in oz_arrival_stand_list]\n",
647 | " \n",
648 | " if (row['departure_zone'] == 'Green') and (row['turn_part'] ==3):\n",
649 | " gates_lst = [ele for ele in gates_lst if ele in gz_departure_stand_list]\n",
650 | " \n",
651 | " if (row['departure_zone'] == 'Orange') and (row['turn_part'] ==3):\n",
652 | " gates_lst = [ele for ele in gates_lst if ele in oz_departure_stand_list]\n",
653 | " \n",
654 | " if row['turn_part'] == 2:\n",
655 | " gates_lst = [ele for ele in gates_lst if ele in stands_for_towing]\n",
656 | " \n",
657 | " compatible_stands[row.turn_no] = np.array(gates_lst)\n",
658 | "\n",
659 | "# print(\"Compatible stands for each turn:\")\n",
660 | "# for k, v in compatible_stands.items():\n",
661 | "# print(k, v)"
662 | ]
663 | },
664 | {
665 | "cell_type": "markdown",
666 | "metadata": {},
667 | "source": [
668 | "#### Parking stands can serve more than one turn in a day if they don't overlap.\n",
669 | "#### Set a 5-minute interval as index of time-series ranging from STA of first flight and STD of the last flight. Columns are binary variables to indicate whether the turn is at the airport"
670 | ]
671 | },
672 | {
673 | "cell_type": "code",
674 | "execution_count": 14,
675 | "metadata": {
676 | "collapsed": false,
677 | "jupyter": {
678 | "outputs_hidden": false
679 | }
680 | },
681 | "outputs": [
682 | {
683 | "data": {
684 | "text/html": [
685 | "\n",
686 | "\n",
699 | "
\n",
700 | " \n",
701 | " \n",
702 | " | \n",
703 | " 0 | \n",
704 | " 1 | \n",
705 | " 2 | \n",
706 | " 3 | \n",
707 | " 4 | \n",
708 | " 5 | \n",
709 | " 6 | \n",
710 | " 7 | \n",
711 | " 8 | \n",
712 | " 9 | \n",
713 | " ... | \n",
714 | " 339 | \n",
715 | " 340 | \n",
716 | " 341 | \n",
717 | " 342 | \n",
718 | " 343 | \n",
719 | " 344 | \n",
720 | " 345 | \n",
721 | " 346 | \n",
722 | " 347 | \n",
723 | " 348 | \n",
724 | "
\n",
725 | " \n",
726 | " \n",
727 | " \n",
728 | " | 2022-11-18 21:52:30 | \n",
729 | " 0 | \n",
730 | " 0 | \n",
731 | " 0 | \n",
732 | " 0 | \n",
733 | " 0 | \n",
734 | " 0 | \n",
735 | " 0 | \n",
736 | " 0 | \n",
737 | " 0 | \n",
738 | " 0 | \n",
739 | " ... | \n",
740 | " 0 | \n",
741 | " 0 | \n",
742 | " 0 | \n",
743 | " 0 | \n",
744 | " 0 | \n",
745 | " 0 | \n",
746 | " 0 | \n",
747 | " 0 | \n",
748 | " 0 | \n",
749 | " 0 | \n",
750 | "
\n",
751 | " \n",
752 | " | 2022-11-18 21:57:30 | \n",
753 | " 0 | \n",
754 | " 0 | \n",
755 | " 0 | \n",
756 | " 0 | \n",
757 | " 0 | \n",
758 | " 0 | \n",
759 | " 0 | \n",
760 | " 0 | \n",
761 | " 0 | \n",
762 | " 0 | \n",
763 | " ... | \n",
764 | " 0 | \n",
765 | " 0 | \n",
766 | " 0 | \n",
767 | " 0 | \n",
768 | " 0 | \n",
769 | " 0 | \n",
770 | " 0 | \n",
771 | " 0 | \n",
772 | " 0 | \n",
773 | " 0 | \n",
774 | "
\n",
775 | " \n",
776 | " | 2022-11-18 22:02:30 | \n",
777 | " 0 | \n",
778 | " 0 | \n",
779 | " 0 | \n",
780 | " 0 | \n",
781 | " 0 | \n",
782 | " 0 | \n",
783 | " 0 | \n",
784 | " 0 | \n",
785 | " 0 | \n",
786 | " 0 | \n",
787 | " ... | \n",
788 | " 0 | \n",
789 | " 0 | \n",
790 | " 0 | \n",
791 | " 0 | \n",
792 | " 0 | \n",
793 | " 0 | \n",
794 | " 0 | \n",
795 | " 0 | \n",
796 | " 0 | \n",
797 | " 0 | \n",
798 | "
\n",
799 | " \n",
800 | " | 2022-11-18 22:07:30 | \n",
801 | " 0 | \n",
802 | " 0 | \n",
803 | " 0 | \n",
804 | " 0 | \n",
805 | " 0 | \n",
806 | " 0 | \n",
807 | " 0 | \n",
808 | " 0 | \n",
809 | " 0 | \n",
810 | " 0 | \n",
811 | " ... | \n",
812 | " 0 | \n",
813 | " 0 | \n",
814 | " 0 | \n",
815 | " 0 | \n",
816 | " 0 | \n",
817 | " 0 | \n",
818 | " 0 | \n",
819 | " 0 | \n",
820 | " 0 | \n",
821 | " 0 | \n",
822 | "
\n",
823 | " \n",
824 | " | 2022-11-18 22:12:30 | \n",
825 | " 0 | \n",
826 | " 0 | \n",
827 | " 0 | \n",
828 | " 0 | \n",
829 | " 0 | \n",
830 | " 0 | \n",
831 | " 0 | \n",
832 | " 0 | \n",
833 | " 0 | \n",
834 | " 0 | \n",
835 | " ... | \n",
836 | " 0 | \n",
837 | " 0 | \n",
838 | " 0 | \n",
839 | " 0 | \n",
840 | " 0 | \n",
841 | " 0 | \n",
842 | " 0 | \n",
843 | " 0 | \n",
844 | " 0 | \n",
845 | " 0 | \n",
846 | "
\n",
847 | " \n",
848 | "
\n",
849 | "
5 rows × 349 columns
\n",
850 | "
"
851 | ],
852 | "text/plain": [
853 | " 0 1 2 3 4 5 6 7 8 9 ... \\\n",
854 | "2022-11-18 21:52:30 0 0 0 0 0 0 0 0 0 0 ... \n",
855 | "2022-11-18 21:57:30 0 0 0 0 0 0 0 0 0 0 ... \n",
856 | "2022-11-18 22:02:30 0 0 0 0 0 0 0 0 0 0 ... \n",
857 | "2022-11-18 22:07:30 0 0 0 0 0 0 0 0 0 0 ... \n",
858 | "2022-11-18 22:12:30 0 0 0 0 0 0 0 0 0 0 ... \n",
859 | "\n",
860 | " 339 340 341 342 343 344 345 346 347 348 \n",
861 | "2022-11-18 21:52:30 0 0 0 0 0 0 0 0 0 0 \n",
862 | "2022-11-18 21:57:30 0 0 0 0 0 0 0 0 0 0 \n",
863 | "2022-11-18 22:02:30 0 0 0 0 0 0 0 0 0 0 \n",
864 | "2022-11-18 22:07:30 0 0 0 0 0 0 0 0 0 0 \n",
865 | "2022-11-18 22:12:30 0 0 0 0 0 0 0 0 0 0 \n",
866 | "\n",
867 | "[5 rows x 349 columns]"
868 | ]
869 | },
870 | "execution_count": 14,
871 | "metadata": {},
872 | "output_type": "execute_result"
873 | }
874 | ],
875 | "source": [
876 | "# Using discrete time-buckets\n",
877 | "min_bucket=5\n",
878 | "\n",
879 | "# Create time-series between arrival of first plane and departure of last\n",
880 | "time_series = pd.Series(True, index= pd.date_range(\n",
881 | " start=turns['occupied_stand_from'].min(),\n",
882 | " end=turns['occupied_stand_to'].max(),\n",
883 | " freq=pd.offsets.Minute(min_bucket)))\n",
884 | " \n",
885 | "# Truncate full time-series to [STA, STD]\n",
886 | "def trunc_ts(series):\n",
887 | " return time_series.truncate(series['occupied_stand_from'], series['occupied_stand_to'])\n",
888 | " \n",
889 | "stand_occupacy_df = turns.apply(trunc_ts, axis=1).T\n",
890 | " \n",
891 | "# Convert columns from index to turn_no\n",
892 | "stand_occupacy_df.columns = turns['turn_no'].values\n",
893 | "\n",
894 | "# Cast to integer\n",
895 | "stand_occupacy_df = stand_occupacy_df.fillna(0).astype(int)\n",
896 | "stand_occupacy_df.head()"
897 | ]
898 | },
899 | {
900 | "cell_type": "code",
901 | "execution_count": 15,
902 | "metadata": {},
903 | "outputs": [],
904 | "source": [
905 | "# Only care about overlaps\n",
906 | "# If the stand only has one turn then don't need constraint that it must have one turn ...\n",
907 | "stand_occupacy_df['tot'] = stand_occupacy_df.sum(axis=1)\n",
908 | "stand_occupacy_df = stand_occupacy_df[stand_occupacy_df.tot > 1]\n",
909 | "stand_occupacy_df.drop(['tot'], axis=1, inplace=True)"
910 | ]
911 | },
912 | {
913 | "cell_type": "markdown",
914 | "metadata": {},
915 | "source": [
916 | "#### *Part 2*\n",
917 | "#### Formulate Model and find the values of variables (i,j) for each turn such that all constraints are satisfied and the objective function is minimized."
918 | ]
919 | },
920 | {
921 | "cell_type": "code",
922 | "execution_count": 16,
923 | "metadata": {},
924 | "outputs": [],
925 | "source": [
926 | "# 0. Initialise model\n",
927 | "prob = LpProblem(\"Flight_Stand_Allocation\", LpMinimize) # minimize cost\n",
928 | "\n",
929 | "# 1. Set the objective function (ignore 0 for now)\n",
930 | "prob += 0\n",
931 | "\n",
932 | "# 2. Define Variable: x[i,j] = (0,1)\n",
933 | "# Binary = turn_i allocated to gate_j\n",
934 | "x = {}\n",
935 | "for t in turn_list:\n",
936 | " for g in compatible_stands[t]:\n",
937 | " x[t, g] = LpVariable(\"t%i_g%s\" % (t, g), 0, 1, LpBinary)\n",
938 | "\n",
939 | "# 3. Constraints\n",
940 | "# i. Each turn must be assigned to one compatible stand\n",
941 | "for t in turn_list:\n",
942 | " prob += lpSum(x[t, g] for g in stand_list if (t, g) in x) == 1"
943 | ]
944 | },
945 | {
946 | "cell_type": "code",
947 | "execution_count": 17,
948 | "metadata": {},
949 | "outputs": [],
950 | "source": [
951 | "# ii. Stands cannot serve more than one turn per time_bucket\n",
952 | "for idx, row in stand_occupacy_df.iterrows():\n",
953 | " # Get all the turns for time-bucket\n",
954 | " turns_in_time_bucket = set(dict(row[row==1]).keys())\n",
955 | "\n",
956 | " for g in stand_list:\n",
957 | " # Constraints may be blank\n",
958 | " cons = [x[t, g] for t in turns_in_time_bucket if (t, g) in x]\n",
959 | " if len(cons) > 1:\n",
960 | " constraint_for_time_bucket = lpSum(cons) <= 1\n",
961 | " prob += constraint_for_time_bucket"
962 | ]
963 | },
964 | {
965 | "cell_type": "code",
966 | "execution_count": 18,
967 | "metadata": {},
968 | "outputs": [],
969 | "source": [
970 | "# iii. Tow-constraint\n",
971 | "# Create dictionary for tows of turn_no ({: towing(0]['turn_no'].to_numpy()\n",
977 | "\n",
978 | "# Each towing turn must be assigned to one towing stand\n",
979 | "for tow_turn in tow_turns:\n",
980 | " w[tow_turn] = LpVariable(\"towing(%s)\" % (tow_turn), 0, 1, LpBinary)\n",
981 | " \n",
982 | "# Create towing dictionary to map original turn to new turns (to group them)\n",
983 | "# For example, original_turn id #20 would split into turn_no #19,#20,#21\n",
984 | "tow_dic = {k: g[\"turn_no\"].tolist() for k,g in turns[turns['turn_part']>0].groupby(\"original_turn\")}"
985 | ]
986 | },
987 | {
988 | "cell_type": "markdown",
989 | "metadata": {},
990 | "source": [
991 | "##### For tow-out: prob += x[tow_turns[0], stand] - x[tow_turns[1], stand] <= w[tow_turns[0]]\n",
992 | "##### For tow-in: prob += x[tow_turns[1], stand] - x[tow_turns[2], stand] <= w[tow_turns[1]]"
993 | ]
994 | },
995 | {
996 | "cell_type": "code",
997 | "execution_count": 19,
998 | "metadata": {},
999 | "outputs": [],
1000 | "source": [
1001 | "for k, v in tow_dic.items():\n",
1002 | " #print(k, v)\n",
1003 | " for stand in stand_list:\n",
1004 | " if ((v[0], stand) in x) and ((v[1], stand) in x):\n",
1005 | " tow_out = x[v[0], stand] - x[v[1], stand] <= w[v[0]]\n",
1006 | " prob += tow_out\n",
1007 | " if ((v[1], stand) in x) and ((v[2], stand) in x):\n",
1008 | " tow_in = x[v[1], stand] - x[v[2], stand] <= w[v[1]]\n",
1009 | " prob += tow_in"
1010 | ]
1011 | },
1012 | {
1013 | "cell_type": "code",
1014 | "execution_count": 20,
1015 | "metadata": {},
1016 | "outputs": [
1017 | {
1018 | "name": "stderr",
1019 | "output_type": "stream",
1020 | "text": [
1021 | "/opt/conda/lib/python3.7/site-packages/pulp/pulp.py:1704: UserWarning: Overwriting previously set objective.\n",
1022 | " warnings.warn(\"Overwriting previously set objective.\")\n"
1023 | ]
1024 | }
1025 | ],
1026 | "source": [
1027 | "# Add towing objective with positive cost\n",
1028 | "pos_cost_coefficient = 100\n",
1029 | "tow_objective = lpSum(pos_cost_coefficient*w[t] for t in w)\n",
1030 | "\n",
1031 | "prob += tow_objective"
1032 | ]
1033 | },
1034 | {
1035 | "cell_type": "markdown",
1036 | "metadata": {},
1037 | "source": [
1038 | "#### Assignment preference: 1st priority: Frontal Stand; (2) Remote Stand (N141-N145)"
1039 | ]
1040 | },
1041 | {
1042 | "cell_type": "code",
1043 | "execution_count": 21,
1044 | "metadata": {},
1045 | "outputs": [],
1046 | "source": [
1047 | "# Add all turns under remote stands with a negative cost coefficient\n",
1048 | "remote_stands = ['N141','N142','N143','N144','N145']\n",
1049 | "\n",
1050 | "neg_cost_coefficient = -5\n",
1051 | "prob += lpSum(neg_cost_coefficient*x[t, g] for t, g in x if g not in remote_stands)"
1052 | ]
1053 | },
1054 | {
1055 | "cell_type": "markdown",
1056 | "metadata": {},
1057 | "source": [
1058 | "#### *Part 3*\n",
1059 | "#### Run the Solver Algorithm to find optimal solution. The default solver used by PuLP is the COIN-OR Branch and Cut Solver (CBC)."
1060 | ]
1061 | },
1062 | {
1063 | "cell_type": "code",
1064 | "execution_count": 22,
1065 | "metadata": {},
1066 | "outputs": [
1067 | {
1068 | "name": "stdout",
1069 | "output_type": "stream",
1070 | "text": [
1071 | "Welcome to the CBC MILP Solver \n",
1072 | "Version: 2.10.3 \n",
1073 | "Build Date: Dec 15 2019 \n",
1074 | "\n",
1075 | "command line - /opt/conda/lib/python3.7/site-packages/pulp/apis/../solverdir/cbc/linux/64/cbc /tmp/0d04a86711ae4721ba28519203d7e7b4-pulp.mps timeMode elapsed branch printingOptions all solution /tmp/0d04a86711ae4721ba28519203d7e7b4-pulp.sol (default strategy 1)\n",
1076 | "At line 2 NAME MODEL\n",
1077 | "At line 3 ROWS\n",
1078 | "At line 78844 COLUMNS\n",
1079 | "At line 1349313 RHS\n",
1080 | "At line 1428153 BOUNDS\n",
1081 | "At line 1442579 ENDATA\n",
1082 | "Problem MODEL has 78839 rows, 14425 columns and 1227275 elements\n",
1083 | "Coin0008I MODEL read with 0 errors\n",
1084 | "Option for timeMode changed from cpu to elapsed\n",
1085 | "Continuous objective value is -1745 - 1.85 seconds\n",
1086 | "Cgl0003I 0 fixed, 0 tightened bounds, 11325 strengthened rows, 0 substitutions\n",
1087 | "Cgl0003I 0 fixed, 0 tightened bounds, 7473 strengthened rows, 0 substitutions\n",
1088 | "Cgl0003I 0 fixed, 0 tightened bounds, 5331 strengthened rows, 0 substitutions\n",
1089 | "Cgl0003I 0 fixed, 0 tightened bounds, 3926 strengthened rows, 0 substitutions\n",
1090 | "Cgl0003I 0 fixed, 0 tightened bounds, 2886 strengthened rows, 0 substitutions\n",
1091 | "Cgl0003I 0 fixed, 0 tightened bounds, 2382 strengthened rows, 0 substitutions\n",
1092 | "Cgl0003I 0 fixed, 0 tightened bounds, 2123 strengthened rows, 0 substitutions\n",
1093 | "Cgl0003I 0 fixed, 0 tightened bounds, 1836 strengthened rows, 0 substitutions\n",
1094 | "Cgl0003I 0 fixed, 0 tightened bounds, 1592 strengthened rows, 0 substitutions\n",
1095 | "Cgl0004I processed model has 10190 rows, 14250 columns (14250 integer (14250 of which binary)) and 204960 elements\n",
1096 | "Cutoff increment increased from 1e-05 to 4.9999\n",
1097 | "Cbc0038I Initial state - 289 integers unsatisfied sum - 89.2222\n",
1098 | "Cbc0038I Pass 1: (16.85 seconds) suminf. 0.00000 (0) obj. -1745 iterations 949\n",
1099 | "Cbc0038I Solution found of -1745\n",
1100 | "Cbc0038I Before mini branch and bound, 13949 integers at bound fixed and 3 continuous\n",
1101 | "Cbc0038I Mini branch and bound did not improve solution (16.88 seconds)\n",
1102 | "Cbc0038I After 16.91 seconds - Feasibility pump exiting with objective of -1745 - took 0.19 seconds\n",
1103 | "Cbc0012I Integer solution of -1745 found by feasibility pump after 0 iterations and 0 nodes (16.92 seconds)\n",
1104 | "Cbc0001I Search completed - best objective -1745, took 0 iterations and 0 nodes (16.96 seconds)\n",
1105 | "Cbc0035I Maximum depth 0, 0 variables fixed on reduced cost\n",
1106 | "Cuts at root node changed objective from -1745 to -1745\n",
1107 | "Probing was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)\n",
1108 | "Gomory was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)\n",
1109 | "Knapsack was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)\n",
1110 | "Clique was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)\n",
1111 | "MixedIntegerRounding2 was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)\n",
1112 | "FlowCover was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)\n",
1113 | "TwoMirCuts was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)\n",
1114 | "ZeroHalf was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)\n",
1115 | "\n",
1116 | "Result - Optimal solution found\n",
1117 | "\n",
1118 | "Objective value: -1745.00000000\n",
1119 | "Enumerated nodes: 0\n",
1120 | "Total iterations: 0\n",
1121 | "Time (CPU seconds): 17.07\n",
1122 | "Time (Wallclock seconds): 17.56\n",
1123 | "\n",
1124 | "Option for printingOptions changed from normal to all\n",
1125 | "Total time (CPU seconds): 17.79 (Wallclock seconds): 18.35\n",
1126 | "\n",
1127 | "Status: Optimal\n",
1128 | "Minimised Cost: -1745.0\n",
1129 | "Turn 0 assigned to gate S41\n",
1130 | "Turn 1 assigned to gate S11\n",
1131 | "Turn 2 assigned to gate N68\n",
1132 | "Turn 3 assigned to gate N34\n",
1133 | "Turn 4 assigned to gate N24\n",
1134 | "Turn 5 assigned to gate N28\n",
1135 | "Turn 6 assigned to gate N60\n",
1136 | "Turn 7 assigned to gate S35\n",
1137 | "Turn 8 assigned to gate N70\n",
1138 | "Turn 9 assigned to gate S2\n",
1139 | "Turn 10 assigned to gate W50\n",
1140 | "Turn 11 assigned to gate N24\n",
1141 | "Turn 12 assigned to gate S23\n",
1142 | "Turn 13 assigned to gate D205\n",
1143 | "Turn 14 assigned to gate S23\n",
1144 | "Turn 15 assigned to gate N24\n",
1145 | "Turn 16 assigned to gate D219\n",
1146 | "Turn 17 assigned to gate S25\n",
1147 | "Turn 18 assigned to gate S23\n",
1148 | "Turn 19 assigned to gate D214\n",
1149 | "Turn 20 assigned to gate W67\n",
1150 | "Turn 21 assigned to gate N24\n",
1151 | "Turn 22 assigned to gate D317\n",
1152 | "Turn 23 assigned to gate W40\n",
1153 | "Turn 24 assigned to gate S29\n",
1154 | "Turn 25 assigned to gate D211\n",
1155 | "Turn 26 assigned to gate N64\n",
1156 | "Turn 27 assigned to gate S1\n",
1157 | "Turn 28 assigned to gate D203\n",
1158 | "Turn 29 assigned to gate N30\n",
1159 | "Turn 30 assigned to gate N5\n",
1160 | "Turn 31 assigned to gate D208\n",
1161 | "Turn 32 assigned to gate W63\n",
1162 | "Turn 33 assigned to gate N60\n",
1163 | "Turn 34 assigned to gate N64\n",
1164 | "Turn 35 assigned to gate S102\n",
1165 | "Turn 36 assigned to gate N5\n",
1166 | "Turn 37 assigned to gate W50\n",
1167 | "Turn 38 assigned to gate N26\n",
1168 | "Turn 39 assigned to gate W40\n",
1169 | "Turn 40 assigned to gate M1\n",
1170 | "Turn 41 assigned to gate S27\n",
1171 | "Turn 42 assigned to gate W48\n",
1172 | "Turn 43 assigned to gate S103\n",
1173 | "Turn 44 assigned to gate W65\n",
1174 | "Turn 45 assigned to gate N24\n",
1175 | "Turn 46 assigned to gate M8\n",
1176 | "Turn 47 assigned to gate N64\n",
1177 | "Turn 48 assigned to gate S43\n",
1178 | "Turn 49 assigned to gate S1\n",
1179 | "Turn 50 assigned to gate S41\n",
1180 | "Turn 51 assigned to gate N30\n",
1181 | "Turn 52 assigned to gate N24\n",
1182 | "Turn 53 assigned to gate S3\n",
1183 | "Turn 54 assigned to gate N26\n",
1184 | "Turn 55 assigned to gate S29\n",
1185 | "Turn 56 assigned to gate D201\n",
1186 | "Turn 57 assigned to gate S23\n",
1187 | "Turn 58 assigned to gate S4\n",
1188 | "Turn 59 assigned to gate S31\n",
1189 | "Turn 60 assigned to gate S35\n",
1190 | "Turn 61 assigned to gate D307\n",
1191 | "Turn 62 assigned to gate S45\n",
1192 | "Turn 63 assigned to gate N28\n",
1193 | "Turn 64 assigned to gate D204\n",
1194 | "Turn 65 assigned to gate S33\n",
1195 | "Turn 66 assigned to gate W42\n",
1196 | "Turn 67 assigned to gate M4\n",
1197 | "Turn 68 assigned to gate S29\n",
1198 | "Turn 69 assigned to gate S47\n",
1199 | "Turn 70 assigned to gate S23\n",
1200 | "Turn 71 assigned to gate S109\n",
1201 | "Turn 72 assigned to gate N5\n",
1202 | "Turn 73 assigned to gate W42\n",
1203 | "Turn 74 assigned to gate D217\n",
1204 | "Turn 75 assigned to gate S1\n",
1205 | "Turn 76 assigned to gate N26\n",
1206 | "Turn 77 assigned to gate D219\n",
1207 | "Turn 78 assigned to gate S29\n",
1208 | "Turn 79 assigned to gate N32\n",
1209 | "Turn 80 assigned to gate M5\n",
1210 | "Turn 81 assigned to gate N24\n",
1211 | "Turn 82 assigned to gate S23\n",
1212 | "Turn 83 assigned to gate D203\n",
1213 | "Turn 84 assigned to gate N6\n",
1214 | "Turn 85 assigned to gate S41\n",
1215 | "Turn 86 assigned to gate N5\n",
1216 | "Turn 87 assigned to gate S102\n",
1217 | "Turn 88 assigned to gate N24\n",
1218 | "Turn 89 assigned to gate S31\n",
1219 | "Turn 90 assigned to gate D306\n",
1220 | "Turn 91 assigned to gate W50\n",
1221 | "Turn 92 assigned to gate S25\n",
1222 | "Turn 93 assigned to gate D206\n",
1223 | "Turn 94 assigned to gate W67\n",
1224 | "Turn 95 assigned to gate W42\n",
1225 | "Turn 96 assigned to gate D310\n",
1226 | "Turn 97 assigned to gate N24\n",
1227 | "Turn 98 assigned to gate S33\n",
1228 | "Turn 99 assigned to gate D304\n",
1229 | "Turn 100 assigned to gate S35\n",
1230 | "Turn 101 assigned to gate W61\n",
1231 | "Turn 102 assigned to gate D211\n",
1232 | "Turn 103 assigned to gate S45\n",
1233 | "Turn 104 assigned to gate N7\n",
1234 | "Turn 105 assigned to gate D212\n",
1235 | "Turn 106 assigned to gate S23\n",
1236 | "Turn 107 assigned to gate S1\n",
1237 | "Turn 108 assigned to gate S106\n",
1238 | "Turn 109 assigned to gate S25\n",
1239 | "Turn 110 assigned to gate S2\n",
1240 | "Turn 111 assigned to gate D308\n",
1241 | "Turn 112 assigned to gate N28\n",
1242 | "Turn 113 assigned to gate S33\n",
1243 | "Turn 114 assigned to gate W71\n",
1244 | "Turn 115 assigned to gate D307\n",
1245 | "Turn 116 assigned to gate S33\n",
1246 | "Turn 117 assigned to gate S3\n",
1247 | "Turn 118 assigned to gate M4\n",
1248 | "Turn 119 assigned to gate W42\n",
1249 | "Turn 120 assigned to gate N66\n",
1250 | "Turn 121 assigned to gate S25\n",
1251 | "Turn 122 assigned to gate S49\n",
1252 | "Turn 123 assigned to gate N30\n",
1253 | "Turn 124 assigned to gate S25\n",
1254 | "Turn 125 assigned to gate N30\n",
1255 | "Turn 126 assigned to gate S45\n",
1256 | "Turn 127 assigned to gate S35\n",
1257 | "Turn 128 assigned to gate D218\n",
1258 | "Turn 129 assigned to gate N60\n",
1259 | "Turn 130 assigned to gate W40\n",
1260 | "Turn 131 assigned to gate D206\n",
1261 | "Turn 132 assigned to gate N60\n",
1262 | "Turn 133 assigned to gate N26\n",
1263 | "Turn 134 assigned to gate D319\n",
1264 | "Turn 135 assigned to gate N62\n",
1265 | "Turn 136 assigned to gate S45\n",
1266 | "Turn 137 assigned to gate M3\n",
1267 | "Turn 138 assigned to gate W71\n",
1268 | "Turn 139 assigned to gate S33\n",
1269 | "Turn 140 assigned to gate D311\n",
1270 | "Turn 141 assigned to gate W42\n",
1271 | "Turn 142 assigned to gate R21\n",
1272 | "Turn 143 assigned to gate D203\n",
1273 | "Turn 144 assigned to gate S35\n",
1274 | "Turn 145 assigned to gate N60\n",
1275 | "Turn 146 assigned to gate D207\n",
1276 | "Turn 147 assigned to gate S27\n",
1277 | "Turn 148 assigned to gate W46\n",
1278 | "Turn 149 assigned to gate N26\n",
1279 | "Turn 150 assigned to gate D204\n",
1280 | "Turn 151 assigned to gate N66\n",
1281 | "Turn 152 assigned to gate N32\n",
1282 | "Turn 153 assigned to gate D319\n",
1283 | "Turn 154 assigned to gate N26\n",
1284 | "Turn 155 assigned to gate N30\n",
1285 | "Turn 156 assigned to gate D301\n",
1286 | "Turn 157 assigned to gate N30\n",
1287 | "Turn 158 assigned to gate W42\n",
1288 | "Turn 159 assigned to gate M10\n",
1289 | "Turn 160 assigned to gate N26\n",
1290 | "Turn 161 assigned to gate R19\n",
1291 | "Turn 162 assigned to gate S101\n",
1292 | "Turn 163 assigned to gate N68\n",
1293 | "Turn 164 assigned to gate S35\n",
1294 | "Turn 165 assigned to gate D210\n",
1295 | "Turn 166 assigned to gate N32\n",
1296 | "Turn 167 assigned to gate N6\n",
1297 | "Turn 168 assigned to gate D305\n",
1298 | "Turn 169 assigned to gate S33\n",
1299 | "Turn 170 assigned to gate N24\n",
1300 | "Turn 171 assigned to gate D307\n",
1301 | "Turn 172 assigned to gate N6\n",
1302 | "Turn 173 assigned to gate N70\n",
1303 | "Turn 174 assigned to gate D202\n",
1304 | "Turn 175 assigned to gate N36\n",
1305 | "Turn 176 assigned to gate R16\n",
1306 | "Turn 177 assigned to gate D301\n",
1307 | "Turn 178 assigned to gate S11\n",
1308 | "Turn 179 assigned to gate N24\n",
1309 | "Turn 180 assigned to gate M10\n",
1310 | "Turn 181 assigned to gate N24\n",
1311 | "Turn 182 assigned to gate S3\n",
1312 | "Turn 183 assigned to gate D315\n",
1313 | "Turn 184 assigned to gate S2\n",
1314 | "Turn 185 assigned to gate N28\n",
1315 | "Turn 186 assigned to gate D216\n",
1316 | "Turn 187 assigned to gate S49\n",
1317 | "Turn 188 assigned to gate S27\n",
1318 | "Turn 189 assigned to gate D209\n",
1319 | "Turn 190 assigned to gate N26\n",
1320 | "Turn 191 assigned to gate S1\n",
1321 | "Turn 192 assigned to gate D212\n",
1322 | "Turn 193 assigned to gate N32\n",
1323 | "Turn 194 assigned to gate W44\n",
1324 | "Turn 195 assigned to gate N30\n",
1325 | "Turn 196 assigned to gate N28\n",
1326 | "Turn 197 assigned to gate N36\n",
1327 | "Turn 198 assigned to gate D212\n",
1328 | "Turn 199 assigned to gate N68\n",
1329 | "Turn 200 assigned to gate R21\n",
1330 | "Turn 201 assigned to gate D213\n",
1331 | "Turn 202 assigned to gate N68\n",
1332 | "Turn 203 assigned to gate N6\n",
1333 | "Turn 204 assigned to gate D218\n",
1334 | "Turn 205 assigned to gate R17\n",
1335 | "Turn 206 assigned to gate S1\n",
1336 | "Turn 207 assigned to gate S3\n",
1337 | "Turn 208 assigned to gate D311\n",
1338 | "Turn 209 assigned to gate N8\n",
1339 | "Turn 210 assigned to gate N70\n",
1340 | "Turn 211 assigned to gate D207\n",
1341 | "Turn 212 assigned to gate N28\n",
1342 | "Turn 213 assigned to gate N32\n",
1343 | "Turn 214 assigned to gate M1\n",
1344 | "Turn 215 assigned to gate N36\n",
1345 | "Turn 216 assigned to gate W63\n",
1346 | "Turn 217 assigned to gate D219\n",
1347 | "Turn 218 assigned to gate N60\n",
1348 | "Turn 219 assigned to gate N36\n",
1349 | "Turn 220 assigned to gate D309\n",
1350 | "Turn 221 assigned to gate S23\n",
1351 | "Turn 222 assigned to gate S29\n",
1352 | "Turn 223 assigned to gate S107\n",
1353 | "Turn 224 assigned to gate N34\n",
1354 | "Turn 225 assigned to gate S27\n",
1355 | "Turn 226 assigned to gate D317\n",
1356 | "Turn 227 assigned to gate W40\n",
1357 | "Turn 228 assigned to gate N32\n",
1358 | "Turn 229 assigned to gate N28\n",
1359 | "Turn 230 assigned to gate N36\n",
1360 | "Turn 231 assigned to gate N62\n",
1361 | "Turn 232 assigned to gate W46\n",
1362 | "Turn 233 assigned to gate M5\n",
1363 | "Turn 234 assigned to gate N24\n",
1364 | "Turn 235 assigned to gate S27\n",
1365 | "Turn 236 assigned to gate D314\n",
1366 | "Turn 237 assigned to gate N24\n",
1367 | "Turn 238 assigned to gate N66\n",
1368 | "Turn 239 assigned to gate D204\n",
1369 | "Turn 240 assigned to gate R21\n",
1370 | "Turn 241 assigned to gate N34\n",
1371 | "Turn 242 assigned to gate N70\n",
1372 | "Turn 243 assigned to gate D312\n",
1373 | "Turn 244 assigned to gate R21\n",
1374 | "Turn 245 assigned to gate W63\n",
1375 | "Turn 246 assigned to gate D204\n",
1376 | "Turn 247 assigned to gate N34\n",
1377 | "Turn 248 assigned to gate S2\n",
1378 | "Turn 249 assigned to gate D318\n",
1379 | "Turn 250 assigned to gate W69\n",
1380 | "Turn 251 assigned to gate N28\n",
1381 | "Turn 252 assigned to gate M6\n",
1382 | "Turn 253 assigned to gate N26\n",
1383 | "Turn 254 assigned to gate N68\n",
1384 | "Turn 255 assigned to gate N24\n",
1385 | "Turn 256 assigned to gate D305\n",
1386 | "Turn 257 assigned to gate S23\n",
1387 | "Turn 258 assigned to gate N68\n",
1388 | "Turn 259 assigned to gate R21\n",
1389 | "Turn 260 assigned to gate N68\n",
1390 | "Turn 261 assigned to gate N68\n",
1391 | "Turn 262 assigned to gate N62\n",
1392 | "Turn 263 assigned to gate N36\n",
1393 | "Turn 264 assigned to gate S29\n",
1394 | "Turn 265 assigned to gate S4\n",
1395 | "Turn 266 assigned to gate N36\n",
1396 | "Turn 267 assigned to gate N64\n",
1397 | "Turn 268 assigned to gate N60\n",
1398 | "Turn 269 assigned to gate S49\n",
1399 | "Turn 270 assigned to gate N70\n",
1400 | "Turn 271 assigned to gate S11\n",
1401 | "Turn 272 assigned to gate D309\n",
1402 | "Turn 273 assigned to gate N34\n",
1403 | "Turn 274 assigned to gate N8\n",
1404 | "Turn 275 assigned to gate D302\n",
1405 | "Turn 276 assigned to gate N32\n",
1406 | "Turn 277 assigned to gate N32\n",
1407 | "Turn 278 assigned to gate D308\n",
1408 | "Turn 279 assigned to gate N70\n",
1409 | "Turn 280 assigned to gate S33\n",
1410 | "Turn 281 assigned to gate D310\n",
1411 | "Turn 282 assigned to gate N24\n",
1412 | "Turn 283 assigned to gate S35\n",
1413 | "Turn 284 assigned to gate D306\n",
1414 | "Turn 285 assigned to gate N36\n",
1415 | "Turn 286 assigned to gate S47\n",
1416 | "Turn 287 assigned to gate S104\n",
1417 | "Turn 288 assigned to gate N62\n",
1418 | "Turn 289 assigned to gate N24\n",
1419 | "Turn 290 assigned to gate M7\n",
1420 | "Turn 291 assigned to gate N32\n",
1421 | "Turn 292 assigned to gate N34\n",
1422 | "Turn 293 assigned to gate D312\n",
1423 | "Turn 294 assigned to gate R19\n",
1424 | "Turn 295 assigned to gate N66\n",
1425 | "Turn 296 assigned to gate D210\n",
1426 | "Turn 297 assigned to gate N7\n",
1427 | "Turn 298 assigned to gate N9\n",
1428 | "Turn 299 assigned to gate S111\n",
1429 | "Turn 300 assigned to gate N70\n",
1430 | "Turn 301 assigned to gate N26\n",
1431 | "Turn 302 assigned to gate D313\n",
1432 | "Turn 303 assigned to gate R18\n",
1433 | "Turn 304 assigned to gate N62\n",
1434 | "Turn 305 assigned to gate D316\n",
1435 | "Turn 306 assigned to gate R17\n",
1436 | "Turn 307 assigned to gate W44\n",
1437 | "Turn 308 assigned to gate R17\n",
1438 | "Turn 309 assigned to gate D315\n",
1439 | "Turn 310 assigned to gate N24\n",
1440 | "Turn 311 assigned to gate R16\n",
1441 | "Turn 312 assigned to gate N62\n",
1442 | "Turn 313 assigned to gate M8\n",
1443 | "Turn 314 assigned to gate N6\n",
1444 | "Turn 315 assigned to gate N30\n",
1445 | "Turn 316 assigned to gate D209\n",
1446 | "Turn 317 assigned to gate R20\n",
1447 | "Turn 318 assigned to gate R18\n",
1448 | "Turn 319 assigned to gate D216\n",
1449 | "Turn 320 assigned to gate S31\n",
1450 | "Turn 321 assigned to gate R17\n",
1451 | "Turn 322 assigned to gate M7\n",
1452 | "Turn 323 assigned to gate S11\n",
1453 | "Turn 324 assigned to gate R16\n",
1454 | "Turn 325 assigned to gate D202\n",
1455 | "Turn 326 assigned to gate S49\n",
1456 | "Turn 327 assigned to gate R17\n",
1457 | "Turn 328 assigned to gate D214\n",
1458 | "Turn 329 assigned to gate R18\n",
1459 | "Turn 330 assigned to gate R18\n",
1460 | "Turn 331 assigned to gate S105\n",
1461 | "Turn 332 assigned to gate N24\n",
1462 | "Turn 333 assigned to gate N6\n",
1463 | "Turn 334 assigned to gate D215\n",
1464 | "Turn 335 assigned to gate N36\n",
1465 | "Turn 336 assigned to gate R21\n",
1466 | "Turn 337 assigned to gate D209\n",
1467 | "Turn 338 assigned to gate S4\n",
1468 | "Turn 339 assigned to gate N28\n",
1469 | "Turn 340 assigned to gate S107\n",
1470 | "Turn 341 assigned to gate R16\n",
1471 | "Turn 342 assigned to gate R16\n",
1472 | "Turn 343 assigned to gate D201\n",
1473 | "Turn 344 assigned to gate W42\n",
1474 | "Turn 345 assigned to gate N26\n",
1475 | "Turn 346 assigned to gate D303\n",
1476 | "Turn 347 assigned to gate N24\n",
1477 | "Turn 348 assigned to gate N26\n"
1478 | ]
1479 | }
1480 | ],
1481 | "source": [
1482 | "# Solve\n",
1483 | "prob.solve()\n",
1484 | "\n",
1485 | "# Report\n",
1486 | "print(\"Status: \", LpStatus[prob.status])\n",
1487 | "print(\"Minimised Cost: \", value(prob.objective))\n",
1488 | "\n",
1489 | "for alloc in x:\n",
1490 | " if x[alloc].varValue:\n",
1491 | " print(\"Turn %i assigned to gate %s\" % (alloc[0], alloc[-1]))"
1492 | ]
1493 | }
1494 | ],
1495 | "metadata": {
1496 | "anaconda-cloud": {},
1497 | "kernelspec": {
1498 | "display_name": "Python [conda env:root] * (Local)",
1499 | "language": "python",
1500 | "name": "local-conda-root-base"
1501 | },
1502 | "language_info": {
1503 | "codemirror_mode": {
1504 | "name": "ipython",
1505 | "version": 3
1506 | },
1507 | "file_extension": ".py",
1508 | "mimetype": "text/x-python",
1509 | "name": "python",
1510 | "nbconvert_exporter": "python",
1511 | "pygments_lexer": "ipython3",
1512 | "version": "3.7.12"
1513 | }
1514 | },
1515 | "nbformat": 4,
1516 | "nbformat_minor": 4
1517 | }
1518 |
--------------------------------------------------------------------------------
/2nd_model_run/Model_Result.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
--------------------------------------------------------------------------------