simulation_classes.terminal_drybulk

Uses dry bulk terminal class objects to create dry bulk terminal processes. Vessel arrivals, berth and conveyor allocation, cargo unloading, cargo loading, vessel waiting, and vessel departure are simulated.

  1"""
  2Uses dry bulk terminal class objects to create dry bulk terminal processes.
  3Vessel arrivals, berth and conveyor allocation, cargo unloading, cargo loading, vessel waiting, and vessel departure are simulated.
  4"""
  5from simulation_analysis.results import update_ship_logs
  6from simulation_handler.helpers import get_value_by_terminal
  7from simulation_handler.helpers import is_daytime
  8import constants
  9
 10TIME_COMMON_CHANNEL = constants.TIME_COMMON_CHANNEL
 11TIME_FOR_TUG_STEER = constants.TIME_FOR_TUG_STEER
 12TIME_FOR_UTURN = constants.TIME_FOR_UTURN
 13DRY_BULK_EFFICIENCY = constants.DRYBULK_TERMINAL_EFFICIENCY
 14
 15nan = float('nan')
 16
 17
 18class DryBulkTerminal:
 19    """
 20    Represents a dry bulk terminal and the process of handling arriving ships.
 21    This class simulates the arrival, berthing, unloading, loading, waiting, and departure of dry bulk ships at the terminal.
 22    It manages the allocation of berths and conveyors, handles cargo operations, and logs events during the simulation.
 23    The process includes the following steps:
 24    1. Ship arrival at the terminal.
 25    2. Anchorage waiting period.
 26    3. Berth allocation for the ship.
 27    4. Channel process for entering the terminal.
 28    5. Docking the ship to the berth.
 29    6. Unloading dry bulk cargo from the ship.
 30    7. Loading dry bulk cargo onto the ship.
 31    8. Waiting at the terminal until the ship is ready to depart.
 32    9. Detaching the ship from the berth and departing from the terminal.
 33    10. Channel process for leaving the terminal.
 34    The class also logs events and maintains ship logs throughout the process.
 35
 36    Args:
 37        env (SimPy.Environment): The simulation environment.
 38        chassis_bays_utilization (float): Utilization of chassis bays.
 39        run_id (int): Unique identifier for the simulation run.
 40        channel (Channel): The channel object for managing ship movements.  
 41        day_pilots (simpy.Container): Simpy container of available day pilots.
 42        night_pilots (simpy.Container) : Simpy container of available night pilots.
 43        tugboats (simpy.Container): Simpy container of available tugboats.
 44        ship_info (dict): Information about the ships.
 45        last_section (str): The last section of the channel.
 46        selected_terminal (str): The terminal selected for the ship.
 47        id (int): Unique identifier for the ship.
 48        ship_type (str): Type of the ship (e.g., "DryBulk").
 49        draft (float): Draft of the ship.
 50        width (float): Width of the ship.
 51        unload_time (float): Time taken to unload cargo (in hours).
 52        load_time (float): Time taken to load cargo (in hours).
 53        unload_tons (float): Amount of cargo to unload (in tons).
 54        load_tons (float): Amount of cargo to load (in tons).
 55        events (list): List to store events during the simulation.
 56        ship_logs (list): List to store logs of the ship's activities.
 57        port_berths (simpy.Resource): Resource for managing port berths.
 58        port_silos (simpy.Resource): Resource for managing port silos.
 59        SHIPS_IN_CHANNEL (int): Number of ships allowed in the channel.
 60        SHIPS_IN_ANCHORAGE (int): Number of ships allowed in the anchorage.
 61        terminal_data (dict): Data related to the terminal.
 62        """
 63    def __init__(self, env, chassis_bays_utilization, run_id, channel, day_pilots, night_pilots, tugboats, ship_info, last_section, selected_terminal, id, ship_type, draft, width, unload_time, load_time, unload_tons, load_tons, events, ship_logs, port_berths, port_silos, SHIPS_IN_CHANNEL, SHIPS_IN_ANCHORAGE, terminal_data):
 64        # Initialize the environment
 65        self.env = env
 66        self.run_id = run_id
 67        self.SHIPS_IN_CHANNEL = SHIPS_IN_CHANNEL
 68        self.SHIPS_IN_ANCHORAGE = SHIPS_IN_ANCHORAGE
 69        self.terminal_data = terminal_data
 70
 71        # Initalise the channel
 72        self.channel = channel
 73        self.ship_info = ship_info
 74        self.last_section = last_section
 75
 76        # day_pilots, night_pilots and tugboats
 77        self.day_pilots, self.night_pilots = day_pilots, night_pilots
 78        self.tugboats = tugboats
 79
 80        # Set vessel and terminal attributes
 81        self.id = id
 82        self.selected_terminal = selected_terminal
 83        self.ship_type = ship_type
 84        self.num_conveyors = get_value_by_terminal(
 85            self.terminal_data, 'DryBulk', selected_terminal, 'transfer units per berth')
 86        self.unload_tons = unload_tons
 87        self.load_tons = load_tons
 88        self.time_for_uturn_min, self.time_for_uturn_max = TIME_FOR_UTURN
 89
 90        self.draft = draft
 91        self.width = width
 92        self.unload_time = unload_time
 93        self.load_time = load_time
 94
 95        # wait time proxy
 96        self.cargo_wait_time = 0
 97
 98        # The allocation of conveyors and berth will be done during the process
 99        self.current_berth = None
100        self.allocated_conveyors = []
101
102        # Get the ship logs and events
103        self.events = events
104        self.ship_logs = ship_logs
105
106        # Allocate berth and conveyors
107        self.port_berths = port_berths
108        self.port_silos = port_silos
109
110        # Start the process
111        self.env.process(self.process())
112
113    def process(self):
114        """
115        The main process for the dry bulk terminal, simulating the arrival, berthing, unloading, loading, waiting, and departure of ships.
116        This method handles the entire lifecycle of a dry bulk ship at the terminal.
117        """
118
119        self.ship_logs = update_ship_logs(self.ship_logs, "D", self.id, self.selected_terminal, ship_start_time=nan, time_to_get_berth=nan, time_for_restriction_in=nan, time_to_get_pilot_in=nan,
120                                          time_to_get_tugs_in=nan, time_to_common_channel_in=nan, time_to_travel_channel_in=nan, time_to_tug_steer_in=nan, unloading_time=nan, loading_time=nan, waiting_time=nan,
121                                          departure_time=nan, time_to_get_pilot_out=nan, time_to_get_tugs_out=nan, time_to_tug_steer_out=nan, time_for_uturn=nan,
122                                          time_to_travel_channel_out=nan, time_to_common_channel_out=nan, ship_end_time=nan)
123
124        # ship arrival
125        ship_start_time = self.env.now
126        update_ship_logs(self.ship_logs, "D", self.id,
127                         self.selected_terminal, ship_start_time=ship_start_time)
128        self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}",
129                           "arrive", self.env.now, f"Ship {self.id} arrived at the port"))
130
131        # Anchorage waiting
132        yield self.env.timeout(constants.ANCHORAGE_WAITING_DRYBULK)
133
134        # Berth allocation
135        start_time = self.env.now
136        yield self.env.process(self.arrive_and_berth())
137        time_to_get_berth = self.env.now - ship_start_time
138        update_ship_logs(self.ship_logs, "D", self.id,
139                         self.selected_terminal, time_to_get_berth=time_to_get_berth)
140
141        # Channel process (in)
142        self.day = is_daytime(self.env.now, 7, 19)
143        yield self.env.process(self.channel.channel_process(self.ship_info, self.day, self.last_section, self.id, self.selected_terminal, "D", self.run_id))
144
145        # Terminal process
146        self.events.append((f"Ship_{self.id}", "DryBulk", f"L.{self.selected_terminal}", "dock", self.env.now,
147                           f"Ship {self.id} docked to berth {self.current_berth.id} with waiting time {'N/A'}"))
148
149        start_time = self.env.now
150        yield self.env.process(self.unload())
151        unloading_time = self.env.now - start_time
152        update_ship_logs(self.ship_logs, "D", self.id,
153                         self.selected_terminal, unloading_time=unloading_time)
154
155        start_time = self.env.now
156        yield self.env.process(self.load())
157        loading_time = self.env.now - start_time
158        update_ship_logs(self.ship_logs, "D", self.id,
159                         self.selected_terminal, loading_time=loading_time)
160
161        start_time = self.env.now
162        yield self.env.process(self.wait())
163        waiting_time = self.env.now - start_time
164        update_ship_logs(self.ship_logs, "D", self.id,
165                         self.selected_terminal, waiting_time=waiting_time)
166
167        start_time = self.env.now
168        yield self.env.process(self.detach_and_depart())
169        departure_time = self.env.now - start_time
170        update_ship_logs(self.ship_logs, "D", self.id,
171                         self.selected_terminal, departure_time=departure_time)
172
173        self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}", "undock", self.env.now,
174                           f"Ship {self.id} undocked to berth {self.current_berth.id} with waiting time {'N/A'}"))
175
176        # Channel process (out)
177        self.day = is_daytime(self.env.now, 7, 19)
178        yield self.env.process(self.channel.channel_process(self.ship_info, self.day, self.last_section, self.id, self.selected_terminal, "D", self.run_id))
179
180        ship_end_time = self.env.now
181        update_ship_logs(self.ship_logs, "D", self.id,
182                         self.selected_terminal, ship_end_time=ship_end_time)
183        self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}", "depart", self.env.now,
184                           f"Ship {self.id} departed from berth {self.current_berth.id} with waiting time {'N/A'}"))
185
186    def arrive_and_berth(self):
187        """
188        Represents the process of a vessel arriving at the terminal and seizing a berth.
189        This method handles the allocation of a berth for the vessel and prepares it for unloading and loading operations.
190        """
191        self.current_berth = yield self.port_berths.get()
192
193    def unload(self):
194        """
195        Represents the unloading of dry bulk cargo from the vessel at the terminal.
196        This method allocates the required number of conveyors, unloads the cargo, and stores it in the port silos.
197        It also handles the waiting time until the unloading is completed, ensuring that it only proceeds during daytime hours.
198        The unloading process is divided into several steps:
199        1. Check if the terminal has the required import conveyors.
200        2. Allocate the conveyors for unloading.
201        3. Unload the dry bulk cargo from the vessel.   
202        4. Wait until the unloading is completed, ensuring it only occurs during daytime hours.
203        5. Store the unloaded dry bulk cargo in the port silos.
204        """
205        # Request for the required number of conveyors
206        import_terminal = get_value_by_terminal(
207            self.terminal_data, 'DryBulk', self.selected_terminal, 'import')
208        if import_terminal:
209            for _ in range(self.num_conveyors):
210                conveyor = yield self.current_berth.conveyors.get()
211                self.allocated_conveyors.append(conveyor)
212            self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}",
213                               "all_conveyors_attached", self.env.now, f"Ship {self.id} conveyors allocated"))
214            # Unload dry bulk cargo
215            total_unload_time = self.unload_tons * self.unload_time / self.num_conveyors
216            self.cargo_wait_time += total_unload_time
217            # keep waiting until day time
218            while total_unload_time > 0:
219                if not is_daytime(self.env.now, start=7, end=19):
220                    yield self.env.timeout(1)
221                else:
222                    yield self.env.timeout(1)
223                    total_unload_time -= 1
224            # yield self.env.timeout(total_unload_time)
225            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
226                               "all_bulk_unloaded", self.env.now, "Ship {} unloaded all dry bulk".format(self.id)))
227            # Put dry bulk batches in silos
228            yield self.port_silos.put(self.unload_tons)
229            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
230                               "all_bulk_stored", self.env.now, "Ship {} unloaded all dry bulk".format(self.id)))
231        else:
232            self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}",
233                               "all_conveyors_attached", self.env.now, f"Ship {self.id} conveyors allocated"))
234            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
235                               "all_bulk_unloaded", self.env.now, "Ship {} unloaded all dry bulk".format(self.id)))
236            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
237                               "all_bulk_stored", self.env.now, "Ship {} unloaded all dry bulk".format(self.id)))
238
239    def load(self):
240        """
241        Represents the loading of dry bulk cargo onto the vessel at the terminal.
242        This method allocates the required number of conveyors, loads the cargo onto the vessel, and updates the port silos.
243        It also handles the waiting time until the loading is completed, ensuring that it only proceeds during daytime hours.
244        The loading process is divided into several steps:
245        1. Check if the terminal has the required export conveyors.
246        2. Allocate the conveyors for loading.
247        3. Load the dry bulk cargo onto the vessel.
248        4. Wait until the loading is completed, ensuring it only occurs during daytime hours.
249        5. Update the port silos to reflect the loaded cargo.
250        """
251        export_terminal = get_value_by_terminal(
252            self.terminal_data, 'DryBulk', self.selected_terminal, 'export')
253        if export_terminal:
254            # Represents dry bulk cargo being loaded onto the vessel at the input conveyor rate
255            total_load_time = self.load_tons * self.load_time / self.num_conveyors
256            # Load dry bulk cargo
257            yield self.port_silos.get(self.load_tons)
258            self.cargo_wait_time += total_load_time
259            # keep waiting until day time
260            while total_load_time > 0:
261                if not is_daytime(self.env.now, start=10, end=17):
262                    yield self.env.timeout(1)
263                else:
264                    yield self.env.timeout(1)
265                    total_load_time -= 1
266            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
267                               "all_bulk_loaded", self.env.now, "Ship {} loaded all dry bulk".format(self.id)))
268        else:
269            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
270                               "all_bulk_loaded", self.env.now, "Ship {} loaded all dry bulk".format(self.id)))
271
272    def wait(self):
273        """
274        Represents the waiting time for the vessel at the port after unloading and loading operations.
275        This method calculates the waiting time based on the dry bulk efficiency and the cargo wait time.
276        It ensures that the vessel waits at the port until it is ready to depart.
277        """
278        port_waiting_time = (1/DRY_BULK_EFFICIENCY - 1) * self.cargo_wait_time
279        yield self.env.timeout(port_waiting_time)
280        self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
281                           "wait", self.env.now, "Ship {} waited at the port".format(self.id)))
282
283    def detach_and_depart(self):
284        """
285        Represents the process of detaching the vessel from the berth and departing from the port.
286        This method handles the release of the allocated conveyors, updates the ship logs, and releases the berth.
287        It also logs the events related to the vessel's departure.
288        """
289
290        # Release the conveyor belts
291        for conveyor in self.allocated_conveyors:
292            yield self.current_berth.conveyors.put(conveyor)
293            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}", "all_conveyors_disconnected",
294                               self.env.now, "Ship {} disconnected from conveyor {}".format(self.id, conveyor.id)))
295
296        # Depart from the berth
297        yield self.port_berths.put(self.current_berth)
class DryBulkTerminal:
 19class DryBulkTerminal:
 20    """
 21    Represents a dry bulk terminal and the process of handling arriving ships.
 22    This class simulates the arrival, berthing, unloading, loading, waiting, and departure of dry bulk ships at the terminal.
 23    It manages the allocation of berths and conveyors, handles cargo operations, and logs events during the simulation.
 24    The process includes the following steps:
 25    1. Ship arrival at the terminal.
 26    2. Anchorage waiting period.
 27    3. Berth allocation for the ship.
 28    4. Channel process for entering the terminal.
 29    5. Docking the ship to the berth.
 30    6. Unloading dry bulk cargo from the ship.
 31    7. Loading dry bulk cargo onto the ship.
 32    8. Waiting at the terminal until the ship is ready to depart.
 33    9. Detaching the ship from the berth and departing from the terminal.
 34    10. Channel process for leaving the terminal.
 35    The class also logs events and maintains ship logs throughout the process.
 36
 37    Args:
 38        env (SimPy.Environment): The simulation environment.
 39        chassis_bays_utilization (float): Utilization of chassis bays.
 40        run_id (int): Unique identifier for the simulation run.
 41        channel (Channel): The channel object for managing ship movements.  
 42        day_pilots (simpy.Container): Simpy container of available day pilots.
 43        night_pilots (simpy.Container) : Simpy container of available night pilots.
 44        tugboats (simpy.Container): Simpy container of available tugboats.
 45        ship_info (dict): Information about the ships.
 46        last_section (str): The last section of the channel.
 47        selected_terminal (str): The terminal selected for the ship.
 48        id (int): Unique identifier for the ship.
 49        ship_type (str): Type of the ship (e.g., "DryBulk").
 50        draft (float): Draft of the ship.
 51        width (float): Width of the ship.
 52        unload_time (float): Time taken to unload cargo (in hours).
 53        load_time (float): Time taken to load cargo (in hours).
 54        unload_tons (float): Amount of cargo to unload (in tons).
 55        load_tons (float): Amount of cargo to load (in tons).
 56        events (list): List to store events during the simulation.
 57        ship_logs (list): List to store logs of the ship's activities.
 58        port_berths (simpy.Resource): Resource for managing port berths.
 59        port_silos (simpy.Resource): Resource for managing port silos.
 60        SHIPS_IN_CHANNEL (int): Number of ships allowed in the channel.
 61        SHIPS_IN_ANCHORAGE (int): Number of ships allowed in the anchorage.
 62        terminal_data (dict): Data related to the terminal.
 63        """
 64    def __init__(self, env, chassis_bays_utilization, run_id, channel, day_pilots, night_pilots, tugboats, ship_info, last_section, selected_terminal, id, ship_type, draft, width, unload_time, load_time, unload_tons, load_tons, events, ship_logs, port_berths, port_silos, SHIPS_IN_CHANNEL, SHIPS_IN_ANCHORAGE, terminal_data):
 65        # Initialize the environment
 66        self.env = env
 67        self.run_id = run_id
 68        self.SHIPS_IN_CHANNEL = SHIPS_IN_CHANNEL
 69        self.SHIPS_IN_ANCHORAGE = SHIPS_IN_ANCHORAGE
 70        self.terminal_data = terminal_data
 71
 72        # Initalise the channel
 73        self.channel = channel
 74        self.ship_info = ship_info
 75        self.last_section = last_section
 76
 77        # day_pilots, night_pilots and tugboats
 78        self.day_pilots, self.night_pilots = day_pilots, night_pilots
 79        self.tugboats = tugboats
 80
 81        # Set vessel and terminal attributes
 82        self.id = id
 83        self.selected_terminal = selected_terminal
 84        self.ship_type = ship_type
 85        self.num_conveyors = get_value_by_terminal(
 86            self.terminal_data, 'DryBulk', selected_terminal, 'transfer units per berth')
 87        self.unload_tons = unload_tons
 88        self.load_tons = load_tons
 89        self.time_for_uturn_min, self.time_for_uturn_max = TIME_FOR_UTURN
 90
 91        self.draft = draft
 92        self.width = width
 93        self.unload_time = unload_time
 94        self.load_time = load_time
 95
 96        # wait time proxy
 97        self.cargo_wait_time = 0
 98
 99        # The allocation of conveyors and berth will be done during the process
100        self.current_berth = None
101        self.allocated_conveyors = []
102
103        # Get the ship logs and events
104        self.events = events
105        self.ship_logs = ship_logs
106
107        # Allocate berth and conveyors
108        self.port_berths = port_berths
109        self.port_silos = port_silos
110
111        # Start the process
112        self.env.process(self.process())
113
114    def process(self):
115        """
116        The main process for the dry bulk terminal, simulating the arrival, berthing, unloading, loading, waiting, and departure of ships.
117        This method handles the entire lifecycle of a dry bulk ship at the terminal.
118        """
119
120        self.ship_logs = update_ship_logs(self.ship_logs, "D", self.id, self.selected_terminal, ship_start_time=nan, time_to_get_berth=nan, time_for_restriction_in=nan, time_to_get_pilot_in=nan,
121                                          time_to_get_tugs_in=nan, time_to_common_channel_in=nan, time_to_travel_channel_in=nan, time_to_tug_steer_in=nan, unloading_time=nan, loading_time=nan, waiting_time=nan,
122                                          departure_time=nan, time_to_get_pilot_out=nan, time_to_get_tugs_out=nan, time_to_tug_steer_out=nan, time_for_uturn=nan,
123                                          time_to_travel_channel_out=nan, time_to_common_channel_out=nan, ship_end_time=nan)
124
125        # ship arrival
126        ship_start_time = self.env.now
127        update_ship_logs(self.ship_logs, "D", self.id,
128                         self.selected_terminal, ship_start_time=ship_start_time)
129        self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}",
130                           "arrive", self.env.now, f"Ship {self.id} arrived at the port"))
131
132        # Anchorage waiting
133        yield self.env.timeout(constants.ANCHORAGE_WAITING_DRYBULK)
134
135        # Berth allocation
136        start_time = self.env.now
137        yield self.env.process(self.arrive_and_berth())
138        time_to_get_berth = self.env.now - ship_start_time
139        update_ship_logs(self.ship_logs, "D", self.id,
140                         self.selected_terminal, time_to_get_berth=time_to_get_berth)
141
142        # Channel process (in)
143        self.day = is_daytime(self.env.now, 7, 19)
144        yield self.env.process(self.channel.channel_process(self.ship_info, self.day, self.last_section, self.id, self.selected_terminal, "D", self.run_id))
145
146        # Terminal process
147        self.events.append((f"Ship_{self.id}", "DryBulk", f"L.{self.selected_terminal}", "dock", self.env.now,
148                           f"Ship {self.id} docked to berth {self.current_berth.id} with waiting time {'N/A'}"))
149
150        start_time = self.env.now
151        yield self.env.process(self.unload())
152        unloading_time = self.env.now - start_time
153        update_ship_logs(self.ship_logs, "D", self.id,
154                         self.selected_terminal, unloading_time=unloading_time)
155
156        start_time = self.env.now
157        yield self.env.process(self.load())
158        loading_time = self.env.now - start_time
159        update_ship_logs(self.ship_logs, "D", self.id,
160                         self.selected_terminal, loading_time=loading_time)
161
162        start_time = self.env.now
163        yield self.env.process(self.wait())
164        waiting_time = self.env.now - start_time
165        update_ship_logs(self.ship_logs, "D", self.id,
166                         self.selected_terminal, waiting_time=waiting_time)
167
168        start_time = self.env.now
169        yield self.env.process(self.detach_and_depart())
170        departure_time = self.env.now - start_time
171        update_ship_logs(self.ship_logs, "D", self.id,
172                         self.selected_terminal, departure_time=departure_time)
173
174        self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}", "undock", self.env.now,
175                           f"Ship {self.id} undocked to berth {self.current_berth.id} with waiting time {'N/A'}"))
176
177        # Channel process (out)
178        self.day = is_daytime(self.env.now, 7, 19)
179        yield self.env.process(self.channel.channel_process(self.ship_info, self.day, self.last_section, self.id, self.selected_terminal, "D", self.run_id))
180
181        ship_end_time = self.env.now
182        update_ship_logs(self.ship_logs, "D", self.id,
183                         self.selected_terminal, ship_end_time=ship_end_time)
184        self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}", "depart", self.env.now,
185                           f"Ship {self.id} departed from berth {self.current_berth.id} with waiting time {'N/A'}"))
186
187    def arrive_and_berth(self):
188        """
189        Represents the process of a vessel arriving at the terminal and seizing a berth.
190        This method handles the allocation of a berth for the vessel and prepares it for unloading and loading operations.
191        """
192        self.current_berth = yield self.port_berths.get()
193
194    def unload(self):
195        """
196        Represents the unloading of dry bulk cargo from the vessel at the terminal.
197        This method allocates the required number of conveyors, unloads the cargo, and stores it in the port silos.
198        It also handles the waiting time until the unloading is completed, ensuring that it only proceeds during daytime hours.
199        The unloading process is divided into several steps:
200        1. Check if the terminal has the required import conveyors.
201        2. Allocate the conveyors for unloading.
202        3. Unload the dry bulk cargo from the vessel.   
203        4. Wait until the unloading is completed, ensuring it only occurs during daytime hours.
204        5. Store the unloaded dry bulk cargo in the port silos.
205        """
206        # Request for the required number of conveyors
207        import_terminal = get_value_by_terminal(
208            self.terminal_data, 'DryBulk', self.selected_terminal, 'import')
209        if import_terminal:
210            for _ in range(self.num_conveyors):
211                conveyor = yield self.current_berth.conveyors.get()
212                self.allocated_conveyors.append(conveyor)
213            self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}",
214                               "all_conveyors_attached", self.env.now, f"Ship {self.id} conveyors allocated"))
215            # Unload dry bulk cargo
216            total_unload_time = self.unload_tons * self.unload_time / self.num_conveyors
217            self.cargo_wait_time += total_unload_time
218            # keep waiting until day time
219            while total_unload_time > 0:
220                if not is_daytime(self.env.now, start=7, end=19):
221                    yield self.env.timeout(1)
222                else:
223                    yield self.env.timeout(1)
224                    total_unload_time -= 1
225            # yield self.env.timeout(total_unload_time)
226            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
227                               "all_bulk_unloaded", self.env.now, "Ship {} unloaded all dry bulk".format(self.id)))
228            # Put dry bulk batches in silos
229            yield self.port_silos.put(self.unload_tons)
230            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
231                               "all_bulk_stored", self.env.now, "Ship {} unloaded all dry bulk".format(self.id)))
232        else:
233            self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}",
234                               "all_conveyors_attached", self.env.now, f"Ship {self.id} conveyors allocated"))
235            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
236                               "all_bulk_unloaded", self.env.now, "Ship {} unloaded all dry bulk".format(self.id)))
237            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
238                               "all_bulk_stored", self.env.now, "Ship {} unloaded all dry bulk".format(self.id)))
239
240    def load(self):
241        """
242        Represents the loading of dry bulk cargo onto the vessel at the terminal.
243        This method allocates the required number of conveyors, loads the cargo onto the vessel, and updates the port silos.
244        It also handles the waiting time until the loading is completed, ensuring that it only proceeds during daytime hours.
245        The loading process is divided into several steps:
246        1. Check if the terminal has the required export conveyors.
247        2. Allocate the conveyors for loading.
248        3. Load the dry bulk cargo onto the vessel.
249        4. Wait until the loading is completed, ensuring it only occurs during daytime hours.
250        5. Update the port silos to reflect the loaded cargo.
251        """
252        export_terminal = get_value_by_terminal(
253            self.terminal_data, 'DryBulk', self.selected_terminal, 'export')
254        if export_terminal:
255            # Represents dry bulk cargo being loaded onto the vessel at the input conveyor rate
256            total_load_time = self.load_tons * self.load_time / self.num_conveyors
257            # Load dry bulk cargo
258            yield self.port_silos.get(self.load_tons)
259            self.cargo_wait_time += total_load_time
260            # keep waiting until day time
261            while total_load_time > 0:
262                if not is_daytime(self.env.now, start=10, end=17):
263                    yield self.env.timeout(1)
264                else:
265                    yield self.env.timeout(1)
266                    total_load_time -= 1
267            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
268                               "all_bulk_loaded", self.env.now, "Ship {} loaded all dry bulk".format(self.id)))
269        else:
270            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
271                               "all_bulk_loaded", self.env.now, "Ship {} loaded all dry bulk".format(self.id)))
272
273    def wait(self):
274        """
275        Represents the waiting time for the vessel at the port after unloading and loading operations.
276        This method calculates the waiting time based on the dry bulk efficiency and the cargo wait time.
277        It ensures that the vessel waits at the port until it is ready to depart.
278        """
279        port_waiting_time = (1/DRY_BULK_EFFICIENCY - 1) * self.cargo_wait_time
280        yield self.env.timeout(port_waiting_time)
281        self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
282                           "wait", self.env.now, "Ship {} waited at the port".format(self.id)))
283
284    def detach_and_depart(self):
285        """
286        Represents the process of detaching the vessel from the berth and departing from the port.
287        This method handles the release of the allocated conveyors, updates the ship logs, and releases the berth.
288        It also logs the events related to the vessel's departure.
289        """
290
291        # Release the conveyor belts
292        for conveyor in self.allocated_conveyors:
293            yield self.current_berth.conveyors.put(conveyor)
294            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}", "all_conveyors_disconnected",
295                               self.env.now, "Ship {} disconnected from conveyor {}".format(self.id, conveyor.id)))
296
297        # Depart from the berth
298        yield self.port_berths.put(self.current_berth)

Represents a dry bulk terminal and the process of handling arriving ships. This class simulates the arrival, berthing, unloading, loading, waiting, and departure of dry bulk ships at the terminal. It manages the allocation of berths and conveyors, handles cargo operations, and logs events during the simulation. The process includes the following steps:

  1. Ship arrival at the terminal.
  2. Anchorage waiting period.
  3. Berth allocation for the ship.
  4. Channel process for entering the terminal.
  5. Docking the ship to the berth.
  6. Unloading dry bulk cargo from the ship.
  7. Loading dry bulk cargo onto the ship.
  8. Waiting at the terminal until the ship is ready to depart.
  9. Detaching the ship from the berth and departing from the terminal.
  10. Channel process for leaving the terminal. The class also logs events and maintains ship logs throughout the process.
Arguments:
  • env (SimPy.Environment): The simulation environment.
  • chassis_bays_utilization (float): Utilization of chassis bays.
  • run_id (int): Unique identifier for the simulation run.
  • channel (Channel): The channel object for managing ship movements.
  • day_pilots (simpy.Container): Simpy container of available day pilots.
  • night_pilots (simpy.Container) : Simpy container of available night pilots.
  • tugboats (simpy.Container): Simpy container of available tugboats.
  • ship_info (dict): Information about the ships.
  • last_section (str): The last section of the channel.
  • selected_terminal (str): The terminal selected for the ship.
  • id (int): Unique identifier for the ship.
  • ship_type (str): Type of the ship (e.g., "DryBulk").
  • draft (float): Draft of the ship.
  • width (float): Width of the ship.
  • unload_time (float): Time taken to unload cargo (in hours).
  • load_time (float): Time taken to load cargo (in hours).
  • unload_tons (float): Amount of cargo to unload (in tons).
  • load_tons (float): Amount of cargo to load (in tons).
  • events (list): List to store events during the simulation.
  • ship_logs (list): List to store logs of the ship's activities.
  • port_berths (simpy.Resource): Resource for managing port berths.
  • port_silos (simpy.Resource): Resource for managing port silos.
  • SHIPS_IN_CHANNEL (int): Number of ships allowed in the channel.
  • SHIPS_IN_ANCHORAGE (int): Number of ships allowed in the anchorage.
  • terminal_data (dict): Data related to the terminal.
def process(self):
114    def process(self):
115        """
116        The main process for the dry bulk terminal, simulating the arrival, berthing, unloading, loading, waiting, and departure of ships.
117        This method handles the entire lifecycle of a dry bulk ship at the terminal.
118        """
119
120        self.ship_logs = update_ship_logs(self.ship_logs, "D", self.id, self.selected_terminal, ship_start_time=nan, time_to_get_berth=nan, time_for_restriction_in=nan, time_to_get_pilot_in=nan,
121                                          time_to_get_tugs_in=nan, time_to_common_channel_in=nan, time_to_travel_channel_in=nan, time_to_tug_steer_in=nan, unloading_time=nan, loading_time=nan, waiting_time=nan,
122                                          departure_time=nan, time_to_get_pilot_out=nan, time_to_get_tugs_out=nan, time_to_tug_steer_out=nan, time_for_uturn=nan,
123                                          time_to_travel_channel_out=nan, time_to_common_channel_out=nan, ship_end_time=nan)
124
125        # ship arrival
126        ship_start_time = self.env.now
127        update_ship_logs(self.ship_logs, "D", self.id,
128                         self.selected_terminal, ship_start_time=ship_start_time)
129        self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}",
130                           "arrive", self.env.now, f"Ship {self.id} arrived at the port"))
131
132        # Anchorage waiting
133        yield self.env.timeout(constants.ANCHORAGE_WAITING_DRYBULK)
134
135        # Berth allocation
136        start_time = self.env.now
137        yield self.env.process(self.arrive_and_berth())
138        time_to_get_berth = self.env.now - ship_start_time
139        update_ship_logs(self.ship_logs, "D", self.id,
140                         self.selected_terminal, time_to_get_berth=time_to_get_berth)
141
142        # Channel process (in)
143        self.day = is_daytime(self.env.now, 7, 19)
144        yield self.env.process(self.channel.channel_process(self.ship_info, self.day, self.last_section, self.id, self.selected_terminal, "D", self.run_id))
145
146        # Terminal process
147        self.events.append((f"Ship_{self.id}", "DryBulk", f"L.{self.selected_terminal}", "dock", self.env.now,
148                           f"Ship {self.id} docked to berth {self.current_berth.id} with waiting time {'N/A'}"))
149
150        start_time = self.env.now
151        yield self.env.process(self.unload())
152        unloading_time = self.env.now - start_time
153        update_ship_logs(self.ship_logs, "D", self.id,
154                         self.selected_terminal, unloading_time=unloading_time)
155
156        start_time = self.env.now
157        yield self.env.process(self.load())
158        loading_time = self.env.now - start_time
159        update_ship_logs(self.ship_logs, "D", self.id,
160                         self.selected_terminal, loading_time=loading_time)
161
162        start_time = self.env.now
163        yield self.env.process(self.wait())
164        waiting_time = self.env.now - start_time
165        update_ship_logs(self.ship_logs, "D", self.id,
166                         self.selected_terminal, waiting_time=waiting_time)
167
168        start_time = self.env.now
169        yield self.env.process(self.detach_and_depart())
170        departure_time = self.env.now - start_time
171        update_ship_logs(self.ship_logs, "D", self.id,
172                         self.selected_terminal, departure_time=departure_time)
173
174        self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}", "undock", self.env.now,
175                           f"Ship {self.id} undocked to berth {self.current_berth.id} with waiting time {'N/A'}"))
176
177        # Channel process (out)
178        self.day = is_daytime(self.env.now, 7, 19)
179        yield self.env.process(self.channel.channel_process(self.ship_info, self.day, self.last_section, self.id, self.selected_terminal, "D", self.run_id))
180
181        ship_end_time = self.env.now
182        update_ship_logs(self.ship_logs, "D", self.id,
183                         self.selected_terminal, ship_end_time=ship_end_time)
184        self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}", "depart", self.env.now,
185                           f"Ship {self.id} departed from berth {self.current_berth.id} with waiting time {'N/A'}"))

The main process for the dry bulk terminal, simulating the arrival, berthing, unloading, loading, waiting, and departure of ships. This method handles the entire lifecycle of a dry bulk ship at the terminal.

def arrive_and_berth(self):
187    def arrive_and_berth(self):
188        """
189        Represents the process of a vessel arriving at the terminal and seizing a berth.
190        This method handles the allocation of a berth for the vessel and prepares it for unloading and loading operations.
191        """
192        self.current_berth = yield self.port_berths.get()

Represents the process of a vessel arriving at the terminal and seizing a berth. This method handles the allocation of a berth for the vessel and prepares it for unloading and loading operations.

def unload(self):
194    def unload(self):
195        """
196        Represents the unloading of dry bulk cargo from the vessel at the terminal.
197        This method allocates the required number of conveyors, unloads the cargo, and stores it in the port silos.
198        It also handles the waiting time until the unloading is completed, ensuring that it only proceeds during daytime hours.
199        The unloading process is divided into several steps:
200        1. Check if the terminal has the required import conveyors.
201        2. Allocate the conveyors for unloading.
202        3. Unload the dry bulk cargo from the vessel.   
203        4. Wait until the unloading is completed, ensuring it only occurs during daytime hours.
204        5. Store the unloaded dry bulk cargo in the port silos.
205        """
206        # Request for the required number of conveyors
207        import_terminal = get_value_by_terminal(
208            self.terminal_data, 'DryBulk', self.selected_terminal, 'import')
209        if import_terminal:
210            for _ in range(self.num_conveyors):
211                conveyor = yield self.current_berth.conveyors.get()
212                self.allocated_conveyors.append(conveyor)
213            self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}",
214                               "all_conveyors_attached", self.env.now, f"Ship {self.id} conveyors allocated"))
215            # Unload dry bulk cargo
216            total_unload_time = self.unload_tons * self.unload_time / self.num_conveyors
217            self.cargo_wait_time += total_unload_time
218            # keep waiting until day time
219            while total_unload_time > 0:
220                if not is_daytime(self.env.now, start=7, end=19):
221                    yield self.env.timeout(1)
222                else:
223                    yield self.env.timeout(1)
224                    total_unload_time -= 1
225            # yield self.env.timeout(total_unload_time)
226            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
227                               "all_bulk_unloaded", self.env.now, "Ship {} unloaded all dry bulk".format(self.id)))
228            # Put dry bulk batches in silos
229            yield self.port_silos.put(self.unload_tons)
230            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
231                               "all_bulk_stored", self.env.now, "Ship {} unloaded all dry bulk".format(self.id)))
232        else:
233            self.events.append((f"Ship_{self.id}", "DryBulk", f"D.{self.selected_terminal}",
234                               "all_conveyors_attached", self.env.now, f"Ship {self.id} conveyors allocated"))
235            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
236                               "all_bulk_unloaded", self.env.now, "Ship {} unloaded all dry bulk".format(self.id)))
237            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
238                               "all_bulk_stored", self.env.now, "Ship {} unloaded all dry bulk".format(self.id)))

Represents the unloading of dry bulk cargo from the vessel at the terminal. This method allocates the required number of conveyors, unloads the cargo, and stores it in the port silos. It also handles the waiting time until the unloading is completed, ensuring that it only proceeds during daytime hours. The unloading process is divided into several steps:

  1. Check if the terminal has the required import conveyors.
  2. Allocate the conveyors for unloading.
  3. Unload the dry bulk cargo from the vessel.
  4. Wait until the unloading is completed, ensuring it only occurs during daytime hours.
  5. Store the unloaded dry bulk cargo in the port silos.
def load(self):
240    def load(self):
241        """
242        Represents the loading of dry bulk cargo onto the vessel at the terminal.
243        This method allocates the required number of conveyors, loads the cargo onto the vessel, and updates the port silos.
244        It also handles the waiting time until the loading is completed, ensuring that it only proceeds during daytime hours.
245        The loading process is divided into several steps:
246        1. Check if the terminal has the required export conveyors.
247        2. Allocate the conveyors for loading.
248        3. Load the dry bulk cargo onto the vessel.
249        4. Wait until the loading is completed, ensuring it only occurs during daytime hours.
250        5. Update the port silos to reflect the loaded cargo.
251        """
252        export_terminal = get_value_by_terminal(
253            self.terminal_data, 'DryBulk', self.selected_terminal, 'export')
254        if export_terminal:
255            # Represents dry bulk cargo being loaded onto the vessel at the input conveyor rate
256            total_load_time = self.load_tons * self.load_time / self.num_conveyors
257            # Load dry bulk cargo
258            yield self.port_silos.get(self.load_tons)
259            self.cargo_wait_time += total_load_time
260            # keep waiting until day time
261            while total_load_time > 0:
262                if not is_daytime(self.env.now, start=10, end=17):
263                    yield self.env.timeout(1)
264                else:
265                    yield self.env.timeout(1)
266                    total_load_time -= 1
267            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
268                               "all_bulk_loaded", self.env.now, "Ship {} loaded all dry bulk".format(self.id)))
269        else:
270            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
271                               "all_bulk_loaded", self.env.now, "Ship {} loaded all dry bulk".format(self.id)))

Represents the loading of dry bulk cargo onto the vessel at the terminal. This method allocates the required number of conveyors, loads the cargo onto the vessel, and updates the port silos. It also handles the waiting time until the loading is completed, ensuring that it only proceeds during daytime hours. The loading process is divided into several steps:

  1. Check if the terminal has the required export conveyors.
  2. Allocate the conveyors for loading.
  3. Load the dry bulk cargo onto the vessel.
  4. Wait until the loading is completed, ensuring it only occurs during daytime hours.
  5. Update the port silos to reflect the loaded cargo.
def wait(self):
273    def wait(self):
274        """
275        Represents the waiting time for the vessel at the port after unloading and loading operations.
276        This method calculates the waiting time based on the dry bulk efficiency and the cargo wait time.
277        It ensures that the vessel waits at the port until it is ready to depart.
278        """
279        port_waiting_time = (1/DRY_BULK_EFFICIENCY - 1) * self.cargo_wait_time
280        yield self.env.timeout(port_waiting_time)
281        self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}",
282                           "wait", self.env.now, "Ship {} waited at the port".format(self.id)))

Represents the waiting time for the vessel at the port after unloading and loading operations. This method calculates the waiting time based on the dry bulk efficiency and the cargo wait time. It ensures that the vessel waits at the port until it is ready to depart.

def detach_and_depart(self):
284    def detach_and_depart(self):
285        """
286        Represents the process of detaching the vessel from the berth and departing from the port.
287        This method handles the release of the allocated conveyors, updates the ship logs, and releases the berth.
288        It also logs the events related to the vessel's departure.
289        """
290
291        # Release the conveyor belts
292        for conveyor in self.allocated_conveyors:
293            yield self.current_berth.conveyors.put(conveyor)
294            self.events.append(("Ship_{}".format(self.id), "DryBulk", f"D.{self.selected_terminal}", "all_conveyors_disconnected",
295                               self.env.now, "Ship {} disconnected from conveyor {}".format(self.id, conveyor.id)))
296
297        # Depart from the berth
298        yield self.port_berths.put(self.current_berth)

Represents the process of detaching the vessel from the berth and departing from the port. This method handles the release of the allocated conveyors, updates the ship logs, and releases the berth. It also logs the events related to the vessel's departure.