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'])
class ChannelSection:
 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.
def can_accommodate_width(self, ship_info, start_time, end_time):
 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.

def can_accommodate_draft(self, ship_info, start_time, end_time):
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.

def can_accomodate_gap_out(self, start_time, ship_info):
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.

def update_usage(self, run_id, ship_info, start_time, end_time):
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

def space_out(self, start_time, ship_info):
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

class Channel:
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.
def channel_closed(self, time):
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.
def channel_closed_restriction(self, ship_info, lastSection, enter_time):
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.

def beam_restriction(self, ship_info, lastSection, enter_time):
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.

def draft_restriction(self, ship_info, lastSection, enter_time):
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.

def minimal_spacing_not_avlbl(self, ship_info, lastSection, enter_time):
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.

def daylight_restriction(self, ship_info, lastSection, enter_time):
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.

def scheduler(self, ship_info, lastSection, run_id, enter_time):
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

def move_through_channel(self, ship_info, lastSection, run_id):
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

def check_restrictions( self, ship_info, lastSection, request_time, wait_beam, wait_draft, wait_daylight):
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.

def channel_process( self, ship_info, day, lastSection, ship_id, ship_terminal, ship_type, run_id):
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:

  1. Check if the ship is entering or leaving the channel based on its direction.
  2. 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.
  3. 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