simulation_classes.channel
This module defines the Channel and ChannelSection classes for simulating ship navigation through a channel.
1""" 2This module defines the Channel and ChannelSection classes for simulating ship navigation through a channel. 3""" 4import random 5 6from tqdm import tqdm 7 8import constants 9from simulation_analysis.results import log_line, update_ship_logs 10from simulation_handler.helpers import is_daytime, save_warning 11 12CHANNEL_SECTION_DIMENSIONS = constants.CHANNEL_SECTION_DIMENSIONS 13START_DAYLIGHT_RESTRICTION_HR = constants.START_DAYLIGHT_RESTRICTION_HR 14STOP_DAYLIGHT_RESTRICTION_HR = constants.STOP_DAYLIGHT_RESTRICTION_HR 15TIME_FACTOR = constants.TIME_FACTOR 16TIME_COMMON_CHANNEL = constants.TIME_COMMON_CHANNEL 17TIME_FOR_UTURN = constants.TIME_FOR_UTURN 18TIME_FOR_TUG_STEER = constants.TIME_FOR_TUG_STEER 19 20 21class ChannelSection: 22 """ 23 Class for each section of the channel 24 This class manages the dimensions and capacities of a channel section, 25 and provides methods to check if a ship can navigate through it based on its width, draft, and other restrictions. 26 27 Args: 28 env (Env): The simulation environment. 29 id (int): The identifier for the channel section. 30 length (float): The length of the channel section. 31 width (float): The width of the channel section. 32 draft (float): The draft of the channel section. 33 speed (float): The speed of the channel section. 34 simulation_time (int): The total simulation time. 35 safeTwoway (bool): Whether the section is safe for two-way traffic. 36 """ 37 38 def __init__(self, env, id, length, width, draft, speed, simulation_time, safeTwoway): 39 # Initialize the channel section with its dimensions and capacities. 40 self.id = id 41 self.env = env 42 self.width = width 43 self.draft = draft 44 self.length = length 45 self.simulation_time = simulation_time 46 self.speed = speed 47 self.safeTwoway = safeTwoway 48 49 # Initialize the containers for width and draft restrictions. 50 self.width_containers = {} 51 self.draft_containers = {} 52 self.width_containers_in = {} 53 self.draft_containers_in = {} 54 self.width_containers_out = {} 55 self.draft_containers_out = {} 56 57 # Set the capacity for width and draft based on whether the section is safe for two-way traffic. 58 self.width_capacity = width if not safeTwoway else 1000000 59 self.draft_capacity = draft if not safeTwoway else 1000000 60 61 self.gap_out_in = {} 62 self.gap_out_out = {} 63 64 # Initialize the containers for width and draft restrictions for each time step. 65 for time_step in range(TIME_FACTOR * simulation_time): 66 self.width_containers[time_step] = self.width_capacity 67 self.width_containers_in[time_step] = 0 68 self.width_containers_out[time_step] = 0 69 70 self.draft_containers[time_step] = self.draft_capacity 71 self.draft_containers_in[time_step] = 0 72 self.draft_containers_out[time_step] = 0 73 74 self.gap_out_in[time_step] = 1 75 self.gap_out_out[time_step] = 1 76 77 def can_accommodate_width(self, ship_info, start_time, end_time): 78 """ 79 Check if the channel section can accommodate the width of the ship at a given time step. 80 Args: 81 ship_info (dict): Information about the ship, including its width and direction. 82 start_time (int): The start time of the ship's passage through the section. 83 end_time (int): The end time of the ship's passage through the section. 84 Returns: 85 bool: True if the section can accommodate the ship's width, False otherwise. 86 """ 87 for time_step in range(max(0, int(TIME_FACTOR * start_time)-1), int(TIME_FACTOR * end_time)+1): 88 89 if ship_info['direction'] == 'in': # Same direction max beam restrictiom 90 if ship_info['width'] <= self.width_containers_in[time_step]: 91 continue 92 elif ship_info['direction'] == 'out': # Same direction max beam restrictiom 93 if ship_info['width'] <= self.width_containers_out[time_step]: 94 continue 95 # opposite direction combined beam restriction 96 if ship_info['direction'] == 'in': 97 excess = ship_info['width'] - \ 98 self.width_containers_in[time_step] 99 if (self.width_containers[time_step] <= excess): 100 return False 101 elif ship_info['direction'] == 'out': 102 excess = ship_info['width'] - \ 103 self.width_containers_out[time_step] 104 if (self.width_containers[time_step] <= excess): 105 return False 106 return True 107 108 def can_accommodate_draft(self, ship_info, start_time, end_time): 109 """ 110 Check if the channel section can accommodate the draft of the ship at a given time step. 111 Args: 112 ship_info (dict): Information about the ship, including its draft and direction. 113 start_time (int): The start time of the ship's passage through the section. 114 end_time (int): The end time of the ship's passage through the section. 115 Returns: 116 bool: True if the section can accommodate the ship's draft, False otherwise. 117 """ 118 for time_step in range(max(0, int(TIME_FACTOR * start_time)-1), int(TIME_FACTOR * end_time)+1): 119 120 if ship_info['direction'] == 'in': # Same direction max draft restrictiom 121 if ship_info['draft'] <= self.draft_containers_in[time_step]: 122 continue 123 elif ship_info['direction'] == 'out': # Same direction max draft restrictiom 124 if ship_info['draft'] <= self.draft_containers_out[time_step]: 125 continue 126 # opposite direction combined draft restriction 127 if ship_info['direction'] == 'in': 128 excess = ship_info['draft'] - \ 129 self.draft_containers_in[time_step] 130 if (self.draft_containers[time_step] <= excess): 131 return False 132 elif ship_info['direction'] == 'out': 133 excess = ship_info['draft'] - \ 134 self.draft_containers_out[time_step] 135 if (self.draft_containers[time_step] <= excess): 136 return False 137 return True 138 139 def can_accomodate_gap_out(self, start_time, ship_info): 140 """ 141 Check if the channel section can accommodate the minimum spacing for the ship at a given time step. 142 Args: 143 start_time (int): The start time of the ship's passage through the section. 144 ship_info (dict): Information about the ship, including its direction. 145 Returns: 146 bool: True if the section can accommodate the minimum spacing, False otherwise. 147 """ 148 149 if ship_info['direction'] == 'in': 150 for time_step in range(int(TIME_FACTOR * start_time)-1, int(TIME_FACTOR * (start_time + constants.CHANNEL_ENTRY_SPACING))+1): 151 if self.gap_out_in[time_step] == 1: 152 continue 153 return False 154 return True 155 else: 156 for time_step in range(int(TIME_FACTOR * start_time)-1, int(TIME_FACTOR * (start_time + constants.CHANNEL_ENTRY_SPACING))+1): 157 if self.gap_out_out[time_step] == 1: 158 continue 159 return False 160 return True 161 162 def update_usage(self, run_id, ship_info, start_time, end_time): 163 """ 164 Update the usage of the channel section based on the ship's information. 165 Args: 166 run_id (int): The identifier for the current simulation run. 167 ship_info (dict): Information about the ship, including its width, draft, and direction. 168 start_time (int): The start time of the ship's passage through the section. 169 end_time (int): The end time of the ship's passage through the section. 170 Returns: 171 None 172 """ 173 for time_step in range(int(TIME_FACTOR * start_time), int(TIME_FACTOR * end_time)): 174 175 if ship_info['direction'] == 'in': 176 if ship_info['width'] > self.width_containers_in[time_step]: 177 excess_width_in = ship_info['width'] - \ 178 self.width_containers_in[time_step] 179 self.width_containers_in[time_step] += excess_width_in 180 self.width_containers[time_step] -= excess_width_in 181 182 if ship_info['draft'] > self.draft_containers_in[time_step]: 183 excess_draft_in = ship_info['draft'] - \ 184 self.draft_containers_in[time_step] 185 self.draft_containers_in[time_step] += excess_draft_in 186 self.draft_containers[time_step] -= excess_draft_in 187 188 elif ship_info['direction'] == 'out': 189 if ship_info['width'] > self.width_containers_out[time_step]: 190 excess_width_out = ship_info['width'] - \ 191 self.width_containers_out[time_step] 192 self.width_containers_out[time_step] += excess_width_out 193 self.width_containers[time_step] -= excess_width_out 194 195 if ship_info['draft'] > self.draft_containers_out[time_step]: 196 excess_draft_out = ship_info['draft'] - \ 197 self.draft_containers_out[time_step] 198 self.draft_containers_out[time_step] += excess_draft_out 199 self.draft_containers[time_step] -= excess_draft_out 200 201 # assert that the total width and draft in the section is equal to the capacity 202 assert abs(self.width_containers[time_step] + self.width_containers_in[time_step] + 203 self.width_containers_out[time_step] - self.width_capacity) <= 0.1 204 assert abs(self.draft_containers[time_step] + self.draft_containers_in[time_step] + 205 self.draft_containers_out[time_step] - self.draft_capacity) <= 0.1 206 # assert that the width and draft containers are non-negative 207 assert self.width_containers[time_step] >= 0 208 assert self.draft_containers[time_step] >= 0 209 210 def space_out(self, start_time, ship_info): 211 """ 212 Space out the channel section for the ship based on its direction and start time. 213 Args: 214 start_time (int): The start time of the ship's passage through the section. 215 ship_info (dict): Information about the ship, including its direction. 216 Returns: 217 None 218 """ 219 if ship_info['direction'] == 'in': 220 for time_step in range(int(TIME_FACTOR * start_time), int(TIME_FACTOR * (start_time + constants.CHANNEL_ENTRY_SPACING))): 221 assert self.gap_out_in[time_step] == 1 222 self.gap_out_in[time_step] = 0 223 else: 224 for time_step in range(int(TIME_FACTOR * start_time), int(TIME_FACTOR * (start_time + constants.CHANNEL_ENTRY_SPACING))): 225 assert self.gap_out_out[time_step] == 1 226 self.gap_out_out[time_step] = 0 227 228 229class Channel: 230 """ 231 Class for the entire channel. 232 233 This class manages the sections of the channel and handles ship navigation through it. It implements the necessary methods to check for restrictions, schedule ships, and log their movements. 234 235 Args: 236 ship_logs (list): List to log ship information. 237 env (Env): The simulation environment. 238 numSections (int): Number of sections in the channel. 239 simulation_time (int): Total simulation time for the channel. 240 safeTwoway (bool): Whether the channel is safe for two-way traffic. 241 channel_events (list): List to log channel events. 242 channel_logs (list): List to log channel information. 243 day_pilots (simpy.Container): Simpy container for day pilots. 244 night_pilots (simpy.Container): Simpy container for night pilots. 245 tugboats (simpy.Container): Simpy container for tugboats. 246 turnoffTime (dict): Dictionary with turnoff times and conditions. 247 channel_scheduer (Resource): Resource for scheduling channel usage. 248 seed (int): Random seed for reproducibility. 249""" 250 251 252 def __init__(self, ship_logs, env, numSections, simulation_time, safeTwoway, channel_events, channel_logs, day_pilots, night_pilots, tugboats, turnoffTime, channel_scheduer, seed): 253 self.seed = seed 254 self.env = env 255 self.ship_logs = ship_logs 256 self.numSections = numSections 257 self.safeTwoway = safeTwoway 258 self.channel_events = channel_events 259 self.channel_logs = channel_logs 260 self.simulation_time = simulation_time 261 self.day_pilots = day_pilots 262 self.night_pilots = night_pilots 263 self.tugboats = tugboats 264 self.turnoffTime = turnoffTime 265 self.channel_scheduer = channel_scheduer 266 267 self.sections = [ChannelSection(env, i, *CHANNEL_SECTION_DIMENSIONS[i], int(1.1*self.simulation_time), 268 self.safeTwoway) for i in tqdm(range(1, numSections + 1), desc="Creating channel")] 269 random.seed(seed) 270 271 def channel_closed(self, time): 272 """ 273 Check if the channel is closed at a given time step. 274 Args: 275 time (int): The time step to check for channel closure. 276 """ 277 if self.turnoffTime["switch"] == "channel_closed": 278 for start_end_tuples in self.turnoffTime["closed_between"]: 279 if start_end_tuples[0] <= time < start_end_tuples[1]: 280 return True 281 return False 282 283 def channel_closed_restriction(self, ship_info, lastSection, enter_time): 284 """ 285 Check if the ship has beam restriction in the channel in any section at a given time step. 286 Logic: 287 - If the ship is entering the channel, check each section from the start to the last section. 288 - If the ship is leaving the channel, check each section from the last section to the start. 289 - For each section, calculate the end time based on the ship's speed and length of the section. 290 - For each section, check if the channel is closed at the end time. 291 - If the channel is closed at the end time, return True (indicating the channel is closed for the ship). 292 - If no section is closed, return False (indicating the channel is open for the ship). 293 Args: 294 ship_info (dict): Information about the ship, including its direction. 295 lastSection (int): The last section of the channel to check. 296 enter_time (int): The time step when the ship enters the channel. 297 Returns: 298 bool: True if the channel is closed for the ship, False otherwise. 299 """ 300 if ship_info['direction'] == 'in': 301 start_time = enter_time 302 for i, section in enumerate(self.sections): 303 if i > lastSection: 304 break 305 speed = section.speed 306 end_time = start_time + section.length / speed 307 if self.channel_closed(end_time): 308 return True 309 start_time = end_time 310 return False 311 elif ship_info['direction'] == 'out': 312 start_time = enter_time 313 for i, section in enumerate(reversed(self.sections)): 314 section_number = len(self.sections) - i - 1 315 if section_number > lastSection: 316 continue 317 speed = section.speed 318 end_time = start_time + section.length / speed 319 if self.channel_closed(end_time): 320 return True 321 start_time = end_time 322 return False 323 324 def beam_restriction(self, ship_info, lastSection, enter_time): 325 """ 326 Check if the ship has beam restriction in the channel in any section at a given time step. 327 Logic: 328 - If the ship is entering the channel, check each section from the start to the last section. 329 - If the ship is leaving the channel, check each section from the last section to the start. 330 - For each section, calculate the end time based on the ship's speed and length of the section. 331 - For each section, check if the width of the ship exceeds the available width in the section. 332 - If the width of the ship exceeds the available width in any section, return True (indicating beam restriction). 333 - If no section has beam restriction, return False (indicating no beam restriction). 334 Args: 335 ship_info (dict): Information about the ship, including its width and direction. 336 lastSection (int): The last section of the channel to check. 337 enter_time (int): The time step when the ship enters the channel. 338 Returns: 339 bool: True if the ship has beam restriction, False otherwise. 340 """ 341 if ship_info['direction'] == 'in': 342 start_time = enter_time 343 for i, section in enumerate(self.sections): 344 if i > lastSection: 345 break 346 speed = section.speed 347 end_time = start_time + section.length / speed 348 if not section.can_accommodate_width(ship_info, start_time, end_time): 349 return True 350 start_time = end_time 351 return False 352 elif ship_info['direction'] == 'out': 353 start_time = enter_time 354 for i, section in enumerate(reversed(self.sections)): 355 section_number = len(self.sections) - i - 1 356 if section_number > lastSection: 357 continue 358 speed = section.speed 359 end_time = start_time + section.length / speed 360 if not section.can_accommodate_width(ship_info, start_time, end_time): 361 return True 362 start_time = end_time 363 return False 364 365 def draft_restriction(self, ship_info, lastSection, enter_time): 366 """ 367 Check if the ship has draft restriction in the channel in any section at a given time step. 368 Logic is similar to beam_restriction, but checks for draft instead of width. 369 Args: 370 ship_info (dict): Information about the ship, including its draft and direction. 371 lastSection (int): The last section of the channel to check. 372 enter_time (int): The time step when the ship enters the channel. 373 Returns: 374 bool: True if the ship has draft restriction, False otherwise. 375 """ 376 if ship_info['direction'] == 'in': 377 start_time = enter_time 378 for i, section in enumerate(self.sections): 379 if i > lastSection: 380 break 381 speed = section.speed 382 end_time = start_time + section.length / speed 383 if not section.can_accommodate_draft(ship_info, start_time, end_time): 384 return True 385 start_time = end_time 386 return False 387 elif ship_info['direction'] == 'out': 388 start_time = enter_time 389 for i, section in enumerate(reversed(self.sections)): 390 section_number = len(self.sections) - i - 1 391 if section_number > lastSection: 392 continue 393 speed = section.speed 394 end_time = start_time + section.length / speed 395 if not section.can_accommodate_draft(ship_info, start_time, end_time): 396 return True 397 start_time = end_time 398 return False 399 400 def minimal_spacing_not_avlbl(self, ship_info, lastSection, enter_time): 401 """ 402 Check if the minimal spacing for the ship is not available in the channel in any section at a given time step. 403 Logic is similar to beam_restriction, but checks for minimal spacing instead of width. 404 Args: 405 ship_info (dict): Information about the ship, including its direction. 406 lastSection (int): The last section of the channel to check. 407 enter_time (int): The time step when the ship enters the channel. 408 Returns: 409 bool: True if the minimal spacing is not available, False otherwise. 410 """ 411 if ship_info['direction'] == 'in': 412 start_time = enter_time 413 for i, section in enumerate(self.sections): 414 if i > lastSection: 415 break 416 speed = section.speed 417 end_time = start_time + section.length / speed 418 if not section.can_accomodate_gap_out(start_time, ship_info): 419 return True 420 start_time = end_time 421 return False 422 elif ship_info['direction'] == 'out': 423 start_time = enter_time 424 for i, section in enumerate(reversed(self.sections)): 425 section_number = len(self.sections) - i - 1 426 if section_number > lastSection: 427 continue 428 speed = section.speed 429 end_time = start_time + section.length / speed 430 if not section.can_accomodate_gap_out(start_time, ship_info): 431 return True 432 start_time = end_time 433 return False 434 435 def daylight_restriction(self, ship_info, lastSection, enter_time): 436 """ 437 Check if the ship has daylight restriction in the channel at a given time step. 438 Logic: 439 - Check the ship's type and dimensions (width and length). 440 - If the ship is a Container, Liquid, or DryBulk type, check if its width or length exceeds the specified limits. 441 - If the ship exceeds the limits, check if it is daytime at the time of entry. 442 - If it is not daytime, return True (indicating daylight restriction). 443 - If the ship does not exceed the limits or it is daytime, return False (indicating no daylight restriction). 444 Args: 445 ship_info (dict): Information about the ship, including its type, width, and length. 446 lastSection (int): The last section of the channel to check. 447 enter_time (int): The time step when the ship enters the channel. 448 Returns: 449 bool: True if the ship has daylight restriction, False otherwise. 450 """ 451 if ship_info['ship_type'] == 'Container': 452 if ship_info['width'] > 120 or ship_info['length'] > 1000: 453 # if not daytime return True 454 return not is_daytime(enter_time, START_DAYLIGHT_RESTRICTION_HR, STOP_DAYLIGHT_RESTRICTION_HR) 455 elif ship_info['ship_type'] == 'Liquid': 456 if ship_info['width'] > 120 or ship_info['length'] > 900: 457 return not is_daytime(enter_time, START_DAYLIGHT_RESTRICTION_HR, STOP_DAYLIGHT_RESTRICTION_HR) 458 elif ship_info['ship_type'] == 'DryBulk': 459 if ship_info['width'] > 120 or ship_info['length'] > 900: 460 return not is_daytime(enter_time, START_DAYLIGHT_RESTRICTION_HR, STOP_DAYLIGHT_RESTRICTION_HR) 461 return False 462 463 def scheduler(self, ship_info, lastSection, run_id, enter_time): 464 """ 465 Schedule the ship to navigate through the channel. This happens before the ship actually moves through the channel. 466 Logic: 467 - Check if the ship has any restrictions in the channel (beam, draft, daylight, minimal spacing). 468 - If there are any restrictions, log the ship's information and return. 469 - If there are no restrictions, update the ship's logs and schedule the ship to move through the channel. 470 - If the ship is entering the channel, update the usage of each section in the channel. 471 - If the ship is leaving the channel, update the usage of each section in reverse order. 472 - Log the ship's movement through the channel. 473 - Finally, yield the ship's movement through the channel. 474 Note this is different from the move_through_channel method, which is called when the ship actually moves through the channel. 475 The scheduler method is called as soon as the ship requests to enter the channel. 476 If the ship has any restrictions, it will not be scheduled to move through the channel. 477 Args: 478 ship_info (dict): Information about the ship, including its direction, width, draft, and speed. 479 lastSection (int): The last section of the channel to check. 480 run_id (int): The identifier for the current simulation run. 481 enter_time (int): The time step when the ship enters the channel. 482 Returns: 483 None 484 """ 485 start_time = enter_time 486 487 if ship_info['direction'] == 'in': 488 for i, section in enumerate(self.sections): 489 if i > lastSection: 490 break 491 speed = section.speed 492 end_time = start_time + section.length / speed 493 section.update_usage(run_id, ship_info, start_time, end_time) 494 self.sections[i].space_out(start_time, ship_info) 495 start_time = end_time 496 497 elif ship_info['direction'] == 'out': 498 for i, section in enumerate(reversed(self.sections)): 499 section_number = len(self.sections) - i - 1 500 if section_number > lastSection: 501 continue 502 speed = section.speed 503 end_time = start_time + section.length / speed 504 section.update_usage(run_id, ship_info, start_time, end_time) 505 self.sections[section_number].space_out(start_time, ship_info) 506 start_time = end_time 507 508 def move_through_channel(self, ship_info, lastSection, run_id): 509 """ 510 Simulate the movement of the ship through the channel. 511 Logic: 512 - If the ship is entering the channel, iterate through each section from the start to the last section. 513 - If the ship is leaving the channel, iterate through each section from the last section to the start. 514 - For each section, calculate the speed and end time based on the ship's speed and length of the section. 515 - Log the ship's movement through the section, including its ship_id, ship_type, width, draft, and direction. 516 - Yield the ship's movement through the section. 517 - Log the time spent in each section. 518 - Finally, update the ship's logs with the time spent in the channel. 519 - If the ship is entering the channel, log the time spent in each section going in. 520 - If the ship is leaving the channel, log the time spent in each section going out. 521 Note this is different from the scheduler method,which schedules before the ship actually moves through the channel. 522 The sheduler happens as soon as the ship requests to enter the channel. 523 The move_through_channel method is called when the ship actually moves through the channel (when there are no restrictions). 524 Args: 525 ship_info (dict): Information about the ship, including its ship_id, ship_type, width, draft, and direction. 526 lastSection (int): The last section of the channel to check. 527 run_id (int): The identifier for the current simulation run. 528 Returns: 529 None 530 """ 531 enter_time = self.env.now 532 start_time = enter_time 533 534 if ship_info['direction'] == 'in': 535 for i, section in enumerate(self.sections): 536 if i > lastSection: 537 break 538 speed = section.speed 539 end_time = start_time + section.length / speed 540 yield self.env.timeout(section.length / speed) 541 log_line(run_id, "ships_in_channel.txt", 542 f"Ship {ship_info['ship_id']} of type {ship_info['ship_type']} of width {ship_info['width']} and draft {ship_info['draft']} spent from {start_time} to {end_time} in section {i} going in") 543 start_time = end_time 544 545 elif ship_info['direction'] == 'out': 546 for i, section in enumerate(reversed(self.sections)): 547 section_number = len(self.sections) - i - 1 548 if section_number > lastSection: 549 continue 550 speed = section.speed 551 end_time = start_time + section.length / speed 552 yield self.env.timeout(section.length / speed) 553 log_line(run_id, "ships_in_channel.txt", 554 f"Ship {ship_info['ship_id']} of type {ship_info['ship_type']} of width {ship_info['width']} and draft {ship_info['draft']} spent from {start_time} to {end_time} in section {section_number} going out") 555 start_time = end_time 556 557 def check_restrictions(self, ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight): 558 """ 559 Check if the ship has any restrictions in the channel at a given time step. 560 Args: 561 ship_info (dict): Information about the ship, including its direction, width, draft, and ship type. 562 lastSection (int): The last section of the channel to check. 563 request_time (int): The time step when the ship requests to enter the channel. 564 wait_beam (int): The number of time steps the ship has waited for beam restrictions. 565 wait_draft (int): The number of time steps the ship has waited for draft restrictions. 566 wait_daylight (int): The number of time steps the ship has waited for daylight restrictions. 567 Returns: 568 tuple: A tuple containing: 569 - restrictions (bool): True if there are any restrictions, False otherwise. 570 - wait_beam (int): The updated number of time steps the ship has waited for beam restrictions. 571 - wait_draft (int): The updated number of time steps the ship has waited for draft restrictions. 572 - wait_daylight (int): The updated number of time steps the ship has waited for daylight restrictions. 573 """ 574 channel_closed_restriction = self.channel_closed_restriction( 575 ship_info, lastSection, request_time) 576 beam_restriction = self.beam_restriction( 577 ship_info, lastSection, request_time) 578 draft_restriction = self.draft_restriction( 579 ship_info, lastSection, request_time) 580 daylight_restriction = self.daylight_restriction( 581 ship_info, lastSection, request_time) 582 583 if beam_restriction: 584 wait_beam += 1 585 if draft_restriction: 586 wait_draft += 1 587 if daylight_restriction: 588 wait_daylight += 1 589 590 restrictions = beam_restriction or draft_restriction or daylight_restriction or channel_closed_restriction 591 592 return restrictions, wait_beam, wait_draft, wait_daylight 593 594 def channel_process(self, ship_info, day, lastSection, ship_id, ship_terminal, ship_type, run_id): 595 """ 596 Process for handling the ship's entry into the channel. 597 It handles the scheduling and movement of the ship through the channel, including checking for restrictions and logging the ship's information. 598 The process has the following steps: 599 1. Check if the ship is entering or leaving the channel based on its direction. 600 2. If the ship is entering the channel, then: 601 2.1. Get the pilots based on the time of day (day or night). 602 2.2. Check when the ship is allowed to enter being free of restrictions. 603 2.3 Schedule the ship to move through the channel. 604 2.4 Wait till the ship can move through the channel. 605 2.5 Move the ship through the channel. 606 2.6 Get the tugboats and steer the ship. 607 3. If the ship is leaving the channel, then: 608 3.1. Get the pilots based on the time of day (day or night). 609 3.2 Get the tugboats and steer the ship. 610 3.3.Check when the ship is allowed to leave being free of restrictions. 611 3.4 Schedule the ship to move through the channel. 612 3.5 Wait till the ship can move through the channel. 613 3.6 Move the ship through the channel. 614 Args: 615 ship_info (dict): Information about the ship, including its direction, pilots, tugboats, and other attributes. 616 day (bool): Whether it is daytime or nighttime. 617 lastSection (int): The last section of the channel to check. 618 ship_id (int): The identifier for the ship. 619 ship_terminal (str): The terminal where the ship is headed. 620 ship_type (str): The type of the ship (e.g., Container, Liquid, DryBulk). 621 run_id (int): The identifier for the current simulation run. 622 Returns: 623 None 624 """ 625 lastSection = lastSection - 1 626 request_time_initial = self.env.now 627 628 inbound_closed_tuples = constants.INBOUND_CLOSED 629 outbound_closed_tuples = constants.OUTBOUND_CLOSED 630 631 wait_queue = self.env.now - request_time_initial 632 633 # If ship travels in 634 if ship_info['direction'] == 'in': 635 ship_entering_channel_time = self.env.now 636 637 # get pilots 638 day = is_daytime(ship_entering_channel_time, 7, 19) 639 640 if day: 641 yield self.day_pilots.get(ship_info['pilots']) 642 else: 643 yield self.night_pilots.get(ship_info['pilots']) 644 time_to_get_pilot = self.env.now - ship_entering_channel_time 645 update_ship_logs(self.ship_logs, ship_type, ship_id, 646 ship_terminal, time_to_get_pilot_in=time_to_get_pilot) 647 648 # travel anchorage to mouth of the channel 649 time_to_common_channel = random.uniform( 650 TIME_COMMON_CHANNEL[0], TIME_COMMON_CHANNEL[1]) 651 yield self.env.timeout(time_to_common_channel) 652 time_to_common_channel = self.env.now - \ 653 (ship_entering_channel_time + time_to_get_pilot) 654 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 655 time_to_common_channel_in=time_to_common_channel) 656 657 # Schedule the ship to move through the channel 658 with self.channel_scheduer.request() as request: 659 yield request 660 661 request_time = self.env.now 662 wait_daylight = 0 663 wait_beam = 0 664 wait_draft = 0 665 # Note that multiple restrictions can be active at the same time 666 wait_total_restriction = 0 667 wait_total = 0 668 669 if not self.safeTwoway: 670 restrcictions, wait_beam, wait_draft, wait_daylight = self.check_restrictions( 671 ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight) 672 channel_closwed = self.channel_closed(request_time) 673 channel_closed_inbound = False 674 675 for start, end in inbound_closed_tuples: 676 if start <= request_time <= end: 677 channel_closed_inbound = True 678 679 no_spacing = self.minimal_spacing_not_avlbl( 680 ship_info, lastSection, request_time) 681 682 while restrcictions or channel_closwed or channel_closed_inbound or no_spacing: 683 if channel_closed_inbound: 684 print("Channel closed inbound at time", request_time) 685 686 wait_total += 1 687 wait_total_restriction += 1 688 request_time += 1 689 restrcictions, wait_beam, wait_draft, wait_daylight = self.check_restrictions( 690 ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight) 691 channel_closwed = self.channel_closed(request_time) 692 no_spacing = self.minimal_spacing_not_avlbl( 693 ship_info, lastSection, request_time) 694 channel_closed_inbound = False 695 for start, end in inbound_closed_tuples: 696 if start <= request_time <= end: 697 channel_closed_inbound = True 698 699 else: 700 channel_closwed = self.channel_closed(request_time) 701 702 channel_closed_inbound = False 703 for start, end in inbound_closed_tuples: 704 if start <= request_time <= end: 705 channel_closed_inbound = True 706 707 no_spacing = self.minimal_spacing_not_avlbl( 708 ship_info, lastSection, request_time) 709 710 while channel_closwed or channel_closed_inbound or no_spacing: 711 wait_total += 1 712 request_time += 1 713 channel_closwed = self.channel_closed(request_time) 714 no_spacing = self.minimal_spacing_not_avlbl( 715 ship_info, lastSection, request_time) 716 channel_closed_inbound = False 717 for start, end in inbound_closed_tuples: 718 if start <= request_time <= end: 719 channel_closed_inbound = True 720 721 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 722 time_for_restriction_in=f'B:{wait_beam}, D:{wait_draft}, DL:{wait_daylight}, Q:{wait_queue}, T:{wait_total}') 723 724 self.scheduler(ship_info, lastSection, run_id, 725 enter_time=request_time) 726 727 # travel through the channel 728 yield self.env.timeout(request_time - self.env.now) 729 time_entered_channel = self.env.now 730 yield self.env.process(self.move_through_channel(ship_info, lastSection, run_id)) 731 time_to_travel_channel = self.env.now - time_entered_channel 732 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 733 time_to_travel_channel_in=time_to_travel_channel) 734 735 # get tugs 736 time_tug_req = self.env.now 737 yield self.tugboats.get(ship_info['tugboats']) 738 time_to_get_tugs = self.env.now - time_tug_req 739 update_ship_logs(self.ship_logs, ship_type, ship_id, 740 ship_terminal, time_to_get_tugs_in=time_to_get_tugs) 741 742 # steer the ship 743 time_to_tug_steer = random.uniform( 744 TIME_FOR_TUG_STEER[0], TIME_FOR_TUG_STEER[1]) 745 yield self.env.timeout(time_to_tug_steer) 746 update_ship_logs(self.ship_logs, ship_type, ship_id, 747 ship_terminal, time_to_tug_steer_in=time_to_tug_steer) 748 749 # put the tugs back 750 yield self.tugboats.put(ship_info['tugboats']) 751 752 # Release pilots 753 if day: 754 yield self.day_pilots.put(ship_info['pilots']) 755 else: 756 yield self.night_pilots.put(ship_info['pilots']) 757 758 ship_info['direction'] = 'out' if ship_info['direction'] == 'in' else 'in' 759 760 # If ship travels out 761 # Dont enter this if ship is going in even though ship_info['direction'] is 'out' 762 elif ship_info['direction'] == 'out': 763 ship_entering_channel_time = self.env.now 764 765 # get pilots 766 day = is_daytime(ship_entering_channel_time, 7, 19) 767 if day: 768 yield self.day_pilots.get(ship_info['pilots']) 769 else: 770 yield self.night_pilots.get(ship_info['pilots']) 771 time_to_get_pilot = self.env.now - ship_entering_channel_time 772 update_ship_logs(self.ship_logs, ship_type, ship_id, 773 ship_terminal, time_to_get_pilot_out=time_to_get_pilot) 774 775 # get tugs 776 yield self.tugboats.get(ship_info['tugboats']) 777 time_to_get_tugs = self.env.now - \ 778 (ship_entering_channel_time + time_to_get_pilot) 779 update_ship_logs(self.ship_logs, ship_type, ship_id, 780 ship_terminal, time_to_get_tugs_out=time_to_get_tugs) 781 782 # make a uturn 783 time_to_uturn = random.uniform( 784 TIME_FOR_UTURN[0], TIME_FOR_UTURN[1]) 785 yield self.env.timeout(time_to_uturn) 786 time_for_uturn = self.env.now - \ 787 (ship_entering_channel_time + time_to_get_pilot + time_to_get_tugs) 788 update_ship_logs(self.ship_logs, ship_type, ship_id, 789 ship_terminal, time_for_uturn=time_for_uturn) 790 791 # steer the ship 792 time_to_tug_steer = random.uniform( 793 TIME_FOR_TUG_STEER[0], TIME_FOR_TUG_STEER[1]) 794 yield self.env.timeout(time_to_tug_steer) 795 update_ship_logs(self.ship_logs, ship_type, ship_id, 796 ship_terminal, time_to_tug_steer_out=time_to_tug_steer) 797 798 # put the tugs back 799 yield self.tugboats.put(ship_info['tugboats']) 800 801 # Schedule the ship to move through the channel 802 with self.channel_scheduer.request() as request: 803 yield request 804 request_time = self.env.now 805 wait_daylight = 0 806 wait_beam = 0 807 wait_draft = 0 808 # Note that multiple restrictions can be active at the same time 809 wait_total_restriction = 0 810 wait_queue = request_time - request_time_initial 811 wait_total = 0 812 if not self.safeTwoway: 813 restrcictions, wait_beam, wait_draft, wait_daylight = self.check_restrictions( 814 ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight) 815 channel_closwed = self.channel_closed(request_time) 816 channel_closed_outbound = False 817 for start, end in outbound_closed_tuples: 818 if start <= request_time <= end: 819 channel_closed_outbound = True 820 no_spacing = self.minimal_spacing_not_avlbl( 821 ship_info, lastSection, request_time) 822 while restrcictions or channel_closed_outbound or channel_closwed or no_spacing: 823 824 if channel_closed_outbound: 825 print("Channel closed outbound at time", request_time) 826 827 wait_total += 1 828 wait_total_restriction += 1 829 request_time += 1 830 restrcictions, wait_beam, wait_draft, wait_daylight = self.check_restrictions( 831 ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight) 832 channel_closwed = self.channel_closed(request_time) 833 no_spacing = self.minimal_spacing_not_avlbl( 834 ship_info, lastSection, request_time) 835 836 channel_closed_outbound = False 837 for start, end in outbound_closed_tuples: 838 if start <= request_time <= end: 839 channel_closed_outbound = True 840 841 else: 842 channel_closwed = self.channel_closed(request_time) 843 channel_closed_outbound = False 844 845 for start, end in outbound_closed_tuples: 846 if start <= request_time <= end: 847 channel_closed_outbound = True 848 no_spacing = self.minimal_spacing_not_avlbl( 849 ship_info, lastSection, request_time) 850 no_spacing = self.minimal_spacing_not_avlbl( 851 ship_info, lastSection, request_time) 852 while channel_closwed or channel_closed_outbound or no_spacing: 853 wait_total += 1 854 request_time += 1 855 channel_closwed = self.channel_closed(request_time) 856 no_spacing = self.minimal_spacing_not_avlbl( 857 ship_info, lastSection, request_time) 858 859 channel_closed_outbound = False 860 for start, end in outbound_closed_tuples: 861 if start <= request_time <= end: 862 channel_closed_outbound = True 863 864 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 865 time_for_restriction_out=f'B:{wait_beam}, D:{wait_draft}, DL:{wait_daylight}, Q:{wait_queue}, T:{wait_total}') 866 self.scheduler(ship_info, lastSection, run_id, 867 enter_time=request_time) 868 869 # travel through the channel 870 yield self.env.timeout(request_time - self.env.now) 871 time_entered_channel = self.env.now 872 yield self.env.process(self.move_through_channel(ship_info, lastSection, run_id)) 873 time_to_travel_channel = self.env.now - time_entered_channel 874 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 875 time_to_travel_channel_out=time_to_travel_channel) 876 877 # travel from mouth of the channel to anchorage 878 time_to_common_channel = random.uniform( 879 TIME_COMMON_CHANNEL[0], TIME_COMMON_CHANNEL[1]) 880 yield self.env.timeout(time_to_common_channel) 881 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 882 time_to_common_channel_out=time_to_common_channel) 883 884 # Release pilots 885 if day: 886 yield self.day_pilots.put(ship_info['pilots']) 887 else: 888 yield self.night_pilots.put(ship_info['pilots'])
22class ChannelSection: 23 """ 24 Class for each section of the channel 25 This class manages the dimensions and capacities of a channel section, 26 and provides methods to check if a ship can navigate through it based on its width, draft, and other restrictions. 27 28 Args: 29 env (Env): The simulation environment. 30 id (int): The identifier for the channel section. 31 length (float): The length of the channel section. 32 width (float): The width of the channel section. 33 draft (float): The draft of the channel section. 34 speed (float): The speed of the channel section. 35 simulation_time (int): The total simulation time. 36 safeTwoway (bool): Whether the section is safe for two-way traffic. 37 """ 38 39 def __init__(self, env, id, length, width, draft, speed, simulation_time, safeTwoway): 40 # Initialize the channel section with its dimensions and capacities. 41 self.id = id 42 self.env = env 43 self.width = width 44 self.draft = draft 45 self.length = length 46 self.simulation_time = simulation_time 47 self.speed = speed 48 self.safeTwoway = safeTwoway 49 50 # Initialize the containers for width and draft restrictions. 51 self.width_containers = {} 52 self.draft_containers = {} 53 self.width_containers_in = {} 54 self.draft_containers_in = {} 55 self.width_containers_out = {} 56 self.draft_containers_out = {} 57 58 # Set the capacity for width and draft based on whether the section is safe for two-way traffic. 59 self.width_capacity = width if not safeTwoway else 1000000 60 self.draft_capacity = draft if not safeTwoway else 1000000 61 62 self.gap_out_in = {} 63 self.gap_out_out = {} 64 65 # Initialize the containers for width and draft restrictions for each time step. 66 for time_step in range(TIME_FACTOR * simulation_time): 67 self.width_containers[time_step] = self.width_capacity 68 self.width_containers_in[time_step] = 0 69 self.width_containers_out[time_step] = 0 70 71 self.draft_containers[time_step] = self.draft_capacity 72 self.draft_containers_in[time_step] = 0 73 self.draft_containers_out[time_step] = 0 74 75 self.gap_out_in[time_step] = 1 76 self.gap_out_out[time_step] = 1 77 78 def can_accommodate_width(self, ship_info, start_time, end_time): 79 """ 80 Check if the channel section can accommodate the width of the ship at a given time step. 81 Args: 82 ship_info (dict): Information about the ship, including its width and direction. 83 start_time (int): The start time of the ship's passage through the section. 84 end_time (int): The end time of the ship's passage through the section. 85 Returns: 86 bool: True if the section can accommodate the ship's width, False otherwise. 87 """ 88 for time_step in range(max(0, int(TIME_FACTOR * start_time)-1), int(TIME_FACTOR * end_time)+1): 89 90 if ship_info['direction'] == 'in': # Same direction max beam restrictiom 91 if ship_info['width'] <= self.width_containers_in[time_step]: 92 continue 93 elif ship_info['direction'] == 'out': # Same direction max beam restrictiom 94 if ship_info['width'] <= self.width_containers_out[time_step]: 95 continue 96 # opposite direction combined beam restriction 97 if ship_info['direction'] == 'in': 98 excess = ship_info['width'] - \ 99 self.width_containers_in[time_step] 100 if (self.width_containers[time_step] <= excess): 101 return False 102 elif ship_info['direction'] == 'out': 103 excess = ship_info['width'] - \ 104 self.width_containers_out[time_step] 105 if (self.width_containers[time_step] <= excess): 106 return False 107 return True 108 109 def can_accommodate_draft(self, ship_info, start_time, end_time): 110 """ 111 Check if the channel section can accommodate the draft of the ship at a given time step. 112 Args: 113 ship_info (dict): Information about the ship, including its draft and direction. 114 start_time (int): The start time of the ship's passage through the section. 115 end_time (int): The end time of the ship's passage through the section. 116 Returns: 117 bool: True if the section can accommodate the ship's draft, False otherwise. 118 """ 119 for time_step in range(max(0, int(TIME_FACTOR * start_time)-1), int(TIME_FACTOR * end_time)+1): 120 121 if ship_info['direction'] == 'in': # Same direction max draft restrictiom 122 if ship_info['draft'] <= self.draft_containers_in[time_step]: 123 continue 124 elif ship_info['direction'] == 'out': # Same direction max draft restrictiom 125 if ship_info['draft'] <= self.draft_containers_out[time_step]: 126 continue 127 # opposite direction combined draft restriction 128 if ship_info['direction'] == 'in': 129 excess = ship_info['draft'] - \ 130 self.draft_containers_in[time_step] 131 if (self.draft_containers[time_step] <= excess): 132 return False 133 elif ship_info['direction'] == 'out': 134 excess = ship_info['draft'] - \ 135 self.draft_containers_out[time_step] 136 if (self.draft_containers[time_step] <= excess): 137 return False 138 return True 139 140 def can_accomodate_gap_out(self, start_time, ship_info): 141 """ 142 Check if the channel section can accommodate the minimum spacing for the ship at a given time step. 143 Args: 144 start_time (int): The start time of the ship's passage through the section. 145 ship_info (dict): Information about the ship, including its direction. 146 Returns: 147 bool: True if the section can accommodate the minimum spacing, False otherwise. 148 """ 149 150 if ship_info['direction'] == 'in': 151 for time_step in range(int(TIME_FACTOR * start_time)-1, int(TIME_FACTOR * (start_time + constants.CHANNEL_ENTRY_SPACING))+1): 152 if self.gap_out_in[time_step] == 1: 153 continue 154 return False 155 return True 156 else: 157 for time_step in range(int(TIME_FACTOR * start_time)-1, int(TIME_FACTOR * (start_time + constants.CHANNEL_ENTRY_SPACING))+1): 158 if self.gap_out_out[time_step] == 1: 159 continue 160 return False 161 return True 162 163 def update_usage(self, run_id, ship_info, start_time, end_time): 164 """ 165 Update the usage of the channel section based on the ship's information. 166 Args: 167 run_id (int): The identifier for the current simulation run. 168 ship_info (dict): Information about the ship, including its width, draft, and direction. 169 start_time (int): The start time of the ship's passage through the section. 170 end_time (int): The end time of the ship's passage through the section. 171 Returns: 172 None 173 """ 174 for time_step in range(int(TIME_FACTOR * start_time), int(TIME_FACTOR * end_time)): 175 176 if ship_info['direction'] == 'in': 177 if ship_info['width'] > self.width_containers_in[time_step]: 178 excess_width_in = ship_info['width'] - \ 179 self.width_containers_in[time_step] 180 self.width_containers_in[time_step] += excess_width_in 181 self.width_containers[time_step] -= excess_width_in 182 183 if ship_info['draft'] > self.draft_containers_in[time_step]: 184 excess_draft_in = ship_info['draft'] - \ 185 self.draft_containers_in[time_step] 186 self.draft_containers_in[time_step] += excess_draft_in 187 self.draft_containers[time_step] -= excess_draft_in 188 189 elif ship_info['direction'] == 'out': 190 if ship_info['width'] > self.width_containers_out[time_step]: 191 excess_width_out = ship_info['width'] - \ 192 self.width_containers_out[time_step] 193 self.width_containers_out[time_step] += excess_width_out 194 self.width_containers[time_step] -= excess_width_out 195 196 if ship_info['draft'] > self.draft_containers_out[time_step]: 197 excess_draft_out = ship_info['draft'] - \ 198 self.draft_containers_out[time_step] 199 self.draft_containers_out[time_step] += excess_draft_out 200 self.draft_containers[time_step] -= excess_draft_out 201 202 # assert that the total width and draft in the section is equal to the capacity 203 assert abs(self.width_containers[time_step] + self.width_containers_in[time_step] + 204 self.width_containers_out[time_step] - self.width_capacity) <= 0.1 205 assert abs(self.draft_containers[time_step] + self.draft_containers_in[time_step] + 206 self.draft_containers_out[time_step] - self.draft_capacity) <= 0.1 207 # assert that the width and draft containers are non-negative 208 assert self.width_containers[time_step] >= 0 209 assert self.draft_containers[time_step] >= 0 210 211 def space_out(self, start_time, ship_info): 212 """ 213 Space out the channel section for the ship based on its direction and start time. 214 Args: 215 start_time (int): The start time of the ship's passage through the section. 216 ship_info (dict): Information about the ship, including its direction. 217 Returns: 218 None 219 """ 220 if ship_info['direction'] == 'in': 221 for time_step in range(int(TIME_FACTOR * start_time), int(TIME_FACTOR * (start_time + constants.CHANNEL_ENTRY_SPACING))): 222 assert self.gap_out_in[time_step] == 1 223 self.gap_out_in[time_step] = 0 224 else: 225 for time_step in range(int(TIME_FACTOR * start_time), int(TIME_FACTOR * (start_time + constants.CHANNEL_ENTRY_SPACING))): 226 assert self.gap_out_out[time_step] == 1 227 self.gap_out_out[time_step] = 0
Class for each section of the channel
This class manages the dimensions and capacities of a channel section,
and provides methods to check if a ship can navigate through it based on its width, draft, and other restrictions.
Arguments:
- env (Env): The simulation environment.
- id (int): The identifier for the channel section.
- length (float): The length of the channel section.
- width (float): The width of the channel section.
- draft (float): The draft of the channel section.
- speed (float): The speed of the channel section.
- simulation_time (int): The total simulation time.
- safeTwoway (bool): Whether the section is safe for two-way traffic.
78 def can_accommodate_width(self, ship_info, start_time, end_time): 79 """ 80 Check if the channel section can accommodate the width of the ship at a given time step. 81 Args: 82 ship_info (dict): Information about the ship, including its width and direction. 83 start_time (int): The start time of the ship's passage through the section. 84 end_time (int): The end time of the ship's passage through the section. 85 Returns: 86 bool: True if the section can accommodate the ship's width, False otherwise. 87 """ 88 for time_step in range(max(0, int(TIME_FACTOR * start_time)-1), int(TIME_FACTOR * end_time)+1): 89 90 if ship_info['direction'] == 'in': # Same direction max beam restrictiom 91 if ship_info['width'] <= self.width_containers_in[time_step]: 92 continue 93 elif ship_info['direction'] == 'out': # Same direction max beam restrictiom 94 if ship_info['width'] <= self.width_containers_out[time_step]: 95 continue 96 # opposite direction combined beam restriction 97 if ship_info['direction'] == 'in': 98 excess = ship_info['width'] - \ 99 self.width_containers_in[time_step] 100 if (self.width_containers[time_step] <= excess): 101 return False 102 elif ship_info['direction'] == 'out': 103 excess = ship_info['width'] - \ 104 self.width_containers_out[time_step] 105 if (self.width_containers[time_step] <= excess): 106 return False 107 return True
Check if the channel section can accommodate the width of the ship at a given time step.
Arguments:
- ship_info (dict): Information about the ship, including its width and direction.
- start_time (int): The start time of the ship's passage through the section.
- end_time (int): The end time of the ship's passage through the section.
Returns:
bool: True if the section can accommodate the ship's width, False otherwise.
109 def can_accommodate_draft(self, ship_info, start_time, end_time): 110 """ 111 Check if the channel section can accommodate the draft of the ship at a given time step. 112 Args: 113 ship_info (dict): Information about the ship, including its draft and direction. 114 start_time (int): The start time of the ship's passage through the section. 115 end_time (int): The end time of the ship's passage through the section. 116 Returns: 117 bool: True if the section can accommodate the ship's draft, False otherwise. 118 """ 119 for time_step in range(max(0, int(TIME_FACTOR * start_time)-1), int(TIME_FACTOR * end_time)+1): 120 121 if ship_info['direction'] == 'in': # Same direction max draft restrictiom 122 if ship_info['draft'] <= self.draft_containers_in[time_step]: 123 continue 124 elif ship_info['direction'] == 'out': # Same direction max draft restrictiom 125 if ship_info['draft'] <= self.draft_containers_out[time_step]: 126 continue 127 # opposite direction combined draft restriction 128 if ship_info['direction'] == 'in': 129 excess = ship_info['draft'] - \ 130 self.draft_containers_in[time_step] 131 if (self.draft_containers[time_step] <= excess): 132 return False 133 elif ship_info['direction'] == 'out': 134 excess = ship_info['draft'] - \ 135 self.draft_containers_out[time_step] 136 if (self.draft_containers[time_step] <= excess): 137 return False 138 return True
Check if the channel section can accommodate the draft of the ship at a given time step.
Arguments:
- ship_info (dict): Information about the ship, including its draft and direction.
- start_time (int): The start time of the ship's passage through the section.
- end_time (int): The end time of the ship's passage through the section.
Returns:
bool: True if the section can accommodate the ship's draft, False otherwise.
140 def can_accomodate_gap_out(self, start_time, ship_info): 141 """ 142 Check if the channel section can accommodate the minimum spacing for the ship at a given time step. 143 Args: 144 start_time (int): The start time of the ship's passage through the section. 145 ship_info (dict): Information about the ship, including its direction. 146 Returns: 147 bool: True if the section can accommodate the minimum spacing, False otherwise. 148 """ 149 150 if ship_info['direction'] == 'in': 151 for time_step in range(int(TIME_FACTOR * start_time)-1, int(TIME_FACTOR * (start_time + constants.CHANNEL_ENTRY_SPACING))+1): 152 if self.gap_out_in[time_step] == 1: 153 continue 154 return False 155 return True 156 else: 157 for time_step in range(int(TIME_FACTOR * start_time)-1, int(TIME_FACTOR * (start_time + constants.CHANNEL_ENTRY_SPACING))+1): 158 if self.gap_out_out[time_step] == 1: 159 continue 160 return False 161 return True
Check if the channel section can accommodate the minimum spacing for the ship at a given time step.
Arguments:
- start_time (int): The start time of the ship's passage through the section.
- ship_info (dict): Information about the ship, including its direction.
Returns:
bool: True if the section can accommodate the minimum spacing, False otherwise.
163 def update_usage(self, run_id, ship_info, start_time, end_time): 164 """ 165 Update the usage of the channel section based on the ship's information. 166 Args: 167 run_id (int): The identifier for the current simulation run. 168 ship_info (dict): Information about the ship, including its width, draft, and direction. 169 start_time (int): The start time of the ship's passage through the section. 170 end_time (int): The end time of the ship's passage through the section. 171 Returns: 172 None 173 """ 174 for time_step in range(int(TIME_FACTOR * start_time), int(TIME_FACTOR * end_time)): 175 176 if ship_info['direction'] == 'in': 177 if ship_info['width'] > self.width_containers_in[time_step]: 178 excess_width_in = ship_info['width'] - \ 179 self.width_containers_in[time_step] 180 self.width_containers_in[time_step] += excess_width_in 181 self.width_containers[time_step] -= excess_width_in 182 183 if ship_info['draft'] > self.draft_containers_in[time_step]: 184 excess_draft_in = ship_info['draft'] - \ 185 self.draft_containers_in[time_step] 186 self.draft_containers_in[time_step] += excess_draft_in 187 self.draft_containers[time_step] -= excess_draft_in 188 189 elif ship_info['direction'] == 'out': 190 if ship_info['width'] > self.width_containers_out[time_step]: 191 excess_width_out = ship_info['width'] - \ 192 self.width_containers_out[time_step] 193 self.width_containers_out[time_step] += excess_width_out 194 self.width_containers[time_step] -= excess_width_out 195 196 if ship_info['draft'] > self.draft_containers_out[time_step]: 197 excess_draft_out = ship_info['draft'] - \ 198 self.draft_containers_out[time_step] 199 self.draft_containers_out[time_step] += excess_draft_out 200 self.draft_containers[time_step] -= excess_draft_out 201 202 # assert that the total width and draft in the section is equal to the capacity 203 assert abs(self.width_containers[time_step] + self.width_containers_in[time_step] + 204 self.width_containers_out[time_step] - self.width_capacity) <= 0.1 205 assert abs(self.draft_containers[time_step] + self.draft_containers_in[time_step] + 206 self.draft_containers_out[time_step] - self.draft_capacity) <= 0.1 207 # assert that the width and draft containers are non-negative 208 assert self.width_containers[time_step] >= 0 209 assert self.draft_containers[time_step] >= 0
Update the usage of the channel section based on the ship's information.
Arguments:
- run_id (int): The identifier for the current simulation run.
- ship_info (dict): Information about the ship, including its width, draft, and direction.
- start_time (int): The start time of the ship's passage through the section.
- end_time (int): The end time of the ship's passage through the section.
Returns:
None
211 def space_out(self, start_time, ship_info): 212 """ 213 Space out the channel section for the ship based on its direction and start time. 214 Args: 215 start_time (int): The start time of the ship's passage through the section. 216 ship_info (dict): Information about the ship, including its direction. 217 Returns: 218 None 219 """ 220 if ship_info['direction'] == 'in': 221 for time_step in range(int(TIME_FACTOR * start_time), int(TIME_FACTOR * (start_time + constants.CHANNEL_ENTRY_SPACING))): 222 assert self.gap_out_in[time_step] == 1 223 self.gap_out_in[time_step] = 0 224 else: 225 for time_step in range(int(TIME_FACTOR * start_time), int(TIME_FACTOR * (start_time + constants.CHANNEL_ENTRY_SPACING))): 226 assert self.gap_out_out[time_step] == 1 227 self.gap_out_out[time_step] = 0
Space out the channel section for the ship based on its direction and start time.
Arguments:
- start_time (int): The start time of the ship's passage through the section.
- ship_info (dict): Information about the ship, including its direction.
Returns:
None
230class Channel: 231 """ 232 Class for the entire channel. 233 234 This class manages the sections of the channel and handles ship navigation through it. It implements the necessary methods to check for restrictions, schedule ships, and log their movements. 235 236 Args: 237 ship_logs (list): List to log ship information. 238 env (Env): The simulation environment. 239 numSections (int): Number of sections in the channel. 240 simulation_time (int): Total simulation time for the channel. 241 safeTwoway (bool): Whether the channel is safe for two-way traffic. 242 channel_events (list): List to log channel events. 243 channel_logs (list): List to log channel information. 244 day_pilots (simpy.Container): Simpy container for day pilots. 245 night_pilots (simpy.Container): Simpy container for night pilots. 246 tugboats (simpy.Container): Simpy container for tugboats. 247 turnoffTime (dict): Dictionary with turnoff times and conditions. 248 channel_scheduer (Resource): Resource for scheduling channel usage. 249 seed (int): Random seed for reproducibility. 250""" 251 252 253 def __init__(self, ship_logs, env, numSections, simulation_time, safeTwoway, channel_events, channel_logs, day_pilots, night_pilots, tugboats, turnoffTime, channel_scheduer, seed): 254 self.seed = seed 255 self.env = env 256 self.ship_logs = ship_logs 257 self.numSections = numSections 258 self.safeTwoway = safeTwoway 259 self.channel_events = channel_events 260 self.channel_logs = channel_logs 261 self.simulation_time = simulation_time 262 self.day_pilots = day_pilots 263 self.night_pilots = night_pilots 264 self.tugboats = tugboats 265 self.turnoffTime = turnoffTime 266 self.channel_scheduer = channel_scheduer 267 268 self.sections = [ChannelSection(env, i, *CHANNEL_SECTION_DIMENSIONS[i], int(1.1*self.simulation_time), 269 self.safeTwoway) for i in tqdm(range(1, numSections + 1), desc="Creating channel")] 270 random.seed(seed) 271 272 def channel_closed(self, time): 273 """ 274 Check if the channel is closed at a given time step. 275 Args: 276 time (int): The time step to check for channel closure. 277 """ 278 if self.turnoffTime["switch"] == "channel_closed": 279 for start_end_tuples in self.turnoffTime["closed_between"]: 280 if start_end_tuples[0] <= time < start_end_tuples[1]: 281 return True 282 return False 283 284 def channel_closed_restriction(self, ship_info, lastSection, enter_time): 285 """ 286 Check if the ship has beam restriction in the channel in any section at a given time step. 287 Logic: 288 - If the ship is entering the channel, check each section from the start to the last section. 289 - If the ship is leaving the channel, check each section from the last section to the start. 290 - For each section, calculate the end time based on the ship's speed and length of the section. 291 - For each section, check if the channel is closed at the end time. 292 - If the channel is closed at the end time, return True (indicating the channel is closed for the ship). 293 - If no section is closed, return False (indicating the channel is open for the ship). 294 Args: 295 ship_info (dict): Information about the ship, including its direction. 296 lastSection (int): The last section of the channel to check. 297 enter_time (int): The time step when the ship enters the channel. 298 Returns: 299 bool: True if the channel is closed for the ship, False otherwise. 300 """ 301 if ship_info['direction'] == 'in': 302 start_time = enter_time 303 for i, section in enumerate(self.sections): 304 if i > lastSection: 305 break 306 speed = section.speed 307 end_time = start_time + section.length / speed 308 if self.channel_closed(end_time): 309 return True 310 start_time = end_time 311 return False 312 elif ship_info['direction'] == 'out': 313 start_time = enter_time 314 for i, section in enumerate(reversed(self.sections)): 315 section_number = len(self.sections) - i - 1 316 if section_number > lastSection: 317 continue 318 speed = section.speed 319 end_time = start_time + section.length / speed 320 if self.channel_closed(end_time): 321 return True 322 start_time = end_time 323 return False 324 325 def beam_restriction(self, ship_info, lastSection, enter_time): 326 """ 327 Check if the ship has beam restriction in the channel in any section at a given time step. 328 Logic: 329 - If the ship is entering the channel, check each section from the start to the last section. 330 - If the ship is leaving the channel, check each section from the last section to the start. 331 - For each section, calculate the end time based on the ship's speed and length of the section. 332 - For each section, check if the width of the ship exceeds the available width in the section. 333 - If the width of the ship exceeds the available width in any section, return True (indicating beam restriction). 334 - If no section has beam restriction, return False (indicating no beam restriction). 335 Args: 336 ship_info (dict): Information about the ship, including its width and direction. 337 lastSection (int): The last section of the channel to check. 338 enter_time (int): The time step when the ship enters the channel. 339 Returns: 340 bool: True if the ship has beam restriction, False otherwise. 341 """ 342 if ship_info['direction'] == 'in': 343 start_time = enter_time 344 for i, section in enumerate(self.sections): 345 if i > lastSection: 346 break 347 speed = section.speed 348 end_time = start_time + section.length / speed 349 if not section.can_accommodate_width(ship_info, start_time, end_time): 350 return True 351 start_time = end_time 352 return False 353 elif ship_info['direction'] == 'out': 354 start_time = enter_time 355 for i, section in enumerate(reversed(self.sections)): 356 section_number = len(self.sections) - i - 1 357 if section_number > lastSection: 358 continue 359 speed = section.speed 360 end_time = start_time + section.length / speed 361 if not section.can_accommodate_width(ship_info, start_time, end_time): 362 return True 363 start_time = end_time 364 return False 365 366 def draft_restriction(self, ship_info, lastSection, enter_time): 367 """ 368 Check if the ship has draft restriction in the channel in any section at a given time step. 369 Logic is similar to beam_restriction, but checks for draft instead of width. 370 Args: 371 ship_info (dict): Information about the ship, including its draft and direction. 372 lastSection (int): The last section of the channel to check. 373 enter_time (int): The time step when the ship enters the channel. 374 Returns: 375 bool: True if the ship has draft restriction, False otherwise. 376 """ 377 if ship_info['direction'] == 'in': 378 start_time = enter_time 379 for i, section in enumerate(self.sections): 380 if i > lastSection: 381 break 382 speed = section.speed 383 end_time = start_time + section.length / speed 384 if not section.can_accommodate_draft(ship_info, start_time, end_time): 385 return True 386 start_time = end_time 387 return False 388 elif ship_info['direction'] == 'out': 389 start_time = enter_time 390 for i, section in enumerate(reversed(self.sections)): 391 section_number = len(self.sections) - i - 1 392 if section_number > lastSection: 393 continue 394 speed = section.speed 395 end_time = start_time + section.length / speed 396 if not section.can_accommodate_draft(ship_info, start_time, end_time): 397 return True 398 start_time = end_time 399 return False 400 401 def minimal_spacing_not_avlbl(self, ship_info, lastSection, enter_time): 402 """ 403 Check if the minimal spacing for the ship is not available in the channel in any section at a given time step. 404 Logic is similar to beam_restriction, but checks for minimal spacing instead of width. 405 Args: 406 ship_info (dict): Information about the ship, including its direction. 407 lastSection (int): The last section of the channel to check. 408 enter_time (int): The time step when the ship enters the channel. 409 Returns: 410 bool: True if the minimal spacing is not available, False otherwise. 411 """ 412 if ship_info['direction'] == 'in': 413 start_time = enter_time 414 for i, section in enumerate(self.sections): 415 if i > lastSection: 416 break 417 speed = section.speed 418 end_time = start_time + section.length / speed 419 if not section.can_accomodate_gap_out(start_time, ship_info): 420 return True 421 start_time = end_time 422 return False 423 elif ship_info['direction'] == 'out': 424 start_time = enter_time 425 for i, section in enumerate(reversed(self.sections)): 426 section_number = len(self.sections) - i - 1 427 if section_number > lastSection: 428 continue 429 speed = section.speed 430 end_time = start_time + section.length / speed 431 if not section.can_accomodate_gap_out(start_time, ship_info): 432 return True 433 start_time = end_time 434 return False 435 436 def daylight_restriction(self, ship_info, lastSection, enter_time): 437 """ 438 Check if the ship has daylight restriction in the channel at a given time step. 439 Logic: 440 - Check the ship's type and dimensions (width and length). 441 - If the ship is a Container, Liquid, or DryBulk type, check if its width or length exceeds the specified limits. 442 - If the ship exceeds the limits, check if it is daytime at the time of entry. 443 - If it is not daytime, return True (indicating daylight restriction). 444 - If the ship does not exceed the limits or it is daytime, return False (indicating no daylight restriction). 445 Args: 446 ship_info (dict): Information about the ship, including its type, width, and length. 447 lastSection (int): The last section of the channel to check. 448 enter_time (int): The time step when the ship enters the channel. 449 Returns: 450 bool: True if the ship has daylight restriction, False otherwise. 451 """ 452 if ship_info['ship_type'] == 'Container': 453 if ship_info['width'] > 120 or ship_info['length'] > 1000: 454 # if not daytime return True 455 return not is_daytime(enter_time, START_DAYLIGHT_RESTRICTION_HR, STOP_DAYLIGHT_RESTRICTION_HR) 456 elif ship_info['ship_type'] == 'Liquid': 457 if ship_info['width'] > 120 or ship_info['length'] > 900: 458 return not is_daytime(enter_time, START_DAYLIGHT_RESTRICTION_HR, STOP_DAYLIGHT_RESTRICTION_HR) 459 elif ship_info['ship_type'] == 'DryBulk': 460 if ship_info['width'] > 120 or ship_info['length'] > 900: 461 return not is_daytime(enter_time, START_DAYLIGHT_RESTRICTION_HR, STOP_DAYLIGHT_RESTRICTION_HR) 462 return False 463 464 def scheduler(self, ship_info, lastSection, run_id, enter_time): 465 """ 466 Schedule the ship to navigate through the channel. This happens before the ship actually moves through the channel. 467 Logic: 468 - Check if the ship has any restrictions in the channel (beam, draft, daylight, minimal spacing). 469 - If there are any restrictions, log the ship's information and return. 470 - If there are no restrictions, update the ship's logs and schedule the ship to move through the channel. 471 - If the ship is entering the channel, update the usage of each section in the channel. 472 - If the ship is leaving the channel, update the usage of each section in reverse order. 473 - Log the ship's movement through the channel. 474 - Finally, yield the ship's movement through the channel. 475 Note this is different from the move_through_channel method, which is called when the ship actually moves through the channel. 476 The scheduler method is called as soon as the ship requests to enter the channel. 477 If the ship has any restrictions, it will not be scheduled to move through the channel. 478 Args: 479 ship_info (dict): Information about the ship, including its direction, width, draft, and speed. 480 lastSection (int): The last section of the channel to check. 481 run_id (int): The identifier for the current simulation run. 482 enter_time (int): The time step when the ship enters the channel. 483 Returns: 484 None 485 """ 486 start_time = enter_time 487 488 if ship_info['direction'] == 'in': 489 for i, section in enumerate(self.sections): 490 if i > lastSection: 491 break 492 speed = section.speed 493 end_time = start_time + section.length / speed 494 section.update_usage(run_id, ship_info, start_time, end_time) 495 self.sections[i].space_out(start_time, ship_info) 496 start_time = end_time 497 498 elif ship_info['direction'] == 'out': 499 for i, section in enumerate(reversed(self.sections)): 500 section_number = len(self.sections) - i - 1 501 if section_number > lastSection: 502 continue 503 speed = section.speed 504 end_time = start_time + section.length / speed 505 section.update_usage(run_id, ship_info, start_time, end_time) 506 self.sections[section_number].space_out(start_time, ship_info) 507 start_time = end_time 508 509 def move_through_channel(self, ship_info, lastSection, run_id): 510 """ 511 Simulate the movement of the ship through the channel. 512 Logic: 513 - If the ship is entering the channel, iterate through each section from the start to the last section. 514 - If the ship is leaving the channel, iterate through each section from the last section to the start. 515 - For each section, calculate the speed and end time based on the ship's speed and length of the section. 516 - Log the ship's movement through the section, including its ship_id, ship_type, width, draft, and direction. 517 - Yield the ship's movement through the section. 518 - Log the time spent in each section. 519 - Finally, update the ship's logs with the time spent in the channel. 520 - If the ship is entering the channel, log the time spent in each section going in. 521 - If the ship is leaving the channel, log the time spent in each section going out. 522 Note this is different from the scheduler method,which schedules before the ship actually moves through the channel. 523 The sheduler happens as soon as the ship requests to enter the channel. 524 The move_through_channel method is called when the ship actually moves through the channel (when there are no restrictions). 525 Args: 526 ship_info (dict): Information about the ship, including its ship_id, ship_type, width, draft, and direction. 527 lastSection (int): The last section of the channel to check. 528 run_id (int): The identifier for the current simulation run. 529 Returns: 530 None 531 """ 532 enter_time = self.env.now 533 start_time = enter_time 534 535 if ship_info['direction'] == 'in': 536 for i, section in enumerate(self.sections): 537 if i > lastSection: 538 break 539 speed = section.speed 540 end_time = start_time + section.length / speed 541 yield self.env.timeout(section.length / speed) 542 log_line(run_id, "ships_in_channel.txt", 543 f"Ship {ship_info['ship_id']} of type {ship_info['ship_type']} of width {ship_info['width']} and draft {ship_info['draft']} spent from {start_time} to {end_time} in section {i} going in") 544 start_time = end_time 545 546 elif ship_info['direction'] == 'out': 547 for i, section in enumerate(reversed(self.sections)): 548 section_number = len(self.sections) - i - 1 549 if section_number > lastSection: 550 continue 551 speed = section.speed 552 end_time = start_time + section.length / speed 553 yield self.env.timeout(section.length / speed) 554 log_line(run_id, "ships_in_channel.txt", 555 f"Ship {ship_info['ship_id']} of type {ship_info['ship_type']} of width {ship_info['width']} and draft {ship_info['draft']} spent from {start_time} to {end_time} in section {section_number} going out") 556 start_time = end_time 557 558 def check_restrictions(self, ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight): 559 """ 560 Check if the ship has any restrictions in the channel at a given time step. 561 Args: 562 ship_info (dict): Information about the ship, including its direction, width, draft, and ship type. 563 lastSection (int): The last section of the channel to check. 564 request_time (int): The time step when the ship requests to enter the channel. 565 wait_beam (int): The number of time steps the ship has waited for beam restrictions. 566 wait_draft (int): The number of time steps the ship has waited for draft restrictions. 567 wait_daylight (int): The number of time steps the ship has waited for daylight restrictions. 568 Returns: 569 tuple: A tuple containing: 570 - restrictions (bool): True if there are any restrictions, False otherwise. 571 - wait_beam (int): The updated number of time steps the ship has waited for beam restrictions. 572 - wait_draft (int): The updated number of time steps the ship has waited for draft restrictions. 573 - wait_daylight (int): The updated number of time steps the ship has waited for daylight restrictions. 574 """ 575 channel_closed_restriction = self.channel_closed_restriction( 576 ship_info, lastSection, request_time) 577 beam_restriction = self.beam_restriction( 578 ship_info, lastSection, request_time) 579 draft_restriction = self.draft_restriction( 580 ship_info, lastSection, request_time) 581 daylight_restriction = self.daylight_restriction( 582 ship_info, lastSection, request_time) 583 584 if beam_restriction: 585 wait_beam += 1 586 if draft_restriction: 587 wait_draft += 1 588 if daylight_restriction: 589 wait_daylight += 1 590 591 restrictions = beam_restriction or draft_restriction or daylight_restriction or channel_closed_restriction 592 593 return restrictions, wait_beam, wait_draft, wait_daylight 594 595 def channel_process(self, ship_info, day, lastSection, ship_id, ship_terminal, ship_type, run_id): 596 """ 597 Process for handling the ship's entry into the channel. 598 It handles the scheduling and movement of the ship through the channel, including checking for restrictions and logging the ship's information. 599 The process has the following steps: 600 1. Check if the ship is entering or leaving the channel based on its direction. 601 2. If the ship is entering the channel, then: 602 2.1. Get the pilots based on the time of day (day or night). 603 2.2. Check when the ship is allowed to enter being free of restrictions. 604 2.3 Schedule the ship to move through the channel. 605 2.4 Wait till the ship can move through the channel. 606 2.5 Move the ship through the channel. 607 2.6 Get the tugboats and steer the ship. 608 3. If the ship is leaving the channel, then: 609 3.1. Get the pilots based on the time of day (day or night). 610 3.2 Get the tugboats and steer the ship. 611 3.3.Check when the ship is allowed to leave being free of restrictions. 612 3.4 Schedule the ship to move through the channel. 613 3.5 Wait till the ship can move through the channel. 614 3.6 Move the ship through the channel. 615 Args: 616 ship_info (dict): Information about the ship, including its direction, pilots, tugboats, and other attributes. 617 day (bool): Whether it is daytime or nighttime. 618 lastSection (int): The last section of the channel to check. 619 ship_id (int): The identifier for the ship. 620 ship_terminal (str): The terminal where the ship is headed. 621 ship_type (str): The type of the ship (e.g., Container, Liquid, DryBulk). 622 run_id (int): The identifier for the current simulation run. 623 Returns: 624 None 625 """ 626 lastSection = lastSection - 1 627 request_time_initial = self.env.now 628 629 inbound_closed_tuples = constants.INBOUND_CLOSED 630 outbound_closed_tuples = constants.OUTBOUND_CLOSED 631 632 wait_queue = self.env.now - request_time_initial 633 634 # If ship travels in 635 if ship_info['direction'] == 'in': 636 ship_entering_channel_time = self.env.now 637 638 # get pilots 639 day = is_daytime(ship_entering_channel_time, 7, 19) 640 641 if day: 642 yield self.day_pilots.get(ship_info['pilots']) 643 else: 644 yield self.night_pilots.get(ship_info['pilots']) 645 time_to_get_pilot = self.env.now - ship_entering_channel_time 646 update_ship_logs(self.ship_logs, ship_type, ship_id, 647 ship_terminal, time_to_get_pilot_in=time_to_get_pilot) 648 649 # travel anchorage to mouth of the channel 650 time_to_common_channel = random.uniform( 651 TIME_COMMON_CHANNEL[0], TIME_COMMON_CHANNEL[1]) 652 yield self.env.timeout(time_to_common_channel) 653 time_to_common_channel = self.env.now - \ 654 (ship_entering_channel_time + time_to_get_pilot) 655 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 656 time_to_common_channel_in=time_to_common_channel) 657 658 # Schedule the ship to move through the channel 659 with self.channel_scheduer.request() as request: 660 yield request 661 662 request_time = self.env.now 663 wait_daylight = 0 664 wait_beam = 0 665 wait_draft = 0 666 # Note that multiple restrictions can be active at the same time 667 wait_total_restriction = 0 668 wait_total = 0 669 670 if not self.safeTwoway: 671 restrcictions, wait_beam, wait_draft, wait_daylight = self.check_restrictions( 672 ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight) 673 channel_closwed = self.channel_closed(request_time) 674 channel_closed_inbound = False 675 676 for start, end in inbound_closed_tuples: 677 if start <= request_time <= end: 678 channel_closed_inbound = True 679 680 no_spacing = self.minimal_spacing_not_avlbl( 681 ship_info, lastSection, request_time) 682 683 while restrcictions or channel_closwed or channel_closed_inbound or no_spacing: 684 if channel_closed_inbound: 685 print("Channel closed inbound at time", request_time) 686 687 wait_total += 1 688 wait_total_restriction += 1 689 request_time += 1 690 restrcictions, wait_beam, wait_draft, wait_daylight = self.check_restrictions( 691 ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight) 692 channel_closwed = self.channel_closed(request_time) 693 no_spacing = self.minimal_spacing_not_avlbl( 694 ship_info, lastSection, request_time) 695 channel_closed_inbound = False 696 for start, end in inbound_closed_tuples: 697 if start <= request_time <= end: 698 channel_closed_inbound = True 699 700 else: 701 channel_closwed = self.channel_closed(request_time) 702 703 channel_closed_inbound = False 704 for start, end in inbound_closed_tuples: 705 if start <= request_time <= end: 706 channel_closed_inbound = True 707 708 no_spacing = self.minimal_spacing_not_avlbl( 709 ship_info, lastSection, request_time) 710 711 while channel_closwed or channel_closed_inbound or no_spacing: 712 wait_total += 1 713 request_time += 1 714 channel_closwed = self.channel_closed(request_time) 715 no_spacing = self.minimal_spacing_not_avlbl( 716 ship_info, lastSection, request_time) 717 channel_closed_inbound = False 718 for start, end in inbound_closed_tuples: 719 if start <= request_time <= end: 720 channel_closed_inbound = True 721 722 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 723 time_for_restriction_in=f'B:{wait_beam}, D:{wait_draft}, DL:{wait_daylight}, Q:{wait_queue}, T:{wait_total}') 724 725 self.scheduler(ship_info, lastSection, run_id, 726 enter_time=request_time) 727 728 # travel through the channel 729 yield self.env.timeout(request_time - self.env.now) 730 time_entered_channel = self.env.now 731 yield self.env.process(self.move_through_channel(ship_info, lastSection, run_id)) 732 time_to_travel_channel = self.env.now - time_entered_channel 733 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 734 time_to_travel_channel_in=time_to_travel_channel) 735 736 # get tugs 737 time_tug_req = self.env.now 738 yield self.tugboats.get(ship_info['tugboats']) 739 time_to_get_tugs = self.env.now - time_tug_req 740 update_ship_logs(self.ship_logs, ship_type, ship_id, 741 ship_terminal, time_to_get_tugs_in=time_to_get_tugs) 742 743 # steer the ship 744 time_to_tug_steer = random.uniform( 745 TIME_FOR_TUG_STEER[0], TIME_FOR_TUG_STEER[1]) 746 yield self.env.timeout(time_to_tug_steer) 747 update_ship_logs(self.ship_logs, ship_type, ship_id, 748 ship_terminal, time_to_tug_steer_in=time_to_tug_steer) 749 750 # put the tugs back 751 yield self.tugboats.put(ship_info['tugboats']) 752 753 # Release pilots 754 if day: 755 yield self.day_pilots.put(ship_info['pilots']) 756 else: 757 yield self.night_pilots.put(ship_info['pilots']) 758 759 ship_info['direction'] = 'out' if ship_info['direction'] == 'in' else 'in' 760 761 # If ship travels out 762 # Dont enter this if ship is going in even though ship_info['direction'] is 'out' 763 elif ship_info['direction'] == 'out': 764 ship_entering_channel_time = self.env.now 765 766 # get pilots 767 day = is_daytime(ship_entering_channel_time, 7, 19) 768 if day: 769 yield self.day_pilots.get(ship_info['pilots']) 770 else: 771 yield self.night_pilots.get(ship_info['pilots']) 772 time_to_get_pilot = self.env.now - ship_entering_channel_time 773 update_ship_logs(self.ship_logs, ship_type, ship_id, 774 ship_terminal, time_to_get_pilot_out=time_to_get_pilot) 775 776 # get tugs 777 yield self.tugboats.get(ship_info['tugboats']) 778 time_to_get_tugs = self.env.now - \ 779 (ship_entering_channel_time + time_to_get_pilot) 780 update_ship_logs(self.ship_logs, ship_type, ship_id, 781 ship_terminal, time_to_get_tugs_out=time_to_get_tugs) 782 783 # make a uturn 784 time_to_uturn = random.uniform( 785 TIME_FOR_UTURN[0], TIME_FOR_UTURN[1]) 786 yield self.env.timeout(time_to_uturn) 787 time_for_uturn = self.env.now - \ 788 (ship_entering_channel_time + time_to_get_pilot + time_to_get_tugs) 789 update_ship_logs(self.ship_logs, ship_type, ship_id, 790 ship_terminal, time_for_uturn=time_for_uturn) 791 792 # steer the ship 793 time_to_tug_steer = random.uniform( 794 TIME_FOR_TUG_STEER[0], TIME_FOR_TUG_STEER[1]) 795 yield self.env.timeout(time_to_tug_steer) 796 update_ship_logs(self.ship_logs, ship_type, ship_id, 797 ship_terminal, time_to_tug_steer_out=time_to_tug_steer) 798 799 # put the tugs back 800 yield self.tugboats.put(ship_info['tugboats']) 801 802 # Schedule the ship to move through the channel 803 with self.channel_scheduer.request() as request: 804 yield request 805 request_time = self.env.now 806 wait_daylight = 0 807 wait_beam = 0 808 wait_draft = 0 809 # Note that multiple restrictions can be active at the same time 810 wait_total_restriction = 0 811 wait_queue = request_time - request_time_initial 812 wait_total = 0 813 if not self.safeTwoway: 814 restrcictions, wait_beam, wait_draft, wait_daylight = self.check_restrictions( 815 ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight) 816 channel_closwed = self.channel_closed(request_time) 817 channel_closed_outbound = False 818 for start, end in outbound_closed_tuples: 819 if start <= request_time <= end: 820 channel_closed_outbound = True 821 no_spacing = self.minimal_spacing_not_avlbl( 822 ship_info, lastSection, request_time) 823 while restrcictions or channel_closed_outbound or channel_closwed or no_spacing: 824 825 if channel_closed_outbound: 826 print("Channel closed outbound at time", request_time) 827 828 wait_total += 1 829 wait_total_restriction += 1 830 request_time += 1 831 restrcictions, wait_beam, wait_draft, wait_daylight = self.check_restrictions( 832 ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight) 833 channel_closwed = self.channel_closed(request_time) 834 no_spacing = self.minimal_spacing_not_avlbl( 835 ship_info, lastSection, request_time) 836 837 channel_closed_outbound = False 838 for start, end in outbound_closed_tuples: 839 if start <= request_time <= end: 840 channel_closed_outbound = True 841 842 else: 843 channel_closwed = self.channel_closed(request_time) 844 channel_closed_outbound = False 845 846 for start, end in outbound_closed_tuples: 847 if start <= request_time <= end: 848 channel_closed_outbound = True 849 no_spacing = self.minimal_spacing_not_avlbl( 850 ship_info, lastSection, request_time) 851 no_spacing = self.minimal_spacing_not_avlbl( 852 ship_info, lastSection, request_time) 853 while channel_closwed or channel_closed_outbound or no_spacing: 854 wait_total += 1 855 request_time += 1 856 channel_closwed = self.channel_closed(request_time) 857 no_spacing = self.minimal_spacing_not_avlbl( 858 ship_info, lastSection, request_time) 859 860 channel_closed_outbound = False 861 for start, end in outbound_closed_tuples: 862 if start <= request_time <= end: 863 channel_closed_outbound = True 864 865 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 866 time_for_restriction_out=f'B:{wait_beam}, D:{wait_draft}, DL:{wait_daylight}, Q:{wait_queue}, T:{wait_total}') 867 self.scheduler(ship_info, lastSection, run_id, 868 enter_time=request_time) 869 870 # travel through the channel 871 yield self.env.timeout(request_time - self.env.now) 872 time_entered_channel = self.env.now 873 yield self.env.process(self.move_through_channel(ship_info, lastSection, run_id)) 874 time_to_travel_channel = self.env.now - time_entered_channel 875 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 876 time_to_travel_channel_out=time_to_travel_channel) 877 878 # travel from mouth of the channel to anchorage 879 time_to_common_channel = random.uniform( 880 TIME_COMMON_CHANNEL[0], TIME_COMMON_CHANNEL[1]) 881 yield self.env.timeout(time_to_common_channel) 882 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 883 time_to_common_channel_out=time_to_common_channel) 884 885 # Release pilots 886 if day: 887 yield self.day_pilots.put(ship_info['pilots']) 888 else: 889 yield self.night_pilots.put(ship_info['pilots'])
Class for the entire channel.
This class manages the sections of the channel and handles ship navigation through it. It implements the necessary methods to check for restrictions, schedule ships, and log their movements.
Arguments:
- ship_logs (list): List to log ship information.
- env (Env): The simulation environment.
- numSections (int): Number of sections in the channel.
- simulation_time (int): Total simulation time for the channel.
- safeTwoway (bool): Whether the channel is safe for two-way traffic.
- channel_events (list): List to log channel events.
- channel_logs (list): List to log channel information.
- day_pilots (simpy.Container): Simpy container for day pilots.
- night_pilots (simpy.Container): Simpy container for night pilots.
- tugboats (simpy.Container): Simpy container for tugboats.
- turnoffTime (dict): Dictionary with turnoff times and conditions.
- channel_scheduer (Resource): Resource for scheduling channel usage.
- seed (int): Random seed for reproducibility.
272 def channel_closed(self, time): 273 """ 274 Check if the channel is closed at a given time step. 275 Args: 276 time (int): The time step to check for channel closure. 277 """ 278 if self.turnoffTime["switch"] == "channel_closed": 279 for start_end_tuples in self.turnoffTime["closed_between"]: 280 if start_end_tuples[0] <= time < start_end_tuples[1]: 281 return True 282 return False
Check if the channel is closed at a given time step.
Arguments:
- time (int): The time step to check for channel closure.
284 def channel_closed_restriction(self, ship_info, lastSection, enter_time): 285 """ 286 Check if the ship has beam restriction in the channel in any section at a given time step. 287 Logic: 288 - If the ship is entering the channel, check each section from the start to the last section. 289 - If the ship is leaving the channel, check each section from the last section to the start. 290 - For each section, calculate the end time based on the ship's speed and length of the section. 291 - For each section, check if the channel is closed at the end time. 292 - If the channel is closed at the end time, return True (indicating the channel is closed for the ship). 293 - If no section is closed, return False (indicating the channel is open for the ship). 294 Args: 295 ship_info (dict): Information about the ship, including its direction. 296 lastSection (int): The last section of the channel to check. 297 enter_time (int): The time step when the ship enters the channel. 298 Returns: 299 bool: True if the channel is closed for the ship, False otherwise. 300 """ 301 if ship_info['direction'] == 'in': 302 start_time = enter_time 303 for i, section in enumerate(self.sections): 304 if i > lastSection: 305 break 306 speed = section.speed 307 end_time = start_time + section.length / speed 308 if self.channel_closed(end_time): 309 return True 310 start_time = end_time 311 return False 312 elif ship_info['direction'] == 'out': 313 start_time = enter_time 314 for i, section in enumerate(reversed(self.sections)): 315 section_number = len(self.sections) - i - 1 316 if section_number > lastSection: 317 continue 318 speed = section.speed 319 end_time = start_time + section.length / speed 320 if self.channel_closed(end_time): 321 return True 322 start_time = end_time 323 return False
Check if the ship has beam restriction in the channel in any section at a given time step. Logic:
- If the ship is entering the channel, check each section from the start to the last section.
- If the ship is leaving the channel, check each section from the last section to the start.
- For each section, calculate the end time based on the ship's speed and length of the section.
- For each section, check if the channel is closed at the end time.
- If the channel is closed at the end time, return True (indicating the channel is closed for the ship).
- If no section is closed, return False (indicating the channel is open for the ship).
Arguments:
- ship_info (dict): Information about the ship, including its direction.
- lastSection (int): The last section of the channel to check.
- enter_time (int): The time step when the ship enters the channel.
Returns:
bool: True if the channel is closed for the ship, False otherwise.
325 def beam_restriction(self, ship_info, lastSection, enter_time): 326 """ 327 Check if the ship has beam restriction in the channel in any section at a given time step. 328 Logic: 329 - If the ship is entering the channel, check each section from the start to the last section. 330 - If the ship is leaving the channel, check each section from the last section to the start. 331 - For each section, calculate the end time based on the ship's speed and length of the section. 332 - For each section, check if the width of the ship exceeds the available width in the section. 333 - If the width of the ship exceeds the available width in any section, return True (indicating beam restriction). 334 - If no section has beam restriction, return False (indicating no beam restriction). 335 Args: 336 ship_info (dict): Information about the ship, including its width and direction. 337 lastSection (int): The last section of the channel to check. 338 enter_time (int): The time step when the ship enters the channel. 339 Returns: 340 bool: True if the ship has beam restriction, False otherwise. 341 """ 342 if ship_info['direction'] == 'in': 343 start_time = enter_time 344 for i, section in enumerate(self.sections): 345 if i > lastSection: 346 break 347 speed = section.speed 348 end_time = start_time + section.length / speed 349 if not section.can_accommodate_width(ship_info, start_time, end_time): 350 return True 351 start_time = end_time 352 return False 353 elif ship_info['direction'] == 'out': 354 start_time = enter_time 355 for i, section in enumerate(reversed(self.sections)): 356 section_number = len(self.sections) - i - 1 357 if section_number > lastSection: 358 continue 359 speed = section.speed 360 end_time = start_time + section.length / speed 361 if not section.can_accommodate_width(ship_info, start_time, end_time): 362 return True 363 start_time = end_time 364 return False
Check if the ship has beam restriction in the channel in any section at a given time step. Logic:
- If the ship is entering the channel, check each section from the start to the last section.
- If the ship is leaving the channel, check each section from the last section to the start.
- For each section, calculate the end time based on the ship's speed and length of the section.
- For each section, check if the width of the ship exceeds the available width in the section.
- If the width of the ship exceeds the available width in any section, return True (indicating beam restriction).
- If no section has beam restriction, return False (indicating no beam restriction).
Arguments:
- ship_info (dict): Information about the ship, including its width and direction.
- lastSection (int): The last section of the channel to check.
- enter_time (int): The time step when the ship enters the channel.
Returns:
bool: True if the ship has beam restriction, False otherwise.
366 def draft_restriction(self, ship_info, lastSection, enter_time): 367 """ 368 Check if the ship has draft restriction in the channel in any section at a given time step. 369 Logic is similar to beam_restriction, but checks for draft instead of width. 370 Args: 371 ship_info (dict): Information about the ship, including its draft and direction. 372 lastSection (int): The last section of the channel to check. 373 enter_time (int): The time step when the ship enters the channel. 374 Returns: 375 bool: True if the ship has draft restriction, False otherwise. 376 """ 377 if ship_info['direction'] == 'in': 378 start_time = enter_time 379 for i, section in enumerate(self.sections): 380 if i > lastSection: 381 break 382 speed = section.speed 383 end_time = start_time + section.length / speed 384 if not section.can_accommodate_draft(ship_info, start_time, end_time): 385 return True 386 start_time = end_time 387 return False 388 elif ship_info['direction'] == 'out': 389 start_time = enter_time 390 for i, section in enumerate(reversed(self.sections)): 391 section_number = len(self.sections) - i - 1 392 if section_number > lastSection: 393 continue 394 speed = section.speed 395 end_time = start_time + section.length / speed 396 if not section.can_accommodate_draft(ship_info, start_time, end_time): 397 return True 398 start_time = end_time 399 return False
Check if the ship has draft restriction in the channel in any section at a given time step. Logic is similar to beam_restriction, but checks for draft instead of width.
Arguments:
- ship_info (dict): Information about the ship, including its draft and direction.
- lastSection (int): The last section of the channel to check.
- enter_time (int): The time step when the ship enters the channel.
Returns:
bool: True if the ship has draft restriction, False otherwise.
401 def minimal_spacing_not_avlbl(self, ship_info, lastSection, enter_time): 402 """ 403 Check if the minimal spacing for the ship is not available in the channel in any section at a given time step. 404 Logic is similar to beam_restriction, but checks for minimal spacing instead of width. 405 Args: 406 ship_info (dict): Information about the ship, including its direction. 407 lastSection (int): The last section of the channel to check. 408 enter_time (int): The time step when the ship enters the channel. 409 Returns: 410 bool: True if the minimal spacing is not available, False otherwise. 411 """ 412 if ship_info['direction'] == 'in': 413 start_time = enter_time 414 for i, section in enumerate(self.sections): 415 if i > lastSection: 416 break 417 speed = section.speed 418 end_time = start_time + section.length / speed 419 if not section.can_accomodate_gap_out(start_time, ship_info): 420 return True 421 start_time = end_time 422 return False 423 elif ship_info['direction'] == 'out': 424 start_time = enter_time 425 for i, section in enumerate(reversed(self.sections)): 426 section_number = len(self.sections) - i - 1 427 if section_number > lastSection: 428 continue 429 speed = section.speed 430 end_time = start_time + section.length / speed 431 if not section.can_accomodate_gap_out(start_time, ship_info): 432 return True 433 start_time = end_time 434 return False
Check if the minimal spacing for the ship is not available in the channel in any section at a given time step. Logic is similar to beam_restriction, but checks for minimal spacing instead of width.
Arguments:
- ship_info (dict): Information about the ship, including its direction.
- lastSection (int): The last section of the channel to check.
- enter_time (int): The time step when the ship enters the channel.
Returns:
bool: True if the minimal spacing is not available, False otherwise.
436 def daylight_restriction(self, ship_info, lastSection, enter_time): 437 """ 438 Check if the ship has daylight restriction in the channel at a given time step. 439 Logic: 440 - Check the ship's type and dimensions (width and length). 441 - If the ship is a Container, Liquid, or DryBulk type, check if its width or length exceeds the specified limits. 442 - If the ship exceeds the limits, check if it is daytime at the time of entry. 443 - If it is not daytime, return True (indicating daylight restriction). 444 - If the ship does not exceed the limits or it is daytime, return False (indicating no daylight restriction). 445 Args: 446 ship_info (dict): Information about the ship, including its type, width, and length. 447 lastSection (int): The last section of the channel to check. 448 enter_time (int): The time step when the ship enters the channel. 449 Returns: 450 bool: True if the ship has daylight restriction, False otherwise. 451 """ 452 if ship_info['ship_type'] == 'Container': 453 if ship_info['width'] > 120 or ship_info['length'] > 1000: 454 # if not daytime return True 455 return not is_daytime(enter_time, START_DAYLIGHT_RESTRICTION_HR, STOP_DAYLIGHT_RESTRICTION_HR) 456 elif ship_info['ship_type'] == 'Liquid': 457 if ship_info['width'] > 120 or ship_info['length'] > 900: 458 return not is_daytime(enter_time, START_DAYLIGHT_RESTRICTION_HR, STOP_DAYLIGHT_RESTRICTION_HR) 459 elif ship_info['ship_type'] == 'DryBulk': 460 if ship_info['width'] > 120 or ship_info['length'] > 900: 461 return not is_daytime(enter_time, START_DAYLIGHT_RESTRICTION_HR, STOP_DAYLIGHT_RESTRICTION_HR) 462 return False
Check if the ship has daylight restriction in the channel at a given time step. Logic:
- Check the ship's type and dimensions (width and length).
- If the ship is a Container, Liquid, or DryBulk type, check if its width or length exceeds the specified limits.
- If the ship exceeds the limits, check if it is daytime at the time of entry.
- If it is not daytime, return True (indicating daylight restriction).
- If the ship does not exceed the limits or it is daytime, return False (indicating no daylight restriction).
Arguments:
- ship_info (dict): Information about the ship, including its type, width, and length.
- lastSection (int): The last section of the channel to check.
- enter_time (int): The time step when the ship enters the channel.
Returns:
bool: True if the ship has daylight restriction, False otherwise.
464 def scheduler(self, ship_info, lastSection, run_id, enter_time): 465 """ 466 Schedule the ship to navigate through the channel. This happens before the ship actually moves through the channel. 467 Logic: 468 - Check if the ship has any restrictions in the channel (beam, draft, daylight, minimal spacing). 469 - If there are any restrictions, log the ship's information and return. 470 - If there are no restrictions, update the ship's logs and schedule the ship to move through the channel. 471 - If the ship is entering the channel, update the usage of each section in the channel. 472 - If the ship is leaving the channel, update the usage of each section in reverse order. 473 - Log the ship's movement through the channel. 474 - Finally, yield the ship's movement through the channel. 475 Note this is different from the move_through_channel method, which is called when the ship actually moves through the channel. 476 The scheduler method is called as soon as the ship requests to enter the channel. 477 If the ship has any restrictions, it will not be scheduled to move through the channel. 478 Args: 479 ship_info (dict): Information about the ship, including its direction, width, draft, and speed. 480 lastSection (int): The last section of the channel to check. 481 run_id (int): The identifier for the current simulation run. 482 enter_time (int): The time step when the ship enters the channel. 483 Returns: 484 None 485 """ 486 start_time = enter_time 487 488 if ship_info['direction'] == 'in': 489 for i, section in enumerate(self.sections): 490 if i > lastSection: 491 break 492 speed = section.speed 493 end_time = start_time + section.length / speed 494 section.update_usage(run_id, ship_info, start_time, end_time) 495 self.sections[i].space_out(start_time, ship_info) 496 start_time = end_time 497 498 elif ship_info['direction'] == 'out': 499 for i, section in enumerate(reversed(self.sections)): 500 section_number = len(self.sections) - i - 1 501 if section_number > lastSection: 502 continue 503 speed = section.speed 504 end_time = start_time + section.length / speed 505 section.update_usage(run_id, ship_info, start_time, end_time) 506 self.sections[section_number].space_out(start_time, ship_info) 507 start_time = end_time
Schedule the ship to navigate through the channel. This happens before the ship actually moves through the channel. Logic:
- Check if the ship has any restrictions in the channel (beam, draft, daylight, minimal spacing).
- If there are any restrictions, log the ship's information and return.
- If there are no restrictions, update the ship's logs and schedule the ship to move through the channel.
- If the ship is entering the channel, update the usage of each section in the channel.
- If the ship is leaving the channel, update the usage of each section in reverse order.
- Log the ship's movement through the channel.
- Finally, yield the ship's movement through the channel. Note this is different from the move_through_channel method, which is called when the ship actually moves through the channel. The scheduler method is called as soon as the ship requests to enter the channel. If the ship has any restrictions, it will not be scheduled to move through the channel.
Arguments:
- ship_info (dict): Information about the ship, including its direction, width, draft, and speed.
- lastSection (int): The last section of the channel to check.
- run_id (int): The identifier for the current simulation run.
- enter_time (int): The time step when the ship enters the channel.
Returns:
None
509 def move_through_channel(self, ship_info, lastSection, run_id): 510 """ 511 Simulate the movement of the ship through the channel. 512 Logic: 513 - If the ship is entering the channel, iterate through each section from the start to the last section. 514 - If the ship is leaving the channel, iterate through each section from the last section to the start. 515 - For each section, calculate the speed and end time based on the ship's speed and length of the section. 516 - Log the ship's movement through the section, including its ship_id, ship_type, width, draft, and direction. 517 - Yield the ship's movement through the section. 518 - Log the time spent in each section. 519 - Finally, update the ship's logs with the time spent in the channel. 520 - If the ship is entering the channel, log the time spent in each section going in. 521 - If the ship is leaving the channel, log the time spent in each section going out. 522 Note this is different from the scheduler method,which schedules before the ship actually moves through the channel. 523 The sheduler happens as soon as the ship requests to enter the channel. 524 The move_through_channel method is called when the ship actually moves through the channel (when there are no restrictions). 525 Args: 526 ship_info (dict): Information about the ship, including its ship_id, ship_type, width, draft, and direction. 527 lastSection (int): The last section of the channel to check. 528 run_id (int): The identifier for the current simulation run. 529 Returns: 530 None 531 """ 532 enter_time = self.env.now 533 start_time = enter_time 534 535 if ship_info['direction'] == 'in': 536 for i, section in enumerate(self.sections): 537 if i > lastSection: 538 break 539 speed = section.speed 540 end_time = start_time + section.length / speed 541 yield self.env.timeout(section.length / speed) 542 log_line(run_id, "ships_in_channel.txt", 543 f"Ship {ship_info['ship_id']} of type {ship_info['ship_type']} of width {ship_info['width']} and draft {ship_info['draft']} spent from {start_time} to {end_time} in section {i} going in") 544 start_time = end_time 545 546 elif ship_info['direction'] == 'out': 547 for i, section in enumerate(reversed(self.sections)): 548 section_number = len(self.sections) - i - 1 549 if section_number > lastSection: 550 continue 551 speed = section.speed 552 end_time = start_time + section.length / speed 553 yield self.env.timeout(section.length / speed) 554 log_line(run_id, "ships_in_channel.txt", 555 f"Ship {ship_info['ship_id']} of type {ship_info['ship_type']} of width {ship_info['width']} and draft {ship_info['draft']} spent from {start_time} to {end_time} in section {section_number} going out") 556 start_time = end_time
Simulate the movement of the ship through the channel. Logic:
- If the ship is entering the channel, iterate through each section from the start to the last section.
- If the ship is leaving the channel, iterate through each section from the last section to the start.
- For each section, calculate the speed and end time based on the ship's speed and length of the section.
- Log the ship's movement through the section, including its ship_id, ship_type, width, draft, and direction.
- Yield the ship's movement through the section.
- Log the time spent in each section.
- Finally, update the ship's logs with the time spent in the channel.
- If the ship is entering the channel, log the time spent in each section going in.
- If the ship is leaving the channel, log the time spent in each section going out. Note this is different from the scheduler method,which schedules before the ship actually moves through the channel. The sheduler happens as soon as the ship requests to enter the channel. The move_through_channel method is called when the ship actually moves through the channel (when there are no restrictions).
Arguments:
- ship_info (dict): Information about the ship, including its ship_id, ship_type, width, draft, and direction.
- lastSection (int): The last section of the channel to check.
- run_id (int): The identifier for the current simulation run.
Returns:
None
558 def check_restrictions(self, ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight): 559 """ 560 Check if the ship has any restrictions in the channel at a given time step. 561 Args: 562 ship_info (dict): Information about the ship, including its direction, width, draft, and ship type. 563 lastSection (int): The last section of the channel to check. 564 request_time (int): The time step when the ship requests to enter the channel. 565 wait_beam (int): The number of time steps the ship has waited for beam restrictions. 566 wait_draft (int): The number of time steps the ship has waited for draft restrictions. 567 wait_daylight (int): The number of time steps the ship has waited for daylight restrictions. 568 Returns: 569 tuple: A tuple containing: 570 - restrictions (bool): True if there are any restrictions, False otherwise. 571 - wait_beam (int): The updated number of time steps the ship has waited for beam restrictions. 572 - wait_draft (int): The updated number of time steps the ship has waited for draft restrictions. 573 - wait_daylight (int): The updated number of time steps the ship has waited for daylight restrictions. 574 """ 575 channel_closed_restriction = self.channel_closed_restriction( 576 ship_info, lastSection, request_time) 577 beam_restriction = self.beam_restriction( 578 ship_info, lastSection, request_time) 579 draft_restriction = self.draft_restriction( 580 ship_info, lastSection, request_time) 581 daylight_restriction = self.daylight_restriction( 582 ship_info, lastSection, request_time) 583 584 if beam_restriction: 585 wait_beam += 1 586 if draft_restriction: 587 wait_draft += 1 588 if daylight_restriction: 589 wait_daylight += 1 590 591 restrictions = beam_restriction or draft_restriction or daylight_restriction or channel_closed_restriction 592 593 return restrictions, wait_beam, wait_draft, wait_daylight
Check if the ship has any restrictions in the channel at a given time step.
Arguments:
- ship_info (dict): Information about the ship, including its direction, width, draft, and ship type.
- lastSection (int): The last section of the channel to check.
- request_time (int): The time step when the ship requests to enter the channel.
- wait_beam (int): The number of time steps the ship has waited for beam restrictions.
- wait_draft (int): The number of time steps the ship has waited for draft restrictions.
- wait_daylight (int): The number of time steps the ship has waited for daylight restrictions.
Returns:
tuple: A tuple containing: - restrictions (bool): True if there are any restrictions, False otherwise. - wait_beam (int): The updated number of time steps the ship has waited for beam restrictions. - wait_draft (int): The updated number of time steps the ship has waited for draft restrictions. - wait_daylight (int): The updated number of time steps the ship has waited for daylight restrictions.
595 def channel_process(self, ship_info, day, lastSection, ship_id, ship_terminal, ship_type, run_id): 596 """ 597 Process for handling the ship's entry into the channel. 598 It handles the scheduling and movement of the ship through the channel, including checking for restrictions and logging the ship's information. 599 The process has the following steps: 600 1. Check if the ship is entering or leaving the channel based on its direction. 601 2. If the ship is entering the channel, then: 602 2.1. Get the pilots based on the time of day (day or night). 603 2.2. Check when the ship is allowed to enter being free of restrictions. 604 2.3 Schedule the ship to move through the channel. 605 2.4 Wait till the ship can move through the channel. 606 2.5 Move the ship through the channel. 607 2.6 Get the tugboats and steer the ship. 608 3. If the ship is leaving the channel, then: 609 3.1. Get the pilots based on the time of day (day or night). 610 3.2 Get the tugboats and steer the ship. 611 3.3.Check when the ship is allowed to leave being free of restrictions. 612 3.4 Schedule the ship to move through the channel. 613 3.5 Wait till the ship can move through the channel. 614 3.6 Move the ship through the channel. 615 Args: 616 ship_info (dict): Information about the ship, including its direction, pilots, tugboats, and other attributes. 617 day (bool): Whether it is daytime or nighttime. 618 lastSection (int): The last section of the channel to check. 619 ship_id (int): The identifier for the ship. 620 ship_terminal (str): The terminal where the ship is headed. 621 ship_type (str): The type of the ship (e.g., Container, Liquid, DryBulk). 622 run_id (int): The identifier for the current simulation run. 623 Returns: 624 None 625 """ 626 lastSection = lastSection - 1 627 request_time_initial = self.env.now 628 629 inbound_closed_tuples = constants.INBOUND_CLOSED 630 outbound_closed_tuples = constants.OUTBOUND_CLOSED 631 632 wait_queue = self.env.now - request_time_initial 633 634 # If ship travels in 635 if ship_info['direction'] == 'in': 636 ship_entering_channel_time = self.env.now 637 638 # get pilots 639 day = is_daytime(ship_entering_channel_time, 7, 19) 640 641 if day: 642 yield self.day_pilots.get(ship_info['pilots']) 643 else: 644 yield self.night_pilots.get(ship_info['pilots']) 645 time_to_get_pilot = self.env.now - ship_entering_channel_time 646 update_ship_logs(self.ship_logs, ship_type, ship_id, 647 ship_terminal, time_to_get_pilot_in=time_to_get_pilot) 648 649 # travel anchorage to mouth of the channel 650 time_to_common_channel = random.uniform( 651 TIME_COMMON_CHANNEL[0], TIME_COMMON_CHANNEL[1]) 652 yield self.env.timeout(time_to_common_channel) 653 time_to_common_channel = self.env.now - \ 654 (ship_entering_channel_time + time_to_get_pilot) 655 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 656 time_to_common_channel_in=time_to_common_channel) 657 658 # Schedule the ship to move through the channel 659 with self.channel_scheduer.request() as request: 660 yield request 661 662 request_time = self.env.now 663 wait_daylight = 0 664 wait_beam = 0 665 wait_draft = 0 666 # Note that multiple restrictions can be active at the same time 667 wait_total_restriction = 0 668 wait_total = 0 669 670 if not self.safeTwoway: 671 restrcictions, wait_beam, wait_draft, wait_daylight = self.check_restrictions( 672 ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight) 673 channel_closwed = self.channel_closed(request_time) 674 channel_closed_inbound = False 675 676 for start, end in inbound_closed_tuples: 677 if start <= request_time <= end: 678 channel_closed_inbound = True 679 680 no_spacing = self.minimal_spacing_not_avlbl( 681 ship_info, lastSection, request_time) 682 683 while restrcictions or channel_closwed or channel_closed_inbound or no_spacing: 684 if channel_closed_inbound: 685 print("Channel closed inbound at time", request_time) 686 687 wait_total += 1 688 wait_total_restriction += 1 689 request_time += 1 690 restrcictions, wait_beam, wait_draft, wait_daylight = self.check_restrictions( 691 ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight) 692 channel_closwed = self.channel_closed(request_time) 693 no_spacing = self.minimal_spacing_not_avlbl( 694 ship_info, lastSection, request_time) 695 channel_closed_inbound = False 696 for start, end in inbound_closed_tuples: 697 if start <= request_time <= end: 698 channel_closed_inbound = True 699 700 else: 701 channel_closwed = self.channel_closed(request_time) 702 703 channel_closed_inbound = False 704 for start, end in inbound_closed_tuples: 705 if start <= request_time <= end: 706 channel_closed_inbound = True 707 708 no_spacing = self.minimal_spacing_not_avlbl( 709 ship_info, lastSection, request_time) 710 711 while channel_closwed or channel_closed_inbound or no_spacing: 712 wait_total += 1 713 request_time += 1 714 channel_closwed = self.channel_closed(request_time) 715 no_spacing = self.minimal_spacing_not_avlbl( 716 ship_info, lastSection, request_time) 717 channel_closed_inbound = False 718 for start, end in inbound_closed_tuples: 719 if start <= request_time <= end: 720 channel_closed_inbound = True 721 722 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 723 time_for_restriction_in=f'B:{wait_beam}, D:{wait_draft}, DL:{wait_daylight}, Q:{wait_queue}, T:{wait_total}') 724 725 self.scheduler(ship_info, lastSection, run_id, 726 enter_time=request_time) 727 728 # travel through the channel 729 yield self.env.timeout(request_time - self.env.now) 730 time_entered_channel = self.env.now 731 yield self.env.process(self.move_through_channel(ship_info, lastSection, run_id)) 732 time_to_travel_channel = self.env.now - time_entered_channel 733 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 734 time_to_travel_channel_in=time_to_travel_channel) 735 736 # get tugs 737 time_tug_req = self.env.now 738 yield self.tugboats.get(ship_info['tugboats']) 739 time_to_get_tugs = self.env.now - time_tug_req 740 update_ship_logs(self.ship_logs, ship_type, ship_id, 741 ship_terminal, time_to_get_tugs_in=time_to_get_tugs) 742 743 # steer the ship 744 time_to_tug_steer = random.uniform( 745 TIME_FOR_TUG_STEER[0], TIME_FOR_TUG_STEER[1]) 746 yield self.env.timeout(time_to_tug_steer) 747 update_ship_logs(self.ship_logs, ship_type, ship_id, 748 ship_terminal, time_to_tug_steer_in=time_to_tug_steer) 749 750 # put the tugs back 751 yield self.tugboats.put(ship_info['tugboats']) 752 753 # Release pilots 754 if day: 755 yield self.day_pilots.put(ship_info['pilots']) 756 else: 757 yield self.night_pilots.put(ship_info['pilots']) 758 759 ship_info['direction'] = 'out' if ship_info['direction'] == 'in' else 'in' 760 761 # If ship travels out 762 # Dont enter this if ship is going in even though ship_info['direction'] is 'out' 763 elif ship_info['direction'] == 'out': 764 ship_entering_channel_time = self.env.now 765 766 # get pilots 767 day = is_daytime(ship_entering_channel_time, 7, 19) 768 if day: 769 yield self.day_pilots.get(ship_info['pilots']) 770 else: 771 yield self.night_pilots.get(ship_info['pilots']) 772 time_to_get_pilot = self.env.now - ship_entering_channel_time 773 update_ship_logs(self.ship_logs, ship_type, ship_id, 774 ship_terminal, time_to_get_pilot_out=time_to_get_pilot) 775 776 # get tugs 777 yield self.tugboats.get(ship_info['tugboats']) 778 time_to_get_tugs = self.env.now - \ 779 (ship_entering_channel_time + time_to_get_pilot) 780 update_ship_logs(self.ship_logs, ship_type, ship_id, 781 ship_terminal, time_to_get_tugs_out=time_to_get_tugs) 782 783 # make a uturn 784 time_to_uturn = random.uniform( 785 TIME_FOR_UTURN[0], TIME_FOR_UTURN[1]) 786 yield self.env.timeout(time_to_uturn) 787 time_for_uturn = self.env.now - \ 788 (ship_entering_channel_time + time_to_get_pilot + time_to_get_tugs) 789 update_ship_logs(self.ship_logs, ship_type, ship_id, 790 ship_terminal, time_for_uturn=time_for_uturn) 791 792 # steer the ship 793 time_to_tug_steer = random.uniform( 794 TIME_FOR_TUG_STEER[0], TIME_FOR_TUG_STEER[1]) 795 yield self.env.timeout(time_to_tug_steer) 796 update_ship_logs(self.ship_logs, ship_type, ship_id, 797 ship_terminal, time_to_tug_steer_out=time_to_tug_steer) 798 799 # put the tugs back 800 yield self.tugboats.put(ship_info['tugboats']) 801 802 # Schedule the ship to move through the channel 803 with self.channel_scheduer.request() as request: 804 yield request 805 request_time = self.env.now 806 wait_daylight = 0 807 wait_beam = 0 808 wait_draft = 0 809 # Note that multiple restrictions can be active at the same time 810 wait_total_restriction = 0 811 wait_queue = request_time - request_time_initial 812 wait_total = 0 813 if not self.safeTwoway: 814 restrcictions, wait_beam, wait_draft, wait_daylight = self.check_restrictions( 815 ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight) 816 channel_closwed = self.channel_closed(request_time) 817 channel_closed_outbound = False 818 for start, end in outbound_closed_tuples: 819 if start <= request_time <= end: 820 channel_closed_outbound = True 821 no_spacing = self.minimal_spacing_not_avlbl( 822 ship_info, lastSection, request_time) 823 while restrcictions or channel_closed_outbound or channel_closwed or no_spacing: 824 825 if channel_closed_outbound: 826 print("Channel closed outbound at time", request_time) 827 828 wait_total += 1 829 wait_total_restriction += 1 830 request_time += 1 831 restrcictions, wait_beam, wait_draft, wait_daylight = self.check_restrictions( 832 ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight) 833 channel_closwed = self.channel_closed(request_time) 834 no_spacing = self.minimal_spacing_not_avlbl( 835 ship_info, lastSection, request_time) 836 837 channel_closed_outbound = False 838 for start, end in outbound_closed_tuples: 839 if start <= request_time <= end: 840 channel_closed_outbound = True 841 842 else: 843 channel_closwed = self.channel_closed(request_time) 844 channel_closed_outbound = False 845 846 for start, end in outbound_closed_tuples: 847 if start <= request_time <= end: 848 channel_closed_outbound = True 849 no_spacing = self.minimal_spacing_not_avlbl( 850 ship_info, lastSection, request_time) 851 no_spacing = self.minimal_spacing_not_avlbl( 852 ship_info, lastSection, request_time) 853 while channel_closwed or channel_closed_outbound or no_spacing: 854 wait_total += 1 855 request_time += 1 856 channel_closwed = self.channel_closed(request_time) 857 no_spacing = self.minimal_spacing_not_avlbl( 858 ship_info, lastSection, request_time) 859 860 channel_closed_outbound = False 861 for start, end in outbound_closed_tuples: 862 if start <= request_time <= end: 863 channel_closed_outbound = True 864 865 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 866 time_for_restriction_out=f'B:{wait_beam}, D:{wait_draft}, DL:{wait_daylight}, Q:{wait_queue}, T:{wait_total}') 867 self.scheduler(ship_info, lastSection, run_id, 868 enter_time=request_time) 869 870 # travel through the channel 871 yield self.env.timeout(request_time - self.env.now) 872 time_entered_channel = self.env.now 873 yield self.env.process(self.move_through_channel(ship_info, lastSection, run_id)) 874 time_to_travel_channel = self.env.now - time_entered_channel 875 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 876 time_to_travel_channel_out=time_to_travel_channel) 877 878 # travel from mouth of the channel to anchorage 879 time_to_common_channel = random.uniform( 880 TIME_COMMON_CHANNEL[0], TIME_COMMON_CHANNEL[1]) 881 yield self.env.timeout(time_to_common_channel) 882 update_ship_logs(self.ship_logs, ship_type, ship_id, ship_terminal, 883 time_to_common_channel_out=time_to_common_channel) 884 885 # Release pilots 886 if day: 887 yield self.day_pilots.put(ship_info['pilots']) 888 else: 889 yield self.night_pilots.put(ship_info['pilots'])
Process for handling the ship's entry into the channel. It handles the scheduling and movement of the ship through the channel, including checking for restrictions and logging the ship's information. The process has the following steps:
- Check if the ship is entering or leaving the channel based on its direction.
- If the ship is entering the channel, then: 2.1. Get the pilots based on the time of day (day or night). 2.2. Check when the ship is allowed to enter being free of restrictions. 2.3 Schedule the ship to move through the channel. 2.4 Wait till the ship can move through the channel. 2.5 Move the ship through the channel. 2.6 Get the tugboats and steer the ship.
- If the ship is leaving the channel, then: 3.1. Get the pilots based on the time of day (day or night). 3.2 Get the tugboats and steer the ship. 3.3.Check when the ship is allowed to leave being free of restrictions. 3.4 Schedule the ship to move through the channel. 3.5 Wait till the ship can move through the channel. 3.6 Move the ship through the channel.
Arguments:
- ship_info (dict): Information about the ship, including its direction, pilots, tugboats, and other attributes.
- day (bool): Whether it is daytime or nighttime.
- lastSection (int): The last section of the channel to check.
- ship_id (int): The identifier for the ship.
- ship_terminal (str): The terminal where the ship is headed.
- ship_type (str): The type of the ship (e.g., Container, Liquid, DryBulk).
- run_id (int): The identifier for the current simulation run.
Returns:
None