simulation_analysis.results
Creates output logs and plots for interpreting and analyzing results.
1""" 2Creates output logs and plots for interpreting and analyzing results. 3""" 4 5import os 6import re 7import math 8 9import pandas as pd 10import matplotlib.animation as animation 11import matplotlib.pyplot as plt 12import matplotlib.colors as mcolors 13import numpy as np 14from tqdm import tqdm 15 16import constants 17from simulation_handler.helpers import log_line 18 19 20nan = float('nan') 21 22####################### 23# Process charts 24####################### 25 26 27def plot_process_chart(events, run_id): 28 """ 29 # TODO: Ensure this is compatible with the new event structure. 30 Generates process charts for each terminal based on the events. 31 Args: 32 events (list): List of events where each event is a tuple containing: 33 (event_name, terminal_type, terminal_id, event_type, time, additional_info) 34 run_id (str): Unique identifier for the run to save the plots. 35 Returns: 36 None: Saves process charts as images in the specified directory. 37 """ 38 terminal_events = {} 39 40 output_dir = f'.{run_id}/plots/TerminalProcessCharts' 41 if not os.path.exists(output_dir): 42 os.makedirs(output_dir) 43 44 for event in events: 45 try: 46 _, terminal_type, terminal_id, event_type, time, _ = event 47 key = (terminal_type, terminal_id) 48 if key not in terminal_events: 49 terminal_events[key] = {} 50 terminal_events[key].setdefault(event_type, []).append(time) 51 except: 52 if event[0] != "Truck Arrival" and event[0] != "Truck Departure": 53 print(event) 54 pass 55 56 for (terminal_type, terminal_id), events in terminal_events.items(): 57 try: 58 df = pd.DataFrame() 59 df['Time'] = events['arrive'] + events['dock'] + \ 60 sum([events[e] for e in events if e not in [ 61 'arrive', 'dock', 'depart']], []) + events['depart'] 62 df['Event'] = ['Ship Arrival'] * len(events['arrive']) + ['Ship Dock'] * len(events['dock']) + sum([[e] * len( 63 events[e]) for e in events if e not in ['arrive', 'dock', 'depart']], []) + ['Ship Depart'] * len(events['depart']) 64 65 plt.figure(figsize=(10, 6)) 66 plt.scatter(df['Time'], df['Event']) 67 plt.title( 68 f'Process Chart for {terminal_type} Terminal {terminal_id}') 69 plt.xlabel('Time') 70 plt.ylabel('Event') 71 72 plt.savefig( 73 f'{output_dir}/process_chart_{terminal_type}_{terminal_id}.jpg') 74 plt.close() 75 except KeyError: 76 pass 77 78####################### 79# Dwell times and Turn times 80####################### 81 82 83def plot_dwell_times(events, run_id): 84 """ 85 Generates plots for dwell times and turn times of ships at terminals. 86 Args: 87 events (list): List of events where each event is a tuple containing: 88 (ship_name, terminal_type, terminal_id, event_type, time, additional_info) 89 run_id (str): Unique identifier for the run to save the plots. 90 Returns: 91 None: Saves plots as images in the specified directory. 92 """ 93 94 all_dwell_times = [] 95 all_turn_times = [] 96 97 ship_arrival_times = [] 98 ship_dock_times = [] 99 ship_undock_times = [] 100 ship_departure_times = [] 101 102 dwell_times = {} 103 turn_times = {} 104 105 # read the events 106 for event in events: 107 try: 108 name, terminal_type, terminal_id, event_type, time, _ = event 109 if event_type == "arrive": # arrive at anchorage 110 ship_arrival_times.append( 111 (name, terminal_type, terminal_id, time)) 112 if event_type == "dock": 113 ship_dock_times.append( 114 (name, terminal_type, terminal_id, time)) 115 if event_type == "undock": 116 ship_undock_times.append( 117 (name, terminal_type, terminal_id, time)) 118 elif event_type == "depart": 119 ship_departure_times.append( 120 (name, terminal_type, terminal_id, time)) 121 except: 122 pass 123 124 # Calculate turn times 125 for ship, terminal_type, terminal_id, arrival_time in ship_arrival_times: 126 for name, t_type, t_id, departure_time in ship_departure_times: 127 if ship == name and terminal_type == t_type and terminal_id == t_id: 128 key = (terminal_type, terminal_id) 129 if key not in turn_times: 130 turn_times[key] = {} 131 turn_times[key][ship] = departure_time - arrival_time 132 all_turn_times.append(departure_time - arrival_time) 133 134 # Calculate dwell times 135 for ship, terminal_type, terminal_id, dock_time in ship_dock_times: 136 for name, t_type, t_id, undock_time in ship_undock_times: 137 if ship == name and terminal_type == t_type and terminal_id == t_id: 138 key = (terminal_type, terminal_id) 139 if key not in dwell_times: 140 dwell_times[key] = {} 141 dwell_times[key][ship] = undock_time - dock_time 142 all_dwell_times.append(undock_time - dock_time) 143 144 # Create bar plot of the dwell times for individual vessels, by unique terminal 145 for (terminal_type, terminal_id), ships_dwell_times in dwell_times.items(): 146 try: 147 df = pd.DataFrame(ships_dwell_times.items(), 148 columns=['Ship', 'Dwell Time']) 149 # Set 'Ship' as index for proper labeling on x-axis 150 df.set_index('Ship', inplace=True) 151 df.plot(kind='bar', y='Dwell Time', legend=False, 152 title=f'Dwell times of ships at {terminal_type} terminal {terminal_id}') 153 plt.ylabel('Dwell time (hr)') 154 plt.savefig( 155 f'.{run_id}/plots/DwellTimes/dwell_times_{terminal_type}_{terminal_id}.jpg') 156 plt.close() # Clear the figure after saving the plot 157 except Exception as e: 158 print( 159 f"Error in dwell time plot generation for {terminal_type} terminal {terminal_id}: {e}") 160 pass 161 162 # Create bar plot of the turn times for individual vessels, by unique terminal 163 for (terminal_type, terminal_id), ships_turn_times in turn_times.items(): 164 try: 165 df = pd.DataFrame(ships_turn_times.items(), 166 columns=['Ship', 'Turn Time']) 167 # Set 'Ship' as index for proper labeling on x-axis 168 df.set_index('Ship', inplace=True) 169 df.plot(kind='bar', y='Turn Time', legend=False, 170 title=f'Turn Times of Ships at {terminal_type} Terminal {terminal_id}') 171 plt.ylabel('Turn Time') 172 plt.savefig( 173 f'.{run_id}/plots/TurnTimes/turn_times_{terminal_type}_{terminal_id}.jpg') 174 plt.close() # Clear the figure after saving the plot 175 except Exception as e: 176 print( 177 f"Error in turn time plot generation for {terminal_type} Terminal {terminal_id}: {e}") 178 pass 179 180 # Create histogram of the dwell times of all vessels visiting each unique terminal 181 for (terminal_type, terminal_id), ships_dwell_times in dwell_times.items(): 182 try: 183 dwell_times_list = list(ships_dwell_times.values()) 184 plt.figure(figsize=(10, 6)) 185 plt.hist(dwell_times_list, bins=20, edgecolor='black') 186 plt.title( 187 f'Dwell time distribution at {terminal_type} terminal {terminal_id}') 188 plt.xlabel('Dwell time (hr)') 189 plt.ylabel('Frequency') 190 plt.savefig( 191 f'.{run_id}/plots/DwellTimesDist/dwell_distribution_{terminal_type}_{terminal_id}.jpg') 192 plt.close() 193 except Exception as e: 194 print( 195 f"Error in dwell time distribution plot generation for {terminal_type} terminal {terminal_id}: {e}") 196 pass 197 198 # Create histogram of the turn times of all vessels visiting each unique terminal 199 for (terminal_type, terminal_id), ships_turn_times in turn_times.items(): 200 try: 201 turn_times_list = list(ships_turn_times.values()) 202 plt.figure(figsize=(10, 6)) 203 plt.hist(turn_times_list, bins=20, edgecolor='black') 204 plt.title( 205 f'Turn time distribution at {terminal_type} terminal {terminal_id}', fontsize=20) 206 plt.xlabel('Turn time (hr)', fontsize=16) 207 plt.ylabel('Frequency', fontsize=16) 208 plt.savefig( 209 f'.{run_id}/plots/TurnTimesDist/turn_distribution_{terminal_type}_{terminal_id}.jpg') 210 plt.close() 211 except Exception as e: 212 print( 213 f"Error in turn time distribution plot generation for {terminal_type} terminal {terminal_id}: {e}") 214 pass 215 216 # Create histogram of dwell times for all vessels in port system (all terminals) 217 all_dwell_times = [time for terminal_dwell_times in dwell_times.values( 218 ) for time in terminal_dwell_times.values()] 219 plt.figure(figsize=(10, 6)) 220 plt.hist(all_dwell_times, bins=20, edgecolor='black') 221 plt.title('Overall vessel terminal dwell time distribution', fontsize=20) 222 plt.xlabel('Dwell time (hr)', fontsize=16) 223 plt.ylabel('Frequency', fontsize=16) 224 plt.xticks(fontsize=12) 225 plt.yticks(fontsize=12) 226 plt.savefig(f'.{run_id}/plots/dwell_time_distribution.jpg') 227 plt.close() 228 229 # Create histogram of turn times for all vessels in port system (all terminals) 230 all_turn_times = [time for terminal_turn_times in turn_times.values() 231 for time in terminal_turn_times.values()] 232 plt.figure(figsize=(10, 6)) 233 plt.hist(all_turn_times, bins=20, edgecolor='black') 234 plt.title('Overall vessel turn time distribution', fontsize=20) 235 plt.xlabel('Turn time (hr)', fontsize=16) 236 plt.ylabel('Frequency', fontsize=16) 237 plt.xticks(fontsize=12) 238 plt.yticks(fontsize=12) 239 plt.savefig(f'.{run_id}/plots/turn_time_distribution.jpg') 240 plt.close() 241 242 report_path = f".{run_id}/logs/final_report.txt" 243 with open(report_path, 'a') as f: 244 f.write("Dwell Time Analysis:\n") 245 f.write(f"Max dwell time: {max(all_dwell_times)}\n") 246 f.write( 247 f"Median dwell time: {sorted(all_dwell_times)[len(all_dwell_times) // 2]}\n") 248 f.write( 249 f"Average dwell time: {sum(all_dwell_times) / len(all_dwell_times)}\n\n") 250 251 f.write("Turn Time Analysis:\n") 252 f.write(f"Max turn time: {max(all_turn_times)}\n") 253 f.write( 254 f"Median turn time: {sorted(all_turn_times)[len(all_turn_times) // 2]}\n") 255 f.write( 256 f"Average turn time: {sum(all_turn_times) / len(all_turn_times)}\n\n") 257 258 return None 259 260 261def analyze_turn_time_ships(run_id): 262 """ Analyze the turn times and dwell times of ships from the Excel file and save the results to a report. 263 Args: 264 run_id (str): Unique identifier for the run to save the report. 265 Returns: 266 None: Saves the analysis results to a text file in the specified directory. 267 """ 268 # Load the Excel file 269 file_path = f'.{run_id}/logs/ship_logs.xlsx' 270 271 df = pd.read_excel(file_path) 272 mean_dwell_times = df.groupby(['Terminal Directed'])[ 273 'Turn Time'].mean().reset_index() 274 with open(f".{run_id}/logs/final_report.txt", 'a') as f: 275 f.write("Mean Turn Times by Terminal Directed:\n") 276 f.write(str(mean_dwell_times) + "\n\n") 277 mean_dwell_times = df.groupby(['Terminal Type'])[ 278 'Turn Time'].mean().reset_index() 279 with open(f".{run_id}/logs/final_report.txt", 'a') as f: 280 f.write("Mean Turn Times by Terminal Type:\n") 281 f.write(str(mean_dwell_times) + "\n\n") 282 283 mean_dwell_times = df.groupby(['Terminal Directed'])[ 284 'Dwell Time'].mean().reset_index() 285 with open(f".{run_id}/logs/final_report.txt", 'a') as f: 286 f.write("Mean Dwell Times by Terminal Directed:\n") 287 f.write(str(mean_dwell_times) + "\n\n") 288 mean_dwell_times = df.groupby(['Terminal Type'])[ 289 'Dwell Time'].mean().reset_index() 290 with open(f".{run_id}/logs/final_report.txt", 'a') as f: 291 f.write("Mean Dwell Times by Terminal Type:\n") 292 f.write(str(mean_dwell_times) + "\n\n") 293 294 295####################### 296# Queues 297####################### 298 299def plot_queues(SHIPS_IN_CHANNEL_TRACK, name, place, run_id): 300 """ 301 Plots the number of ships in the channel over time. 302 Args: 303 SHIPS_IN_CHANNEL_TRACK (list): List of tuples containing time and number of ships in the channel. 304 name (str): Name of the ship type (e.g., "Container", "Liquid", "Dry Bulk"). 305 place (str): Place where the ships are tracked (e.g., "Port"). 306 run_id (str): Unique identifier for the run to save the plot. 307 Returns: 308 None: Saves the plot as a PDF file in the specified directory. 309 """ 310 311 # Plotting 312 plt.figure(figsize=(12, 8)) 313 314 x = [i[0] for i in SHIPS_IN_CHANNEL_TRACK] 315 y = [i[1] for i in SHIPS_IN_CHANNEL_TRACK] 316 plt.plot(x, y) 317 plt.suptitle(f'Number of {name} over time', fontsize=20) 318 plt.title(f'at {place}', fontsize=20) 319 plt.xlabel('Time', fontsize=15) 320 plt.ylabel(f'Number of {name}', fontsize=15) 321 plt.xticks(fontsize=12) 322 plt.yticks(fontsize=12) 323 plt.tight_layout() 324 plt.xlim(min(x)-1, max(x)+2) 325 plt.ylim(min(y)-1, max(y)+2) 326 plt.savefig( 327 f'.{run_id}/plots/{name}in{place}OverTime{constants.mean_interarrival_time_total}.pdf') 328 329 report_path = "./logs/final_report.txt" 330 with open(report_path, 'a') as f: 331 f.write(f"Mean number of {name} in {place}: {sum(y) / len(y)}\n\n") 332 333 plt.close() 334 335 336####################### 337# Resources 338####################### 339 340 341def track_utilization(container_data, liquid_data, drybulk_data, run_id): 342 """ 343 Calculates the mean utilization of resources (berths and yards) for container, liquid, and dry bulk terminals. 344 Args: 345 container_data (pd.DataFrame): DataFrame containing container terminal data. 346 liquid_data (pd.DataFrame): DataFrame containing liquid terminal data. 347 drybulk_data (pd.DataFrame): DataFrame containing dry bulk terminal data. 348 run_id (str): Unique identifier for the run to save the results. 349 Returns: 350 list: A list containing the mean utilization of resources for each terminal type. 351 """ 352 353 track_list = [] 354 355 excel_data = f'.{run_id}/logs/availability.xlsx' 356 357 def calculate_mean_utilization(data, resource_type): 358 available_columns = [ 359 col for col in data.columns if 'Available' in col and resource_type in col] 360 used_columns = [ 361 col for col in data.columns if 'Used' in col and resource_type in col] 362 363 utilization = {} 364 for available, used in zip(available_columns, used_columns): 365 terminal_name = available.split('_Available_')[0] 366 utilization[terminal_name] = data[used].sum( 367 ) / (data[available].sum() + data[used].sum()) 368 369 overall_utilization = sum(data[used_columns].sum( 370 )) / (sum(data[available_columns].sum()) + sum(data[used_columns].sum())) 371 372 return utilization, overall_utilization 373 374 container_berth_utilization, container_berth_overall = calculate_mean_utilization( 375 container_data, 'Berth_Ctr') 376 container_yard_utilization, container_yard_overall = calculate_mean_utilization( 377 container_data, 'Yard') 378 379 liquid_berth_utilization, liquid_berth_overall = calculate_mean_utilization( 380 liquid_data, 'Berth_liq') 381 liquid_tank_utilization, liquid_tank_overall = calculate_mean_utilization( 382 liquid_data, 'Tank') 383 384 drybulk_berth_utilization, drybulk_berth_overall = calculate_mean_utilization( 385 drybulk_data, 'Berth_db') 386 drybulk_silo_utilization, drybulk_silo_overall = calculate_mean_utilization( 387 drybulk_data, 'Silo') 388 389 track_list = [container_berth_utilization, container_berth_overall, container_yard_utilization, container_yard_overall, liquid_berth_utilization, 390 liquid_berth_overall, liquid_tank_utilization, liquid_tank_overall, drybulk_berth_utilization, drybulk_berth_overall, drybulk_silo_utilization, drybulk_silo_overall] 391 392 return track_list 393 394 395def get_utilization(container_data, liquid_data, drybulk_data, run_id): 396 """ 397 Calculates the mean utilization of resources (berths and yards) for container, liquid, and dry bulk terminals. 398 Args: 399 container_data (pd.DataFrame): DataFrame containing container terminal data. 400 liquid_data (pd.DataFrame): DataFrame containing liquid terminal data. 401 drybulk_data (pd.DataFrame): DataFrame containing dry bulk terminal data. 402 run_id (str): Unique identifier for the run to save the results. 403 Returns: 404 list: A list containing the mean utilization of resources for each terminal type. 405 """ 406 407 track_list = [] 408 409 excel_data = f'.{run_id}/logs/availability.xlsx' 410 411 def calculate_mean_utilization(data, resource_type): 412 available_columns = [ 413 col for col in data.columns if 'Available' in col and resource_type in col] 414 used_columns = [ 415 col for col in data.columns if 'Used' in col and resource_type in col] 416 # print("used:", used_columns) 417 utilization = {} 418 for available, used in zip(available_columns, used_columns): 419 terminal_name = available.split('_Available_')[0] 420 # print("data", data) 421 # print("used:", used) 422 # print(data[used]) 423 # print(list(data[used])) 424 utilization[terminal_name] = list( 425 data[used])[-1] / (list(data[available])[-1] + list(data[used])[-1]) 426 overall_utilization = data[used_columns].iloc[-1].sum() / ( 427 data[available_columns].iloc[-1].sum() + data[used_columns].iloc[-1].sum()) 428 429 return utilization, overall_utilization 430 431 container_berth_utilization, container_berth_overall = calculate_mean_utilization( 432 container_data, 'Berth_Ctr') 433 container_yard_utilization, container_yard_overall = calculate_mean_utilization( 434 container_data, 'Yard') 435 436 liquid_berth_utilization, liquid_berth_overall = calculate_mean_utilization( 437 liquid_data, 'Berth_liq') 438 liquid_tank_utilization, liquid_tank_overall = calculate_mean_utilization( 439 liquid_data, 'Tank') 440 441 drybulk_berth_utilization, drybulk_berth_overall = calculate_mean_utilization( 442 drybulk_data, 'Berth_db') 443 drybulk_silo_utilization, drybulk_silo_overall = calculate_mean_utilization( 444 drybulk_data, 'Silo') 445 446 track_list = [container_berth_utilization, container_berth_overall, container_yard_utilization, container_yard_overall, liquid_berth_utilization, 447 liquid_berth_overall, liquid_tank_utilization, liquid_tank_overall, drybulk_berth_utilization, drybulk_berth_overall, drybulk_silo_utilization, drybulk_silo_overall] 448 449 return track_list 450 451 452def add_to_report(report_lines, resource, utilization, overall_utilization): 453 """ 454 Adds the mean utilization of a resource to the report lines. 455 Args: 456 report_lines (list): List of report lines to which the utilization will be added. 457 resource (str): Name of the resource (e.g., "Container berth", "Liquid storage"). 458 utilization (dict): Dictionary containing the mean utilization for each terminal. 459 overall_utilization (float): Overall mean utilization for the resource. 460 Returns: 461 None: Appends the utilization information to the report lines. 462 """ 463 464 report_lines.append(f"Mean {resource} Utilization:") 465 for terminal, mean_util in utilization.items(): 466 report_lines.append(f"{terminal}: {mean_util:.2%}") 467 report_lines.append( 468 f"Overall {resource} Utilization: {overall_utilization:.2%}") 469 report_lines.append("\n") 470 471 472def save_track_list(run_id, timestep, track_list): 473 """ 474 Saves the utilization track list to a text file for the given timestep. 475 Args: 476 run_id (str): Unique identifier for the run to save the report. 477 timestep (int): The current timestep for which the report is generated. 478 track_list (list): List containing the mean utilization of resources for each terminal type. 479 Returns: 480 None: Saves the report to a text file in the specified directory. 481 """ 482 483 container_berth_utilization, container_berth_overall, container_yard_utilization, container_yard_overall, liquid_berth_utilization, liquid_berth_overall, liquid_tank_utilization, liquid_tank_overall, drybulk_berth_utilization, drybulk_berth_overall, drybulk_silo_utilization, drybulk_silo_overall = track_list 484 report_lines = [] 485 486 # Container Terminals 487 add_to_report(report_lines, "Container berth", 488 container_berth_utilization, container_berth_overall) 489 add_to_report(report_lines, "Container storage", 490 container_yard_utilization, container_yard_overall) 491 492 # Liquid Terminals 493 add_to_report(report_lines, "Liquid berth", 494 liquid_berth_utilization, liquid_berth_overall) 495 add_to_report(report_lines, "Liquid storage", 496 liquid_tank_utilization, liquid_tank_overall) 497 498 # Dry Bulk Terminals 499 add_to_report(report_lines, "Drybulk berth", 500 drybulk_berth_utilization, drybulk_berth_overall) 501 add_to_report(report_lines, "Drybulk storage", 502 drybulk_silo_utilization, drybulk_silo_overall) 503 504 # Save report to text file 505 report_path = f'.{run_id}/logs/availability/{timestep}.txt' 506 with open(report_path, 'a') as file: 507 file.write(f"Time Step: {timestep}\n") 508 file.write("\n".join(report_lines)) 509 510 511####################### 512# Train 513####################### 514 515def gen_train_df(train_events, run_id): 516 """ 517 Generates a DataFrame from the train events and saves it to a CSV file. 518 Args: 519 train_events (dict): Dictionary containing train events where keys are event names and values are lists of event data. 520 run_id (str): Unique identifier for the run to save the DataFrame. 521 Returns: 522 pd.DataFrame: DataFrame containing the train events. 523 """ 524 train_events_df = pd.DataFrame(train_events).T 525 train_events_df.to_csv(f'.{run_id}/logs/train_logs.csv') 526 return train_events_df 527 528 529####################### 530# Time distribution 531####################### 532 533def get_dists(run_id, plot=True): 534 """ 535 Analyzes the ship logs to calculate the mean, min, max, and standard deviation of various time distributions for each terminal type. 536 Args: 537 run_id (str): Unique identifier for the run to save the analysis results. 538 plot (bool): Whether to generate and save plots of the time distributions. 539 Returns: 540 tuple: A tuple containing the mean, min, max, and standard deviation DataFrames for each terminal type. 541 """ 542 def extract_t(restriction_str): 543 if isinstance(restriction_str, str): 544 parts = restriction_str.split(", ") 545 for part in parts: 546 if part.startswith("T"): 547 return float(part.split(":")[1]) 548 return 0.0 549 550 data = pd.read_excel(f'.{run_id}/logs/ship_logs.xlsx', sheet_name='Sheet1') 551 552 data['Restriction In (T)'] = data['Time for Restriction In'].apply( 553 extract_t) 554 data['Restriction Out (T)'] = data['Time for Restriction Out'].apply( 555 extract_t) 556 557 data['Channel In'] = data['Time to Common Channel In'] + \ 558 data['Time to Travel Channel In'] 559 data['Channel Out'] = data['Time to Common Channel Out'] + \ 560 data['Time to Travel Channel Out'] 561 data['Terminal Ops'] = data['Loading Time'] + data['Unloading Time'] 562 data['Terminal Other'] = data['Waiting Time'] + data['Departure Time'] 563 564 columns = [ 565 'Time to get Berth', 'Restriction In (T)', 'Channel In', 'Time to get Pilot In', 566 'Time to get Tugs In', 'Terminal Ops', 'Terminal Other', 'Restriction Out (T)', 567 'Time to get Pilot Out', 'Time to get Tugs Out', 'Channel Out' 568 ] 569 570 mean_values = data.groupby('Terminal Type')[columns].mean() 571 min_values = data.groupby('Terminal Type')[columns].min() 572 max_values = data.groupby('Terminal Type')[columns].max() 573 std_values = data.groupby('Terminal Type')[columns].std() 574 575 error_bars = {'min': mean_values - min_values, 576 'max': max_values - mean_values} 577 578 with open(f'.{run_id}/logs/final_report.txt', 'a') as file: 579 file.write("Mean Time Distribution:\n") 580 file.write(mean_values.to_string()) 581 file.write("\n\nMin Time Distribution:\n") 582 file.write(min_values.to_string()) 583 file.write("\n\nMax Time Distribution:\n") 584 file.write(max_values.to_string()) 585 file.write("\n\nStandard Deviation:\n") 586 file.write(std_values.to_string()) 587 588 if plot: 589 for terminal in mean_values.index: 590 plt.figure(figsize=(10, 6)) 591 means = mean_values.loc[terminal] 592 min_err = error_bars['min'].loc[terminal] 593 max_err = error_bars['max'].loc[terminal] 594 stds = std_values.loc[terminal] 595 596 plt.errorbar(columns, means, yerr=[ 597 min_err, max_err], fmt='-o', capsize=5) 598 599 for i, (mean, min_e, max_e, std) in enumerate(zip(means, min_err, max_err, stds)): 600 plt.annotate( 601 f'Mean: {mean:.1f}\nSD: {std:.1f}', 602 (i, mean), 603 textcoords="offset points", 604 xytext=(0, 10), 605 ha='center' 606 ) 607 608 plt.xticks(rotation=90) 609 plt.title(f'{terminal} Terminal: Mean with Min/Max and SD') 610 plt.xlabel('Processes') 611 plt.ylabel('Time (Units)') 612 plt.tight_layout() 613 # os.makedirs('/plots/TerminalProcessDist/', exist_ok=True) 614 plt.savefig( 615 f'.{run_id}/plots/TerminalProcessDist/{terminal}_terminal_plot.png') 616 plt.close() 617 return mean_values, min_values, max_values, std_values 618 619 620####################### 621# Channel and Anchorage 622####################### 623 624 625def plot_channel(run_id): 626 """ 627 Plots the channel sections and their respective terminals. 628 Args: 629 run_id (str): Unique identifier for the run to save the plot. 630 Returns: 631 None: Saves the plot as a PDF file in the specified directory. 632 """ 633 634 # Normalize the draft values for color mapping 635 channel_sections = constants.CHANNEL_SECTION_DIMENSIONS 636 last_section_dict = constants.LAST_SECTION_DICT 637 drafts = [draft for _, _, draft, _ in channel_sections.values()] 638 norm = mcolors.Normalize(vmin=min(drafts), vmax=max(drafts)) 639 cmap = plt.cm.viridis 640 fig, ax = plt.subplots(figsize=(20, 8)) 641 max_width = max(width for _, width, _, _ in channel_sections.values()) 642 643 # Calculate cumulative lengths for x positions (rotated plot) 644 cumulative_length = 0 645 x_positions = {} 646 for section, (length, width, draft, speed) in channel_sections.items(): 647 x_positions[section] = cumulative_length + \ 648 length / 2 # Middle of the section 649 cumulative_length += length 650 651 for section, (length, width, draft, speed) in channel_sections.items(): 652 color = cmap(norm(draft)) 653 y_pos = (max_width - width) / 2 # Center-align the rectangle 654 ax.add_patch(plt.Rectangle( 655 (x_positions[section] - length / 2, y_pos), length, width, edgecolor='black', facecolor=color)) 656 terminal_num = next((f'{terminal_type} Terminal : #{terminal}' for terminal_type, terminals in last_section_dict.items( 657 ) for terminal, sec in terminals.items() if sec == section), None) 658 annotation = f'Section {section}' 659 if terminal_num: 660 annotation += f'\n{terminal_num}' 661 ax.text(x_positions[section], max_width / 2, annotation, 662 ha='center', va='center', color='white', fontsize=8, rotation=90) 663 664 cbar = plt.colorbar(plt.cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax) 665 cbar.set_label('Depth (Draft)') 666 667 ax.set_xlim(0, cumulative_length) 668 ax.set_ylim(0, max_width + 5) 669 ax.set_xlabel('Length') 670 ax.set_ylabel('Breadth') 671 ax.set_title('Channel Sections and Terminals') 672 plt.savefig(f'.{run_id}/plots/ChannelSections.pdf') 673 plt.close() 674 675 676def ship_data_csv_1(run_id): 677 """ 678 Parses the ship data from the log file and saves it to a CSV file. 679 The CSV file contains information about each ship's entry and exit times in the channel sections. 680 Args: 681 run_id (str): Unique identifier for the run to save the CSV file. 682 Returns: 683 pd.DataFrame: DataFrame containing the parsed ship data. 684 """ 685 ship_data = [] 686 file_path = f'.{run_id}/logs/ships_in_channel.txt' 687 688 with open(file_path, 'r') as file: 689 for line in file: 690 match = re.search( 691 r"Ship (\d+) of type (\w+) of width ([\d.]+) and draft ([\d.]+) spent from ([\d.]+) to ([\d.]+) in section (\d+) going (\w+)", line) 692 if match: 693 ship_data.append({ 694 "ship": int(match.group(1)), 695 "ship_type": match.group(2), 696 "width": float(match.group(3)), 697 "draft": float(match.group(4)), 698 "section": int(match.group(7)), 699 "start": float(match.group(5)), 700 "end": float(match.group(6)), 701 "direction": match.group(8) 702 }) 703 else: 704 print(f"Error parsing line: {line}") 705 706 df = pd.DataFrame(ship_data) 707 df = df.sort_values(by=['ship', 'direction', 'section']) 708 709 df.to_csv( 710 f'.{run_id}/logs/channel_section_entry_exit_times.csv', index=False) 711 712 return df 713 714 715def ship_data_csv_2(run_id): 716 """ 717 Parses the ship data from the log file and saves it to a CSV file with time spent in each section. 718 Args: 719 run_id (str): Unique identifier for the run to save the CSV file. 720 Returns: 721 pd.DataFrame: DataFrame containing the parsed ship data with time spent in each section. 722 """ 723 ship_data = {} 724 file_path = f'.{run_id}/logs/ships_in_channel.txt' 725 pattern = r"Ship (\d+) of type (\w+) of width ([\d.]+) and draft ([\d.]+) spent from ([\d.]+) to ([\d.]+) in section (\d+) going (\w+)" 726 727 with open(file_path, 'r') as file: 728 for line in file: 729 match = re.search(pattern, line) 730 if match: 731 ship_num = int(match.group(1)) 732 ship_dir = match.group(8) 733 ship_type = match.group(2) 734 section = int(match.group(7)) 735 time_spent = float(match.group(6)) - float(match.group(5)) 736 737 if (ship_num, ship_dir) not in ship_data: 738 ship_data[(ship_num, ship_dir)] = {} 739 740 ship_data[(ship_num, ship_dir)][section] = time_spent 741 else: 742 print(f"Error parsing line: {line}") 743 744 max_section = max(max(ship_info.keys()) 745 for ship_info in ship_data.values()) 746 df = pd.DataFrame(ship_data).T 747 df = df.reindex(columns=range(max_section + 1)).fillna(np.nan) 748 df.columns = [f"Time in Sec {i}" for i in range(max_section + 1)] 749 df['Total Time in Channel'] = df.sum(axis=1) 750 df['Ship'] = [ship[0] for ship in df.index] 751 df['Direction'] = [ship[1] for ship in df.index] 752 section_columns = [f"Time in Sec {i}" for i in range(max_section + 1)] 753 df = df[['Ship', 'Direction'] + 754 ['Total Time in Channel'] + section_columns] 755 df = df.sort_values(by=['Ship', 'Direction']) 756 df.to_csv(f'.{run_id}/logs/ships_time_in_channel.csv', index=False) 757 758 return df 759 760 761def ships_in_channel_analysis(run_id, plot=True): 762 """ 763 Analyzes the ships in the channel by reading the ship data from a text file, calculating the time spent in each section, and generating a time series of ships in the channel. 764 Args: 765 run_id (str): Unique identifier for the run to save the analysis results. 766 plot (bool): Whether to generate and save plots of the channel utilization. 767 Returns: 768 None: Saves the analysis results to CSV files and generates plots if `plot` is True. 769 """ 770 771 ship_data_csv_1(run_id) 772 ship_data_csv_2(run_id) 773 774 os.remove(f'.{run_id}/logs/ships_in_channel.txt') 775 776 file_path = f'.{run_id}/logs/channel_section_entry_exit_times.csv' 777 ships_data = pd.read_csv(file_path) 778 ships_data['time_in_section'] = ships_data['end'] - ships_data['start'] 779 max_time = ships_data['end'].max() 780 781 section_time_series = pd.DataFrame(0, index=np.arange( 782 max_time)-1, columns=range(ships_data['section'].max() + 1)) 783 784 for _, row in ships_data.iterrows(): 785 section_time_series.loc[row['start']:row['end'], row['section']] += 1 786 787 # save section time series to csv 788 section_time_series.to_csv(f'.{run_id}/logs/section_time_series.csv') 789 total_ships_in_channel_full = section_time_series.sum(axis=1) 790 mean_ships_per_time_step = section_time_series.loc[constants.WARMUP_ITERS:].mean( 791 ) 792 total_ships_in_channel = section_time_series.sum(axis=1) 793 mean_total_ships_in_channel = total_ships_in_channel.mean() 794 795 # save total ships in channel to csv 796 total_ships_in_channel.to_csv(f'.{run_id}/logs/total_ships_in_channel.csv') 797 798 if plot: 799 plt.plot(total_ships_in_channel_full) 800 plt.xticks(fontsize=12) 801 plt.yticks(fontsize=12) 802 plt.xlim(left=0) 803 plt.ylim(bottom=0) 804 plt.title('Ships in channel', fontsize=15) 805 plt.xlabel('Time (hr)', fontsize=15) 806 plt.ylabel('Number of ships', fontsize=15) 807 plt.grid(True) 808 plt.tight_layout() 809 plt.savefig(f'.{run_id}/plots/channel_utilization.pdf') 810 plt.close() 811 812 report_path = f".{run_id}/logs/final_report.txt" 813 with open(report_path, 'a') as f: 814 f.write( 815 f"Mean number of vessels in channel: {mean_total_ships_in_channel}\n\n") 816 f.write("Mean number of vessel in each section:\n") 817 for section, mean_ships in mean_ships_per_time_step.items(): 818 f.write(f"Section {section}: {mean_ships}\n") 819 f.write("\n") 820 821 # Calculate entry and exit times for each ship 822 file_path = f'.{run_id}/logs/channel_section_entry_exit_times.csv' 823 ships_data = pd.read_csv(file_path) 824 825 # Identify the first and last sections for each ship 826 first_section = ships_data[ships_data['section'] == 0] 827 last_sections = ships_data.groupby(['ship', 'direction'])[ 828 'section'].max().reset_index() 829 830 # Merge last sections with the original dataset to get exit times 831 last_section_exit = ships_data.merge( 832 last_sections, 833 on=['ship', 'section', 'direction'], 834 how='inner' 835 ) 836 837 # Get entry and exit times for each ship 838 entry_exit_times = pd.merge( 839 first_section[['ship', 'direction', 'start']].rename( 840 columns={'start': 'entry_time_section_0'}), 841 last_section_exit[['ship', 'direction', 'end']].rename( 842 columns={'end': 'exit_time_last_section'}), 843 on=['ship', 'direction'], 844 how='outer' 845 ) 846 847 # Save the entry and exit times to csv 848 entry_exit_times.to_csv( 849 f'.{run_id}/logs/entry_exit_times.csv', index=False) 850 851 852def ships_in_anchorage_analysis(ship_type, run_id, plot=True): 853 """ 854 Analyzes the ships in anchorage by reading the ship data from a CSV file, calculating the cumulative arrivals and channel entries, and generating a time series of ships waiting in anchorage. 855 Args: 856 ship_type (str): Type of ships to analyze ('all', 'Container', 'Liquid', 'DryBulk'). 857 run_id (str): Unique identifier for the run to save the analysis results. 858 plot (bool): Whether to generate and save plots of the anchorage queue. 859 Returns: 860 None: Saves the analysis results to CSV files and generates plots if `plot` is True. 861 """ 862 863 # time series of ships that entered anchorage 864 input_ship_data = pd.read_csv(f'.{run_id}/logs/ship_data.csv') 865 866 if ship_type == 'all': 867 input_ship_data = input_ship_data 868 else: 869 input_ship_data = input_ship_data[input_ship_data['ship_type'] == ship_type] 870 871 arrival_times = input_ship_data['arrival'].dropna( 872 ).sort_values().reset_index(drop=True) 873 cumulative_arrivals = pd.DataFrame( 874 {'time_index': range(1, int(arrival_times.max()) + 1)}) 875 cumulative_arrivals['cumulative_arrivals'] = cumulative_arrivals['time_index'].apply( 876 lambda x: (arrival_times <= x).sum() 877 ) 878 879 # save the cumulative arrivals to csv 880 cumulative_arrivals.to_csv(f'.{run_id}/logs/cumulative_arrivals.csv') 881 882 # time series of ships that entered channel 883 file_path = f'.{run_id}/logs/channel_section_entry_exit_times.csv' 884 ships_data = pd.read_csv(file_path) 885 886 if ship_type != 'all': 887 ships_data = ships_data[ships_data['ship_type'] == ship_type] 888 889 ships_data = ships_data[ships_data['direction'] == 'in'] 890 ships_data = ships_data.groupby('ship').first().reset_index() 891 channel_enter_times = ships_data['start'].reset_index(drop=True) 892 cumulative_channel_enter = pd.DataFrame( 893 {'time_index': range(1, int(constants.SIMULATION_TIME) + 1)}) 894 cumulative_channel_enter['cumulative_channel_enter'] = cumulative_channel_enter['time_index'].apply( 895 lambda x: (channel_enter_times <= x).sum() 896 ) 897 898 # combine the two dfs 899 combined = cumulative_arrivals.merge( 900 cumulative_channel_enter, on='time_index', how='outer') 901 combined['waiting_in_anchorage'] = combined['cumulative_arrivals'] - \ 902 combined['cumulative_channel_enter'] 903 904 # svae the combined dataframe to csv 905 combined.to_csv(f'.{run_id}/logs/waiting_in_anchorage_{ship_type}.csv') 906 907 # Calculate the total number of rows 908 rows_in_combined = len(combined) 909 mean_waiting_in_anchorage = combined.loc[constants.WARMUP_ITERS:]['waiting_in_anchorage'].mean( 910 ) 911 912 # plot time series of waiting in anchorage 913 if plot: 914 plt.plot(combined['time_index'], combined['waiting_in_anchorage']) 915 plt.title(f'Anchorage queue - {ship_type} ships', fontsize=15) 916 plt.xticks(fontsize=12) 917 plt.yticks(fontsize=12) 918 plt.xlim(left=0) 919 plt.ylim(bottom=0) 920 plt.xlabel('Time (hr)', fontsize=15) 921 plt.ylabel('Number of ships', fontsize=15) 922 plt.grid(True) 923 plt.tight_layout() 924 plt.savefig(f'.{run_id}/plots/anchorage_queue_{ship_type}.pdf') 925 926 with open(f'.{run_id}/logs/final_report.txt', 'a') as f: 927 f.write( 928 f"Mean number of {ship_type} ships waiting in anchorage: {mean_waiting_in_anchorage}\n\n") 929 930 plt.close() 931 932 return 933 934###################### 935# Trucks 936###################### 937 938 939def create_truck_csv(run_id): 940 """ 941 Read truck_data.txt, parse into a DataFrame, save as CSV under ./<run_id>/truck_data.csv, 942 and return the DataFrame. 943 Args: 944 run_id (str): Unique identifier for the run to save the truck data. 945 Returns: 946 pd.DataFrame: DataFrame containing the truck data with columns for truck ID, start time, dwell time, terminal ID, terminal type, and arrival time. 947 """ 948 txt_path = f'.{run_id}/logs/truck_data.txt' 949 data = [] 950 951 # only take first 100000 units of data 952 count = 0 953 with open(txt_path, 'r') as f: 954 for line in f: 955 count += 1 956 if count >= 100000: 957 break 958 line = line.strip() 959 if not line: 960 continue 961 parts = [p.strip() for p in line.split(',')] 962 row = {} 963 for part in parts: 964 key, val = part.split(':', 1) 965 row[key.strip()] = val.strip() 966 data.append(row) 967 968 df = pd.DataFrame(data) 969 # convert types 970 df = df.astype({ 971 'truck_id' : int, 972 'start_time' : float, 973 'dwell_time' : float, 974 'terminal_id' : int, 975 'terminal_type': str 976 }) 977 # compute arrival-of-day 978 df['arrival_time'] = df['start_time'] % 24 979 980 # make run directory & save CSV 981 out_dir = f'.{run_id}/logs/' 982 os.makedirs(out_dir, exist_ok=True) 983 csv_path = os.path.join(out_dir, 'truck_data.csv') 984 df.to_csv(csv_path, index=False) 985 986 return df 987 988 989def plot_dwell_by_type(run_id): 990 """ 991 Plots the dwell time distribution of trucks by terminal type. 992 Args: 993 run_id (str): Unique identifier for the run to save the plots. 994 Returns: 995 None: Saves the plots as PDF files in the specified directories. 996 """ 997 out_dir = f'.{run_id}/plots/TruckDwellByCargo' 998 df = pd.read_csv(f'.{run_id}/logs/truck_data.csv') 999 os.makedirs(out_dir, exist_ok=True) 1000 1001 for ttype, grp in df.groupby('terminal_type'): 1002 plt.figure() 1003 grp['dwell_time'].hist(bins=30) 1004 plt.title(f'Dwell Time Distribution — {ttype}') 1005 plt.xlabel('Dwell Time') 1006 plt.ylabel('Frequency') 1007 # make the y ticks as percentage 1008 plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{x/len(grp)*100:.1f}%')) 1009 plt.xticks(rotation=45) 1010 plt.grid(axis='y', linestyle='--', alpha=0.7) 1011 # save the plot 1012 plt.tight_layout() 1013 fname = f'dwell_distribution_{ttype}.pdf' 1014 plt.savefig(os.path.join(out_dir, fname)) 1015 # plt.show() 1016 plt.close() 1017 1018 1019def plot_dwell_by_terminal(run_id): 1020 """ 1021 Plots the dwell time distribution of trucks by terminal type and terminal ID. 1022 Args: 1023 run_id (str): Unique identifier for the run to save the plots. 1024 Returns: 1025 None: Saves the plots as PDF files in the specified directories. 1026 """ 1027 df = pd.read_csv(f'.{run_id}/logs/truck_data.csv') 1028 dir_term = f'.{run_id}/plots/TruckDwellByTerminal' 1029 os.makedirs(dir_term, exist_ok=True) 1030 1031 for (ttype, tid), grp in df.groupby(['terminal_type','terminal_id']): 1032 plt.figure() 1033 grp['dwell_time'].hist(bins=30) 1034 plt.title(f'Dwell Time — {ttype} (Terminal {tid})') 1035 plt.xlabel('Dwell Time') 1036 plt.ylabel('Frequency') 1037 # make the y ticks as percentage 1038 plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{x/len(grp)*100:.1f}%')) 1039 plt.xticks(rotation=45) 1040 plt.grid(axis='y', linestyle='--', alpha=0.7) 1041 plt.tight_layout() 1042 fname = f'dwell_distribution_{ttype}_{tid}.pdf' 1043 plt.savefig(os.path.join(dir_term, fname)) 1044 plt.close() 1045 1046 1047def plot_arrival_distributions(run_id): 1048 """ 1049 Plots the arrival time distribution of trucks by terminal type and terminal ID. 1050 Args: 1051 run_id (str): Unique identifier for the run to save the plots. 1052 Returns: 1053 None: Saves the plots as JPEG files in the specified directories. 1054 """ 1055 df = pd.read_csv(f'.{run_id}/logs/truck_data.csv') 1056 dir_type = f'.{run_id}/plots/TruckArrivalByCargo' 1057 dir_cargo = f'.{run_id}/plots/TruckArrivalByTerminal' 1058 os.makedirs(dir_type, exist_ok=True) 1059 os.makedirs(dir_cargo, exist_ok=True) 1060 1061 # by type 1062 for ttype, grp in df.groupby('terminal_type'): 1063 plt.figure() 1064 grp['arrival_time'].hist(bins=24, range=(0,24)) 1065 plt.title(f'Arrival Time Distribution — {ttype}') 1066 plt.xlabel('Hour of Day') 1067 plt.ylabel('Frequency') 1068 # make the y ticks as percentage 1069 plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{x/len(grp)*100:.1f}%')) 1070 plt.xticks(rotation=45) 1071 plt.grid(axis='y', linestyle='--', alpha=0.7) 1072 plt.tight_layout() 1073 fname = f'arrival_distribution_{ttype}.jpg' 1074 plt.savefig(os.path.join(dir_type, fname)) 1075 plt.close() 1076 1077 # by terminal 1078 for (ttype, tid), grp in df.groupby(['terminal_type','terminal_id']): 1079 plt.figure() 1080 grp['arrival_time'].hist(bins=24, range=(0,24)) 1081 plt.title(f'Arrival Time — {ttype} (Terminal {tid})') 1082 plt.xlabel('Hour of Day') 1083 plt.ylabel('Frequency') 1084 # make the y ticks as percentage 1085 plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{x/len(grp)*100:.1f}%')) 1086 plt.xticks(rotation=45) 1087 plt.grid(axis='y', linestyle='--', alpha=0.7) 1088 plt.tight_layout() 1089 fname = f'arrival_distribution_{ttype}_{tid}.jpg' 1090 plt.savefig(os.path.join(dir_cargo, fname)) 1091 plt.close() 1092 1093 1094def delete_truck_data_csv(run_id): 1095 """ 1096 Delete the truck_data.csv file if it exists and save the truck data as a pickle file. 1097 Args: 1098 run_id (str): Unique identifier for the run to save the truck data as a pickle file. 1099 Returns: 1100 None: Deletes the truck_data.csv file and saves the truck data as a pickle file. 1101 """ 1102 csv_path = f'.{run_id}/logs/truck_data.csv' 1103 pkl_path = f'.{run_id}/logs/truck_data.pkl' 1104 df = pd.read_csv(csv_path) 1105 df.to_pickle(pkl_path) 1106 1107 1108 1109 1110###################### 1111# Logs and Plots 1112####################### 1113 1114def update_ship_logs(ship_logs, ship_type, ship_id, selected_terminal, ship_start_time="nan", time_to_get_berth="nan", time_for_restriction_in="nan", time_to_get_pilot_in="nan", 1115 time_to_get_tugs_in="nan", time_to_common_channel_in="nan", time_to_travel_channel_in="nan", time_to_tug_steer_in="nan", unloading_time="nan", loading_time="nan", waiting_time="nan", 1116 departure_time="nan", time_to_get_pilot_out="nan", time_to_get_tugs_out="nan", time_to_tug_steer_out="nan", time_for_restriction_out='nan', time_for_uturn="nan", 1117 time_to_travel_channel_out="nan", time_to_common_channel_out="nan", ship_end_time="nan"): 1118 """ 1119 Updates the ship logs with the provided information. If a log with the same Ship ID exists, it updates the existing log; otherwise, it appends a new log. 1120 Note 1: The ship type is represented by a single character (e.g., "C" for Container, "L" for Liquid, "D" for Dry Bulk). 1121 Note 2: If the log already exists, it updates the existing log with the new values, ignoring 'nan' values. 1122 Args: 1123 ship_logs (list): List of existing ship logs. 1124 ship_type (str): Type of the ship (e.g., "C" for Container, "L" for Liquid, "D" for Dry Bulk). 1125 ship_id (int): Unique identifier for the ship. 1126 selected_terminal (str): The terminal where the ship is directed. 1127 ship_start_time (str): Start time of the ship's operation. 1128 time_to_get_berth (str): Time taken to get to the berth. 1129 time_for_restriction_in (str): Time for restriction in. 1130 time_to_get_pilot_in (str): Time taken to get the pilot in. 1131 time_to_get_tugs_in (str): Time taken to get tugs in. 1132 time_to_common_channel_in (str): Time taken to get to the common channel in. 1133 time_to_travel_channel_in (str): Time taken to travel the channel in. 1134 time_to_tug_steer_in (str): Time taken for tug steering in. 1135 unloading_time (str): Time taken for unloading. 1136 loading_time (str): Time taken for loading. 1137 waiting_time (str): Time spent waiting. 1138 departure_time (str): Departure time of the ship. 1139 time_to_get_pilot_out (str): Time taken to get the pilot out. 1140 time_to_get_tugs_out (str): Time taken to get tugs out. 1141 time_to_tug_steer_out (str): Time taken for tug steering out. 1142 time_for_restriction_out (str): Time for restriction out. 1143 time_for_uturn (str): Time taken for U-turn. 1144 time_to_travel_channel_out (str): Time taken to travel the channel out. 1145 time_to_common_channel_out (str): Time taken to get to the common channel out. 1146 ship_end_time (str): End time of the ship's operation. 1147 Returns: 1148 list: Updated ship logs with the new or modified log entry. 1149 """ 1150 1151 if ship_type == "C": 1152 ship_type_full = "Container" 1153 elif ship_type == "L": 1154 ship_type_full = "Liquid" 1155 elif ship_type == "D": 1156 ship_type_full = "Dry Bulk" 1157 1158 # Create the log entry 1159 new_log = [ 1160 f"Ship_{ship_id}", 1161 ship_type_full, 1162 f"{ship_type}.{selected_terminal}", 1163 ship_start_time, 1164 time_to_get_berth, 1165 time_for_restriction_in, 1166 time_to_get_pilot_in, 1167 time_to_get_tugs_in, 1168 time_to_common_channel_in, 1169 time_to_travel_channel_in, 1170 time_to_tug_steer_in, 1171 unloading_time, 1172 loading_time, 1173 waiting_time, 1174 departure_time, 1175 time_to_get_pilot_out, 1176 time_to_get_tugs_out, 1177 time_for_uturn, 1178 time_to_tug_steer_out, 1179 time_for_restriction_out, 1180 time_to_travel_channel_out, 1181 time_to_common_channel_out, 1182 None, 1183 None, 1184 ship_end_time 1185 ] 1186 1187 # Check if the log with the same Ship ID exists 1188 for i, log in enumerate(ship_logs): 1189 if log[0] == f"Ship_{ship_id}": 1190 for j, vals in enumerate(new_log): 1191 if new_log[j] != 'nan': 1192 ship_logs[i][j] = vals 1193 1194 return ship_logs 1195 1196 # If no existing log is found, append the new log 1197 ship_logs.append(new_log) 1198 1199 return ship_logs 1200 1201 1202def analyse_bays_chassis_utilization(chassis_bays_utilization, num_terminals_list, run_id): 1203 """ 1204 Generates plots for chassis and bay utilization at each terminal type. 1205 Args: 1206 chassis_bays_utilization (dict): Dictionary containing chassis and bay utilization data for each terminal type. 1207 num_terminals_list (list): List containing the number of terminals for each terminal type. 1208 run_id (str): Unique identifier for the run to save the plots. 1209 Returns: 1210 None: Saves the plots as PDF files in the specified directory. 1211 """ 1212 for terminal_type in ["Container", "Liquid", "DryBulk"]: 1213 for terminal_id in range(1, num_terminals_list[["Container", "Liquid", "DryBulk"].index(terminal_type)] + 1): 1214 to_plot = chassis_bays_utilization[terminal_type][terminal_id] 1215 1216 x_vals = [] 1217 y_vals = [] 1218 1219 for vals in to_plot: 1220 x_vals.append(vals[0]) 1221 y_vals.append(vals[1]) 1222 1223 plt.figure(figsize=(12, 8)) 1224 plt.plot(x_vals, y_vals) 1225 plt.title( 1226 f'Chassis / Bay Utilization at {terminal_type} Terminal {terminal_id}') 1227 plt.xlabel('Time') 1228 plt.ylabel('Utilization') 1229 plt.savefig( 1230 f'.{run_id}/bottlenecks/chassisBays/{terminal_type}_Terminal_{terminal_id}.pdf') 1231 plt.close() 1232 1233 1234def gen_logs_and_plots(run_id, ship_logs, events, chassis_bays_utilization, num_terminals_list, train_events, channel_logs, channel_events, channel, animate=True): 1235 """ 1236 Generates logs and plots from the provided ship logs, events, chassis and bay utilization data, train events, and channel logs. 1237 Saves the logs to an Excel file and generates various plots including process charts, dwell times, turn times, truck dwell times, and channel utilization. 1238 Also generates utilization reports for resources such as berths and yards. 1239 The following plots are generated: 1240 - Process chart for ship operations 1241 - Dwell time chart for ships 1242 - Turn time analysis for ships 1243 - Truck dwell times and arrival times at terminals 1244 - Channel utilization over time 1245 - Chassis and bay utilization at each terminal type 1246 The results are saved in the specified run directory. 1247 Args: 1248 run_id (str): Unique identifier for the run to save the logs and plots. 1249 ship_logs (list): List of ship logs containing information about each ship's operations. 1250 events (list): List of events where each event is a tuple containing: 1251 (event_type, time, name, terminal_id, terminal_type) 1252 chassis_bays_utilization (dict): Dictionary containing chassis and bay utilization data for each terminal type. 1253 num_terminals_list (list): List containing the number of terminals for each terminal type. 1254 train_events (dict): Dictionary containing train events where keys are event names and values are lists of event data. 1255 channel_logs (list): List of channel logs containing information about the channel's operations. 1256 channel_events (list): List of channel events where each event is a tuple containing: 1257 (event_type, time, name, section_id, section_type) 1258 channel (object): Channel object containing sections and their respective containers. 1259 Returns: 1260 None: Saves the logs and plots in the specified directory. 1261 """ 1262 def extract_t(restriction_str): 1263 if isinstance(restriction_str, str): 1264 parts = restriction_str.split(", ") 1265 for part in parts: 1266 if part.startswith("T"): 1267 return float(part.split(":")[1]) 1268 return 0.0 1269 1270 # Ship logs 1271 columns = ["Ship_Id", "Terminal Type", "Terminal Directed", "Start Time", "Time to get Berth", "Time for Restriction In", "Time to get Pilot In", "Time to get Tugs In", "Time to Common Channel In", "Time to Travel Channel In", "Time to Tug Steer In", "Unloading Time", 1272 "Loading Time", "Waiting Time", "Departure Time", "Time to get Pilot Out", "Time to get Tugs Out", "Time for U-Turn", "Time to Tug Steer Out", "Time for Restriction Out", "Time to Travel Channel Out", "Time to Common Channel Out", "Turn Time", "Dwell Time", "End Time"] 1273 df = pd.DataFrame(ship_logs, columns=columns) 1274 1275 # all columns in float format of 2 decimal places 1276 for col in df.columns: 1277 if col not in ["Ship_Id", "Terminal Type", "Terminal Directed", "Time for Restriction In", "Time for Restriction Out"]: 1278 df[col] = df[col].astype(float).round(2) 1279 1280 df['Ship_Id'] = df['Ship_Id'].str.extract(r'(\d+)').astype(int) 1281 df = df.sort_values(by='Ship_Id') 1282 df['Ship_Id'] = "Ship_" + df['Ship_Id'].astype(str) 1283 df['Turn Time'] = df['End Time'] - df['Start Time'] 1284 df['Dwell Time'] = df['Unloading Time'] + \ 1285 df['Loading Time'] + df['Waiting Time'] 1286 1287 df['Restriction In (T)'] = df['Time for Restriction In'].apply(extract_t) 1288 df['Anchorage Time'] = df['Time to get Berth'] + \ 1289 df['Restriction In (T)'] + df['Time to get Pilot In'] + \ 1290 df['Time to get Tugs In'] 1291 # if ship type is container add anchorage waiting_container as a column 1292 for index, row in df.iterrows(): 1293 if row['Terminal Type'] == 'Container': 1294 df.at[index, 'Anchorage Time'] += constants.ANCHORAGE_WAITING_CONTAINER 1295 elif row['Terminal Type'] == 'Liquid': 1296 df.at[index, 'Anchorage Time'] += constants.ANCHORAGE_WAITING_LIQUID 1297 elif row['Terminal Type'] == 'Dry Bulk': 1298 df.at[index, 'Anchorage Time'] += constants.ANCHORAGE_WAITING_DRYBULK 1299 1300 df.to_excel(f'.{run_id}/logs/ship_logs.xlsx', index=False) 1301 1302 warmup_period = constants.WARMUP_ITERS / len(df) 1303 mean_anchorage_time = df['Anchorage Time'].sort_values( 1304 ).iloc[constants.WARMUP_ITERS:].mean() 1305 mean_anchorage_time_ctr = df[df['Terminal Type'] == 'Container']['Anchorage Time'].sort_values( 1306 ).iloc[int(warmup_period * len(df[df['Terminal Type'] == 'Container'])):].mean() 1307 mean_anchorage_time_liq = df[df['Terminal Type'] == 'Liquid']['Anchorage Time'].sort_values( 1308 ).iloc[int(warmup_period * len(df[df['Terminal Type'] == 'Liquid'])):].mean() 1309 mean_anchorage_time_db = df[df['Terminal Type'] == 'Dry Bulk']['Anchorage Time'].sort_values( 1310 ).iloc[int(warmup_period * len(df[df['Terminal Type'] == 'Dry Bulk'])):].mean() 1311 1312 report_path = f".{run_id}/logs/final_report.txt" 1313 with open(report_path, 'a') as f: 1314 f.write(f"Mean Anchorage Time: {mean_anchorage_time}\n") 1315 f.write(f"Mean Anchorage Time Container: {mean_anchorage_time_ctr}\n") 1316 f.write(f"Mean Anchorage Time Liquid: {mean_anchorage_time_liq}\n") 1317 f.write(f"Mean Anchorage Time Dry Bulk: {mean_anchorage_time_db}\n\n") 1318 1319 # Generate the process chart 1320 plot_process_chart(events, run_id=run_id) 1321 1322 # Generate the dwell time chart 1323 plot_dwell_times(events, run_id) 1324 analyze_turn_time_ships(run_id) 1325 get_dists(run_id) 1326 1327 # Plot queueus in channel 1328 ships_in_channel_analysis(run_id) 1329 ships_in_anchorage_analysis(ship_type='all', run_id=run_id) 1330 ships_in_anchorage_analysis(ship_type='Container', run_id=run_id) 1331 ships_in_anchorage_analysis(ship_type='DryBulk', run_id=run_id) 1332 ships_in_anchorage_analysis(ship_type='Liquid', run_id=run_id) 1333 1334 # Trucks 1335 create_truck_csv(run_id) 1336 plot_dwell_by_type(run_id) 1337 plot_dwell_by_terminal(run_id) 1338 plot_arrival_distributions(run_id) 1339 delete_truck_data_csv(run_id) 1340 1341 # Bays & Chassis Utilization 1342 analyse_bays_chassis_utilization( 1343 chassis_bays_utilization, num_terminals_list, run_id) 1344 1345 # Train 1346 gen_train_df(train_events, run_id) 1347 1348 # tugs and pilots 1349 tug_pilot_df = pd.read_csv(f'.{run_id}/logs/pilots_tugs_data.csv') 1350 1351 # plot day anf night pilots 1352 plt.figure(figsize=(12, 8)) 1353 plt.plot(tug_pilot_df['Time'], 1354 tug_pilot_df['Day Pilots'], label='Day Pilots') 1355 plt.title('Number of day pilots available') 1356 plt.xlabel('Time') 1357 plt.ylabel('Number of pilots') 1358 plt.ylim(0, constants.NUM_PILOTS_DAY[1]) 1359 plt.savefig(f'.{run_id}/plots/DayPilots.pdf') 1360 plt.close() 1361 1362 plt.figure(figsize=(12, 8)) 1363 plt.plot(tug_pilot_df['Time'], 1364 tug_pilot_df['Night Pilots'], label='Night Pilots') 1365 plt.title('Number of night pilots available') 1366 plt.xlabel('Time') 1367 plt.ylabel('Number of pilots') 1368 plt.ylim(0, constants.NUM_PILOTS_NIGHT[1]) 1369 plt.savefig(f'.{run_id}/plots/NightPilots.pdf') 1370 plt.close() 1371 1372 # plot tugboats 1373 plt.figure(figsize=(12, 8)) 1374 plt.plot(tug_pilot_df['Time'], tug_pilot_df['Tugboats'], label='Tugboats') 1375 plt.title('Tugboats') 1376 plt.xlabel('Time') 1377 plt.ylabel('Number of tugboats available') 1378 plt.ylim(0, constants.NUM_TUGBOATS[1]) 1379 plt.legend() 1380 plt.savefig(f'.{run_id}/plots/Tugboats.pdf') 1381 plt.close() 1382 1383 # Animate the channel usage 1384 if animate: 1385 animate_channel_usage(run_id, channel) 1386 1387 # print("Done!") 1388 1389 1390####################### 1391# Channel Utilization Animation (DEPRECATED) 1392######################## 1393 1394def animate_channel_usage(run_id, channel): 1395 """ 1396 DEPRICIATED 1397 Animates the channel usage over time, showing width and draft utilization. 1398 Args: 1399 run_id (str): Unique identifier for the run to save the animation. 1400 channel (object): Channel object containing sections and their respective containers. 1401 Returns: 1402 None: Saves the animation as a GIF file in the specified directory. 1403 """ 1404 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6)) 1405 1406 channel_sections = constants.CHANNEL_SECTION_DIMENSIONS 1407 1408 x_max = len(channel.sections) 1409 y_max = 100 1410 1411 def init(): 1412 1413 for i in range(1, len(channel.sections)+1): 1414 ax1.bar(i, 100 * channel.sections[i-1].width_containers[0].level / 1415 channel.sections[i-1].width_containers[0].capacity, color='b') 1416 ax2.bar(i, 100 * channel.sections[i-1].draft_containers[0].level / 1417 channel.sections[i-1].draft_containers[0].capacity, color='r') 1418 ax1.set_xlim(0, x_max) 1419 ax1.set_ylim(0, y_max) 1420 ax2.set_xlim(0, x_max) 1421 ax2.set_ylim(0, y_max) 1422 ax1.set_title('Width utilization') 1423 ax2.set_title('Draft utilization') 1424 return ax1.patches + ax2.patches 1425 1426 progress_bar = tqdm(total=int(len(channel.sections[0].width_containers)/2)) 1427 1428 def animate(t): 1429 1430 ax1.clear() 1431 ax2.clear() 1432 1433 # print(f'Animating {t}') 1434 progress_bar.update(1) 1435 for i in range(1, len(channel.sections) + 1): 1436 ax1.bar(i, 100 * channel.sections[i-1].width_containers[t].level / 1437 channel.sections[i-1].width_containers[t].capacity, color='b') 1438 ax2.bar(i, 100 * channel.sections[i-1].draft_containers[t].level / 1439 channel.sections[i-1].draft_containers[t].capacity, color='r') 1440 # print(t, i, channel.sections[i-1].width_containers[0].level, channel.sections[i-1].width_containers[0].capacity) 1441 1442 ax1.set_xlim(0, x_max) 1443 ax1.set_ylim(0, y_max) 1444 ax2.set_xlim(0, x_max) 1445 ax2.set_ylim(0, y_max) 1446 fig.suptitle(f'Time Step: {t}', fontsize=16, fontweight='bold') 1447 return ax1.patches + ax2.patches 1448 1449 ani = animation.FuncAnimation(fig, animate, init_func=init, frames=range( 1450 int(len(channel.sections[0].width_containers)/2)), interval=1, blit=False) 1451 1452 # Save the animation as gif 1453 ani.save(f'.{run_id}/animations/channel_avilability.gif', 1454 writer='pillow', fps=1000) 1455 plt.close()
28def plot_process_chart(events, run_id): 29 """ 30 # TODO: Ensure this is compatible with the new event structure. 31 Generates process charts for each terminal based on the events. 32 Args: 33 events (list): List of events where each event is a tuple containing: 34 (event_name, terminal_type, terminal_id, event_type, time, additional_info) 35 run_id (str): Unique identifier for the run to save the plots. 36 Returns: 37 None: Saves process charts as images in the specified directory. 38 """ 39 terminal_events = {} 40 41 output_dir = f'.{run_id}/plots/TerminalProcessCharts' 42 if not os.path.exists(output_dir): 43 os.makedirs(output_dir) 44 45 for event in events: 46 try: 47 _, terminal_type, terminal_id, event_type, time, _ = event 48 key = (terminal_type, terminal_id) 49 if key not in terminal_events: 50 terminal_events[key] = {} 51 terminal_events[key].setdefault(event_type, []).append(time) 52 except: 53 if event[0] != "Truck Arrival" and event[0] != "Truck Departure": 54 print(event) 55 pass 56 57 for (terminal_type, terminal_id), events in terminal_events.items(): 58 try: 59 df = pd.DataFrame() 60 df['Time'] = events['arrive'] + events['dock'] + \ 61 sum([events[e] for e in events if e not in [ 62 'arrive', 'dock', 'depart']], []) + events['depart'] 63 df['Event'] = ['Ship Arrival'] * len(events['arrive']) + ['Ship Dock'] * len(events['dock']) + sum([[e] * len( 64 events[e]) for e in events if e not in ['arrive', 'dock', 'depart']], []) + ['Ship Depart'] * len(events['depart']) 65 66 plt.figure(figsize=(10, 6)) 67 plt.scatter(df['Time'], df['Event']) 68 plt.title( 69 f'Process Chart for {terminal_type} Terminal {terminal_id}') 70 plt.xlabel('Time') 71 plt.ylabel('Event') 72 73 plt.savefig( 74 f'{output_dir}/process_chart_{terminal_type}_{terminal_id}.jpg') 75 plt.close() 76 except KeyError: 77 pass
TODO: Ensure this is compatible with the new event structure.
Generates process charts for each terminal based on the events.
Arguments:
- events (list): List of events where each event is a tuple containing: (event_name, terminal_type, terminal_id, event_type, time, additional_info)
- run_id (str): Unique identifier for the run to save the plots.
Returns:
None: Saves process charts as images in the specified directory.
84def plot_dwell_times(events, run_id): 85 """ 86 Generates plots for dwell times and turn times of ships at terminals. 87 Args: 88 events (list): List of events where each event is a tuple containing: 89 (ship_name, terminal_type, terminal_id, event_type, time, additional_info) 90 run_id (str): Unique identifier for the run to save the plots. 91 Returns: 92 None: Saves plots as images in the specified directory. 93 """ 94 95 all_dwell_times = [] 96 all_turn_times = [] 97 98 ship_arrival_times = [] 99 ship_dock_times = [] 100 ship_undock_times = [] 101 ship_departure_times = [] 102 103 dwell_times = {} 104 turn_times = {} 105 106 # read the events 107 for event in events: 108 try: 109 name, terminal_type, terminal_id, event_type, time, _ = event 110 if event_type == "arrive": # arrive at anchorage 111 ship_arrival_times.append( 112 (name, terminal_type, terminal_id, time)) 113 if event_type == "dock": 114 ship_dock_times.append( 115 (name, terminal_type, terminal_id, time)) 116 if event_type == "undock": 117 ship_undock_times.append( 118 (name, terminal_type, terminal_id, time)) 119 elif event_type == "depart": 120 ship_departure_times.append( 121 (name, terminal_type, terminal_id, time)) 122 except: 123 pass 124 125 # Calculate turn times 126 for ship, terminal_type, terminal_id, arrival_time in ship_arrival_times: 127 for name, t_type, t_id, departure_time in ship_departure_times: 128 if ship == name and terminal_type == t_type and terminal_id == t_id: 129 key = (terminal_type, terminal_id) 130 if key not in turn_times: 131 turn_times[key] = {} 132 turn_times[key][ship] = departure_time - arrival_time 133 all_turn_times.append(departure_time - arrival_time) 134 135 # Calculate dwell times 136 for ship, terminal_type, terminal_id, dock_time in ship_dock_times: 137 for name, t_type, t_id, undock_time in ship_undock_times: 138 if ship == name and terminal_type == t_type and terminal_id == t_id: 139 key = (terminal_type, terminal_id) 140 if key not in dwell_times: 141 dwell_times[key] = {} 142 dwell_times[key][ship] = undock_time - dock_time 143 all_dwell_times.append(undock_time - dock_time) 144 145 # Create bar plot of the dwell times for individual vessels, by unique terminal 146 for (terminal_type, terminal_id), ships_dwell_times in dwell_times.items(): 147 try: 148 df = pd.DataFrame(ships_dwell_times.items(), 149 columns=['Ship', 'Dwell Time']) 150 # Set 'Ship' as index for proper labeling on x-axis 151 df.set_index('Ship', inplace=True) 152 df.plot(kind='bar', y='Dwell Time', legend=False, 153 title=f'Dwell times of ships at {terminal_type} terminal {terminal_id}') 154 plt.ylabel('Dwell time (hr)') 155 plt.savefig( 156 f'.{run_id}/plots/DwellTimes/dwell_times_{terminal_type}_{terminal_id}.jpg') 157 plt.close() # Clear the figure after saving the plot 158 except Exception as e: 159 print( 160 f"Error in dwell time plot generation for {terminal_type} terminal {terminal_id}: {e}") 161 pass 162 163 # Create bar plot of the turn times for individual vessels, by unique terminal 164 for (terminal_type, terminal_id), ships_turn_times in turn_times.items(): 165 try: 166 df = pd.DataFrame(ships_turn_times.items(), 167 columns=['Ship', 'Turn Time']) 168 # Set 'Ship' as index for proper labeling on x-axis 169 df.set_index('Ship', inplace=True) 170 df.plot(kind='bar', y='Turn Time', legend=False, 171 title=f'Turn Times of Ships at {terminal_type} Terminal {terminal_id}') 172 plt.ylabel('Turn Time') 173 plt.savefig( 174 f'.{run_id}/plots/TurnTimes/turn_times_{terminal_type}_{terminal_id}.jpg') 175 plt.close() # Clear the figure after saving the plot 176 except Exception as e: 177 print( 178 f"Error in turn time plot generation for {terminal_type} Terminal {terminal_id}: {e}") 179 pass 180 181 # Create histogram of the dwell times of all vessels visiting each unique terminal 182 for (terminal_type, terminal_id), ships_dwell_times in dwell_times.items(): 183 try: 184 dwell_times_list = list(ships_dwell_times.values()) 185 plt.figure(figsize=(10, 6)) 186 plt.hist(dwell_times_list, bins=20, edgecolor='black') 187 plt.title( 188 f'Dwell time distribution at {terminal_type} terminal {terminal_id}') 189 plt.xlabel('Dwell time (hr)') 190 plt.ylabel('Frequency') 191 plt.savefig( 192 f'.{run_id}/plots/DwellTimesDist/dwell_distribution_{terminal_type}_{terminal_id}.jpg') 193 plt.close() 194 except Exception as e: 195 print( 196 f"Error in dwell time distribution plot generation for {terminal_type} terminal {terminal_id}: {e}") 197 pass 198 199 # Create histogram of the turn times of all vessels visiting each unique terminal 200 for (terminal_type, terminal_id), ships_turn_times in turn_times.items(): 201 try: 202 turn_times_list = list(ships_turn_times.values()) 203 plt.figure(figsize=(10, 6)) 204 plt.hist(turn_times_list, bins=20, edgecolor='black') 205 plt.title( 206 f'Turn time distribution at {terminal_type} terminal {terminal_id}', fontsize=20) 207 plt.xlabel('Turn time (hr)', fontsize=16) 208 plt.ylabel('Frequency', fontsize=16) 209 plt.savefig( 210 f'.{run_id}/plots/TurnTimesDist/turn_distribution_{terminal_type}_{terminal_id}.jpg') 211 plt.close() 212 except Exception as e: 213 print( 214 f"Error in turn time distribution plot generation for {terminal_type} terminal {terminal_id}: {e}") 215 pass 216 217 # Create histogram of dwell times for all vessels in port system (all terminals) 218 all_dwell_times = [time for terminal_dwell_times in dwell_times.values( 219 ) for time in terminal_dwell_times.values()] 220 plt.figure(figsize=(10, 6)) 221 plt.hist(all_dwell_times, bins=20, edgecolor='black') 222 plt.title('Overall vessel terminal dwell time distribution', fontsize=20) 223 plt.xlabel('Dwell time (hr)', fontsize=16) 224 plt.ylabel('Frequency', fontsize=16) 225 plt.xticks(fontsize=12) 226 plt.yticks(fontsize=12) 227 plt.savefig(f'.{run_id}/plots/dwell_time_distribution.jpg') 228 plt.close() 229 230 # Create histogram of turn times for all vessels in port system (all terminals) 231 all_turn_times = [time for terminal_turn_times in turn_times.values() 232 for time in terminal_turn_times.values()] 233 plt.figure(figsize=(10, 6)) 234 plt.hist(all_turn_times, bins=20, edgecolor='black') 235 plt.title('Overall vessel turn time distribution', fontsize=20) 236 plt.xlabel('Turn time (hr)', fontsize=16) 237 plt.ylabel('Frequency', fontsize=16) 238 plt.xticks(fontsize=12) 239 plt.yticks(fontsize=12) 240 plt.savefig(f'.{run_id}/plots/turn_time_distribution.jpg') 241 plt.close() 242 243 report_path = f".{run_id}/logs/final_report.txt" 244 with open(report_path, 'a') as f: 245 f.write("Dwell Time Analysis:\n") 246 f.write(f"Max dwell time: {max(all_dwell_times)}\n") 247 f.write( 248 f"Median dwell time: {sorted(all_dwell_times)[len(all_dwell_times) // 2]}\n") 249 f.write( 250 f"Average dwell time: {sum(all_dwell_times) / len(all_dwell_times)}\n\n") 251 252 f.write("Turn Time Analysis:\n") 253 f.write(f"Max turn time: {max(all_turn_times)}\n") 254 f.write( 255 f"Median turn time: {sorted(all_turn_times)[len(all_turn_times) // 2]}\n") 256 f.write( 257 f"Average turn time: {sum(all_turn_times) / len(all_turn_times)}\n\n") 258 259 return None
Generates plots for dwell times and turn times of ships at terminals.
Arguments:
- events (list): List of events where each event is a tuple containing: (ship_name, terminal_type, terminal_id, event_type, time, additional_info)
- run_id (str): Unique identifier for the run to save the plots.
Returns:
None: Saves plots as images in the specified directory.
262def analyze_turn_time_ships(run_id): 263 """ Analyze the turn times and dwell times of ships from the Excel file and save the results to a report. 264 Args: 265 run_id (str): Unique identifier for the run to save the report. 266 Returns: 267 None: Saves the analysis results to a text file in the specified directory. 268 """ 269 # Load the Excel file 270 file_path = f'.{run_id}/logs/ship_logs.xlsx' 271 272 df = pd.read_excel(file_path) 273 mean_dwell_times = df.groupby(['Terminal Directed'])[ 274 'Turn Time'].mean().reset_index() 275 with open(f".{run_id}/logs/final_report.txt", 'a') as f: 276 f.write("Mean Turn Times by Terminal Directed:\n") 277 f.write(str(mean_dwell_times) + "\n\n") 278 mean_dwell_times = df.groupby(['Terminal Type'])[ 279 'Turn Time'].mean().reset_index() 280 with open(f".{run_id}/logs/final_report.txt", 'a') as f: 281 f.write("Mean Turn Times by Terminal Type:\n") 282 f.write(str(mean_dwell_times) + "\n\n") 283 284 mean_dwell_times = df.groupby(['Terminal Directed'])[ 285 'Dwell Time'].mean().reset_index() 286 with open(f".{run_id}/logs/final_report.txt", 'a') as f: 287 f.write("Mean Dwell Times by Terminal Directed:\n") 288 f.write(str(mean_dwell_times) + "\n\n") 289 mean_dwell_times = df.groupby(['Terminal Type'])[ 290 'Dwell Time'].mean().reset_index() 291 with open(f".{run_id}/logs/final_report.txt", 'a') as f: 292 f.write("Mean Dwell Times by Terminal Type:\n") 293 f.write(str(mean_dwell_times) + "\n\n")
Analyze the turn times and dwell times of ships from the Excel file and save the results to a report.
Arguments:
- run_id (str): Unique identifier for the run to save the report.
Returns:
None: Saves the analysis results to a text file in the specified directory.
300def plot_queues(SHIPS_IN_CHANNEL_TRACK, name, place, run_id): 301 """ 302 Plots the number of ships in the channel over time. 303 Args: 304 SHIPS_IN_CHANNEL_TRACK (list): List of tuples containing time and number of ships in the channel. 305 name (str): Name of the ship type (e.g., "Container", "Liquid", "Dry Bulk"). 306 place (str): Place where the ships are tracked (e.g., "Port"). 307 run_id (str): Unique identifier for the run to save the plot. 308 Returns: 309 None: Saves the plot as a PDF file in the specified directory. 310 """ 311 312 # Plotting 313 plt.figure(figsize=(12, 8)) 314 315 x = [i[0] for i in SHIPS_IN_CHANNEL_TRACK] 316 y = [i[1] for i in SHIPS_IN_CHANNEL_TRACK] 317 plt.plot(x, y) 318 plt.suptitle(f'Number of {name} over time', fontsize=20) 319 plt.title(f'at {place}', fontsize=20) 320 plt.xlabel('Time', fontsize=15) 321 plt.ylabel(f'Number of {name}', fontsize=15) 322 plt.xticks(fontsize=12) 323 plt.yticks(fontsize=12) 324 plt.tight_layout() 325 plt.xlim(min(x)-1, max(x)+2) 326 plt.ylim(min(y)-1, max(y)+2) 327 plt.savefig( 328 f'.{run_id}/plots/{name}in{place}OverTime{constants.mean_interarrival_time_total}.pdf') 329 330 report_path = "./logs/final_report.txt" 331 with open(report_path, 'a') as f: 332 f.write(f"Mean number of {name} in {place}: {sum(y) / len(y)}\n\n") 333 334 plt.close()
Plots the number of ships in the channel over time.
Arguments:
- SHIPS_IN_CHANNEL_TRACK (list): List of tuples containing time and number of ships in the channel.
- name (str): Name of the ship type (e.g., "Container", "Liquid", "Dry Bulk").
- place (str): Place where the ships are tracked (e.g., "Port").
- run_id (str): Unique identifier for the run to save the plot.
Returns:
None: Saves the plot as a PDF file in the specified directory.
342def track_utilization(container_data, liquid_data, drybulk_data, run_id): 343 """ 344 Calculates the mean utilization of resources (berths and yards) for container, liquid, and dry bulk terminals. 345 Args: 346 container_data (pd.DataFrame): DataFrame containing container terminal data. 347 liquid_data (pd.DataFrame): DataFrame containing liquid terminal data. 348 drybulk_data (pd.DataFrame): DataFrame containing dry bulk terminal data. 349 run_id (str): Unique identifier for the run to save the results. 350 Returns: 351 list: A list containing the mean utilization of resources for each terminal type. 352 """ 353 354 track_list = [] 355 356 excel_data = f'.{run_id}/logs/availability.xlsx' 357 358 def calculate_mean_utilization(data, resource_type): 359 available_columns = [ 360 col for col in data.columns if 'Available' in col and resource_type in col] 361 used_columns = [ 362 col for col in data.columns if 'Used' in col and resource_type in col] 363 364 utilization = {} 365 for available, used in zip(available_columns, used_columns): 366 terminal_name = available.split('_Available_')[0] 367 utilization[terminal_name] = data[used].sum( 368 ) / (data[available].sum() + data[used].sum()) 369 370 overall_utilization = sum(data[used_columns].sum( 371 )) / (sum(data[available_columns].sum()) + sum(data[used_columns].sum())) 372 373 return utilization, overall_utilization 374 375 container_berth_utilization, container_berth_overall = calculate_mean_utilization( 376 container_data, 'Berth_Ctr') 377 container_yard_utilization, container_yard_overall = calculate_mean_utilization( 378 container_data, 'Yard') 379 380 liquid_berth_utilization, liquid_berth_overall = calculate_mean_utilization( 381 liquid_data, 'Berth_liq') 382 liquid_tank_utilization, liquid_tank_overall = calculate_mean_utilization( 383 liquid_data, 'Tank') 384 385 drybulk_berth_utilization, drybulk_berth_overall = calculate_mean_utilization( 386 drybulk_data, 'Berth_db') 387 drybulk_silo_utilization, drybulk_silo_overall = calculate_mean_utilization( 388 drybulk_data, 'Silo') 389 390 track_list = [container_berth_utilization, container_berth_overall, container_yard_utilization, container_yard_overall, liquid_berth_utilization, 391 liquid_berth_overall, liquid_tank_utilization, liquid_tank_overall, drybulk_berth_utilization, drybulk_berth_overall, drybulk_silo_utilization, drybulk_silo_overall] 392 393 return track_list
Calculates the mean utilization of resources (berths and yards) for container, liquid, and dry bulk terminals.
Arguments:
- container_data (pd.DataFrame): DataFrame containing container terminal data.
- liquid_data (pd.DataFrame): DataFrame containing liquid terminal data.
- drybulk_data (pd.DataFrame): DataFrame containing dry bulk terminal data.
- run_id (str): Unique identifier for the run to save the results.
Returns:
list: A list containing the mean utilization of resources for each terminal type.
396def get_utilization(container_data, liquid_data, drybulk_data, run_id): 397 """ 398 Calculates the mean utilization of resources (berths and yards) for container, liquid, and dry bulk terminals. 399 Args: 400 container_data (pd.DataFrame): DataFrame containing container terminal data. 401 liquid_data (pd.DataFrame): DataFrame containing liquid terminal data. 402 drybulk_data (pd.DataFrame): DataFrame containing dry bulk terminal data. 403 run_id (str): Unique identifier for the run to save the results. 404 Returns: 405 list: A list containing the mean utilization of resources for each terminal type. 406 """ 407 408 track_list = [] 409 410 excel_data = f'.{run_id}/logs/availability.xlsx' 411 412 def calculate_mean_utilization(data, resource_type): 413 available_columns = [ 414 col for col in data.columns if 'Available' in col and resource_type in col] 415 used_columns = [ 416 col for col in data.columns if 'Used' in col and resource_type in col] 417 # print("used:", used_columns) 418 utilization = {} 419 for available, used in zip(available_columns, used_columns): 420 terminal_name = available.split('_Available_')[0] 421 # print("data", data) 422 # print("used:", used) 423 # print(data[used]) 424 # print(list(data[used])) 425 utilization[terminal_name] = list( 426 data[used])[-1] / (list(data[available])[-1] + list(data[used])[-1]) 427 overall_utilization = data[used_columns].iloc[-1].sum() / ( 428 data[available_columns].iloc[-1].sum() + data[used_columns].iloc[-1].sum()) 429 430 return utilization, overall_utilization 431 432 container_berth_utilization, container_berth_overall = calculate_mean_utilization( 433 container_data, 'Berth_Ctr') 434 container_yard_utilization, container_yard_overall = calculate_mean_utilization( 435 container_data, 'Yard') 436 437 liquid_berth_utilization, liquid_berth_overall = calculate_mean_utilization( 438 liquid_data, 'Berth_liq') 439 liquid_tank_utilization, liquid_tank_overall = calculate_mean_utilization( 440 liquid_data, 'Tank') 441 442 drybulk_berth_utilization, drybulk_berth_overall = calculate_mean_utilization( 443 drybulk_data, 'Berth_db') 444 drybulk_silo_utilization, drybulk_silo_overall = calculate_mean_utilization( 445 drybulk_data, 'Silo') 446 447 track_list = [container_berth_utilization, container_berth_overall, container_yard_utilization, container_yard_overall, liquid_berth_utilization, 448 liquid_berth_overall, liquid_tank_utilization, liquid_tank_overall, drybulk_berth_utilization, drybulk_berth_overall, drybulk_silo_utilization, drybulk_silo_overall] 449 450 return track_list
Calculates the mean utilization of resources (berths and yards) for container, liquid, and dry bulk terminals.
Arguments:
- container_data (pd.DataFrame): DataFrame containing container terminal data.
- liquid_data (pd.DataFrame): DataFrame containing liquid terminal data.
- drybulk_data (pd.DataFrame): DataFrame containing dry bulk terminal data.
- run_id (str): Unique identifier for the run to save the results.
Returns:
list: A list containing the mean utilization of resources for each terminal type.
453def add_to_report(report_lines, resource, utilization, overall_utilization): 454 """ 455 Adds the mean utilization of a resource to the report lines. 456 Args: 457 report_lines (list): List of report lines to which the utilization will be added. 458 resource (str): Name of the resource (e.g., "Container berth", "Liquid storage"). 459 utilization (dict): Dictionary containing the mean utilization for each terminal. 460 overall_utilization (float): Overall mean utilization for the resource. 461 Returns: 462 None: Appends the utilization information to the report lines. 463 """ 464 465 report_lines.append(f"Mean {resource} Utilization:") 466 for terminal, mean_util in utilization.items(): 467 report_lines.append(f"{terminal}: {mean_util:.2%}") 468 report_lines.append( 469 f"Overall {resource} Utilization: {overall_utilization:.2%}") 470 report_lines.append("\n")
Adds the mean utilization of a resource to the report lines.
Arguments:
- report_lines (list): List of report lines to which the utilization will be added.
- resource (str): Name of the resource (e.g., "Container berth", "Liquid storage").
- utilization (dict): Dictionary containing the mean utilization for each terminal.
- overall_utilization (float): Overall mean utilization for the resource.
Returns:
None: Appends the utilization information to the report lines.
473def save_track_list(run_id, timestep, track_list): 474 """ 475 Saves the utilization track list to a text file for the given timestep. 476 Args: 477 run_id (str): Unique identifier for the run to save the report. 478 timestep (int): The current timestep for which the report is generated. 479 track_list (list): List containing the mean utilization of resources for each terminal type. 480 Returns: 481 None: Saves the report to a text file in the specified directory. 482 """ 483 484 container_berth_utilization, container_berth_overall, container_yard_utilization, container_yard_overall, liquid_berth_utilization, liquid_berth_overall, liquid_tank_utilization, liquid_tank_overall, drybulk_berth_utilization, drybulk_berth_overall, drybulk_silo_utilization, drybulk_silo_overall = track_list 485 report_lines = [] 486 487 # Container Terminals 488 add_to_report(report_lines, "Container berth", 489 container_berth_utilization, container_berth_overall) 490 add_to_report(report_lines, "Container storage", 491 container_yard_utilization, container_yard_overall) 492 493 # Liquid Terminals 494 add_to_report(report_lines, "Liquid berth", 495 liquid_berth_utilization, liquid_berth_overall) 496 add_to_report(report_lines, "Liquid storage", 497 liquid_tank_utilization, liquid_tank_overall) 498 499 # Dry Bulk Terminals 500 add_to_report(report_lines, "Drybulk berth", 501 drybulk_berth_utilization, drybulk_berth_overall) 502 add_to_report(report_lines, "Drybulk storage", 503 drybulk_silo_utilization, drybulk_silo_overall) 504 505 # Save report to text file 506 report_path = f'.{run_id}/logs/availability/{timestep}.txt' 507 with open(report_path, 'a') as file: 508 file.write(f"Time Step: {timestep}\n") 509 file.write("\n".join(report_lines))
Saves the utilization track list to a text file for the given timestep.
Arguments:
- run_id (str): Unique identifier for the run to save the report.
- timestep (int): The current timestep for which the report is generated.
- track_list (list): List containing the mean utilization of resources for each terminal type.
Returns:
None: Saves the report to a text file in the specified directory.
516def gen_train_df(train_events, run_id): 517 """ 518 Generates a DataFrame from the train events and saves it to a CSV file. 519 Args: 520 train_events (dict): Dictionary containing train events where keys are event names and values are lists of event data. 521 run_id (str): Unique identifier for the run to save the DataFrame. 522 Returns: 523 pd.DataFrame: DataFrame containing the train events. 524 """ 525 train_events_df = pd.DataFrame(train_events).T 526 train_events_df.to_csv(f'.{run_id}/logs/train_logs.csv') 527 return train_events_df
Generates a DataFrame from the train events and saves it to a CSV file.
Arguments:
- train_events (dict): Dictionary containing train events where keys are event names and values are lists of event data.
- run_id (str): Unique identifier for the run to save the DataFrame.
Returns:
pd.DataFrame: DataFrame containing the train events.
534def get_dists(run_id, plot=True): 535 """ 536 Analyzes the ship logs to calculate the mean, min, max, and standard deviation of various time distributions for each terminal type. 537 Args: 538 run_id (str): Unique identifier for the run to save the analysis results. 539 plot (bool): Whether to generate and save plots of the time distributions. 540 Returns: 541 tuple: A tuple containing the mean, min, max, and standard deviation DataFrames for each terminal type. 542 """ 543 def extract_t(restriction_str): 544 if isinstance(restriction_str, str): 545 parts = restriction_str.split(", ") 546 for part in parts: 547 if part.startswith("T"): 548 return float(part.split(":")[1]) 549 return 0.0 550 551 data = pd.read_excel(f'.{run_id}/logs/ship_logs.xlsx', sheet_name='Sheet1') 552 553 data['Restriction In (T)'] = data['Time for Restriction In'].apply( 554 extract_t) 555 data['Restriction Out (T)'] = data['Time for Restriction Out'].apply( 556 extract_t) 557 558 data['Channel In'] = data['Time to Common Channel In'] + \ 559 data['Time to Travel Channel In'] 560 data['Channel Out'] = data['Time to Common Channel Out'] + \ 561 data['Time to Travel Channel Out'] 562 data['Terminal Ops'] = data['Loading Time'] + data['Unloading Time'] 563 data['Terminal Other'] = data['Waiting Time'] + data['Departure Time'] 564 565 columns = [ 566 'Time to get Berth', 'Restriction In (T)', 'Channel In', 'Time to get Pilot In', 567 'Time to get Tugs In', 'Terminal Ops', 'Terminal Other', 'Restriction Out (T)', 568 'Time to get Pilot Out', 'Time to get Tugs Out', 'Channel Out' 569 ] 570 571 mean_values = data.groupby('Terminal Type')[columns].mean() 572 min_values = data.groupby('Terminal Type')[columns].min() 573 max_values = data.groupby('Terminal Type')[columns].max() 574 std_values = data.groupby('Terminal Type')[columns].std() 575 576 error_bars = {'min': mean_values - min_values, 577 'max': max_values - mean_values} 578 579 with open(f'.{run_id}/logs/final_report.txt', 'a') as file: 580 file.write("Mean Time Distribution:\n") 581 file.write(mean_values.to_string()) 582 file.write("\n\nMin Time Distribution:\n") 583 file.write(min_values.to_string()) 584 file.write("\n\nMax Time Distribution:\n") 585 file.write(max_values.to_string()) 586 file.write("\n\nStandard Deviation:\n") 587 file.write(std_values.to_string()) 588 589 if plot: 590 for terminal in mean_values.index: 591 plt.figure(figsize=(10, 6)) 592 means = mean_values.loc[terminal] 593 min_err = error_bars['min'].loc[terminal] 594 max_err = error_bars['max'].loc[terminal] 595 stds = std_values.loc[terminal] 596 597 plt.errorbar(columns, means, yerr=[ 598 min_err, max_err], fmt='-o', capsize=5) 599 600 for i, (mean, min_e, max_e, std) in enumerate(zip(means, min_err, max_err, stds)): 601 plt.annotate( 602 f'Mean: {mean:.1f}\nSD: {std:.1f}', 603 (i, mean), 604 textcoords="offset points", 605 xytext=(0, 10), 606 ha='center' 607 ) 608 609 plt.xticks(rotation=90) 610 plt.title(f'{terminal} Terminal: Mean with Min/Max and SD') 611 plt.xlabel('Processes') 612 plt.ylabel('Time (Units)') 613 plt.tight_layout() 614 # os.makedirs('/plots/TerminalProcessDist/', exist_ok=True) 615 plt.savefig( 616 f'.{run_id}/plots/TerminalProcessDist/{terminal}_terminal_plot.png') 617 plt.close() 618 return mean_values, min_values, max_values, std_values
Analyzes the ship logs to calculate the mean, min, max, and standard deviation of various time distributions for each terminal type.
Arguments:
- run_id (str): Unique identifier for the run to save the analysis results.
- plot (bool): Whether to generate and save plots of the time distributions.
Returns:
tuple: A tuple containing the mean, min, max, and standard deviation DataFrames for each terminal type.
626def plot_channel(run_id): 627 """ 628 Plots the channel sections and their respective terminals. 629 Args: 630 run_id (str): Unique identifier for the run to save the plot. 631 Returns: 632 None: Saves the plot as a PDF file in the specified directory. 633 """ 634 635 # Normalize the draft values for color mapping 636 channel_sections = constants.CHANNEL_SECTION_DIMENSIONS 637 last_section_dict = constants.LAST_SECTION_DICT 638 drafts = [draft for _, _, draft, _ in channel_sections.values()] 639 norm = mcolors.Normalize(vmin=min(drafts), vmax=max(drafts)) 640 cmap = plt.cm.viridis 641 fig, ax = plt.subplots(figsize=(20, 8)) 642 max_width = max(width for _, width, _, _ in channel_sections.values()) 643 644 # Calculate cumulative lengths for x positions (rotated plot) 645 cumulative_length = 0 646 x_positions = {} 647 for section, (length, width, draft, speed) in channel_sections.items(): 648 x_positions[section] = cumulative_length + \ 649 length / 2 # Middle of the section 650 cumulative_length += length 651 652 for section, (length, width, draft, speed) in channel_sections.items(): 653 color = cmap(norm(draft)) 654 y_pos = (max_width - width) / 2 # Center-align the rectangle 655 ax.add_patch(plt.Rectangle( 656 (x_positions[section] - length / 2, y_pos), length, width, edgecolor='black', facecolor=color)) 657 terminal_num = next((f'{terminal_type} Terminal : #{terminal}' for terminal_type, terminals in last_section_dict.items( 658 ) for terminal, sec in terminals.items() if sec == section), None) 659 annotation = f'Section {section}' 660 if terminal_num: 661 annotation += f'\n{terminal_num}' 662 ax.text(x_positions[section], max_width / 2, annotation, 663 ha='center', va='center', color='white', fontsize=8, rotation=90) 664 665 cbar = plt.colorbar(plt.cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax) 666 cbar.set_label('Depth (Draft)') 667 668 ax.set_xlim(0, cumulative_length) 669 ax.set_ylim(0, max_width + 5) 670 ax.set_xlabel('Length') 671 ax.set_ylabel('Breadth') 672 ax.set_title('Channel Sections and Terminals') 673 plt.savefig(f'.{run_id}/plots/ChannelSections.pdf') 674 plt.close()
Plots the channel sections and their respective terminals.
Arguments:
- run_id (str): Unique identifier for the run to save the plot.
Returns:
None: Saves the plot as a PDF file in the specified directory.
677def ship_data_csv_1(run_id): 678 """ 679 Parses the ship data from the log file and saves it to a CSV file. 680 The CSV file contains information about each ship's entry and exit times in the channel sections. 681 Args: 682 run_id (str): Unique identifier for the run to save the CSV file. 683 Returns: 684 pd.DataFrame: DataFrame containing the parsed ship data. 685 """ 686 ship_data = [] 687 file_path = f'.{run_id}/logs/ships_in_channel.txt' 688 689 with open(file_path, 'r') as file: 690 for line in file: 691 match = re.search( 692 r"Ship (\d+) of type (\w+) of width ([\d.]+) and draft ([\d.]+) spent from ([\d.]+) to ([\d.]+) in section (\d+) going (\w+)", line) 693 if match: 694 ship_data.append({ 695 "ship": int(match.group(1)), 696 "ship_type": match.group(2), 697 "width": float(match.group(3)), 698 "draft": float(match.group(4)), 699 "section": int(match.group(7)), 700 "start": float(match.group(5)), 701 "end": float(match.group(6)), 702 "direction": match.group(8) 703 }) 704 else: 705 print(f"Error parsing line: {line}") 706 707 df = pd.DataFrame(ship_data) 708 df = df.sort_values(by=['ship', 'direction', 'section']) 709 710 df.to_csv( 711 f'.{run_id}/logs/channel_section_entry_exit_times.csv', index=False) 712 713 return df
Parses the ship data from the log file and saves it to a CSV file. The CSV file contains information about each ship's entry and exit times in the channel sections.
Arguments:
- run_id (str): Unique identifier for the run to save the CSV file.
Returns:
pd.DataFrame: DataFrame containing the parsed ship data.
716def ship_data_csv_2(run_id): 717 """ 718 Parses the ship data from the log file and saves it to a CSV file with time spent in each section. 719 Args: 720 run_id (str): Unique identifier for the run to save the CSV file. 721 Returns: 722 pd.DataFrame: DataFrame containing the parsed ship data with time spent in each section. 723 """ 724 ship_data = {} 725 file_path = f'.{run_id}/logs/ships_in_channel.txt' 726 pattern = r"Ship (\d+) of type (\w+) of width ([\d.]+) and draft ([\d.]+) spent from ([\d.]+) to ([\d.]+) in section (\d+) going (\w+)" 727 728 with open(file_path, 'r') as file: 729 for line in file: 730 match = re.search(pattern, line) 731 if match: 732 ship_num = int(match.group(1)) 733 ship_dir = match.group(8) 734 ship_type = match.group(2) 735 section = int(match.group(7)) 736 time_spent = float(match.group(6)) - float(match.group(5)) 737 738 if (ship_num, ship_dir) not in ship_data: 739 ship_data[(ship_num, ship_dir)] = {} 740 741 ship_data[(ship_num, ship_dir)][section] = time_spent 742 else: 743 print(f"Error parsing line: {line}") 744 745 max_section = max(max(ship_info.keys()) 746 for ship_info in ship_data.values()) 747 df = pd.DataFrame(ship_data).T 748 df = df.reindex(columns=range(max_section + 1)).fillna(np.nan) 749 df.columns = [f"Time in Sec {i}" for i in range(max_section + 1)] 750 df['Total Time in Channel'] = df.sum(axis=1) 751 df['Ship'] = [ship[0] for ship in df.index] 752 df['Direction'] = [ship[1] for ship in df.index] 753 section_columns = [f"Time in Sec {i}" for i in range(max_section + 1)] 754 df = df[['Ship', 'Direction'] + 755 ['Total Time in Channel'] + section_columns] 756 df = df.sort_values(by=['Ship', 'Direction']) 757 df.to_csv(f'.{run_id}/logs/ships_time_in_channel.csv', index=False) 758 759 return df
Parses the ship data from the log file and saves it to a CSV file with time spent in each section.
Arguments:
- run_id (str): Unique identifier for the run to save the CSV file.
Returns:
pd.DataFrame: DataFrame containing the parsed ship data with time spent in each section.
762def ships_in_channel_analysis(run_id, plot=True): 763 """ 764 Analyzes the ships in the channel by reading the ship data from a text file, calculating the time spent in each section, and generating a time series of ships in the channel. 765 Args: 766 run_id (str): Unique identifier for the run to save the analysis results. 767 plot (bool): Whether to generate and save plots of the channel utilization. 768 Returns: 769 None: Saves the analysis results to CSV files and generates plots if `plot` is True. 770 """ 771 772 ship_data_csv_1(run_id) 773 ship_data_csv_2(run_id) 774 775 os.remove(f'.{run_id}/logs/ships_in_channel.txt') 776 777 file_path = f'.{run_id}/logs/channel_section_entry_exit_times.csv' 778 ships_data = pd.read_csv(file_path) 779 ships_data['time_in_section'] = ships_data['end'] - ships_data['start'] 780 max_time = ships_data['end'].max() 781 782 section_time_series = pd.DataFrame(0, index=np.arange( 783 max_time)-1, columns=range(ships_data['section'].max() + 1)) 784 785 for _, row in ships_data.iterrows(): 786 section_time_series.loc[row['start']:row['end'], row['section']] += 1 787 788 # save section time series to csv 789 section_time_series.to_csv(f'.{run_id}/logs/section_time_series.csv') 790 total_ships_in_channel_full = section_time_series.sum(axis=1) 791 mean_ships_per_time_step = section_time_series.loc[constants.WARMUP_ITERS:].mean( 792 ) 793 total_ships_in_channel = section_time_series.sum(axis=1) 794 mean_total_ships_in_channel = total_ships_in_channel.mean() 795 796 # save total ships in channel to csv 797 total_ships_in_channel.to_csv(f'.{run_id}/logs/total_ships_in_channel.csv') 798 799 if plot: 800 plt.plot(total_ships_in_channel_full) 801 plt.xticks(fontsize=12) 802 plt.yticks(fontsize=12) 803 plt.xlim(left=0) 804 plt.ylim(bottom=0) 805 plt.title('Ships in channel', fontsize=15) 806 plt.xlabel('Time (hr)', fontsize=15) 807 plt.ylabel('Number of ships', fontsize=15) 808 plt.grid(True) 809 plt.tight_layout() 810 plt.savefig(f'.{run_id}/plots/channel_utilization.pdf') 811 plt.close() 812 813 report_path = f".{run_id}/logs/final_report.txt" 814 with open(report_path, 'a') as f: 815 f.write( 816 f"Mean number of vessels in channel: {mean_total_ships_in_channel}\n\n") 817 f.write("Mean number of vessel in each section:\n") 818 for section, mean_ships in mean_ships_per_time_step.items(): 819 f.write(f"Section {section}: {mean_ships}\n") 820 f.write("\n") 821 822 # Calculate entry and exit times for each ship 823 file_path = f'.{run_id}/logs/channel_section_entry_exit_times.csv' 824 ships_data = pd.read_csv(file_path) 825 826 # Identify the first and last sections for each ship 827 first_section = ships_data[ships_data['section'] == 0] 828 last_sections = ships_data.groupby(['ship', 'direction'])[ 829 'section'].max().reset_index() 830 831 # Merge last sections with the original dataset to get exit times 832 last_section_exit = ships_data.merge( 833 last_sections, 834 on=['ship', 'section', 'direction'], 835 how='inner' 836 ) 837 838 # Get entry and exit times for each ship 839 entry_exit_times = pd.merge( 840 first_section[['ship', 'direction', 'start']].rename( 841 columns={'start': 'entry_time_section_0'}), 842 last_section_exit[['ship', 'direction', 'end']].rename( 843 columns={'end': 'exit_time_last_section'}), 844 on=['ship', 'direction'], 845 how='outer' 846 ) 847 848 # Save the entry and exit times to csv 849 entry_exit_times.to_csv( 850 f'.{run_id}/logs/entry_exit_times.csv', index=False)
Analyzes the ships in the channel by reading the ship data from a text file, calculating the time spent in each section, and generating a time series of ships in the channel.
Arguments:
- run_id (str): Unique identifier for the run to save the analysis results.
- plot (bool): Whether to generate and save plots of the channel utilization.
Returns:
None: Saves the analysis results to CSV files and generates plots if
plotis True.
853def ships_in_anchorage_analysis(ship_type, run_id, plot=True): 854 """ 855 Analyzes the ships in anchorage by reading the ship data from a CSV file, calculating the cumulative arrivals and channel entries, and generating a time series of ships waiting in anchorage. 856 Args: 857 ship_type (str): Type of ships to analyze ('all', 'Container', 'Liquid', 'DryBulk'). 858 run_id (str): Unique identifier for the run to save the analysis results. 859 plot (bool): Whether to generate and save plots of the anchorage queue. 860 Returns: 861 None: Saves the analysis results to CSV files and generates plots if `plot` is True. 862 """ 863 864 # time series of ships that entered anchorage 865 input_ship_data = pd.read_csv(f'.{run_id}/logs/ship_data.csv') 866 867 if ship_type == 'all': 868 input_ship_data = input_ship_data 869 else: 870 input_ship_data = input_ship_data[input_ship_data['ship_type'] == ship_type] 871 872 arrival_times = input_ship_data['arrival'].dropna( 873 ).sort_values().reset_index(drop=True) 874 cumulative_arrivals = pd.DataFrame( 875 {'time_index': range(1, int(arrival_times.max()) + 1)}) 876 cumulative_arrivals['cumulative_arrivals'] = cumulative_arrivals['time_index'].apply( 877 lambda x: (arrival_times <= x).sum() 878 ) 879 880 # save the cumulative arrivals to csv 881 cumulative_arrivals.to_csv(f'.{run_id}/logs/cumulative_arrivals.csv') 882 883 # time series of ships that entered channel 884 file_path = f'.{run_id}/logs/channel_section_entry_exit_times.csv' 885 ships_data = pd.read_csv(file_path) 886 887 if ship_type != 'all': 888 ships_data = ships_data[ships_data['ship_type'] == ship_type] 889 890 ships_data = ships_data[ships_data['direction'] == 'in'] 891 ships_data = ships_data.groupby('ship').first().reset_index() 892 channel_enter_times = ships_data['start'].reset_index(drop=True) 893 cumulative_channel_enter = pd.DataFrame( 894 {'time_index': range(1, int(constants.SIMULATION_TIME) + 1)}) 895 cumulative_channel_enter['cumulative_channel_enter'] = cumulative_channel_enter['time_index'].apply( 896 lambda x: (channel_enter_times <= x).sum() 897 ) 898 899 # combine the two dfs 900 combined = cumulative_arrivals.merge( 901 cumulative_channel_enter, on='time_index', how='outer') 902 combined['waiting_in_anchorage'] = combined['cumulative_arrivals'] - \ 903 combined['cumulative_channel_enter'] 904 905 # svae the combined dataframe to csv 906 combined.to_csv(f'.{run_id}/logs/waiting_in_anchorage_{ship_type}.csv') 907 908 # Calculate the total number of rows 909 rows_in_combined = len(combined) 910 mean_waiting_in_anchorage = combined.loc[constants.WARMUP_ITERS:]['waiting_in_anchorage'].mean( 911 ) 912 913 # plot time series of waiting in anchorage 914 if plot: 915 plt.plot(combined['time_index'], combined['waiting_in_anchorage']) 916 plt.title(f'Anchorage queue - {ship_type} ships', fontsize=15) 917 plt.xticks(fontsize=12) 918 plt.yticks(fontsize=12) 919 plt.xlim(left=0) 920 plt.ylim(bottom=0) 921 plt.xlabel('Time (hr)', fontsize=15) 922 plt.ylabel('Number of ships', fontsize=15) 923 plt.grid(True) 924 plt.tight_layout() 925 plt.savefig(f'.{run_id}/plots/anchorage_queue_{ship_type}.pdf') 926 927 with open(f'.{run_id}/logs/final_report.txt', 'a') as f: 928 f.write( 929 f"Mean number of {ship_type} ships waiting in anchorage: {mean_waiting_in_anchorage}\n\n") 930 931 plt.close() 932 933 return
Analyzes the ships in anchorage by reading the ship data from a CSV file, calculating the cumulative arrivals and channel entries, and generating a time series of ships waiting in anchorage.
Arguments:
- ship_type (str): Type of ships to analyze ('all', 'Container', 'Liquid', 'DryBulk').
- run_id (str): Unique identifier for the run to save the analysis results.
- plot (bool): Whether to generate and save plots of the anchorage queue.
Returns:
None: Saves the analysis results to CSV files and generates plots if
plotis True.
940def create_truck_csv(run_id): 941 """ 942 Read truck_data.txt, parse into a DataFrame, save as CSV under ./<run_id>/truck_data.csv, 943 and return the DataFrame. 944 Args: 945 run_id (str): Unique identifier for the run to save the truck data. 946 Returns: 947 pd.DataFrame: DataFrame containing the truck data with columns for truck ID, start time, dwell time, terminal ID, terminal type, and arrival time. 948 """ 949 txt_path = f'.{run_id}/logs/truck_data.txt' 950 data = [] 951 952 # only take first 100000 units of data 953 count = 0 954 with open(txt_path, 'r') as f: 955 for line in f: 956 count += 1 957 if count >= 100000: 958 break 959 line = line.strip() 960 if not line: 961 continue 962 parts = [p.strip() for p in line.split(',')] 963 row = {} 964 for part in parts: 965 key, val = part.split(':', 1) 966 row[key.strip()] = val.strip() 967 data.append(row) 968 969 df = pd.DataFrame(data) 970 # convert types 971 df = df.astype({ 972 'truck_id' : int, 973 'start_time' : float, 974 'dwell_time' : float, 975 'terminal_id' : int, 976 'terminal_type': str 977 }) 978 # compute arrival-of-day 979 df['arrival_time'] = df['start_time'] % 24 980 981 # make run directory & save CSV 982 out_dir = f'.{run_id}/logs/' 983 os.makedirs(out_dir, exist_ok=True) 984 csv_path = os.path.join(out_dir, 'truck_data.csv') 985 df.to_csv(csv_path, index=False) 986 987 return df
Read truck_data.txt, parse into a DataFrame, save as CSV under ./
Arguments:
- run_id (str): Unique identifier for the run to save the truck data.
Returns:
pd.DataFrame: DataFrame containing the truck data with columns for truck ID, start time, dwell time, terminal ID, terminal type, and arrival time.
990def plot_dwell_by_type(run_id): 991 """ 992 Plots the dwell time distribution of trucks by terminal type. 993 Args: 994 run_id (str): Unique identifier for the run to save the plots. 995 Returns: 996 None: Saves the plots as PDF files in the specified directories. 997 """ 998 out_dir = f'.{run_id}/plots/TruckDwellByCargo' 999 df = pd.read_csv(f'.{run_id}/logs/truck_data.csv') 1000 os.makedirs(out_dir, exist_ok=True) 1001 1002 for ttype, grp in df.groupby('terminal_type'): 1003 plt.figure() 1004 grp['dwell_time'].hist(bins=30) 1005 plt.title(f'Dwell Time Distribution — {ttype}') 1006 plt.xlabel('Dwell Time') 1007 plt.ylabel('Frequency') 1008 # make the y ticks as percentage 1009 plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{x/len(grp)*100:.1f}%')) 1010 plt.xticks(rotation=45) 1011 plt.grid(axis='y', linestyle='--', alpha=0.7) 1012 # save the plot 1013 plt.tight_layout() 1014 fname = f'dwell_distribution_{ttype}.pdf' 1015 plt.savefig(os.path.join(out_dir, fname)) 1016 # plt.show() 1017 plt.close()
Plots the dwell time distribution of trucks by terminal type.
Arguments:
- run_id (str): Unique identifier for the run to save the plots.
Returns:
None: Saves the plots as PDF files in the specified directories.
1020def plot_dwell_by_terminal(run_id): 1021 """ 1022 Plots the dwell time distribution of trucks by terminal type and terminal ID. 1023 Args: 1024 run_id (str): Unique identifier for the run to save the plots. 1025 Returns: 1026 None: Saves the plots as PDF files in the specified directories. 1027 """ 1028 df = pd.read_csv(f'.{run_id}/logs/truck_data.csv') 1029 dir_term = f'.{run_id}/plots/TruckDwellByTerminal' 1030 os.makedirs(dir_term, exist_ok=True) 1031 1032 for (ttype, tid), grp in df.groupby(['terminal_type','terminal_id']): 1033 plt.figure() 1034 grp['dwell_time'].hist(bins=30) 1035 plt.title(f'Dwell Time — {ttype} (Terminal {tid})') 1036 plt.xlabel('Dwell Time') 1037 plt.ylabel('Frequency') 1038 # make the y ticks as percentage 1039 plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{x/len(grp)*100:.1f}%')) 1040 plt.xticks(rotation=45) 1041 plt.grid(axis='y', linestyle='--', alpha=0.7) 1042 plt.tight_layout() 1043 fname = f'dwell_distribution_{ttype}_{tid}.pdf' 1044 plt.savefig(os.path.join(dir_term, fname)) 1045 plt.close()
Plots the dwell time distribution of trucks by terminal type and terminal ID.
Arguments:
- run_id (str): Unique identifier for the run to save the plots.
Returns:
None: Saves the plots as PDF files in the specified directories.
1048def plot_arrival_distributions(run_id): 1049 """ 1050 Plots the arrival time distribution of trucks by terminal type and terminal ID. 1051 Args: 1052 run_id (str): Unique identifier for the run to save the plots. 1053 Returns: 1054 None: Saves the plots as JPEG files in the specified directories. 1055 """ 1056 df = pd.read_csv(f'.{run_id}/logs/truck_data.csv') 1057 dir_type = f'.{run_id}/plots/TruckArrivalByCargo' 1058 dir_cargo = f'.{run_id}/plots/TruckArrivalByTerminal' 1059 os.makedirs(dir_type, exist_ok=True) 1060 os.makedirs(dir_cargo, exist_ok=True) 1061 1062 # by type 1063 for ttype, grp in df.groupby('terminal_type'): 1064 plt.figure() 1065 grp['arrival_time'].hist(bins=24, range=(0,24)) 1066 plt.title(f'Arrival Time Distribution — {ttype}') 1067 plt.xlabel('Hour of Day') 1068 plt.ylabel('Frequency') 1069 # make the y ticks as percentage 1070 plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{x/len(grp)*100:.1f}%')) 1071 plt.xticks(rotation=45) 1072 plt.grid(axis='y', linestyle='--', alpha=0.7) 1073 plt.tight_layout() 1074 fname = f'arrival_distribution_{ttype}.jpg' 1075 plt.savefig(os.path.join(dir_type, fname)) 1076 plt.close() 1077 1078 # by terminal 1079 for (ttype, tid), grp in df.groupby(['terminal_type','terminal_id']): 1080 plt.figure() 1081 grp['arrival_time'].hist(bins=24, range=(0,24)) 1082 plt.title(f'Arrival Time — {ttype} (Terminal {tid})') 1083 plt.xlabel('Hour of Day') 1084 plt.ylabel('Frequency') 1085 # make the y ticks as percentage 1086 plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{x/len(grp)*100:.1f}%')) 1087 plt.xticks(rotation=45) 1088 plt.grid(axis='y', linestyle='--', alpha=0.7) 1089 plt.tight_layout() 1090 fname = f'arrival_distribution_{ttype}_{tid}.jpg' 1091 plt.savefig(os.path.join(dir_cargo, fname)) 1092 plt.close()
Plots the arrival time distribution of trucks by terminal type and terminal ID.
Arguments:
- run_id (str): Unique identifier for the run to save the plots.
Returns:
None: Saves the plots as JPEG files in the specified directories.
1095def delete_truck_data_csv(run_id): 1096 """ 1097 Delete the truck_data.csv file if it exists and save the truck data as a pickle file. 1098 Args: 1099 run_id (str): Unique identifier for the run to save the truck data as a pickle file. 1100 Returns: 1101 None: Deletes the truck_data.csv file and saves the truck data as a pickle file. 1102 """ 1103 csv_path = f'.{run_id}/logs/truck_data.csv' 1104 pkl_path = f'.{run_id}/logs/truck_data.pkl' 1105 df = pd.read_csv(csv_path) 1106 df.to_pickle(pkl_path)
Delete the truck_data.csv file if it exists and save the truck data as a pickle file.
Arguments:
- run_id (str): Unique identifier for the run to save the truck data as a pickle file.
Returns:
None: Deletes the truck_data.csv file and saves the truck data as a pickle file.
1115def update_ship_logs(ship_logs, ship_type, ship_id, selected_terminal, ship_start_time="nan", time_to_get_berth="nan", time_for_restriction_in="nan", time_to_get_pilot_in="nan", 1116 time_to_get_tugs_in="nan", time_to_common_channel_in="nan", time_to_travel_channel_in="nan", time_to_tug_steer_in="nan", unloading_time="nan", loading_time="nan", waiting_time="nan", 1117 departure_time="nan", time_to_get_pilot_out="nan", time_to_get_tugs_out="nan", time_to_tug_steer_out="nan", time_for_restriction_out='nan', time_for_uturn="nan", 1118 time_to_travel_channel_out="nan", time_to_common_channel_out="nan", ship_end_time="nan"): 1119 """ 1120 Updates the ship logs with the provided information. If a log with the same Ship ID exists, it updates the existing log; otherwise, it appends a new log. 1121 Note 1: The ship type is represented by a single character (e.g., "C" for Container, "L" for Liquid, "D" for Dry Bulk). 1122 Note 2: If the log already exists, it updates the existing log with the new values, ignoring 'nan' values. 1123 Args: 1124 ship_logs (list): List of existing ship logs. 1125 ship_type (str): Type of the ship (e.g., "C" for Container, "L" for Liquid, "D" for Dry Bulk). 1126 ship_id (int): Unique identifier for the ship. 1127 selected_terminal (str): The terminal where the ship is directed. 1128 ship_start_time (str): Start time of the ship's operation. 1129 time_to_get_berth (str): Time taken to get to the berth. 1130 time_for_restriction_in (str): Time for restriction in. 1131 time_to_get_pilot_in (str): Time taken to get the pilot in. 1132 time_to_get_tugs_in (str): Time taken to get tugs in. 1133 time_to_common_channel_in (str): Time taken to get to the common channel in. 1134 time_to_travel_channel_in (str): Time taken to travel the channel in. 1135 time_to_tug_steer_in (str): Time taken for tug steering in. 1136 unloading_time (str): Time taken for unloading. 1137 loading_time (str): Time taken for loading. 1138 waiting_time (str): Time spent waiting. 1139 departure_time (str): Departure time of the ship. 1140 time_to_get_pilot_out (str): Time taken to get the pilot out. 1141 time_to_get_tugs_out (str): Time taken to get tugs out. 1142 time_to_tug_steer_out (str): Time taken for tug steering out. 1143 time_for_restriction_out (str): Time for restriction out. 1144 time_for_uturn (str): Time taken for U-turn. 1145 time_to_travel_channel_out (str): Time taken to travel the channel out. 1146 time_to_common_channel_out (str): Time taken to get to the common channel out. 1147 ship_end_time (str): End time of the ship's operation. 1148 Returns: 1149 list: Updated ship logs with the new or modified log entry. 1150 """ 1151 1152 if ship_type == "C": 1153 ship_type_full = "Container" 1154 elif ship_type == "L": 1155 ship_type_full = "Liquid" 1156 elif ship_type == "D": 1157 ship_type_full = "Dry Bulk" 1158 1159 # Create the log entry 1160 new_log = [ 1161 f"Ship_{ship_id}", 1162 ship_type_full, 1163 f"{ship_type}.{selected_terminal}", 1164 ship_start_time, 1165 time_to_get_berth, 1166 time_for_restriction_in, 1167 time_to_get_pilot_in, 1168 time_to_get_tugs_in, 1169 time_to_common_channel_in, 1170 time_to_travel_channel_in, 1171 time_to_tug_steer_in, 1172 unloading_time, 1173 loading_time, 1174 waiting_time, 1175 departure_time, 1176 time_to_get_pilot_out, 1177 time_to_get_tugs_out, 1178 time_for_uturn, 1179 time_to_tug_steer_out, 1180 time_for_restriction_out, 1181 time_to_travel_channel_out, 1182 time_to_common_channel_out, 1183 None, 1184 None, 1185 ship_end_time 1186 ] 1187 1188 # Check if the log with the same Ship ID exists 1189 for i, log in enumerate(ship_logs): 1190 if log[0] == f"Ship_{ship_id}": 1191 for j, vals in enumerate(new_log): 1192 if new_log[j] != 'nan': 1193 ship_logs[i][j] = vals 1194 1195 return ship_logs 1196 1197 # If no existing log is found, append the new log 1198 ship_logs.append(new_log) 1199 1200 return ship_logs
Updates the ship logs with the provided information. If a log with the same Ship ID exists, it updates the existing log; otherwise, it appends a new log. Note 1: The ship type is represented by a single character (e.g., "C" for Container, "L" for Liquid, "D" for Dry Bulk). Note 2: If the log already exists, it updates the existing log with the new values, ignoring 'nan' values.
Arguments:
- ship_logs (list): List of existing ship logs.
- ship_type (str): Type of the ship (e.g., "C" for Container, "L" for Liquid, "D" for Dry Bulk).
- ship_id (int): Unique identifier for the ship.
- selected_terminal (str): The terminal where the ship is directed.
- ship_start_time (str): Start time of the ship's operation.
- time_to_get_berth (str): Time taken to get to the berth.
- time_for_restriction_in (str): Time for restriction in.
- time_to_get_pilot_in (str): Time taken to get the pilot in.
- time_to_get_tugs_in (str): Time taken to get tugs in.
- time_to_common_channel_in (str): Time taken to get to the common channel in.
- time_to_travel_channel_in (str): Time taken to travel the channel in.
- time_to_tug_steer_in (str): Time taken for tug steering in.
- unloading_time (str): Time taken for unloading.
- loading_time (str): Time taken for loading.
- waiting_time (str): Time spent waiting.
- departure_time (str): Departure time of the ship.
- time_to_get_pilot_out (str): Time taken to get the pilot out.
- time_to_get_tugs_out (str): Time taken to get tugs out.
- time_to_tug_steer_out (str): Time taken for tug steering out.
- time_for_restriction_out (str): Time for restriction out.
- time_for_uturn (str): Time taken for U-turn.
- time_to_travel_channel_out (str): Time taken to travel the channel out.
- time_to_common_channel_out (str): Time taken to get to the common channel out.
- ship_end_time (str): End time of the ship's operation.
Returns:
list: Updated ship logs with the new or modified log entry.
1203def analyse_bays_chassis_utilization(chassis_bays_utilization, num_terminals_list, run_id): 1204 """ 1205 Generates plots for chassis and bay utilization at each terminal type. 1206 Args: 1207 chassis_bays_utilization (dict): Dictionary containing chassis and bay utilization data for each terminal type. 1208 num_terminals_list (list): List containing the number of terminals for each terminal type. 1209 run_id (str): Unique identifier for the run to save the plots. 1210 Returns: 1211 None: Saves the plots as PDF files in the specified directory. 1212 """ 1213 for terminal_type in ["Container", "Liquid", "DryBulk"]: 1214 for terminal_id in range(1, num_terminals_list[["Container", "Liquid", "DryBulk"].index(terminal_type)] + 1): 1215 to_plot = chassis_bays_utilization[terminal_type][terminal_id] 1216 1217 x_vals = [] 1218 y_vals = [] 1219 1220 for vals in to_plot: 1221 x_vals.append(vals[0]) 1222 y_vals.append(vals[1]) 1223 1224 plt.figure(figsize=(12, 8)) 1225 plt.plot(x_vals, y_vals) 1226 plt.title( 1227 f'Chassis / Bay Utilization at {terminal_type} Terminal {terminal_id}') 1228 plt.xlabel('Time') 1229 plt.ylabel('Utilization') 1230 plt.savefig( 1231 f'.{run_id}/bottlenecks/chassisBays/{terminal_type}_Terminal_{terminal_id}.pdf') 1232 plt.close()
Generates plots for chassis and bay utilization at each terminal type.
Arguments:
- chassis_bays_utilization (dict): Dictionary containing chassis and bay utilization data for each terminal type.
- num_terminals_list (list): List containing the number of terminals for each terminal type.
- run_id (str): Unique identifier for the run to save the plots.
Returns:
None: Saves the plots as PDF files in the specified directory.
1235def gen_logs_and_plots(run_id, ship_logs, events, chassis_bays_utilization, num_terminals_list, train_events, channel_logs, channel_events, channel, animate=True): 1236 """ 1237 Generates logs and plots from the provided ship logs, events, chassis and bay utilization data, train events, and channel logs. 1238 Saves the logs to an Excel file and generates various plots including process charts, dwell times, turn times, truck dwell times, and channel utilization. 1239 Also generates utilization reports for resources such as berths and yards. 1240 The following plots are generated: 1241 - Process chart for ship operations 1242 - Dwell time chart for ships 1243 - Turn time analysis for ships 1244 - Truck dwell times and arrival times at terminals 1245 - Channel utilization over time 1246 - Chassis and bay utilization at each terminal type 1247 The results are saved in the specified run directory. 1248 Args: 1249 run_id (str): Unique identifier for the run to save the logs and plots. 1250 ship_logs (list): List of ship logs containing information about each ship's operations. 1251 events (list): List of events where each event is a tuple containing: 1252 (event_type, time, name, terminal_id, terminal_type) 1253 chassis_bays_utilization (dict): Dictionary containing chassis and bay utilization data for each terminal type. 1254 num_terminals_list (list): List containing the number of terminals for each terminal type. 1255 train_events (dict): Dictionary containing train events where keys are event names and values are lists of event data. 1256 channel_logs (list): List of channel logs containing information about the channel's operations. 1257 channel_events (list): List of channel events where each event is a tuple containing: 1258 (event_type, time, name, section_id, section_type) 1259 channel (object): Channel object containing sections and their respective containers. 1260 Returns: 1261 None: Saves the logs and plots in the specified directory. 1262 """ 1263 def extract_t(restriction_str): 1264 if isinstance(restriction_str, str): 1265 parts = restriction_str.split(", ") 1266 for part in parts: 1267 if part.startswith("T"): 1268 return float(part.split(":")[1]) 1269 return 0.0 1270 1271 # Ship logs 1272 columns = ["Ship_Id", "Terminal Type", "Terminal Directed", "Start Time", "Time to get Berth", "Time for Restriction In", "Time to get Pilot In", "Time to get Tugs In", "Time to Common Channel In", "Time to Travel Channel In", "Time to Tug Steer In", "Unloading Time", 1273 "Loading Time", "Waiting Time", "Departure Time", "Time to get Pilot Out", "Time to get Tugs Out", "Time for U-Turn", "Time to Tug Steer Out", "Time for Restriction Out", "Time to Travel Channel Out", "Time to Common Channel Out", "Turn Time", "Dwell Time", "End Time"] 1274 df = pd.DataFrame(ship_logs, columns=columns) 1275 1276 # all columns in float format of 2 decimal places 1277 for col in df.columns: 1278 if col not in ["Ship_Id", "Terminal Type", "Terminal Directed", "Time for Restriction In", "Time for Restriction Out"]: 1279 df[col] = df[col].astype(float).round(2) 1280 1281 df['Ship_Id'] = df['Ship_Id'].str.extract(r'(\d+)').astype(int) 1282 df = df.sort_values(by='Ship_Id') 1283 df['Ship_Id'] = "Ship_" + df['Ship_Id'].astype(str) 1284 df['Turn Time'] = df['End Time'] - df['Start Time'] 1285 df['Dwell Time'] = df['Unloading Time'] + \ 1286 df['Loading Time'] + df['Waiting Time'] 1287 1288 df['Restriction In (T)'] = df['Time for Restriction In'].apply(extract_t) 1289 df['Anchorage Time'] = df['Time to get Berth'] + \ 1290 df['Restriction In (T)'] + df['Time to get Pilot In'] + \ 1291 df['Time to get Tugs In'] 1292 # if ship type is container add anchorage waiting_container as a column 1293 for index, row in df.iterrows(): 1294 if row['Terminal Type'] == 'Container': 1295 df.at[index, 'Anchorage Time'] += constants.ANCHORAGE_WAITING_CONTAINER 1296 elif row['Terminal Type'] == 'Liquid': 1297 df.at[index, 'Anchorage Time'] += constants.ANCHORAGE_WAITING_LIQUID 1298 elif row['Terminal Type'] == 'Dry Bulk': 1299 df.at[index, 'Anchorage Time'] += constants.ANCHORAGE_WAITING_DRYBULK 1300 1301 df.to_excel(f'.{run_id}/logs/ship_logs.xlsx', index=False) 1302 1303 warmup_period = constants.WARMUP_ITERS / len(df) 1304 mean_anchorage_time = df['Anchorage Time'].sort_values( 1305 ).iloc[constants.WARMUP_ITERS:].mean() 1306 mean_anchorage_time_ctr = df[df['Terminal Type'] == 'Container']['Anchorage Time'].sort_values( 1307 ).iloc[int(warmup_period * len(df[df['Terminal Type'] == 'Container'])):].mean() 1308 mean_anchorage_time_liq = df[df['Terminal Type'] == 'Liquid']['Anchorage Time'].sort_values( 1309 ).iloc[int(warmup_period * len(df[df['Terminal Type'] == 'Liquid'])):].mean() 1310 mean_anchorage_time_db = df[df['Terminal Type'] == 'Dry Bulk']['Anchorage Time'].sort_values( 1311 ).iloc[int(warmup_period * len(df[df['Terminal Type'] == 'Dry Bulk'])):].mean() 1312 1313 report_path = f".{run_id}/logs/final_report.txt" 1314 with open(report_path, 'a') as f: 1315 f.write(f"Mean Anchorage Time: {mean_anchorage_time}\n") 1316 f.write(f"Mean Anchorage Time Container: {mean_anchorage_time_ctr}\n") 1317 f.write(f"Mean Anchorage Time Liquid: {mean_anchorage_time_liq}\n") 1318 f.write(f"Mean Anchorage Time Dry Bulk: {mean_anchorage_time_db}\n\n") 1319 1320 # Generate the process chart 1321 plot_process_chart(events, run_id=run_id) 1322 1323 # Generate the dwell time chart 1324 plot_dwell_times(events, run_id) 1325 analyze_turn_time_ships(run_id) 1326 get_dists(run_id) 1327 1328 # Plot queueus in channel 1329 ships_in_channel_analysis(run_id) 1330 ships_in_anchorage_analysis(ship_type='all', run_id=run_id) 1331 ships_in_anchorage_analysis(ship_type='Container', run_id=run_id) 1332 ships_in_anchorage_analysis(ship_type='DryBulk', run_id=run_id) 1333 ships_in_anchorage_analysis(ship_type='Liquid', run_id=run_id) 1334 1335 # Trucks 1336 create_truck_csv(run_id) 1337 plot_dwell_by_type(run_id) 1338 plot_dwell_by_terminal(run_id) 1339 plot_arrival_distributions(run_id) 1340 delete_truck_data_csv(run_id) 1341 1342 # Bays & Chassis Utilization 1343 analyse_bays_chassis_utilization( 1344 chassis_bays_utilization, num_terminals_list, run_id) 1345 1346 # Train 1347 gen_train_df(train_events, run_id) 1348 1349 # tugs and pilots 1350 tug_pilot_df = pd.read_csv(f'.{run_id}/logs/pilots_tugs_data.csv') 1351 1352 # plot day anf night pilots 1353 plt.figure(figsize=(12, 8)) 1354 plt.plot(tug_pilot_df['Time'], 1355 tug_pilot_df['Day Pilots'], label='Day Pilots') 1356 plt.title('Number of day pilots available') 1357 plt.xlabel('Time') 1358 plt.ylabel('Number of pilots') 1359 plt.ylim(0, constants.NUM_PILOTS_DAY[1]) 1360 plt.savefig(f'.{run_id}/plots/DayPilots.pdf') 1361 plt.close() 1362 1363 plt.figure(figsize=(12, 8)) 1364 plt.plot(tug_pilot_df['Time'], 1365 tug_pilot_df['Night Pilots'], label='Night Pilots') 1366 plt.title('Number of night pilots available') 1367 plt.xlabel('Time') 1368 plt.ylabel('Number of pilots') 1369 plt.ylim(0, constants.NUM_PILOTS_NIGHT[1]) 1370 plt.savefig(f'.{run_id}/plots/NightPilots.pdf') 1371 plt.close() 1372 1373 # plot tugboats 1374 plt.figure(figsize=(12, 8)) 1375 plt.plot(tug_pilot_df['Time'], tug_pilot_df['Tugboats'], label='Tugboats') 1376 plt.title('Tugboats') 1377 plt.xlabel('Time') 1378 plt.ylabel('Number of tugboats available') 1379 plt.ylim(0, constants.NUM_TUGBOATS[1]) 1380 plt.legend() 1381 plt.savefig(f'.{run_id}/plots/Tugboats.pdf') 1382 plt.close() 1383 1384 # Animate the channel usage 1385 if animate: 1386 animate_channel_usage(run_id, channel) 1387 1388 # print("Done!")
Generates logs and plots from the provided ship logs, events, chassis and bay utilization data, train events, and channel logs. Saves the logs to an Excel file and generates various plots including process charts, dwell times, turn times, truck dwell times, and channel utilization. Also generates utilization reports for resources such as berths and yards.
The following plots are generated:
- Process chart for ship operations
- Dwell time chart for ships
- Turn time analysis for ships
- Truck dwell times and arrival times at terminals
- Channel utilization over time
- Chassis and bay utilization at each terminal type
The results are saved in the specified run directory.
Arguments:
- run_id (str): Unique identifier for the run to save the logs and plots.
- ship_logs (list): List of ship logs containing information about each ship's operations.
- events (list): List of events where each event is a tuple containing: (event_type, time, name, terminal_id, terminal_type)
- chassis_bays_utilization (dict): Dictionary containing chassis and bay utilization data for each terminal type.
- num_terminals_list (list): List containing the number of terminals for each terminal type.
- train_events (dict): Dictionary containing train events where keys are event names and values are lists of event data.
- channel_logs (list): List of channel logs containing information about the channel's operations.
- channel_events (list): List of channel events where each event is a tuple containing: (event_type, time, name, section_id, section_type)
- channel (object): Channel object containing sections and their respective containers.
Returns:
None: Saves the logs and plots in the specified directory.
1395def animate_channel_usage(run_id, channel): 1396 """ 1397 DEPRICIATED 1398 Animates the channel usage over time, showing width and draft utilization. 1399 Args: 1400 run_id (str): Unique identifier for the run to save the animation. 1401 channel (object): Channel object containing sections and their respective containers. 1402 Returns: 1403 None: Saves the animation as a GIF file in the specified directory. 1404 """ 1405 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6)) 1406 1407 channel_sections = constants.CHANNEL_SECTION_DIMENSIONS 1408 1409 x_max = len(channel.sections) 1410 y_max = 100 1411 1412 def init(): 1413 1414 for i in range(1, len(channel.sections)+1): 1415 ax1.bar(i, 100 * channel.sections[i-1].width_containers[0].level / 1416 channel.sections[i-1].width_containers[0].capacity, color='b') 1417 ax2.bar(i, 100 * channel.sections[i-1].draft_containers[0].level / 1418 channel.sections[i-1].draft_containers[0].capacity, color='r') 1419 ax1.set_xlim(0, x_max) 1420 ax1.set_ylim(0, y_max) 1421 ax2.set_xlim(0, x_max) 1422 ax2.set_ylim(0, y_max) 1423 ax1.set_title('Width utilization') 1424 ax2.set_title('Draft utilization') 1425 return ax1.patches + ax2.patches 1426 1427 progress_bar = tqdm(total=int(len(channel.sections[0].width_containers)/2)) 1428 1429 def animate(t): 1430 1431 ax1.clear() 1432 ax2.clear() 1433 1434 # print(f'Animating {t}') 1435 progress_bar.update(1) 1436 for i in range(1, len(channel.sections) + 1): 1437 ax1.bar(i, 100 * channel.sections[i-1].width_containers[t].level / 1438 channel.sections[i-1].width_containers[t].capacity, color='b') 1439 ax2.bar(i, 100 * channel.sections[i-1].draft_containers[t].level / 1440 channel.sections[i-1].draft_containers[t].capacity, color='r') 1441 # print(t, i, channel.sections[i-1].width_containers[0].level, channel.sections[i-1].width_containers[0].capacity) 1442 1443 ax1.set_xlim(0, x_max) 1444 ax1.set_ylim(0, y_max) 1445 ax2.set_xlim(0, x_max) 1446 ax2.set_ylim(0, y_max) 1447 fig.suptitle(f'Time Step: {t}', fontsize=16, fontweight='bold') 1448 return ax1.patches + ax2.patches 1449 1450 ani = animation.FuncAnimation(fig, animate, init_func=init, frames=range( 1451 int(len(channel.sections[0].width_containers)/2)), interval=1, blit=False) 1452 1453 # Save the animation as gif 1454 ani.save(f'.{run_id}/animations/channel_avilability.gif', 1455 writer='pillow', fps=1000) 1456 plt.close()
DEPRICIATED Animates the channel usage over time, showing width and draft utilization.
Arguments:
- run_id (str): Unique identifier for the run to save the animation.
- channel (object): Channel object containing sections and their respective containers.
Returns:
None: Saves the animation as a GIF file in the specified directory.