simulation_handler.generators

Generates resources and processes needed to run the simulation using the object classes defined in truck.py, Channel.py, and Port.py and the DES processes contained in ContainerTerminal.py, LiquidTerminal.py, and DryBulkTerminal.py.

  1"""
  2Generates resources and processes needed to run the simulation using the object classes defined in truck.py, Channel.py, and Port.py 
  3and the DES processes contained in ContainerTerminal.py, LiquidTerminal.py, and DryBulkTerminal.py.
  4"""
  5
  6import os
  7import random
  8
  9import numpy as np
 10import pandas as pd
 11from tqdm import tqdm
 12
 13import constants
 14from simulation_classes.terminal_container import ContainerTerminal
 15from simulation_classes.terminal_liquid import LiquidTerminal
 16from simulation_classes.terminal_drybulk import DryBulkTerminal
 17from simulation_classes.truck import Truck
 18from simulation_classes.train import Train
 19from simulation_classes.pipeline import Pipeline
 20from simulation_classes.port import Container, get_value_by_terminal, get_value_from_terminal_tuple
 21from simulation_classes.channel import Channel
 22from simulation_analysis.results import plot_queues, track_utilization, save_track_list, get_utilization
 23
 24SIMULATION_TIME = constants.SIMULATION_TIME
 25PIPELINE_RATE = constants.PIPELINE_RATE
 26CHANNEL_SAFETWOWAY = constants.CHANNEL_SAFETWOWAY
 27
 28
 29def initialize_rng(seed):
 30    """
 31    Initialize random state objects for repeatable randomness across runs.
 32    This function sets the global random number generator states for both the built-in `random` module and NumPy's random module.
 33    This is useful for ensuring that the simulation can be reproduced with the same random events.
 34    This function should be called at the start of the simulation to ensure that all random processes are initialized with the same seed.
 35    Args:
 36        seed (int): The seed value for the random number generator.
 37    Returns:    
 38        None
 39    """
 40    global rng_random, rng_numpy
 41    rng_random = random.Random(seed)
 42    rng_numpy = np.random.default_rng(seed)
 43
 44
 45def update_availability(run_id, env, it, port_berths_container_terminals, port_yard_container_terminals, port_berths_liquid_terminals, port_tanks_liquid_terminals, port_berths_drybulk_terminals, port_silos_drybulk_terminals, availability_df_container, availability_df_liquid, availability_df_drybulk, ship_data, terminal_data):
 46    """
 47    This function updates the availability of berths, yards, tanks, and silos at the port terminals.
 48    It calculates the number of available and used resources for each terminal type (Container, Liquid, DryBulk) and appends this data to the respective DataFrames.
 49    This function also tracks the queue lengths at each terminal type and updates the availability DataFrames with the current time and resource availability.
 50
 51    Args:
 52        run_id (str): The unique identifier for the simulation run.
 53        env (simpy.Environment): The simulation environment.
 54        it (int): The current iteration of the simulation.
 55        port_berths_container_terminals (list): List of container terminal berths.
 56        port_yard_container_terminals (list): List of container terminal yards.
 57        port_berths_liquid_terminals (list): List of liquid terminal berths.
 58        port_tanks_liquid_terminals (list): List of liquid terminal tanks.
 59        port_berths_drybulk_terminals (list): List of dry bulk terminal berths.
 60        port_silos_drybulk_terminals (list): List of dry bulk terminal silos.
 61        availability_df_container (pd.DataFrame): DataFrame to store container terminal availability data.
 62        availability_df_liquid (pd.DataFrame): DataFrame to store liquid terminal availability data.
 63        availability_df_drybulk (pd.DataFrame): DataFrame to store dry bulk terminal availability data.
 64        ship_data (dict): Dictionary containing ship data for the simulation.
 65        terminal_data (dict): Dictionary containing terminal data for the simulation.
 66    Returns:
 67        availability_df_container (pd.DataFrame): Updated DataFrame with container terminal availability data.
 68        availability_df_liquid (pd.DataFrame): Updated DataFrame with liquid terminal availability data.
 69        availability_df_drybulk (pd.DataFrame): Updated DataFrame with dry bulk terminal availability data.
 70        queue_lengths_in_ctr (list): List of queue lengths in container terminals.
 71        queue_lengths_in_liq (list): List of queue lengths in liquid terminals.
 72        queue_lengths_in_drybulk (list): List of queue lengths in dry bulk terminals.
 73    """
 74    # Initialize lists to store vessel availability data for each terminal
 75    berth_availability_data_container = []
 76    yard_availability_data = []
 77    berth_availability_data_liquid = []
 78    tank_availability_data = []
 79    berth_availability_data_drybulk = []
 80    silo_availability_data = []
 81    queue_lengths_in_ctr = []
 82    queue_lengths_in_liq = []
 83    queue_lengths_in_drybulk = []
 84
 85    # Container Terminals
 86    for i, (port_berths, port_yard) in enumerate(zip(port_berths_container_terminals, port_yard_container_terminals)):
 87        # Determine the vessel queue length and number of available berths for a container terminal
 88        queue = len(port_berths.get_queue)
 89        queue_lengths_in_ctr.append(queue)
 90        available_port_berths = len(port_berths.items)
 91        used_port_berths = get_value_by_terminal(
 92            terminal_data, "Container", i+1, "Berths") - available_port_berths
 93        berth_availability_data_container.extend(
 94            [available_port_berths, used_port_berths, queue])
 95
 96        # Determine the number of containers stored on the dock (yard) and the remaining available storage space
 97        used_yard = len(port_yard.items)
 98        available_yard = get_value_by_terminal(
 99            terminal_data, "Container", i+1, "storage volume") - used_yard
100        yard_availability_data.extend(
101            [available_yard, used_yard])
102
103    # Liquid Terminals
104    for i, port_berths in enumerate(port_berths_liquid_terminals):
105        # Determine the vessel queue length and number of available berths for a liquid bulk terminal
106        queue = len(port_berths.get_queue)
107        queue_lengths_in_liq.append(queue)
108        available_port_berths = len(port_berths.items)
109        used_port_berths = get_value_by_terminal(
110            terminal_data, "Liquid", i+1, "Berths") - available_port_berths
111        berth_availability_data_liquid.extend(
112            [available_port_berths, used_port_berths, queue])
113
114    for i, port_tanks in enumerate(port_tanks_liquid_terminals):
115        # Determine the amount of storage space used and available in the liquid bulk tanks
116        used_tank = port_tanks.level
117        available_tank = get_value_by_terminal(
118            terminal_data, "Liquid", i+1, "storage volume") - used_tank
119        tank_availability_data.extend([available_tank, used_tank])
120
121    # Dry Bulk Terminals
122    for i, port_berths in enumerate(port_berths_drybulk_terminals):
123        # Determine the vessel queue length and number of available berths for a dry bulk terminal
124        queue = len(port_berths.get_queue)
125        queue_lengths_in_drybulk.append(queue)
126        available_port_berths = len(port_berths.items)
127        used_port_berths = get_value_by_terminal(
128            terminal_data, "DryBulk", i+1, "Berths") - available_port_berths
129        berth_availability_data_drybulk.extend(
130            [available_port_berths, used_port_berths, queue])
131
132    for i, port_silos in enumerate(port_silos_drybulk_terminals):
133        # Determine the amount of storage space used and available in the dry bulk silos
134        used_silo = port_silos.level
135        available_silo = get_value_by_terminal(
136            terminal_data, "DryBulk", i+1, "storage volume") - used_silo
137        silo_availability_data.extend([available_silo, used_silo])
138
139    # Update DataFrames for each terminal type to track storage availability over the span of the simulation
140    availability_container = [
141        env.now] + berth_availability_data_container + yard_availability_data
142    columns_container = ["Time"] + [f"Terminal_{i+1}_{col}" for i in range(len(port_berths_container_terminals)) for col in ["Available_Berth_Ctr", "Used_Berth_Ctr", "Berth_Queue_Ctr"]] + [
143        f"Terminal_{i+1}_{col}" for i in range(len(port_yard_container_terminals)) for col in ["Available_Yard", "Used_Yard"]]
144    row_container = pd.DataFrame(
145        [availability_container], columns=columns_container)
146    availability_df_container = pd.concat(
147        [availability_df_container, row_container], ignore_index=True)
148
149    availability_liquid = [env.now] + \
150        berth_availability_data_liquid + tank_availability_data
151    columns_liquid = ["Time"] + [f"Terminal_{i+1}_{col}" for i in range(len(port_berths_liquid_terminals)) for col in ["Available_Berth_liq", "Used_Berth_liq", "Berth_Queue_liq"]] + [
152        f"Terminal_{i+1}_{col}" for i in range(len(port_tanks_liquid_terminals)) for col in ["Available_Tank", "Used_Tank"]]
153    row_liquid = pd.DataFrame([availability_liquid], columns=columns_liquid)
154    availability_df_liquid = pd.concat(
155        [availability_df_liquid, row_liquid], ignore_index=True)
156
157    availability_drybulk = [env.now] + \
158        berth_availability_data_drybulk + silo_availability_data
159    columns_drybulk = ["Time"] + [f"Terminal_{i+1}_{col}" for i in range(len(port_berths_drybulk_terminals)) for col in ["Available_Berth_db", "Used_Berth_db", "Berth_Queue_db"]] + [
160        f"Terminal_{i+1}_{col}" for i in range(len(port_silos_drybulk_terminals)) for col in ["Available_Silo", "Used_Silo"]]
161    row_drybulk = pd.DataFrame([availability_drybulk], columns=columns_drybulk)
162    availability_df_drybulk = pd.concat(
163        [availability_df_drybulk, row_drybulk], ignore_index=True)
164
165    track_list = get_utilization(
166        availability_df_container, availability_df_liquid, availability_df_drybulk, run_id)
167
168    # innitialise empty dataframes
169    if it == 0:
170        availability_df_liquid = pd.DataFrame()
171        availability_df_drybulk = pd.DataFrame()
172    if it > 2:  # save from second iterarion
173        save_track_list(run_id, env.now, track_list)
174    if it == len(ship_data):
175        # Write the data to an Excel file, with different sheets for Container, Liquid Bulk, and Dry Bulk Terminals.
176        file_exists = os.path.isfile("./logs/availability.xlsx")
177        if file_exists:
178            mode = 'a'
179            if_sheet_exists = 'replace'
180        else:
181            mode = 'w'
182            if_sheet_exists = None  # Not used in write mode
183
184        with pd.ExcelWriter(f".{run_id}/logs/availability.xlsx", engine="openpyxl", mode=mode, if_sheet_exists=if_sheet_exists) as writer:
185            availability_df_container.to_excel(
186                writer, sheet_name="Container_Terminals", index=False)
187            availability_df_liquid.to_excel(
188                writer, sheet_name="Liquid_Terminals", index=False)
189            availability_df_drybulk.to_excel(
190                writer, sheet_name="Dry_Bulk_Terminals", index=False)
191
192    return availability_df_container, availability_df_liquid, availability_df_drybulk, queue_lengths_in_ctr, queue_lengths_in_liq, queue_lengths_in_drybulk
193
194
195def create_containers(ship_id, ship_info):
196    """
197    This function creates a list of Container objects to be loaded and unloaded from a ship.
198    It generates the number of containers based on the ship's information, specifically the width and the number of containers to load and unload.
199    Args:
200        ship_id (str): The unique identifier for the ship.
201        ship_info (dict): A dictionary containing information about the ship, including its width and the number of containers to load and unload.
202    Returns:
203        containers_to_unload (list): A list of Container objects representing the containers to be unloaded from the ship.
204        containers_to_load (list): A list of Container objects representing the containers to be loaded onto the ship.
205    """
206    containers_to_unload = [Container(id=f"{ship_id}_unload_{j}", width=ship_info['width']) for j in range(
207        int(ship_info['num_container_or_liq_tons_or_dry_tons_to_unload']))]
208    containers_to_load = [Container(id=f"{ship_id}_load_{j}", width=ship_info['width'])for j in range(
209        int(ship_info['num_container_or_liq_tons_or_dry_tons_to_load']))]
210    return containers_to_unload, containers_to_load
211
212
213def tons_to_unload_load(ship_id, ship_info):
214    """
215    This function retrieves the number of tons to unload and load for a ship based on its information.
216    Args:
217        ship_id (str): The unique identifier for the ship.
218        ship_info (dict): A dictionary containing information about the ship, including the number of tons to unload and load.
219    Returns:
220        unload_tons (int): The number of tons to unload from the ship.
221        load_tons (int): The number of tons to load onto the ship.
222    """
223    unload_tons = ship_info['num_container_or_liq_tons_or_dry_tons_to_unload']
224    load_tons = ship_info['num_container_or_liq_tons_or_dry_tons_to_load']
225    return unload_tons, load_tons
226
227
228
229def ship_generator(run_id, env, chassis_bays_utilization, port_berths_container_terminals, port_yard_container_terminals, port_berths_liquid_terminals, port_tanks_liquid_terminals,
230                   port_berths_drybulk_terminals, port_silos_drybulk_terminals, channel, day_pilots, night_pilots, tugboats, events, ship_logs, channel_events, channel_logs, SHIPS_IN_ANCHORAGE, SHIPS_IN_CHANNEL, ship_data, terminal_data, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink):
231    """
232    This function generates ships and processes them in the simulation environment.
233    It iterates through the ship data, simulating the arrival of each ship at the port and processing it based on its type (Container, Liquid, DryBulk).
234    The ContainerTerminal, LiquidTerminal, and DryBulkTerminal classes simulates the vessel's operations at the port.
235    It updates the availability of resources at the port terminals and tracks the number of ships in the channel and anchorage.
236    Args:
237        run_id (str): The unique identifier for the simulation run.
238        env (simpy.Environment): The simulation environment.
239        chassis_bays_utilization (dict): A dictionary to track chassis bays utilization.
240        port_berths_container_terminals (list): List of container terminal berths.
241        port_yard_container_terminals (list): List of container terminal yards.
242        port_berths_liquid_terminals (list): List of liquid terminal berths.
243        port_tanks_liquid_terminals (list): List of liquid terminal tanks.
244        port_berths_drybulk_terminals (list): List of dry bulk terminal berths.
245        port_silos_drybulk_terminals (list): List of dry bulk terminal silos.
246        channel (Channel): The channel object for managing ship movements.
247        day_pilots (int): Number of day pilots available.
248        night_pilots (int): Number of night pilots available.
249        tugboats (int): Number of tugboats available.
250        events (list): List to store events during the simulation.
251        ship_logs (list): List to store ship logs during the simulation.
252        channel_events (list): List to store channel events during the simulation.
253        channel_logs (list): List to store channel logs during the simulation.
254        SHIPS_IN_ANCHORAGE (list): List to track ships in anchorage by type.
255        SHIPS_IN_CHANNEL (list): List to track ships in the channel.
256        ship_data (dict): Dictionary containing ship data for the simulation.
257        terminal_data (dict): Dictionary containing terminal data for the simulation.
258        liq_terminals_with_pipeline_source (list): List of liquid terminals with pipeline source connections.
259        liq_terminals_with_pipeline_sink (list): List of liquid terminals with pipeline sink connections.
260    Yields:
261        simpy.Timeout: A timeout event to simulate the arrival of each ship at the port.
262    Returns:
263        None
264    """ 
265    i = 0
266    terminal_data = terminal_data
267
268    availability_df_container = pd.DataFrame()
269    availability_df_liquid = pd.DataFrame()
270    availability_df_drybulk = pd.DataFrame()
271
272    queue_ctr_its = []
273    queue_liq_its = []
274    queue_drybulk_its = []
275
276    SHIPS_IN_CHANNEL_TRACK = []
277    SHIPS_IN_ANCHORAGE_TRACK = []
278    CONTAINER_VES_IN_ANCHORAGE = []
279    LIQUID_VES_IN_ANCHORAGE = []
280    DRYBULK_VES_IN_ANCHORAGE = []
281
282    it = 0
283
284    for ship_id, ship_info in tqdm(ship_data.items(), desc=f"Simulation Progress {run_id}"):
285        it += 1
286        yield env.timeout(ship_info['arrival'] - env.now)
287        if ship_data[ship_id]['ship_type'] == 'Container':
288            SHIPS_IN_ANCHORAGE[0] += 1
289        elif ship_data[ship_id]['ship_type'] == 'Liquid':
290            SHIPS_IN_ANCHORAGE[1] += 1
291        elif ship_data[ship_id]['ship_type'] == 'DryBulk':
292            SHIPS_IN_ANCHORAGE[2] += 1
293
294        selected_port = ship_info["terminal"] - 1
295        last_section = ship_info["last_section"]
296
297        # Container ship processes
298        if ship_info['ship_type'] == 'Container':
299            port_berths = port_berths_container_terminals[selected_port]
300            port_yard = port_yard_container_terminals[selected_port]
301            selected_terminal = selected_port + 1
302            availability_df_container, availability_df_liquid, availability_df_drybulk, queue_lengths_in_ctr, queue_lengths_in_liq, queue_lengths_in_drybulk = update_availability(run_id, env, it, port_berths_container_terminals,
303                                                                                                                                                                                   port_yard_container_terminals, port_berths_liquid_terminals, port_tanks_liquid_terminals, port_berths_drybulk_terminals, port_silos_drybulk_terminals, availability_df_container, availability_df_liquid, availability_df_drybulk, ship_data, terminal_data)
304            queue_ctr_its.append(queue_lengths_in_ctr)
305            containers_to_unload, containers_to_load = create_containers(
306                ship_id, ship_info)
307            transfer_rate = get_value_by_terminal(
308                terminal_data, "Container", terminal_id=ship_info["terminal"], resource_name="transfer rate per unit")
309            transfer_time = 1 / transfer_rate
310            ContainerTerminal(env, chassis_bays_utilization, run_id, channel, day_pilots, night_pilots, tugboats, ship_info, last_section, selected_terminal=selected_terminal, id=ship_id, ship_type=ship_info['ship_type'], draft=ship_info['draft'],
311                              width=ship_info['width'], unload_time_per_container=transfer_time, load_time_per_container=transfer_time, containers_to_unload=containers_to_unload, containers_to_load=containers_to_load, events=events, ship_logs=ship_logs, port_berths=port_berths, port_yard=port_yard, SHIPS_IN_CHANNEL=SHIPS_IN_CHANNEL, SHIPS_IN_ANCHORAGE=SHIPS_IN_ANCHORAGE, terminal_data=terminal_data)
312       
313        # Liquid and Dry Bulk ship processes
314        elif ship_info['ship_type'] == 'Liquid':
315            port_berths = port_berths_liquid_terminals[selected_port]
316            port_tanks = port_tanks_liquid_terminals[selected_port]
317            selected_terminal = selected_port + 1
318            availability_df_container, availability_df_liquid, availability_df_drybulk, queue_lengths_in_ctr, queue_lengths_in_liq, queue_lengths_in_drybulk = update_availability(run_id, env, it, port_berths_container_terminals,
319                                                                                                                                                                                   port_yard_container_terminals, port_berths_liquid_terminals, port_tanks_liquid_terminals, port_berths_drybulk_terminals, port_silos_drybulk_terminals, availability_df_container, availability_df_liquid, availability_df_drybulk, ship_data, terminal_data)
320            queue_liq_its.append(queue_lengths_in_liq)
321            unload_tons, load_tons = tons_to_unload_load(ship_id, ship_info)
322            transfer_rate = get_value_by_terminal(
323                terminal_data, "Liquid", terminal_id=ship_info["terminal"], resource_name="transfer rate per unit")
324            transfer_time = 1 / transfer_rate
325            LiquidTerminal(env, chassis_bays_utilization, run_id, channel, day_pilots, night_pilots, tugboats, ship_info, last_section, selected_terminal, id=ship_id, ship_type=ship_info['ship_type'], draft=ship_info['draft'],
326                           width=ship_info['width'], unload_time=transfer_time, load_time=transfer_time, unload_tons=unload_tons, load_tons=load_tons, events=events, ship_logs=ship_logs, port_berths=port_berths, port_tanks=port_tanks, SHIPS_IN_CHANNEL=SHIPS_IN_CHANNEL, SHIPS_IN_ANCHORAGE=SHIPS_IN_ANCHORAGE, terminal_data=terminal_data,  liq_terminals_with_pipeline_source=liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink=liq_terminals_with_pipeline_sink)
327        
328        # Dry Bulk ship processes
329        elif ship_info['ship_type'] == 'DryBulk':
330            port_berths = port_berths_drybulk_terminals[selected_port]
331            port_silos = port_silos_drybulk_terminals[selected_port]
332            selected_terminal = selected_port + 1
333            availability_df_container, availability_df_liquid, availability_df_drybulk, queue_lengths_in_ctr, queue_lengths_in_liq, queue_lengths_in_drybulk = update_availability(run_id, env, it, port_berths_container_terminals,
334                                                                                                                                                                                   port_yard_container_terminals, port_berths_liquid_terminals, port_tanks_liquid_terminals, port_berths_drybulk_terminals, port_silos_drybulk_terminals, availability_df_container, availability_df_liquid, availability_df_drybulk, ship_data, terminal_data)
335            queue_drybulk_its.append(queue_lengths_in_drybulk)
336            unload_tons, load_tons = tons_to_unload_load(ship_id, ship_info)
337            transfer_rate = get_value_by_terminal(
338                terminal_data, "DryBulk", terminal_id=ship_info["terminal"], resource_name="transfer rate per unit")
339            transfer_time = 1 / transfer_rate
340            DryBulkTerminal(env, chassis_bays_utilization, run_id, channel, day_pilots, night_pilots, tugboats, ship_info, last_section, selected_terminal=selected_terminal, id=ship_id, ship_type=ship_info['ship_type'], draft=ship_info
341                            ['draft'], width=ship_info['width'], unload_time=transfer_time, load_time=transfer_time, unload_tons=unload_tons, load_tons=load_tons, events=events, ship_logs=ship_logs, port_berths=port_berths, port_silos=port_silos, SHIPS_IN_CHANNEL=SHIPS_IN_CHANNEL, SHIPS_IN_ANCHORAGE=SHIPS_IN_ANCHORAGE, terminal_data=terminal_data)
342
343        i += 1      # Move to the next vessel in the input file
344        SHIPS_IN_CHANNEL_TRACK.append((env.now, SHIPS_IN_CHANNEL[0]))
345        SHIPS_IN_ANCHORAGE_TRACK.append(
346            (env.now, SHIPS_IN_ANCHORAGE[0] + SHIPS_IN_ANCHORAGE[1] + SHIPS_IN_ANCHORAGE[2]))
347        CONTAINER_VES_IN_ANCHORAGE.append((env.now, SHIPS_IN_ANCHORAGE[0]))
348        LIQUID_VES_IN_ANCHORAGE.append((env.now, SHIPS_IN_ANCHORAGE[1]))
349        DRYBULK_VES_IN_ANCHORAGE.append((env.now, SHIPS_IN_ANCHORAGE[2]))
350
351
352
353def truck_generator(run_id, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink, chassis_bays_utilization, env, terminal_tuple_cache, port_tanks_liquid_terminals, port_yard_container_terminals, port_silos_drybulk_terminals, port_loading_bays_liquid_terminals, port_drybulk_bays_drybulk_terminals, port_chassis_container_terminals, truck_gates_ctr, truck_gates_liquid, truck_gates_dk, events, seed, terminal_data):
354    """
355    This function generates trucks and processes them in the simulation environment.
356    It iterates through the truck data, simulating the arrival of each truck at the port and processing it based on its type (Container, Liquid, DryBulk).
357    It creates Truck objects and initializes them with the appropriate parameters based on the truck type and terminal information. (This simulates each truck's processing at the port.)
358    Args:
359        run_id (str): The unique identifier for the simulation run.
360        liq_terminals_with_pipeline_source (list): List of liquid terminals with pipeline source connections.
361        liq_terminals_with_pipeline_sink (list): List of liquid terminals with pipeline sink connections.   
362        chassis_bays_utilization (dict): A dictionary to track chassis bays utilization.
363        env (simpy.Environment): The simulation environment.
364        terminal_tuple_cache (dict): A cache of terminal tuples for quick access to terminal data.
365        port_tanks_liquid_terminals (list): List of liquid terminal tanks.
366        port_yard_container_terminals (list): List of container terminal yards.
367        port_silos_drybulk_terminals (list): List of dry bulk terminal silos.
368        port_loading_bays_liquid_terminals (list): List of liquid terminal loading bays.
369        port_drybulk_bays_drybulk_terminals (list): List of dry bulk terminal loading bays.
370        port_chassis_container_terminals (list): List of container terminal chassis bays.
371        truck_gates_ctr (list): List of container terminal truck gates.
372        truck_gates_liquid (list): List of liquid terminal truck gates.
373        truck_gates_dk (list): List of dry bulk terminal truck gates.
374        events (list): List to store events during the simulation.
375        seed (int): The seed value for the random number generator.
376        terminal_data (dict): Dictionary containing terminal data for the simulation.
377    Yields:
378        simpy.Timeout: A timeout event to simulate the arrival of each truck at the port.
379    Returns:
380        None
381    """
382
383    initialize_rng(seed)
384
385    truck_data = pd.read_pickle(f".{run_id}/logs/truck_data.pkl")
386    truck_data = truck_data.to_dict(orient="index")
387
388    for truck_id, truck_info in truck_data.items():
389        yield env.timeout(truck_info['arrival'] - env.now)
390        if truck_info['truck_type'] == 'Container':
391            terminal_type = "Container"
392            terminal_id = truck_info['terminal_id']
393            port_tanks = None
394            port_yard = port_yard_container_terminals[terminal_id]
395            port_silos = None
396            loading_bays = None
397            drybulk_bays = None
398            truck_chassis = port_chassis_container_terminals[terminal_id]
399            container_load_amount = get_value_from_terminal_tuple(
400                terminal_tuple_cache, "Container", terminal_id=terminal_id+1, resource_name="truck payload size")
401            container_unload_amount = get_value_from_terminal_tuple(
402                terminal_tuple_cache, "Container", terminal_id=terminal_id+1, resource_name="truck payload size")
403            container_amount = (container_load_amount, container_unload_amount)
404            Truck(env, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink, chassis_bays_utilization, truck_id, run_id, terminal_type, terminal_id+1, container_amount,
405                  None, None, loading_bays, port_tanks, truck_chassis, port_yard, port_silos, drybulk_bays, events, seed=rng_random.randint(1, 100000000), terminal_data=terminal_data)
406        elif truck_info['truck_type'] == 'Liquid':
407            terminal_type = "Liquid"
408            terminal_id = truck_info['terminal_id']
409            port_tanks = port_tanks_liquid_terminals[terminal_id]
410            port_yard = None
411            port_silos = None
412            loading_bays = port_loading_bays_liquid_terminals[terminal_id]
413            drybulk_bays = None
414            truck_chassis = None
415            liquid_amount = get_value_from_terminal_tuple(
416                terminal_tuple_cache, "Liquid", terminal_id=terminal_id+1, resource_name="truck payload size")
417            Truck(env, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink, chassis_bays_utilization, truck_id, run_id, terminal_type, terminal_id+1, None, liquid_amount,
418                  None, loading_bays, port_tanks, truck_chassis, port_yard, port_silos, drybulk_bays, events, seed=rng_random.randint(1, 100000000), terminal_data=terminal_data)
419        elif truck_info['truck_type'] == 'DryBulk':
420            terminal_type = "DryBulk"
421            terminal_id = truck_info['terminal_id']
422            port_tanks = None
423            port_yard = None
424            port_silos = port_silos_drybulk_terminals[terminal_id]
425            loading_bays = None
426            drybulk_bays = port_drybulk_bays_drybulk_terminals[terminal_id]
427            truck_chassis = None
428            drybulk_amount = get_value_from_terminal_tuple(
429                terminal_tuple_cache, "DryBulk", terminal_id=terminal_id+1, resource_name="truck payload size")
430            Truck(env, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink, chassis_bays_utilization, truck_id, run_id, terminal_type, terminal_id+1, None, None,
431                  drybulk_amount, loading_bays, port_tanks, truck_chassis, port_yard, port_silos, drybulk_bays, events, seed=rng_random.randint(1, 100000000), terminal_data=terminal_data)
432
433
434def train_generator(run_id, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink, env, terminal_tuple_cache, train_loading_racks_ctr, train_loading_racks_liquid, train_loading_racks_dk, train_events, port_tanks_liquid_terminals, port_yard_container_terminals, port_silos_drybulk_terminals, seed):
435    """
436    This function generates trains and processes them in the simulation environment.
437    It iterates through the train data, simulating the arrival of each train at the port and processing it based on its cargo type (Container, Liquid, DryBulk).
438    It creates Train objects and initializes them with the appropriate parameters based on the train type and terminal information. (This simulates each train's processing at the port.)
439    Args:
440        run_id (str): The unique identifier for the simulation run.
441        liq_terminals_with_pipeline_source (list): List of liquid terminals with pipeline source connections.
442        liq_terminals_with_pipeline_sink (list): List of liquid terminals with pipeline sink connections.
443        env (simpy.Environment): The simulation environment.
444        terminal_tuple_cache (dict): A cache of terminal tuples for quick access to terminal data.
445        train_loading_racks_ctr (dict): Dictionary of loading racks for container trains.
446        train_loading_racks_liquid (dict): Dictionary of loading racks for liquid trains.
447        train_loading_racks_dk (dict): Dictionary of loading racks for dry bulk trains.
448        train_events (list): List to store train events during the simulation.
449        port_tanks_liquid_terminals (list): List of liquid terminal tanks.
450        port_yard_container_terminals (list): List of container terminal yards.
451        port_silos_drybulk_terminals (list): List of dry bulk terminal silos.
452        seed (int): The seed value for the random number generator.
453    Yields:
454        simpy.Timeout: A timeout event to simulate the arrival of each train at the port.
455    Returns:
456        None
457    """
458    initialize_rng(seed)
459
460    train_df = pd.read_csv(f".{run_id}/logs/train_data.csv")
461    train_data = train_df.to_dict(orient="index")
462
463    for train_id, train_info in train_data.items():
464        yield env.timeout(train_info['arrival at'] - env.now)
465
466        cargo_type = train_info['cargo type']
467        terminal_id = train_info['terminal_id']
468        terminal = train_info['terminal']
469
470        if cargo_type == 'Container':
471            racks = train_loading_racks_ctr[terminal_id]
472            cargo_yard = port_yard_container_terminals[terminal_id]
473        elif cargo_type == 'Liquid':
474            racks = train_loading_racks_liquid[terminal_id]
475            cargo_yard = port_tanks_liquid_terminals[terminal_id]
476
477            if cargo_yard.level >= 0.8 * cargo_yard.capacity:
478                if terminal in liq_terminals_with_pipeline_sink:
479                    Pipeline(run_id, env, cargo_yard, mode='sink',
480                             rate=constants.PIPELINE_RATE)
481                    with open(f'.{run_id}/logs/force_action.txt', 'a') as f:
482                        f.write(
483                            f"Pipeline from train sink activated {env.now}")
484                        f.write('\n')
485
486            if cargo_yard.level <= 0.2 * cargo_yard.capacity:
487                if terminal in liq_terminals_with_pipeline_source:
488                    Pipeline(run_id, env, cargo_yard, mode='source',
489                             rate=constants.PIPELINE_RATE)
490                    with open(f'.{run_id}/logs/force_action.txt', 'a') as f:
491                        f.write(
492                            f"Pipeline from train source activated {env.now}")
493                        f.write('\n')
494
495        elif cargo_type == 'DryBulk':
496            racks = train_loading_racks_dk[terminal_id]
497            cargo_yard = port_silos_drybulk_terminals[terminal_id]
498
499        car_amount = train_info['car amount']
500        cargo_transfer_rate = train_info['cargo transfer rate']
501
502        transfer_amount = train_info['total transfer cargo']
503        import_bool = train_info['import']
504        export_bool = train_info['export']
505
506        Train(env, train_id, terminal_id, car_amount, cargo_transfer_rate, racks,
507              cargo_yard, train_events, transfer_amount, import_bool, export_bool, cargo_type)
508
509
510def data_logger(run_id, env, pilots_tugs_data, day_pilots, night_pilots, tugboats):
511    """
512    This function logs the availability of pilots and tugboats at the port over time.
513    It creates a DataFrame to store the time, number of day pilots, night pilots, and tugboats available at each time step.
514    Args:
515        run_id (str): The unique identifier for the simulation run.
516        env (simpy.Environment): The simulation environment.
517        pilots_tugs_data (pd.DataFrame): DataFrame to store pilots and tugboats data.
518        day_pilots (simpy.Resource): Resource representing day pilots.
519        night_pilots (simpy.Resource): Resource representing night pilots.
520        tugboats (simpy.Resource): Resource representing tugboats.
521    Yields:
522        simpy.Timeout: A timeout event to log the data at each time step.
523    Returns:
524        None
525    """
526
527    while env.now < constants.SIMULATION_TIME-1:
528
529        new_row = pd.DataFrame([{
530            'Time': env.now,
531            'Day Pilots': day_pilots.level,
532            'Night Pilots': night_pilots.level,
533            'Tugboats': tugboats.level
534        }])
535
536        pilots_tugs_data = pd.concat(
537            [pilots_tugs_data, new_row], ignore_index=True)
538        yield env.timeout(1)
539
540    pilots_tugs_data.to_csv(
541        f'.{run_id}/logs/pilots_tugs_data.csv', index=False)
def initialize_rng(seed):
30def initialize_rng(seed):
31    """
32    Initialize random state objects for repeatable randomness across runs.
33    This function sets the global random number generator states for both the built-in `random` module and NumPy's random module.
34    This is useful for ensuring that the simulation can be reproduced with the same random events.
35    This function should be called at the start of the simulation to ensure that all random processes are initialized with the same seed.
36    Args:
37        seed (int): The seed value for the random number generator.
38    Returns:    
39        None
40    """
41    global rng_random, rng_numpy
42    rng_random = random.Random(seed)
43    rng_numpy = np.random.default_rng(seed)

Initialize random state objects for repeatable randomness across runs. This function sets the global random number generator states for both the built-in random module and NumPy's random module. This is useful for ensuring that the simulation can be reproduced with the same random events. This function should be called at the start of the simulation to ensure that all random processes are initialized with the same seed.

Arguments:
  • seed (int): The seed value for the random number generator.

Returns:
None

def update_availability( run_id, env, it, port_berths_container_terminals, port_yard_container_terminals, port_berths_liquid_terminals, port_tanks_liquid_terminals, port_berths_drybulk_terminals, port_silos_drybulk_terminals, availability_df_container, availability_df_liquid, availability_df_drybulk, ship_data, terminal_data):
 46def update_availability(run_id, env, it, port_berths_container_terminals, port_yard_container_terminals, port_berths_liquid_terminals, port_tanks_liquid_terminals, port_berths_drybulk_terminals, port_silos_drybulk_terminals, availability_df_container, availability_df_liquid, availability_df_drybulk, ship_data, terminal_data):
 47    """
 48    This function updates the availability of berths, yards, tanks, and silos at the port terminals.
 49    It calculates the number of available and used resources for each terminal type (Container, Liquid, DryBulk) and appends this data to the respective DataFrames.
 50    This function also tracks the queue lengths at each terminal type and updates the availability DataFrames with the current time and resource availability.
 51
 52    Args:
 53        run_id (str): The unique identifier for the simulation run.
 54        env (simpy.Environment): The simulation environment.
 55        it (int): The current iteration of the simulation.
 56        port_berths_container_terminals (list): List of container terminal berths.
 57        port_yard_container_terminals (list): List of container terminal yards.
 58        port_berths_liquid_terminals (list): List of liquid terminal berths.
 59        port_tanks_liquid_terminals (list): List of liquid terminal tanks.
 60        port_berths_drybulk_terminals (list): List of dry bulk terminal berths.
 61        port_silos_drybulk_terminals (list): List of dry bulk terminal silos.
 62        availability_df_container (pd.DataFrame): DataFrame to store container terminal availability data.
 63        availability_df_liquid (pd.DataFrame): DataFrame to store liquid terminal availability data.
 64        availability_df_drybulk (pd.DataFrame): DataFrame to store dry bulk terminal availability data.
 65        ship_data (dict): Dictionary containing ship data for the simulation.
 66        terminal_data (dict): Dictionary containing terminal data for the simulation.
 67    Returns:
 68        availability_df_container (pd.DataFrame): Updated DataFrame with container terminal availability data.
 69        availability_df_liquid (pd.DataFrame): Updated DataFrame with liquid terminal availability data.
 70        availability_df_drybulk (pd.DataFrame): Updated DataFrame with dry bulk terminal availability data.
 71        queue_lengths_in_ctr (list): List of queue lengths in container terminals.
 72        queue_lengths_in_liq (list): List of queue lengths in liquid terminals.
 73        queue_lengths_in_drybulk (list): List of queue lengths in dry bulk terminals.
 74    """
 75    # Initialize lists to store vessel availability data for each terminal
 76    berth_availability_data_container = []
 77    yard_availability_data = []
 78    berth_availability_data_liquid = []
 79    tank_availability_data = []
 80    berth_availability_data_drybulk = []
 81    silo_availability_data = []
 82    queue_lengths_in_ctr = []
 83    queue_lengths_in_liq = []
 84    queue_lengths_in_drybulk = []
 85
 86    # Container Terminals
 87    for i, (port_berths, port_yard) in enumerate(zip(port_berths_container_terminals, port_yard_container_terminals)):
 88        # Determine the vessel queue length and number of available berths for a container terminal
 89        queue = len(port_berths.get_queue)
 90        queue_lengths_in_ctr.append(queue)
 91        available_port_berths = len(port_berths.items)
 92        used_port_berths = get_value_by_terminal(
 93            terminal_data, "Container", i+1, "Berths") - available_port_berths
 94        berth_availability_data_container.extend(
 95            [available_port_berths, used_port_berths, queue])
 96
 97        # Determine the number of containers stored on the dock (yard) and the remaining available storage space
 98        used_yard = len(port_yard.items)
 99        available_yard = get_value_by_terminal(
100            terminal_data, "Container", i+1, "storage volume") - used_yard
101        yard_availability_data.extend(
102            [available_yard, used_yard])
103
104    # Liquid Terminals
105    for i, port_berths in enumerate(port_berths_liquid_terminals):
106        # Determine the vessel queue length and number of available berths for a liquid bulk terminal
107        queue = len(port_berths.get_queue)
108        queue_lengths_in_liq.append(queue)
109        available_port_berths = len(port_berths.items)
110        used_port_berths = get_value_by_terminal(
111            terminal_data, "Liquid", i+1, "Berths") - available_port_berths
112        berth_availability_data_liquid.extend(
113            [available_port_berths, used_port_berths, queue])
114
115    for i, port_tanks in enumerate(port_tanks_liquid_terminals):
116        # Determine the amount of storage space used and available in the liquid bulk tanks
117        used_tank = port_tanks.level
118        available_tank = get_value_by_terminal(
119            terminal_data, "Liquid", i+1, "storage volume") - used_tank
120        tank_availability_data.extend([available_tank, used_tank])
121
122    # Dry Bulk Terminals
123    for i, port_berths in enumerate(port_berths_drybulk_terminals):
124        # Determine the vessel queue length and number of available berths for a dry bulk terminal
125        queue = len(port_berths.get_queue)
126        queue_lengths_in_drybulk.append(queue)
127        available_port_berths = len(port_berths.items)
128        used_port_berths = get_value_by_terminal(
129            terminal_data, "DryBulk", i+1, "Berths") - available_port_berths
130        berth_availability_data_drybulk.extend(
131            [available_port_berths, used_port_berths, queue])
132
133    for i, port_silos in enumerate(port_silos_drybulk_terminals):
134        # Determine the amount of storage space used and available in the dry bulk silos
135        used_silo = port_silos.level
136        available_silo = get_value_by_terminal(
137            terminal_data, "DryBulk", i+1, "storage volume") - used_silo
138        silo_availability_data.extend([available_silo, used_silo])
139
140    # Update DataFrames for each terminal type to track storage availability over the span of the simulation
141    availability_container = [
142        env.now] + berth_availability_data_container + yard_availability_data
143    columns_container = ["Time"] + [f"Terminal_{i+1}_{col}" for i in range(len(port_berths_container_terminals)) for col in ["Available_Berth_Ctr", "Used_Berth_Ctr", "Berth_Queue_Ctr"]] + [
144        f"Terminal_{i+1}_{col}" for i in range(len(port_yard_container_terminals)) for col in ["Available_Yard", "Used_Yard"]]
145    row_container = pd.DataFrame(
146        [availability_container], columns=columns_container)
147    availability_df_container = pd.concat(
148        [availability_df_container, row_container], ignore_index=True)
149
150    availability_liquid = [env.now] + \
151        berth_availability_data_liquid + tank_availability_data
152    columns_liquid = ["Time"] + [f"Terminal_{i+1}_{col}" for i in range(len(port_berths_liquid_terminals)) for col in ["Available_Berth_liq", "Used_Berth_liq", "Berth_Queue_liq"]] + [
153        f"Terminal_{i+1}_{col}" for i in range(len(port_tanks_liquid_terminals)) for col in ["Available_Tank", "Used_Tank"]]
154    row_liquid = pd.DataFrame([availability_liquid], columns=columns_liquid)
155    availability_df_liquid = pd.concat(
156        [availability_df_liquid, row_liquid], ignore_index=True)
157
158    availability_drybulk = [env.now] + \
159        berth_availability_data_drybulk + silo_availability_data
160    columns_drybulk = ["Time"] + [f"Terminal_{i+1}_{col}" for i in range(len(port_berths_drybulk_terminals)) for col in ["Available_Berth_db", "Used_Berth_db", "Berth_Queue_db"]] + [
161        f"Terminal_{i+1}_{col}" for i in range(len(port_silos_drybulk_terminals)) for col in ["Available_Silo", "Used_Silo"]]
162    row_drybulk = pd.DataFrame([availability_drybulk], columns=columns_drybulk)
163    availability_df_drybulk = pd.concat(
164        [availability_df_drybulk, row_drybulk], ignore_index=True)
165
166    track_list = get_utilization(
167        availability_df_container, availability_df_liquid, availability_df_drybulk, run_id)
168
169    # innitialise empty dataframes
170    if it == 0:
171        availability_df_liquid = pd.DataFrame()
172        availability_df_drybulk = pd.DataFrame()
173    if it > 2:  # save from second iterarion
174        save_track_list(run_id, env.now, track_list)
175    if it == len(ship_data):
176        # Write the data to an Excel file, with different sheets for Container, Liquid Bulk, and Dry Bulk Terminals.
177        file_exists = os.path.isfile("./logs/availability.xlsx")
178        if file_exists:
179            mode = 'a'
180            if_sheet_exists = 'replace'
181        else:
182            mode = 'w'
183            if_sheet_exists = None  # Not used in write mode
184
185        with pd.ExcelWriter(f".{run_id}/logs/availability.xlsx", engine="openpyxl", mode=mode, if_sheet_exists=if_sheet_exists) as writer:
186            availability_df_container.to_excel(
187                writer, sheet_name="Container_Terminals", index=False)
188            availability_df_liquid.to_excel(
189                writer, sheet_name="Liquid_Terminals", index=False)
190            availability_df_drybulk.to_excel(
191                writer, sheet_name="Dry_Bulk_Terminals", index=False)
192
193    return availability_df_container, availability_df_liquid, availability_df_drybulk, queue_lengths_in_ctr, queue_lengths_in_liq, queue_lengths_in_drybulk

This function updates the availability of berths, yards, tanks, and silos at the port terminals. It calculates the number of available and used resources for each terminal type (Container, Liquid, DryBulk) and appends this data to the respective DataFrames. This function also tracks the queue lengths at each terminal type and updates the availability DataFrames with the current time and resource availability.

Arguments:
  • run_id (str): The unique identifier for the simulation run.
  • env (simpy.Environment): The simulation environment.
  • it (int): The current iteration of the simulation.
  • port_berths_container_terminals (list): List of container terminal berths.
  • port_yard_container_terminals (list): List of container terminal yards.
  • port_berths_liquid_terminals (list): List of liquid terminal berths.
  • port_tanks_liquid_terminals (list): List of liquid terminal tanks.
  • port_berths_drybulk_terminals (list): List of dry bulk terminal berths.
  • port_silos_drybulk_terminals (list): List of dry bulk terminal silos.
  • availability_df_container (pd.DataFrame): DataFrame to store container terminal availability data.
  • availability_df_liquid (pd.DataFrame): DataFrame to store liquid terminal availability data.
  • availability_df_drybulk (pd.DataFrame): DataFrame to store dry bulk terminal availability data.
  • ship_data (dict): Dictionary containing ship data for the simulation.
  • terminal_data (dict): Dictionary containing terminal data for the simulation.
Returns:

availability_df_container (pd.DataFrame): Updated DataFrame with container terminal availability data. availability_df_liquid (pd.DataFrame): Updated DataFrame with liquid terminal availability data. availability_df_drybulk (pd.DataFrame): Updated DataFrame with dry bulk terminal availability data. queue_lengths_in_ctr (list): List of queue lengths in container terminals. queue_lengths_in_liq (list): List of queue lengths in liquid terminals. queue_lengths_in_drybulk (list): List of queue lengths in dry bulk terminals.

def create_containers(ship_id, ship_info):
196def create_containers(ship_id, ship_info):
197    """
198    This function creates a list of Container objects to be loaded and unloaded from a ship.
199    It generates the number of containers based on the ship's information, specifically the width and the number of containers to load and unload.
200    Args:
201        ship_id (str): The unique identifier for the ship.
202        ship_info (dict): A dictionary containing information about the ship, including its width and the number of containers to load and unload.
203    Returns:
204        containers_to_unload (list): A list of Container objects representing the containers to be unloaded from the ship.
205        containers_to_load (list): A list of Container objects representing the containers to be loaded onto the ship.
206    """
207    containers_to_unload = [Container(id=f"{ship_id}_unload_{j}", width=ship_info['width']) for j in range(
208        int(ship_info['num_container_or_liq_tons_or_dry_tons_to_unload']))]
209    containers_to_load = [Container(id=f"{ship_id}_load_{j}", width=ship_info['width'])for j in range(
210        int(ship_info['num_container_or_liq_tons_or_dry_tons_to_load']))]
211    return containers_to_unload, containers_to_load

This function creates a list of Container objects to be loaded and unloaded from a ship. It generates the number of containers based on the ship's information, specifically the width and the number of containers to load and unload.

Arguments:
  • ship_id (str): The unique identifier for the ship.
  • ship_info (dict): A dictionary containing information about the ship, including its width and the number of containers to load and unload.
Returns:

containers_to_unload (list): A list of Container objects representing the containers to be unloaded from the ship. containers_to_load (list): A list of Container objects representing the containers to be loaded onto the ship.

def tons_to_unload_load(ship_id, ship_info):
214def tons_to_unload_load(ship_id, ship_info):
215    """
216    This function retrieves the number of tons to unload and load for a ship based on its information.
217    Args:
218        ship_id (str): The unique identifier for the ship.
219        ship_info (dict): A dictionary containing information about the ship, including the number of tons to unload and load.
220    Returns:
221        unload_tons (int): The number of tons to unload from the ship.
222        load_tons (int): The number of tons to load onto the ship.
223    """
224    unload_tons = ship_info['num_container_or_liq_tons_or_dry_tons_to_unload']
225    load_tons = ship_info['num_container_or_liq_tons_or_dry_tons_to_load']
226    return unload_tons, load_tons

This function retrieves the number of tons to unload and load for a ship based on its information.

Arguments:
  • ship_id (str): The unique identifier for the ship.
  • ship_info (dict): A dictionary containing information about the ship, including the number of tons to unload and load.
Returns:

unload_tons (int): The number of tons to unload from the ship. load_tons (int): The number of tons to load onto the ship.

def ship_generator( run_id, env, chassis_bays_utilization, port_berths_container_terminals, port_yard_container_terminals, port_berths_liquid_terminals, port_tanks_liquid_terminals, port_berths_drybulk_terminals, port_silos_drybulk_terminals, channel, day_pilots, night_pilots, tugboats, events, ship_logs, channel_events, channel_logs, SHIPS_IN_ANCHORAGE, SHIPS_IN_CHANNEL, ship_data, terminal_data, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink):
230def ship_generator(run_id, env, chassis_bays_utilization, port_berths_container_terminals, port_yard_container_terminals, port_berths_liquid_terminals, port_tanks_liquid_terminals,
231                   port_berths_drybulk_terminals, port_silos_drybulk_terminals, channel, day_pilots, night_pilots, tugboats, events, ship_logs, channel_events, channel_logs, SHIPS_IN_ANCHORAGE, SHIPS_IN_CHANNEL, ship_data, terminal_data, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink):
232    """
233    This function generates ships and processes them in the simulation environment.
234    It iterates through the ship data, simulating the arrival of each ship at the port and processing it based on its type (Container, Liquid, DryBulk).
235    The ContainerTerminal, LiquidTerminal, and DryBulkTerminal classes simulates the vessel's operations at the port.
236    It updates the availability of resources at the port terminals and tracks the number of ships in the channel and anchorage.
237    Args:
238        run_id (str): The unique identifier for the simulation run.
239        env (simpy.Environment): The simulation environment.
240        chassis_bays_utilization (dict): A dictionary to track chassis bays utilization.
241        port_berths_container_terminals (list): List of container terminal berths.
242        port_yard_container_terminals (list): List of container terminal yards.
243        port_berths_liquid_terminals (list): List of liquid terminal berths.
244        port_tanks_liquid_terminals (list): List of liquid terminal tanks.
245        port_berths_drybulk_terminals (list): List of dry bulk terminal berths.
246        port_silos_drybulk_terminals (list): List of dry bulk terminal silos.
247        channel (Channel): The channel object for managing ship movements.
248        day_pilots (int): Number of day pilots available.
249        night_pilots (int): Number of night pilots available.
250        tugboats (int): Number of tugboats available.
251        events (list): List to store events during the simulation.
252        ship_logs (list): List to store ship logs during the simulation.
253        channel_events (list): List to store channel events during the simulation.
254        channel_logs (list): List to store channel logs during the simulation.
255        SHIPS_IN_ANCHORAGE (list): List to track ships in anchorage by type.
256        SHIPS_IN_CHANNEL (list): List to track ships in the channel.
257        ship_data (dict): Dictionary containing ship data for the simulation.
258        terminal_data (dict): Dictionary containing terminal data for the simulation.
259        liq_terminals_with_pipeline_source (list): List of liquid terminals with pipeline source connections.
260        liq_terminals_with_pipeline_sink (list): List of liquid terminals with pipeline sink connections.
261    Yields:
262        simpy.Timeout: A timeout event to simulate the arrival of each ship at the port.
263    Returns:
264        None
265    """ 
266    i = 0
267    terminal_data = terminal_data
268
269    availability_df_container = pd.DataFrame()
270    availability_df_liquid = pd.DataFrame()
271    availability_df_drybulk = pd.DataFrame()
272
273    queue_ctr_its = []
274    queue_liq_its = []
275    queue_drybulk_its = []
276
277    SHIPS_IN_CHANNEL_TRACK = []
278    SHIPS_IN_ANCHORAGE_TRACK = []
279    CONTAINER_VES_IN_ANCHORAGE = []
280    LIQUID_VES_IN_ANCHORAGE = []
281    DRYBULK_VES_IN_ANCHORAGE = []
282
283    it = 0
284
285    for ship_id, ship_info in tqdm(ship_data.items(), desc=f"Simulation Progress {run_id}"):
286        it += 1
287        yield env.timeout(ship_info['arrival'] - env.now)
288        if ship_data[ship_id]['ship_type'] == 'Container':
289            SHIPS_IN_ANCHORAGE[0] += 1
290        elif ship_data[ship_id]['ship_type'] == 'Liquid':
291            SHIPS_IN_ANCHORAGE[1] += 1
292        elif ship_data[ship_id]['ship_type'] == 'DryBulk':
293            SHIPS_IN_ANCHORAGE[2] += 1
294
295        selected_port = ship_info["terminal"] - 1
296        last_section = ship_info["last_section"]
297
298        # Container ship processes
299        if ship_info['ship_type'] == 'Container':
300            port_berths = port_berths_container_terminals[selected_port]
301            port_yard = port_yard_container_terminals[selected_port]
302            selected_terminal = selected_port + 1
303            availability_df_container, availability_df_liquid, availability_df_drybulk, queue_lengths_in_ctr, queue_lengths_in_liq, queue_lengths_in_drybulk = update_availability(run_id, env, it, port_berths_container_terminals,
304                                                                                                                                                                                   port_yard_container_terminals, port_berths_liquid_terminals, port_tanks_liquid_terminals, port_berths_drybulk_terminals, port_silos_drybulk_terminals, availability_df_container, availability_df_liquid, availability_df_drybulk, ship_data, terminal_data)
305            queue_ctr_its.append(queue_lengths_in_ctr)
306            containers_to_unload, containers_to_load = create_containers(
307                ship_id, ship_info)
308            transfer_rate = get_value_by_terminal(
309                terminal_data, "Container", terminal_id=ship_info["terminal"], resource_name="transfer rate per unit")
310            transfer_time = 1 / transfer_rate
311            ContainerTerminal(env, chassis_bays_utilization, run_id, channel, day_pilots, night_pilots, tugboats, ship_info, last_section, selected_terminal=selected_terminal, id=ship_id, ship_type=ship_info['ship_type'], draft=ship_info['draft'],
312                              width=ship_info['width'], unload_time_per_container=transfer_time, load_time_per_container=transfer_time, containers_to_unload=containers_to_unload, containers_to_load=containers_to_load, events=events, ship_logs=ship_logs, port_berths=port_berths, port_yard=port_yard, SHIPS_IN_CHANNEL=SHIPS_IN_CHANNEL, SHIPS_IN_ANCHORAGE=SHIPS_IN_ANCHORAGE, terminal_data=terminal_data)
313       
314        # Liquid and Dry Bulk ship processes
315        elif ship_info['ship_type'] == 'Liquid':
316            port_berths = port_berths_liquid_terminals[selected_port]
317            port_tanks = port_tanks_liquid_terminals[selected_port]
318            selected_terminal = selected_port + 1
319            availability_df_container, availability_df_liquid, availability_df_drybulk, queue_lengths_in_ctr, queue_lengths_in_liq, queue_lengths_in_drybulk = update_availability(run_id, env, it, port_berths_container_terminals,
320                                                                                                                                                                                   port_yard_container_terminals, port_berths_liquid_terminals, port_tanks_liquid_terminals, port_berths_drybulk_terminals, port_silos_drybulk_terminals, availability_df_container, availability_df_liquid, availability_df_drybulk, ship_data, terminal_data)
321            queue_liq_its.append(queue_lengths_in_liq)
322            unload_tons, load_tons = tons_to_unload_load(ship_id, ship_info)
323            transfer_rate = get_value_by_terminal(
324                terminal_data, "Liquid", terminal_id=ship_info["terminal"], resource_name="transfer rate per unit")
325            transfer_time = 1 / transfer_rate
326            LiquidTerminal(env, chassis_bays_utilization, run_id, channel, day_pilots, night_pilots, tugboats, ship_info, last_section, selected_terminal, id=ship_id, ship_type=ship_info['ship_type'], draft=ship_info['draft'],
327                           width=ship_info['width'], unload_time=transfer_time, load_time=transfer_time, unload_tons=unload_tons, load_tons=load_tons, events=events, ship_logs=ship_logs, port_berths=port_berths, port_tanks=port_tanks, SHIPS_IN_CHANNEL=SHIPS_IN_CHANNEL, SHIPS_IN_ANCHORAGE=SHIPS_IN_ANCHORAGE, terminal_data=terminal_data,  liq_terminals_with_pipeline_source=liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink=liq_terminals_with_pipeline_sink)
328        
329        # Dry Bulk ship processes
330        elif ship_info['ship_type'] == 'DryBulk':
331            port_berths = port_berths_drybulk_terminals[selected_port]
332            port_silos = port_silos_drybulk_terminals[selected_port]
333            selected_terminal = selected_port + 1
334            availability_df_container, availability_df_liquid, availability_df_drybulk, queue_lengths_in_ctr, queue_lengths_in_liq, queue_lengths_in_drybulk = update_availability(run_id, env, it, port_berths_container_terminals,
335                                                                                                                                                                                   port_yard_container_terminals, port_berths_liquid_terminals, port_tanks_liquid_terminals, port_berths_drybulk_terminals, port_silos_drybulk_terminals, availability_df_container, availability_df_liquid, availability_df_drybulk, ship_data, terminal_data)
336            queue_drybulk_its.append(queue_lengths_in_drybulk)
337            unload_tons, load_tons = tons_to_unload_load(ship_id, ship_info)
338            transfer_rate = get_value_by_terminal(
339                terminal_data, "DryBulk", terminal_id=ship_info["terminal"], resource_name="transfer rate per unit")
340            transfer_time = 1 / transfer_rate
341            DryBulkTerminal(env, chassis_bays_utilization, run_id, channel, day_pilots, night_pilots, tugboats, ship_info, last_section, selected_terminal=selected_terminal, id=ship_id, ship_type=ship_info['ship_type'], draft=ship_info
342                            ['draft'], width=ship_info['width'], unload_time=transfer_time, load_time=transfer_time, unload_tons=unload_tons, load_tons=load_tons, events=events, ship_logs=ship_logs, port_berths=port_berths, port_silos=port_silos, SHIPS_IN_CHANNEL=SHIPS_IN_CHANNEL, SHIPS_IN_ANCHORAGE=SHIPS_IN_ANCHORAGE, terminal_data=terminal_data)
343
344        i += 1      # Move to the next vessel in the input file
345        SHIPS_IN_CHANNEL_TRACK.append((env.now, SHIPS_IN_CHANNEL[0]))
346        SHIPS_IN_ANCHORAGE_TRACK.append(
347            (env.now, SHIPS_IN_ANCHORAGE[0] + SHIPS_IN_ANCHORAGE[1] + SHIPS_IN_ANCHORAGE[2]))
348        CONTAINER_VES_IN_ANCHORAGE.append((env.now, SHIPS_IN_ANCHORAGE[0]))
349        LIQUID_VES_IN_ANCHORAGE.append((env.now, SHIPS_IN_ANCHORAGE[1]))
350        DRYBULK_VES_IN_ANCHORAGE.append((env.now, SHIPS_IN_ANCHORAGE[2]))

This function generates ships and processes them in the simulation environment. It iterates through the ship data, simulating the arrival of each ship at the port and processing it based on its type (Container, Liquid, DryBulk). The ContainerTerminal, LiquidTerminal, and DryBulkTerminal classes simulates the vessel's operations at the port. It updates the availability of resources at the port terminals and tracks the number of ships in the channel and anchorage.

Arguments:
  • run_id (str): The unique identifier for the simulation run.
  • env (simpy.Environment): The simulation environment.
  • chassis_bays_utilization (dict): A dictionary to track chassis bays utilization.
  • port_berths_container_terminals (list): List of container terminal berths.
  • port_yard_container_terminals (list): List of container terminal yards.
  • port_berths_liquid_terminals (list): List of liquid terminal berths.
  • port_tanks_liquid_terminals (list): List of liquid terminal tanks.
  • port_berths_drybulk_terminals (list): List of dry bulk terminal berths.
  • port_silos_drybulk_terminals (list): List of dry bulk terminal silos.
  • channel (Channel): The channel object for managing ship movements.
  • day_pilots (int): Number of day pilots available.
  • night_pilots (int): Number of night pilots available.
  • tugboats (int): Number of tugboats available.
  • events (list): List to store events during the simulation.
  • ship_logs (list): List to store ship logs during the simulation.
  • channel_events (list): List to store channel events during the simulation.
  • channel_logs (list): List to store channel logs during the simulation.
  • SHIPS_IN_ANCHORAGE (list): List to track ships in anchorage by type.
  • SHIPS_IN_CHANNEL (list): List to track ships in the channel.
  • ship_data (dict): Dictionary containing ship data for the simulation.
  • terminal_data (dict): Dictionary containing terminal data for the simulation.
  • liq_terminals_with_pipeline_source (list): List of liquid terminals with pipeline source connections.
  • liq_terminals_with_pipeline_sink (list): List of liquid terminals with pipeline sink connections.
Yields:

simpy.Timeout: A timeout event to simulate the arrival of each ship at the port.

Returns:

None

def truck_generator( run_id, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink, chassis_bays_utilization, env, terminal_tuple_cache, port_tanks_liquid_terminals, port_yard_container_terminals, port_silos_drybulk_terminals, port_loading_bays_liquid_terminals, port_drybulk_bays_drybulk_terminals, port_chassis_container_terminals, truck_gates_ctr, truck_gates_liquid, truck_gates_dk, events, seed, terminal_data):
354def truck_generator(run_id, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink, chassis_bays_utilization, env, terminal_tuple_cache, port_tanks_liquid_terminals, port_yard_container_terminals, port_silos_drybulk_terminals, port_loading_bays_liquid_terminals, port_drybulk_bays_drybulk_terminals, port_chassis_container_terminals, truck_gates_ctr, truck_gates_liquid, truck_gates_dk, events, seed, terminal_data):
355    """
356    This function generates trucks and processes them in the simulation environment.
357    It iterates through the truck data, simulating the arrival of each truck at the port and processing it based on its type (Container, Liquid, DryBulk).
358    It creates Truck objects and initializes them with the appropriate parameters based on the truck type and terminal information. (This simulates each truck's processing at the port.)
359    Args:
360        run_id (str): The unique identifier for the simulation run.
361        liq_terminals_with_pipeline_source (list): List of liquid terminals with pipeline source connections.
362        liq_terminals_with_pipeline_sink (list): List of liquid terminals with pipeline sink connections.   
363        chassis_bays_utilization (dict): A dictionary to track chassis bays utilization.
364        env (simpy.Environment): The simulation environment.
365        terminal_tuple_cache (dict): A cache of terminal tuples for quick access to terminal data.
366        port_tanks_liquid_terminals (list): List of liquid terminal tanks.
367        port_yard_container_terminals (list): List of container terminal yards.
368        port_silos_drybulk_terminals (list): List of dry bulk terminal silos.
369        port_loading_bays_liquid_terminals (list): List of liquid terminal loading bays.
370        port_drybulk_bays_drybulk_terminals (list): List of dry bulk terminal loading bays.
371        port_chassis_container_terminals (list): List of container terminal chassis bays.
372        truck_gates_ctr (list): List of container terminal truck gates.
373        truck_gates_liquid (list): List of liquid terminal truck gates.
374        truck_gates_dk (list): List of dry bulk terminal truck gates.
375        events (list): List to store events during the simulation.
376        seed (int): The seed value for the random number generator.
377        terminal_data (dict): Dictionary containing terminal data for the simulation.
378    Yields:
379        simpy.Timeout: A timeout event to simulate the arrival of each truck at the port.
380    Returns:
381        None
382    """
383
384    initialize_rng(seed)
385
386    truck_data = pd.read_pickle(f".{run_id}/logs/truck_data.pkl")
387    truck_data = truck_data.to_dict(orient="index")
388
389    for truck_id, truck_info in truck_data.items():
390        yield env.timeout(truck_info['arrival'] - env.now)
391        if truck_info['truck_type'] == 'Container':
392            terminal_type = "Container"
393            terminal_id = truck_info['terminal_id']
394            port_tanks = None
395            port_yard = port_yard_container_terminals[terminal_id]
396            port_silos = None
397            loading_bays = None
398            drybulk_bays = None
399            truck_chassis = port_chassis_container_terminals[terminal_id]
400            container_load_amount = get_value_from_terminal_tuple(
401                terminal_tuple_cache, "Container", terminal_id=terminal_id+1, resource_name="truck payload size")
402            container_unload_amount = get_value_from_terminal_tuple(
403                terminal_tuple_cache, "Container", terminal_id=terminal_id+1, resource_name="truck payload size")
404            container_amount = (container_load_amount, container_unload_amount)
405            Truck(env, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink, chassis_bays_utilization, truck_id, run_id, terminal_type, terminal_id+1, container_amount,
406                  None, None, loading_bays, port_tanks, truck_chassis, port_yard, port_silos, drybulk_bays, events, seed=rng_random.randint(1, 100000000), terminal_data=terminal_data)
407        elif truck_info['truck_type'] == 'Liquid':
408            terminal_type = "Liquid"
409            terminal_id = truck_info['terminal_id']
410            port_tanks = port_tanks_liquid_terminals[terminal_id]
411            port_yard = None
412            port_silos = None
413            loading_bays = port_loading_bays_liquid_terminals[terminal_id]
414            drybulk_bays = None
415            truck_chassis = None
416            liquid_amount = get_value_from_terminal_tuple(
417                terminal_tuple_cache, "Liquid", terminal_id=terminal_id+1, resource_name="truck payload size")
418            Truck(env, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink, chassis_bays_utilization, truck_id, run_id, terminal_type, terminal_id+1, None, liquid_amount,
419                  None, loading_bays, port_tanks, truck_chassis, port_yard, port_silos, drybulk_bays, events, seed=rng_random.randint(1, 100000000), terminal_data=terminal_data)
420        elif truck_info['truck_type'] == 'DryBulk':
421            terminal_type = "DryBulk"
422            terminal_id = truck_info['terminal_id']
423            port_tanks = None
424            port_yard = None
425            port_silos = port_silos_drybulk_terminals[terminal_id]
426            loading_bays = None
427            drybulk_bays = port_drybulk_bays_drybulk_terminals[terminal_id]
428            truck_chassis = None
429            drybulk_amount = get_value_from_terminal_tuple(
430                terminal_tuple_cache, "DryBulk", terminal_id=terminal_id+1, resource_name="truck payload size")
431            Truck(env, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink, chassis_bays_utilization, truck_id, run_id, terminal_type, terminal_id+1, None, None,
432                  drybulk_amount, loading_bays, port_tanks, truck_chassis, port_yard, port_silos, drybulk_bays, events, seed=rng_random.randint(1, 100000000), terminal_data=terminal_data)

This function generates trucks and processes them in the simulation environment. It iterates through the truck data, simulating the arrival of each truck at the port and processing it based on its type (Container, Liquid, DryBulk). It creates Truck objects and initializes them with the appropriate parameters based on the truck type and terminal information. (This simulates each truck's processing at the port.)

Arguments:
  • run_id (str): The unique identifier for the simulation run.
  • liq_terminals_with_pipeline_source (list): List of liquid terminals with pipeline source connections.
  • liq_terminals_with_pipeline_sink (list): List of liquid terminals with pipeline sink connections.
  • chassis_bays_utilization (dict): A dictionary to track chassis bays utilization.
  • env (simpy.Environment): The simulation environment.
  • terminal_tuple_cache (dict): A cache of terminal tuples for quick access to terminal data.
  • port_tanks_liquid_terminals (list): List of liquid terminal tanks.
  • port_yard_container_terminals (list): List of container terminal yards.
  • port_silos_drybulk_terminals (list): List of dry bulk terminal silos.
  • port_loading_bays_liquid_terminals (list): List of liquid terminal loading bays.
  • port_drybulk_bays_drybulk_terminals (list): List of dry bulk terminal loading bays.
  • port_chassis_container_terminals (list): List of container terminal chassis bays.
  • truck_gates_ctr (list): List of container terminal truck gates.
  • truck_gates_liquid (list): List of liquid terminal truck gates.
  • truck_gates_dk (list): List of dry bulk terminal truck gates.
  • events (list): List to store events during the simulation.
  • seed (int): The seed value for the random number generator.
  • terminal_data (dict): Dictionary containing terminal data for the simulation.
Yields:

simpy.Timeout: A timeout event to simulate the arrival of each truck at the port.

Returns:

None

def train_generator( run_id, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink, env, terminal_tuple_cache, train_loading_racks_ctr, train_loading_racks_liquid, train_loading_racks_dk, train_events, port_tanks_liquid_terminals, port_yard_container_terminals, port_silos_drybulk_terminals, seed):
435def train_generator(run_id, liq_terminals_with_pipeline_source, liq_terminals_with_pipeline_sink, env, terminal_tuple_cache, train_loading_racks_ctr, train_loading_racks_liquid, train_loading_racks_dk, train_events, port_tanks_liquid_terminals, port_yard_container_terminals, port_silos_drybulk_terminals, seed):
436    """
437    This function generates trains and processes them in the simulation environment.
438    It iterates through the train data, simulating the arrival of each train at the port and processing it based on its cargo type (Container, Liquid, DryBulk).
439    It creates Train objects and initializes them with the appropriate parameters based on the train type and terminal information. (This simulates each train's processing at the port.)
440    Args:
441        run_id (str): The unique identifier for the simulation run.
442        liq_terminals_with_pipeline_source (list): List of liquid terminals with pipeline source connections.
443        liq_terminals_with_pipeline_sink (list): List of liquid terminals with pipeline sink connections.
444        env (simpy.Environment): The simulation environment.
445        terminal_tuple_cache (dict): A cache of terminal tuples for quick access to terminal data.
446        train_loading_racks_ctr (dict): Dictionary of loading racks for container trains.
447        train_loading_racks_liquid (dict): Dictionary of loading racks for liquid trains.
448        train_loading_racks_dk (dict): Dictionary of loading racks for dry bulk trains.
449        train_events (list): List to store train events during the simulation.
450        port_tanks_liquid_terminals (list): List of liquid terminal tanks.
451        port_yard_container_terminals (list): List of container terminal yards.
452        port_silos_drybulk_terminals (list): List of dry bulk terminal silos.
453        seed (int): The seed value for the random number generator.
454    Yields:
455        simpy.Timeout: A timeout event to simulate the arrival of each train at the port.
456    Returns:
457        None
458    """
459    initialize_rng(seed)
460
461    train_df = pd.read_csv(f".{run_id}/logs/train_data.csv")
462    train_data = train_df.to_dict(orient="index")
463
464    for train_id, train_info in train_data.items():
465        yield env.timeout(train_info['arrival at'] - env.now)
466
467        cargo_type = train_info['cargo type']
468        terminal_id = train_info['terminal_id']
469        terminal = train_info['terminal']
470
471        if cargo_type == 'Container':
472            racks = train_loading_racks_ctr[terminal_id]
473            cargo_yard = port_yard_container_terminals[terminal_id]
474        elif cargo_type == 'Liquid':
475            racks = train_loading_racks_liquid[terminal_id]
476            cargo_yard = port_tanks_liquid_terminals[terminal_id]
477
478            if cargo_yard.level >= 0.8 * cargo_yard.capacity:
479                if terminal in liq_terminals_with_pipeline_sink:
480                    Pipeline(run_id, env, cargo_yard, mode='sink',
481                             rate=constants.PIPELINE_RATE)
482                    with open(f'.{run_id}/logs/force_action.txt', 'a') as f:
483                        f.write(
484                            f"Pipeline from train sink activated {env.now}")
485                        f.write('\n')
486
487            if cargo_yard.level <= 0.2 * cargo_yard.capacity:
488                if terminal in liq_terminals_with_pipeline_source:
489                    Pipeline(run_id, env, cargo_yard, mode='source',
490                             rate=constants.PIPELINE_RATE)
491                    with open(f'.{run_id}/logs/force_action.txt', 'a') as f:
492                        f.write(
493                            f"Pipeline from train source activated {env.now}")
494                        f.write('\n')
495
496        elif cargo_type == 'DryBulk':
497            racks = train_loading_racks_dk[terminal_id]
498            cargo_yard = port_silos_drybulk_terminals[terminal_id]
499
500        car_amount = train_info['car amount']
501        cargo_transfer_rate = train_info['cargo transfer rate']
502
503        transfer_amount = train_info['total transfer cargo']
504        import_bool = train_info['import']
505        export_bool = train_info['export']
506
507        Train(env, train_id, terminal_id, car_amount, cargo_transfer_rate, racks,
508              cargo_yard, train_events, transfer_amount, import_bool, export_bool, cargo_type)

This function generates trains and processes them in the simulation environment. It iterates through the train data, simulating the arrival of each train at the port and processing it based on its cargo type (Container, Liquid, DryBulk). It creates Train objects and initializes them with the appropriate parameters based on the train type and terminal information. (This simulates each train's processing at the port.)

Arguments:
  • run_id (str): The unique identifier for the simulation run.
  • liq_terminals_with_pipeline_source (list): List of liquid terminals with pipeline source connections.
  • liq_terminals_with_pipeline_sink (list): List of liquid terminals with pipeline sink connections.
  • env (simpy.Environment): The simulation environment.
  • terminal_tuple_cache (dict): A cache of terminal tuples for quick access to terminal data.
  • train_loading_racks_ctr (dict): Dictionary of loading racks for container trains.
  • train_loading_racks_liquid (dict): Dictionary of loading racks for liquid trains.
  • train_loading_racks_dk (dict): Dictionary of loading racks for dry bulk trains.
  • train_events (list): List to store train events during the simulation.
  • port_tanks_liquid_terminals (list): List of liquid terminal tanks.
  • port_yard_container_terminals (list): List of container terminal yards.
  • port_silos_drybulk_terminals (list): List of dry bulk terminal silos.
  • seed (int): The seed value for the random number generator.
Yields:

simpy.Timeout: A timeout event to simulate the arrival of each train at the port.

Returns:

None

def data_logger(run_id, env, pilots_tugs_data, day_pilots, night_pilots, tugboats):
511def data_logger(run_id, env, pilots_tugs_data, day_pilots, night_pilots, tugboats):
512    """
513    This function logs the availability of pilots and tugboats at the port over time.
514    It creates a DataFrame to store the time, number of day pilots, night pilots, and tugboats available at each time step.
515    Args:
516        run_id (str): The unique identifier for the simulation run.
517        env (simpy.Environment): The simulation environment.
518        pilots_tugs_data (pd.DataFrame): DataFrame to store pilots and tugboats data.
519        day_pilots (simpy.Resource): Resource representing day pilots.
520        night_pilots (simpy.Resource): Resource representing night pilots.
521        tugboats (simpy.Resource): Resource representing tugboats.
522    Yields:
523        simpy.Timeout: A timeout event to log the data at each time step.
524    Returns:
525        None
526    """
527
528    while env.now < constants.SIMULATION_TIME-1:
529
530        new_row = pd.DataFrame([{
531            'Time': env.now,
532            'Day Pilots': day_pilots.level,
533            'Night Pilots': night_pilots.level,
534            'Tugboats': tugboats.level
535        }])
536
537        pilots_tugs_data = pd.concat(
538            [pilots_tugs_data, new_row], ignore_index=True)
539        yield env.timeout(1)
540
541    pilots_tugs_data.to_csv(
542        f'.{run_id}/logs/pilots_tugs_data.csv', index=False)

This function logs the availability of pilots and tugboats at the port over time. It creates a DataFrame to store the time, number of day pilots, night pilots, and tugboats available at each time step.

Arguments:
  • run_id (str): The unique identifier for the simulation run.
  • env (simpy.Environment): The simulation environment.
  • pilots_tugs_data (pd.DataFrame): DataFrame to store pilots and tugboats data.
  • day_pilots (simpy.Resource): Resource representing day pilots.
  • night_pilots (simpy.Resource): Resource representing night pilots.
  • tugboats (simpy.Resource): Resource representing tugboats.
Yields:

simpy.Timeout: A timeout event to log the data at each time step.

Returns:

None