simulation_classes.terminal_liquid
Uses liquid bulk terminal class objects to create liquid terminal processes. Vessel arrivals, berth and pipeline allocation, cargo unloading, cargo loading, vessel waiting, and vessel departure are simulated.
1""" 2Uses liquid bulk terminal class objects to create liquid terminal processes. 3Vessel arrivals, berth and pipeline 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, is_daytime 7from simulation_classes.pipeline import Pipeline 8import constants 9import simpy 10 11LIQUID_TERMINAL_EFFICIENCY = constants.LIQ_TERMINAL_EFFICIENCY 12TIME_COMMON_CHANNEL = constants.TIME_COMMON_CHANNEL 13TIME_FOR_TUG_STEER = constants.TIME_FOR_TUG_STEER 14TIME_FOR_UTURN = constants.TIME_FOR_UTURN 15 16nan = float('nan') 17 18 19class LiquidTerminal: 20 """ 21 LiquidTerminal class simulates the liquid bulk terminal operations, including ship arrivals, berthing, unloading, loading, waiting, and departure. 22 It manages the allocation of berths and pipelines, handles cargo operations, and logs events related to the terminal operations. 23 This class is initialized with various parameters such as the simulation environment, channel, pilots, tugboats, ship information, terminal data, and more. 24 The processes include the following steps: 25 1. Ship arrival and anchorage waiting. 26 2. Berth allocation. 27 3. Channel process for entering the terminal. 28 4. Unloading of liquid bulk cargo. 29 5. Loading of liquid bulk cargo. 30 6. Waiting time at the terminal. 31 7. Detachment from the berth and departure from the terminal. 32 8. Channel process for exiting the terminal. 33 The class also handles the allocation of pipelines based on the terminal type (import/export) and manages the storage of cargo in port tanks. 34 It logs all events and updates 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 (str): Unique identifier for the simulation run. 40 channel (Channel): The channel object for managing ship movements. 41 day_pilots (simpy.Container): Simpy container for day pilots available. 42 night_pilots (simpy.Container) : Simpy container for night pilots available. 43 tugboats (simpy.Container): Simpy container for tugboats available. 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., "Liquid"). 49 draft (float): Draft of the ship. 50 width (float): Width of the ship. 51 unload_time (float): Time taken to unload the ship. 52 load_time (float): Time taken to load the ship. 53 unload_tons (float): Amount of cargo to unload from the ship. 54 load_tons (float): Amount of cargo to load onto the ship. 55 events (list): List to store events related to the ship. 56 ship_logs (dict): Dictionary to store logs for the ship. 57 port_berths (simpy.Resource): Resource for managing port berths. 58 port_tanks (simpy.Container): Container for managing port tanks. 59 SHIPS_IN_CHANNEL (int): Number of ships currently in the channel. 60 SHIPS_IN_ANCHORAGE (int): Number of ships currently in the anchorage. 61 terminal_data (dict): Data related to the terminal. 62 liq_terminals_with_pipeline_source (list): List of liquid terminals with pipeline source. 63 liq_terminals_with_pipeline_sink (list): List of liquid terminals with pipeline sink. 64 """ 65 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_tanks, SHIPS_IN_CHANNEL, SHIPS_IN_ANCHORAGE, terminal_data, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink): 66 # Initialize the environment 67 self.env = env 68 self.run_id = run_id 69 self.SHIPS_IN_CHANNEL = SHIPS_IN_CHANNEL 70 self.SHIPS_IN_ANCHORAGE = SHIPS_IN_ANCHORAGE 71 self.terminal_data = terminal_data 72 73 # Initialize the channel 74 self.channel = channel 75 self.ship_info = ship_info 76 self.last_section = last_section 77 78 # Pilots and tugboats 79 self.day_pilots, self.night_pilots = day_pilots, night_pilots 80 self.tugboats = tugboats 81 82 # Set vessel and terminal attributes 83 self.id = id 84 self.selected_terminal = selected_terminal 85 self.ship_type = ship_type 86 self.num_pipelines = get_value_by_terminal( 87 self.terminal_data, 'Liquid', selected_terminal, 'transfer units per berth') 88 89 # Set vessel dimensions and cargo handling parameters 90 self.unload_tons = unload_tons 91 self.load_tons = load_tons 92 self.draft = draft 93 self.width = width 94 self.unload_time = unload_time 95 self.load_time = load_time 96 self.day = None 97 98 # Cargo wait time 99 self.cargo_wait_time = 0 100 101 # Pipelines 102 self.liq_terminals_with_pipeline_source = liq_terminals_with_pipeline_source 103 self.liq_terminals_with_pipeline_sink = liq_terminals_with_pipeline_sink 104 self.time_for_uturn_min, self.time_for_uturn_max = TIME_FOR_UTURN 105 106 # The allocation of pipelines and berth will be done during the process 107 self.current_berth = None 108 self.allocated_pipelines = [] 109 110 # Get the ship logs and events 111 self.events = events 112 self.ship_logs = ship_logs 113 114 # Allocate berth and pipelines 115 self.port_berths = port_berths 116 self.port_tanks = port_tanks 117 118 # Start the process 119 self.env.process(self.process()) 120 121 def process(self): 122 """ 123 Process for the liquid bulk terminal, simulating the arrival, berthing, unloading, loading, waiting, and departure of ships. 124 This method handles the entire lifecycle of a ship at the terminal, including logging events and updating ship logs.""" 125 126 self.ship_logs = update_ship_logs(self.ship_logs, "L", 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, 127 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, 128 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, 129 time_to_travel_channel_out=nan, time_to_common_channel_out=nan, ship_end_time=nan) 130 131 # ship arrival 132 ship_start_time = self.env.now 133 update_ship_logs(self.ship_logs, "L", self.id, 134 self.selected_terminal, ship_start_time=ship_start_time) 135 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", 136 "arrive", self.env.now, f"Ship {self.id} arrived at the port")) 137 138 # Anchorage waiting 139 yield self.env.timeout(constants.ANCHORAGE_WAITING_LIQUID) 140 141 # Berth allocation 142 start_time = self.env.now 143 yield self.env.process(self.arrive_and_berth()) 144 time_to_get_berth = self.env.now - ship_start_time 145 update_ship_logs(self.ship_logs, "L", self.id, 146 self.selected_terminal, time_to_get_berth=time_to_get_berth) 147 148 # Channel process (in) 149 self.day = is_daytime(self.env.now, 7, 19) 150 yield self.env.process(self.channel.channel_process(self.ship_info, self.day, self.last_section, self.id, self.selected_terminal, "L", self.run_id)) 151 152 # Terminal process 153 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", "dock", self.env.now, 154 f"Ship {self.id} docked to berth {self.current_berth.id} with waiting time {'N/A'}")) 155 start_time = self.env.now 156 yield self.env.process(self.unload()) 157 unloading_time = self.env.now - start_time 158 update_ship_logs(self.ship_logs, "L", self.id, 159 self.selected_terminal, unloading_time=unloading_time) 160 161 start_time = self.env.now 162 yield self.env.process(self.load()) 163 loading_time = self.env.now - start_time 164 update_ship_logs(self.ship_logs, "L", self.id, 165 self.selected_terminal, loading_time=loading_time) 166 167 start_time = self.env.now 168 yield self.env.process(self.wait()) 169 waiting_time = self.env.now - start_time 170 update_ship_logs(self.ship_logs, "L", self.id, 171 self.selected_terminal, waiting_time=waiting_time) 172 173 start_time = self.env.now 174 yield self.env.process(self.detach_and_depart()) 175 departure_time = self.env.now - start_time 176 update_ship_logs(self.ship_logs, "L", self.id, 177 self.selected_terminal, departure_time=departure_time) 178 179 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", "undock", self.env.now, 180 f"Ship {self.id} undocked to berth {self.current_berth.id} with waiting time {'N/A'}")) 181 182 # Channel process (out) 183 self.day = is_daytime(self.env.now, 7, 19) 184 yield self.env.process(self.channel.channel_process(self.ship_info, self.day, self.last_section, self.id, self.selected_terminal, "L", self.run_id)) 185 ship_end_time = self.env.now 186 update_ship_logs(self.ship_logs, "L", self.id, 187 self.selected_terminal, ship_end_time=ship_end_time) 188 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", "depart", self.env.now, 189 f"Ship {self.id} departed from berth {self.current_berth.id} with waiting time {'N/A'}")) 190 191 def arrive_and_berth(self): 192 """ 193 Represents the arrival of the ship at the terminal and allocation of a berth. 194 This method waits for a berth to become available and allocates it to the ship. 195 """ 196 # Wait for a berth to become available 197 self.current_berth = yield self.port_berths.get() 198 199 def unload(self): 200 """ 201 Represents the unloading of liquid bulk cargo from the ship at the terminal. 202 This method checks if the terminal is exporting or importing, allocates pipelines, and unloads the cargo. 203 It also handles the storage of the unloaded cargo in the port tanks and ensures that the tanks have enough capacity. 204 If the port tanks are full, it activates a pipeline sink to empty the tanks. 205 If the port tanks are empty, it activates a pipeline source to fill the tanks. 206 It also ensures that the unloading process does not exceed the tank capacity. 207 The method logs events related to the unloading process. 208 """ 209 210 # check if terminal is exporting or importing 211 import_terminal = get_value_by_terminal( 212 self.terminal_data, 'Liquid', self.selected_terminal, 'import') 213 if import_terminal: 214 # Request for the required number of pipelines 215 for _ in range(self.num_pipelines): 216 pipeline = yield self.current_berth.pipelines.get() 217 self.allocated_pipelines.append(pipeline) 218 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", 219 "all_pipelines_attached", self.env.now, f"Ship {self.id} pipelines allocated")) 220 221 # Unload liquid bulk cargo 222 total_unload_time = self.unload_tons * self.unload_time / self.num_pipelines 223 self.cargo_wait_time += total_unload_time 224 yield self.env.timeout(total_unload_time) 225 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 226 "all_liquid_unloaded", self.env.now, "Tanker {} unloaded all liquid".format(self.id))) 227 228 # Put liquid batches in tanks 229 if self.port_tanks.level + self.unload_tons >= 0.9 * self.port_tanks.capacity: 230 if self.selected_terminal in self.liq_terminals_with_pipeline_sink: 231 Pipeline(self.run_id, self.env, self.port_tanks, 232 mode='sink', rate=constants.PIPELINE_RATE) 233 with open(f'.{self.run_id}/logs/force_action.txt', 'a') as f: 234 f.write(f"Pipeline sink activated {self.env.now}") 235 f.write('\n') 236 237 # Do not offload until there is enough space in the port tanks (New feature added on 2025-03-24) #TODO: Check if this is the right way to do it 238 while self.port_tanks.level + self.unload_tons >= self.port_tanks.capacity: 239 yield self.env.timeout(1) 240 241 yield self.port_tanks.put(self.unload_tons) 242 243 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 244 "all_liquid_stored", self.env.now, "Tanker {} unloaded all liquid".format(self.id))) 245 else: 246 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", 247 "all_pipelines_attached", self.env.now, f"Ship {self.id} pipelines allocated")) 248 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 249 "all_liquid_unloaded", self.env.now, "Tanker {} unloaded all liquid".format(self.id))) 250 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 251 "all_liquid_stored", self.env.now, "Tanker {} unloaded all liquid".format(self.id))) 252 253 def load(self): 254 """ 255 Represents the loading of liquid bulk cargo onto the ship at the terminal. 256 This method checks if the terminal is exporting or importing, allocates pipelines, and loads the cargo. 257 It also handles the storage of the loaded cargo in the port tanks and ensures that the tanks have enough capacity. 258 If the port tanks are empty, it activates a pipeline source to fill the tanks. 259 It also ensures that the loading process does not exceed the tank capacity. 260 The method logs events related to the loading process. 261 """ 262 263 export_terminal = get_value_by_terminal( 264 self.terminal_data, 'Liquid', self.selected_terminal, 'export') 265 if export_terminal: 266 # Represents liquid bulk cargo being loaded onto the vessel at the input pump rate 267 total_load_time = self.load_tons * self.load_time / self.num_pipelines 268 # Load liquid bulk cargo 269 if self.port_tanks.level - self.load_tons <= 0.1 * self.port_tanks.capacity: 270 if self.selected_terminal in self.liq_terminals_with_pipeline_source: 271 Pipeline(self.run_id, self.env, self.port_tanks, 272 mode='source', rate=constants.PIPELINE_RATE) 273 with open(f'.{self.run_id}/logs/force_action.txt', 'a') as f: 274 f.write(f"Pipeline source activated {self.env.now}") 275 f.write('\n') 276 277 # Do not load until there is enough liquid in the port tanks (New feature added on 2025-03-24) #TODO: Check if this is the right way to do it 278 while self.port_tanks.level - self.load_tons <= 0.0 * self.port_tanks.capacity: 279 yield self.env.timeout(1) 280 281 yield self.port_tanks.get(self.load_tons) 282 self.cargo_wait_time += total_load_time 283 yield self.env.timeout(total_load_time) 284 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 285 "all_liquid_loaded", self.env.now, "Tanker {} loaded all liquid".format(self.id))) 286 else: 287 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 288 "all_liquid_loaded", self.env.now, "Tanker {} loaded all liquid".format(self.id))) 289 290 def wait(self): 291 """ 292 Represents the waiting time of the vessel at the terminal after loading or unloading. 293 This method simulates the waiting time before the vessel departs the terminal. 294 It calculates the waiting time based on the liquid terminal efficiency and the cargo wait time. 295 The method logs events related to the waiting process. 296 """ 297 port_waiting_time = ( 298 1/LIQUID_TERMINAL_EFFICIENCY - 1) * self.cargo_wait_time 299 yield self.env.timeout(port_waiting_time) 300 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 301 "wait", self.env.now, "Tanker {} waited at the port".format(self.id))) 302 303 def detach_and_depart(self): 304 """ 305 Represents the detachment of the ship from the berth and its departure from the terminal. 306 This method releases the allocated pipelines, puts the berth back into the port berths resource, and logs events related to the detachment and departure process. 307 """ 308 # Release the pipelines 309 for pipeline in self.allocated_pipelines: 310 yield self.current_berth.pipelines.put(pipeline) 311 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", "all_pipelines_disconnected", 312 self.env.now, "Tanker {} disconnected from pipeline {}".format(self.id, pipeline.id))) 313 314 # Depart from the berth 315 yield self.port_berths.put(self.current_berth)
20class LiquidTerminal: 21 """ 22 LiquidTerminal class simulates the liquid bulk terminal operations, including ship arrivals, berthing, unloading, loading, waiting, and departure. 23 It manages the allocation of berths and pipelines, handles cargo operations, and logs events related to the terminal operations. 24 This class is initialized with various parameters such as the simulation environment, channel, pilots, tugboats, ship information, terminal data, and more. 25 The processes include the following steps: 26 1. Ship arrival and anchorage waiting. 27 2. Berth allocation. 28 3. Channel process for entering the terminal. 29 4. Unloading of liquid bulk cargo. 30 5. Loading of liquid bulk cargo. 31 6. Waiting time at the terminal. 32 7. Detachment from the berth and departure from the terminal. 33 8. Channel process for exiting the terminal. 34 The class also handles the allocation of pipelines based on the terminal type (import/export) and manages the storage of cargo in port tanks. 35 It logs all events and updates 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 (str): Unique identifier for the simulation run. 41 channel (Channel): The channel object for managing ship movements. 42 day_pilots (simpy.Container): Simpy container for day pilots available. 43 night_pilots (simpy.Container) : Simpy container for night pilots available. 44 tugboats (simpy.Container): Simpy container for tugboats available. 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., "Liquid"). 50 draft (float): Draft of the ship. 51 width (float): Width of the ship. 52 unload_time (float): Time taken to unload the ship. 53 load_time (float): Time taken to load the ship. 54 unload_tons (float): Amount of cargo to unload from the ship. 55 load_tons (float): Amount of cargo to load onto the ship. 56 events (list): List to store events related to the ship. 57 ship_logs (dict): Dictionary to store logs for the ship. 58 port_berths (simpy.Resource): Resource for managing port berths. 59 port_tanks (simpy.Container): Container for managing port tanks. 60 SHIPS_IN_CHANNEL (int): Number of ships currently in the channel. 61 SHIPS_IN_ANCHORAGE (int): Number of ships currently in the anchorage. 62 terminal_data (dict): Data related to the terminal. 63 liq_terminals_with_pipeline_source (list): List of liquid terminals with pipeline source. 64 liq_terminals_with_pipeline_sink (list): List of liquid terminals with pipeline sink. 65 """ 66 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_tanks, SHIPS_IN_CHANNEL, SHIPS_IN_ANCHORAGE, terminal_data, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink): 67 # Initialize the environment 68 self.env = env 69 self.run_id = run_id 70 self.SHIPS_IN_CHANNEL = SHIPS_IN_CHANNEL 71 self.SHIPS_IN_ANCHORAGE = SHIPS_IN_ANCHORAGE 72 self.terminal_data = terminal_data 73 74 # Initialize the channel 75 self.channel = channel 76 self.ship_info = ship_info 77 self.last_section = last_section 78 79 # Pilots and tugboats 80 self.day_pilots, self.night_pilots = day_pilots, night_pilots 81 self.tugboats = tugboats 82 83 # Set vessel and terminal attributes 84 self.id = id 85 self.selected_terminal = selected_terminal 86 self.ship_type = ship_type 87 self.num_pipelines = get_value_by_terminal( 88 self.terminal_data, 'Liquid', selected_terminal, 'transfer units per berth') 89 90 # Set vessel dimensions and cargo handling parameters 91 self.unload_tons = unload_tons 92 self.load_tons = load_tons 93 self.draft = draft 94 self.width = width 95 self.unload_time = unload_time 96 self.load_time = load_time 97 self.day = None 98 99 # Cargo wait time 100 self.cargo_wait_time = 0 101 102 # Pipelines 103 self.liq_terminals_with_pipeline_source = liq_terminals_with_pipeline_source 104 self.liq_terminals_with_pipeline_sink = liq_terminals_with_pipeline_sink 105 self.time_for_uturn_min, self.time_for_uturn_max = TIME_FOR_UTURN 106 107 # The allocation of pipelines and berth will be done during the process 108 self.current_berth = None 109 self.allocated_pipelines = [] 110 111 # Get the ship logs and events 112 self.events = events 113 self.ship_logs = ship_logs 114 115 # Allocate berth and pipelines 116 self.port_berths = port_berths 117 self.port_tanks = port_tanks 118 119 # Start the process 120 self.env.process(self.process()) 121 122 def process(self): 123 """ 124 Process for the liquid bulk terminal, simulating the arrival, berthing, unloading, loading, waiting, and departure of ships. 125 This method handles the entire lifecycle of a ship at the terminal, including logging events and updating ship logs.""" 126 127 self.ship_logs = update_ship_logs(self.ship_logs, "L", 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, 128 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, 129 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, 130 time_to_travel_channel_out=nan, time_to_common_channel_out=nan, ship_end_time=nan) 131 132 # ship arrival 133 ship_start_time = self.env.now 134 update_ship_logs(self.ship_logs, "L", self.id, 135 self.selected_terminal, ship_start_time=ship_start_time) 136 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", 137 "arrive", self.env.now, f"Ship {self.id} arrived at the port")) 138 139 # Anchorage waiting 140 yield self.env.timeout(constants.ANCHORAGE_WAITING_LIQUID) 141 142 # Berth allocation 143 start_time = self.env.now 144 yield self.env.process(self.arrive_and_berth()) 145 time_to_get_berth = self.env.now - ship_start_time 146 update_ship_logs(self.ship_logs, "L", self.id, 147 self.selected_terminal, time_to_get_berth=time_to_get_berth) 148 149 # Channel process (in) 150 self.day = is_daytime(self.env.now, 7, 19) 151 yield self.env.process(self.channel.channel_process(self.ship_info, self.day, self.last_section, self.id, self.selected_terminal, "L", self.run_id)) 152 153 # Terminal process 154 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", "dock", self.env.now, 155 f"Ship {self.id} docked to berth {self.current_berth.id} with waiting time {'N/A'}")) 156 start_time = self.env.now 157 yield self.env.process(self.unload()) 158 unloading_time = self.env.now - start_time 159 update_ship_logs(self.ship_logs, "L", self.id, 160 self.selected_terminal, unloading_time=unloading_time) 161 162 start_time = self.env.now 163 yield self.env.process(self.load()) 164 loading_time = self.env.now - start_time 165 update_ship_logs(self.ship_logs, "L", self.id, 166 self.selected_terminal, loading_time=loading_time) 167 168 start_time = self.env.now 169 yield self.env.process(self.wait()) 170 waiting_time = self.env.now - start_time 171 update_ship_logs(self.ship_logs, "L", self.id, 172 self.selected_terminal, waiting_time=waiting_time) 173 174 start_time = self.env.now 175 yield self.env.process(self.detach_and_depart()) 176 departure_time = self.env.now - start_time 177 update_ship_logs(self.ship_logs, "L", self.id, 178 self.selected_terminal, departure_time=departure_time) 179 180 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", "undock", self.env.now, 181 f"Ship {self.id} undocked to berth {self.current_berth.id} with waiting time {'N/A'}")) 182 183 # Channel process (out) 184 self.day = is_daytime(self.env.now, 7, 19) 185 yield self.env.process(self.channel.channel_process(self.ship_info, self.day, self.last_section, self.id, self.selected_terminal, "L", self.run_id)) 186 ship_end_time = self.env.now 187 update_ship_logs(self.ship_logs, "L", self.id, 188 self.selected_terminal, ship_end_time=ship_end_time) 189 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", "depart", self.env.now, 190 f"Ship {self.id} departed from berth {self.current_berth.id} with waiting time {'N/A'}")) 191 192 def arrive_and_berth(self): 193 """ 194 Represents the arrival of the ship at the terminal and allocation of a berth. 195 This method waits for a berth to become available and allocates it to the ship. 196 """ 197 # Wait for a berth to become available 198 self.current_berth = yield self.port_berths.get() 199 200 def unload(self): 201 """ 202 Represents the unloading of liquid bulk cargo from the ship at the terminal. 203 This method checks if the terminal is exporting or importing, allocates pipelines, and unloads the cargo. 204 It also handles the storage of the unloaded cargo in the port tanks and ensures that the tanks have enough capacity. 205 If the port tanks are full, it activates a pipeline sink to empty the tanks. 206 If the port tanks are empty, it activates a pipeline source to fill the tanks. 207 It also ensures that the unloading process does not exceed the tank capacity. 208 The method logs events related to the unloading process. 209 """ 210 211 # check if terminal is exporting or importing 212 import_terminal = get_value_by_terminal( 213 self.terminal_data, 'Liquid', self.selected_terminal, 'import') 214 if import_terminal: 215 # Request for the required number of pipelines 216 for _ in range(self.num_pipelines): 217 pipeline = yield self.current_berth.pipelines.get() 218 self.allocated_pipelines.append(pipeline) 219 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", 220 "all_pipelines_attached", self.env.now, f"Ship {self.id} pipelines allocated")) 221 222 # Unload liquid bulk cargo 223 total_unload_time = self.unload_tons * self.unload_time / self.num_pipelines 224 self.cargo_wait_time += total_unload_time 225 yield self.env.timeout(total_unload_time) 226 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 227 "all_liquid_unloaded", self.env.now, "Tanker {} unloaded all liquid".format(self.id))) 228 229 # Put liquid batches in tanks 230 if self.port_tanks.level + self.unload_tons >= 0.9 * self.port_tanks.capacity: 231 if self.selected_terminal in self.liq_terminals_with_pipeline_sink: 232 Pipeline(self.run_id, self.env, self.port_tanks, 233 mode='sink', rate=constants.PIPELINE_RATE) 234 with open(f'.{self.run_id}/logs/force_action.txt', 'a') as f: 235 f.write(f"Pipeline sink activated {self.env.now}") 236 f.write('\n') 237 238 # Do not offload until there is enough space in the port tanks (New feature added on 2025-03-24) #TODO: Check if this is the right way to do it 239 while self.port_tanks.level + self.unload_tons >= self.port_tanks.capacity: 240 yield self.env.timeout(1) 241 242 yield self.port_tanks.put(self.unload_tons) 243 244 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 245 "all_liquid_stored", self.env.now, "Tanker {} unloaded all liquid".format(self.id))) 246 else: 247 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", 248 "all_pipelines_attached", self.env.now, f"Ship {self.id} pipelines allocated")) 249 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 250 "all_liquid_unloaded", self.env.now, "Tanker {} unloaded all liquid".format(self.id))) 251 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 252 "all_liquid_stored", self.env.now, "Tanker {} unloaded all liquid".format(self.id))) 253 254 def load(self): 255 """ 256 Represents the loading of liquid bulk cargo onto the ship at the terminal. 257 This method checks if the terminal is exporting or importing, allocates pipelines, and loads the cargo. 258 It also handles the storage of the loaded cargo in the port tanks and ensures that the tanks have enough capacity. 259 If the port tanks are empty, it activates a pipeline source to fill the tanks. 260 It also ensures that the loading process does not exceed the tank capacity. 261 The method logs events related to the loading process. 262 """ 263 264 export_terminal = get_value_by_terminal( 265 self.terminal_data, 'Liquid', self.selected_terminal, 'export') 266 if export_terminal: 267 # Represents liquid bulk cargo being loaded onto the vessel at the input pump rate 268 total_load_time = self.load_tons * self.load_time / self.num_pipelines 269 # Load liquid bulk cargo 270 if self.port_tanks.level - self.load_tons <= 0.1 * self.port_tanks.capacity: 271 if self.selected_terminal in self.liq_terminals_with_pipeline_source: 272 Pipeline(self.run_id, self.env, self.port_tanks, 273 mode='source', rate=constants.PIPELINE_RATE) 274 with open(f'.{self.run_id}/logs/force_action.txt', 'a') as f: 275 f.write(f"Pipeline source activated {self.env.now}") 276 f.write('\n') 277 278 # Do not load until there is enough liquid in the port tanks (New feature added on 2025-03-24) #TODO: Check if this is the right way to do it 279 while self.port_tanks.level - self.load_tons <= 0.0 * self.port_tanks.capacity: 280 yield self.env.timeout(1) 281 282 yield self.port_tanks.get(self.load_tons) 283 self.cargo_wait_time += total_load_time 284 yield self.env.timeout(total_load_time) 285 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 286 "all_liquid_loaded", self.env.now, "Tanker {} loaded all liquid".format(self.id))) 287 else: 288 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 289 "all_liquid_loaded", self.env.now, "Tanker {} loaded all liquid".format(self.id))) 290 291 def wait(self): 292 """ 293 Represents the waiting time of the vessel at the terminal after loading or unloading. 294 This method simulates the waiting time before the vessel departs the terminal. 295 It calculates the waiting time based on the liquid terminal efficiency and the cargo wait time. 296 The method logs events related to the waiting process. 297 """ 298 port_waiting_time = ( 299 1/LIQUID_TERMINAL_EFFICIENCY - 1) * self.cargo_wait_time 300 yield self.env.timeout(port_waiting_time) 301 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 302 "wait", self.env.now, "Tanker {} waited at the port".format(self.id))) 303 304 def detach_and_depart(self): 305 """ 306 Represents the detachment of the ship from the berth and its departure from the terminal. 307 This method releases the allocated pipelines, puts the berth back into the port berths resource, and logs events related to the detachment and departure process. 308 """ 309 # Release the pipelines 310 for pipeline in self.allocated_pipelines: 311 yield self.current_berth.pipelines.put(pipeline) 312 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", "all_pipelines_disconnected", 313 self.env.now, "Tanker {} disconnected from pipeline {}".format(self.id, pipeline.id))) 314 315 # Depart from the berth 316 yield self.port_berths.put(self.current_berth)
LiquidTerminal class simulates the liquid bulk terminal operations, including ship arrivals, berthing, unloading, loading, waiting, and departure. It manages the allocation of berths and pipelines, handles cargo operations, and logs events related to the terminal operations. This class is initialized with various parameters such as the simulation environment, channel, pilots, tugboats, ship information, terminal data, and more. The processes include the following steps:
- Ship arrival and anchorage waiting.
- Berth allocation.
- Channel process for entering the terminal.
- Unloading of liquid bulk cargo.
- Loading of liquid bulk cargo.
- Waiting time at the terminal.
- Detachment from the berth and departure from the terminal.
- Channel process for exiting the terminal. The class also handles the allocation of pipelines based on the terminal type (import/export) and manages the storage of cargo in port tanks. It logs all events and updates ship logs throughout the process.
Arguments:
- env (simpy.Environment): The simulation environment.
- chassis_bays_utilization (float): Utilization of chassis bays.
- run_id (str): Unique identifier for the simulation run.
- channel (Channel): The channel object for managing ship movements.
- day_pilots (simpy.Container): Simpy container for day pilots available.
- night_pilots (simpy.Container) : Simpy container for night pilots available.
- tugboats (simpy.Container): Simpy container for tugboats available.
- 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., "Liquid").
- draft (float): Draft of the ship.
- width (float): Width of the ship.
- unload_time (float): Time taken to unload the ship.
- load_time (float): Time taken to load the ship.
- unload_tons (float): Amount of cargo to unload from the ship.
- load_tons (float): Amount of cargo to load onto the ship.
- events (list): List to store events related to the ship.
- ship_logs (dict): Dictionary to store logs for the ship.
- port_berths (simpy.Resource): Resource for managing port berths.
- port_tanks (simpy.Container): Container for managing port tanks.
- SHIPS_IN_CHANNEL (int): Number of ships currently in the channel.
- SHIPS_IN_ANCHORAGE (int): Number of ships currently in the anchorage.
- terminal_data (dict): Data related to the terminal.
- liq_terminals_with_pipeline_source (list): List of liquid terminals with pipeline source.
- liq_terminals_with_pipeline_sink (list): List of liquid terminals with pipeline sink.
122 def process(self): 123 """ 124 Process for the liquid bulk terminal, simulating the arrival, berthing, unloading, loading, waiting, and departure of ships. 125 This method handles the entire lifecycle of a ship at the terminal, including logging events and updating ship logs.""" 126 127 self.ship_logs = update_ship_logs(self.ship_logs, "L", 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, 128 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, 129 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, 130 time_to_travel_channel_out=nan, time_to_common_channel_out=nan, ship_end_time=nan) 131 132 # ship arrival 133 ship_start_time = self.env.now 134 update_ship_logs(self.ship_logs, "L", self.id, 135 self.selected_terminal, ship_start_time=ship_start_time) 136 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", 137 "arrive", self.env.now, f"Ship {self.id} arrived at the port")) 138 139 # Anchorage waiting 140 yield self.env.timeout(constants.ANCHORAGE_WAITING_LIQUID) 141 142 # Berth allocation 143 start_time = self.env.now 144 yield self.env.process(self.arrive_and_berth()) 145 time_to_get_berth = self.env.now - ship_start_time 146 update_ship_logs(self.ship_logs, "L", self.id, 147 self.selected_terminal, time_to_get_berth=time_to_get_berth) 148 149 # Channel process (in) 150 self.day = is_daytime(self.env.now, 7, 19) 151 yield self.env.process(self.channel.channel_process(self.ship_info, self.day, self.last_section, self.id, self.selected_terminal, "L", self.run_id)) 152 153 # Terminal process 154 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", "dock", self.env.now, 155 f"Ship {self.id} docked to berth {self.current_berth.id} with waiting time {'N/A'}")) 156 start_time = self.env.now 157 yield self.env.process(self.unload()) 158 unloading_time = self.env.now - start_time 159 update_ship_logs(self.ship_logs, "L", self.id, 160 self.selected_terminal, unloading_time=unloading_time) 161 162 start_time = self.env.now 163 yield self.env.process(self.load()) 164 loading_time = self.env.now - start_time 165 update_ship_logs(self.ship_logs, "L", self.id, 166 self.selected_terminal, loading_time=loading_time) 167 168 start_time = self.env.now 169 yield self.env.process(self.wait()) 170 waiting_time = self.env.now - start_time 171 update_ship_logs(self.ship_logs, "L", self.id, 172 self.selected_terminal, waiting_time=waiting_time) 173 174 start_time = self.env.now 175 yield self.env.process(self.detach_and_depart()) 176 departure_time = self.env.now - start_time 177 update_ship_logs(self.ship_logs, "L", self.id, 178 self.selected_terminal, departure_time=departure_time) 179 180 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", "undock", self.env.now, 181 f"Ship {self.id} undocked to berth {self.current_berth.id} with waiting time {'N/A'}")) 182 183 # Channel process (out) 184 self.day = is_daytime(self.env.now, 7, 19) 185 yield self.env.process(self.channel.channel_process(self.ship_info, self.day, self.last_section, self.id, self.selected_terminal, "L", self.run_id)) 186 ship_end_time = self.env.now 187 update_ship_logs(self.ship_logs, "L", self.id, 188 self.selected_terminal, ship_end_time=ship_end_time) 189 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", "depart", self.env.now, 190 f"Ship {self.id} departed from berth {self.current_berth.id} with waiting time {'N/A'}"))
Process for the liquid bulk terminal, simulating the arrival, berthing, unloading, loading, waiting, and departure of ships. This method handles the entire lifecycle of a ship at the terminal, including logging events and updating ship logs.
192 def arrive_and_berth(self): 193 """ 194 Represents the arrival of the ship at the terminal and allocation of a berth. 195 This method waits for a berth to become available and allocates it to the ship. 196 """ 197 # Wait for a berth to become available 198 self.current_berth = yield self.port_berths.get()
Represents the arrival of the ship at the terminal and allocation of a berth. This method waits for a berth to become available and allocates it to the ship.
200 def unload(self): 201 """ 202 Represents the unloading of liquid bulk cargo from the ship at the terminal. 203 This method checks if the terminal is exporting or importing, allocates pipelines, and unloads the cargo. 204 It also handles the storage of the unloaded cargo in the port tanks and ensures that the tanks have enough capacity. 205 If the port tanks are full, it activates a pipeline sink to empty the tanks. 206 If the port tanks are empty, it activates a pipeline source to fill the tanks. 207 It also ensures that the unloading process does not exceed the tank capacity. 208 The method logs events related to the unloading process. 209 """ 210 211 # check if terminal is exporting or importing 212 import_terminal = get_value_by_terminal( 213 self.terminal_data, 'Liquid', self.selected_terminal, 'import') 214 if import_terminal: 215 # Request for the required number of pipelines 216 for _ in range(self.num_pipelines): 217 pipeline = yield self.current_berth.pipelines.get() 218 self.allocated_pipelines.append(pipeline) 219 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", 220 "all_pipelines_attached", self.env.now, f"Ship {self.id} pipelines allocated")) 221 222 # Unload liquid bulk cargo 223 total_unload_time = self.unload_tons * self.unload_time / self.num_pipelines 224 self.cargo_wait_time += total_unload_time 225 yield self.env.timeout(total_unload_time) 226 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 227 "all_liquid_unloaded", self.env.now, "Tanker {} unloaded all liquid".format(self.id))) 228 229 # Put liquid batches in tanks 230 if self.port_tanks.level + self.unload_tons >= 0.9 * self.port_tanks.capacity: 231 if self.selected_terminal in self.liq_terminals_with_pipeline_sink: 232 Pipeline(self.run_id, self.env, self.port_tanks, 233 mode='sink', rate=constants.PIPELINE_RATE) 234 with open(f'.{self.run_id}/logs/force_action.txt', 'a') as f: 235 f.write(f"Pipeline sink activated {self.env.now}") 236 f.write('\n') 237 238 # Do not offload until there is enough space in the port tanks (New feature added on 2025-03-24) #TODO: Check if this is the right way to do it 239 while self.port_tanks.level + self.unload_tons >= self.port_tanks.capacity: 240 yield self.env.timeout(1) 241 242 yield self.port_tanks.put(self.unload_tons) 243 244 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 245 "all_liquid_stored", self.env.now, "Tanker {} unloaded all liquid".format(self.id))) 246 else: 247 self.events.append((f"Ship_{self.id}", "Liquid", f"L.{self.selected_terminal}", 248 "all_pipelines_attached", self.env.now, f"Ship {self.id} pipelines allocated")) 249 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 250 "all_liquid_unloaded", self.env.now, "Tanker {} unloaded all liquid".format(self.id))) 251 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 252 "all_liquid_stored", self.env.now, "Tanker {} unloaded all liquid".format(self.id)))
Represents the unloading of liquid bulk cargo from the ship at the terminal. This method checks if the terminal is exporting or importing, allocates pipelines, and unloads the cargo. It also handles the storage of the unloaded cargo in the port tanks and ensures that the tanks have enough capacity. If the port tanks are full, it activates a pipeline sink to empty the tanks. If the port tanks are empty, it activates a pipeline source to fill the tanks. It also ensures that the unloading process does not exceed the tank capacity. The method logs events related to the unloading process.
254 def load(self): 255 """ 256 Represents the loading of liquid bulk cargo onto the ship at the terminal. 257 This method checks if the terminal is exporting or importing, allocates pipelines, and loads the cargo. 258 It also handles the storage of the loaded cargo in the port tanks and ensures that the tanks have enough capacity. 259 If the port tanks are empty, it activates a pipeline source to fill the tanks. 260 It also ensures that the loading process does not exceed the tank capacity. 261 The method logs events related to the loading process. 262 """ 263 264 export_terminal = get_value_by_terminal( 265 self.terminal_data, 'Liquid', self.selected_terminal, 'export') 266 if export_terminal: 267 # Represents liquid bulk cargo being loaded onto the vessel at the input pump rate 268 total_load_time = self.load_tons * self.load_time / self.num_pipelines 269 # Load liquid bulk cargo 270 if self.port_tanks.level - self.load_tons <= 0.1 * self.port_tanks.capacity: 271 if self.selected_terminal in self.liq_terminals_with_pipeline_source: 272 Pipeline(self.run_id, self.env, self.port_tanks, 273 mode='source', rate=constants.PIPELINE_RATE) 274 with open(f'.{self.run_id}/logs/force_action.txt', 'a') as f: 275 f.write(f"Pipeline source activated {self.env.now}") 276 f.write('\n') 277 278 # Do not load until there is enough liquid in the port tanks (New feature added on 2025-03-24) #TODO: Check if this is the right way to do it 279 while self.port_tanks.level - self.load_tons <= 0.0 * self.port_tanks.capacity: 280 yield self.env.timeout(1) 281 282 yield self.port_tanks.get(self.load_tons) 283 self.cargo_wait_time += total_load_time 284 yield self.env.timeout(total_load_time) 285 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 286 "all_liquid_loaded", self.env.now, "Tanker {} loaded all liquid".format(self.id))) 287 else: 288 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 289 "all_liquid_loaded", self.env.now, "Tanker {} loaded all liquid".format(self.id)))
Represents the loading of liquid bulk cargo onto the ship at the terminal. This method checks if the terminal is exporting or importing, allocates pipelines, and loads the cargo. It also handles the storage of the loaded cargo in the port tanks and ensures that the tanks have enough capacity. If the port tanks are empty, it activates a pipeline source to fill the tanks. It also ensures that the loading process does not exceed the tank capacity. The method logs events related to the loading process.
291 def wait(self): 292 """ 293 Represents the waiting time of the vessel at the terminal after loading or unloading. 294 This method simulates the waiting time before the vessel departs the terminal. 295 It calculates the waiting time based on the liquid terminal efficiency and the cargo wait time. 296 The method logs events related to the waiting process. 297 """ 298 port_waiting_time = ( 299 1/LIQUID_TERMINAL_EFFICIENCY - 1) * self.cargo_wait_time 300 yield self.env.timeout(port_waiting_time) 301 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", 302 "wait", self.env.now, "Tanker {} waited at the port".format(self.id)))
Represents the waiting time of the vessel at the terminal after loading or unloading. This method simulates the waiting time before the vessel departs the terminal. It calculates the waiting time based on the liquid terminal efficiency and the cargo wait time. The method logs events related to the waiting process.
304 def detach_and_depart(self): 305 """ 306 Represents the detachment of the ship from the berth and its departure from the terminal. 307 This method releases the allocated pipelines, puts the berth back into the port berths resource, and logs events related to the detachment and departure process. 308 """ 309 # Release the pipelines 310 for pipeline in self.allocated_pipelines: 311 yield self.current_berth.pipelines.put(pipeline) 312 self.events.append(("Tanker_{}".format(self.id), "Liquid", f"L.{self.selected_terminal}", "all_pipelines_disconnected", 313 self.env.now, "Tanker {} disconnected from pipeline {}".format(self.id, pipeline.id))) 314 315 # Depart from the berth 316 yield self.port_berths.put(self.current_berth)
Represents the detachment of the ship from the berth and its departure from the terminal. This method releases the allocated pipelines, puts the berth back into the port berths resource, and logs events related to the detachment and departure process.