simulation_handler.helpers

Helper functions for the simulation environment. This module contains various utility functions for logging, data cleaning, and random number generation used in the simulation.

  1""" 
  2Helper functions for the simulation environment.
  3This module contains various utility functions for logging, data cleaning,
  4and random number generation used in the simulation.
  5"""
  6
  7import os
  8import shutil
  9import re
 10import random
 11import os
 12import glob
 13
 14import numpy as np
 15from scipy.stats import truncnorm
 16import pandas as pd
 17
 18import constants
 19
 20#######################
 21# Logs
 22#######################
 23
 24
 25def log_line(run_id, filename, line):
 26    """
 27    Logs a line to a specified file in the run's logs directory.
 28    Args:
 29        run_id (str): Unique identifier for the run to save the log.
 30        filename (str): Name of the file to log the line.
 31        line (str): The line to log.
 32    Returns:
 33        None: Appends the line to the specified log file.
 34    """
 35    try:
 36        with open(f'.{run_id}/logs/{filename}', 'a') as f:
 37            f.write(line + '\n')
 38    except FileNotFoundError:
 39        with open(f'.{run_id}/logs/{filename}', 'w') as f:
 40            f.write(line + '\n')
 41
 42
 43def save_warning(run_id, message, print_val=True):
 44    """
 45    Save a warning message to the logs directory for the current run.
 46    If the logs directory does not exist, it will be created.
 47    Args:
 48        run_id (str): The identifier for the current run.
 49        message (str): The warning message to be saved.
 50        print_val (bool): Whether to print the message to the console. Default is True.
 51    Returns:
 52        None
 53    """
 54    if not os.path.exists(f'.{run_id}/logs/warnings.txt'):
 55        with open(f'.{run_id}/logs/warnings.txt', 'w') as f:
 56            f.write('Warnings:\n')
 57        with open(f'.{run_id}/logs/warnings.txt', 'a') as f:
 58            f.write(f'{message}\n')
 59    else:
 60        with open(f'.{run_id}/logs/warnings.txt', 'a') as f:
 61            f.write(f'{message}\n')
 62    if print_val:
 63        print(f"{message}")
 64
 65
 66def clear_env(env, ship_proc, truck_proc, train_proc, data_taker_proc):
 67    """
 68    Clear the environment and interrupt all processes.
 69    Args:
 70        env (Simpy.Environment): The simulation environment to be cleared.
 71        ship_proc (Simpy.Process): The process for handling ships.
 72        truck_proc (Simpy.Process): The process for handling trucks.
 73        train_proc (Simpy.Process): The process for handling trains.
 74        data_taker_proc: The process for taking data.
 75    Returns:
 76        None
 77    """
 78    try:
 79        ship_proc.interrupt()
 80    except:
 81        pass
 82    try:
 83        truck_proc.interrupt()
 84    except:
 85        pass
 86    try:
 87        train_proc.interrupt()
 88    except:
 89        pass
 90
 91    try:
 92        data_taker_proc.interrupt()
 93    except:
 94        pass
 95
 96    env._queue.clear()
 97
 98
 99def clear_logs(run_id):
100    """
101    Clear the logs directory for the current run by removing all files and subdirectories.
102    Then create the necessary subdirectories again.
103    This function is useful for resetting the logs directory before starting a new simulation run.
104    Args:
105        run_id (str): The identifier for the current run.
106    Returns:
107        None
108    """
109
110    if constants.DELETE_RESULTS_FOLDER == False:
111        shutil.rmtree(f'.{run_id}/logs', ignore_errors=True)
112        shutil.rmtree(f'.{run_id}/plots', ignore_errors=True)
113        # shutil.rmtree(f'.{run_id}/animations', ignore_errors=True)
114        shutil.rmtree(f'.{run_id}/bottlenecks', ignore_errors=True)
115
116    # os.makedirs(f'.{run_id}/animations')
117
118    os.makedirs(f'.{run_id}/logs')
119    os.makedirs(f'.{run_id}/logs/availability')
120    os.makedirs(f'.{run_id}/plots')
121    os.makedirs(f'.{run_id}/bottlenecks')
122    os.makedirs(f'.{run_id}/bottlenecks/chassisBays/')
123    os.makedirs(f'.{run_id}/plots/TerminalProcessCharts/')
124    os.makedirs(f'.{run_id}/plots/TerminalProcessDist/')
125    os.makedirs(f'.{run_id}/plots/DwellTimes/')
126    os.makedirs(f'.{run_id}/plots/TurnTimes/')
127    os.makedirs(f'.{run_id}/plots/DwellTimesDist/')
128    os.makedirs(f'.{run_id}/plots/TurnTimesDist/')
129    os.makedirs(f'.{run_id}/plots/scenarios')
130    os.makedirs(f'.{run_id}/plots/shipDistribution')
131
132    os.makedirs(f'.{run_id}/plots/truckDistribution')
133    os.makedirs(f'.{run_id}/plots/TruckDwellByCargo')
134    os.makedirs(f'.{run_id}/plots/TruckDwellByTerminal')
135    os.makedirs(f'.{run_id}/plots/TruckArrivalByCargo')
136    os.makedirs(f'.{run_id}/plots/TruckArrivalByTerminal')
137
138    # Create a force_action.txt file under logs
139    with open(f'.{run_id}/logs/force_action.txt', 'w') as f:
140        f.write('Force Actions')
141        f.write('\n')
142
143
144def clean_results_directory():
145    """
146    Remove existing result files and directories matching the pattern `.Results*`.
147    This function is useful for cleaning up the results directory before starting a new simulation run.
148    Args:
149        None
150    Returns:
151        None
152"""
153    for file_path in glob.glob(".Results*"):
154        try:
155            if os.path.isfile(file_path):
156                os.remove(file_path)
157            elif os.path.isdir(file_path):
158                for root, dirs, files in os.walk(file_path, topdown=False):
159                    for name in files:
160                        os.remove(os.path.join(root, name))
161                    for name in dirs:
162                        os.rmdir(os.path.join(root, name))
163                os.rmdir(file_path)
164        except OSError as e:
165            print(f"Error removing {file_path}: {e}")
166
167#######################
168# Data cleaning
169#######################
170
171
172def initialize_rng(seed):
173    """
174    Initialize random state objects for repeatable randomness across runs.
175    Args:
176        seed (int): The seed value for the random number generator.
177    Returns:
178        None
179    """
180    global rng_random, rng_numpy
181    rng_random = random.Random(seed)
182    rng_numpy = np.random.default_rng(seed)
183
184
185def clean_data(directory):
186    """
187    Clean the terminal data by parsing ranges and converting them to tuples.
188    This function reads a CSV file containing terminal data, processes the range strings,
189    and converts them into tuples of integers or floats.
190    Args:
191        directory (str): The directory where the terminal data CSV file is located.
192    Returns:
193        pd.DataFrame: A DataFrame containing the cleaned terminal data with ranges parsed into tuples.
194    """
195
196    def parse_range(range_str):
197        """
198        Parse a range in the format '(a,b)' from a string into a tuple of integers or floats.
199        If the input is a single number, it will be returned as an integer.
200        Args:
201            range_str (str): The range string to be parsed.
202        Returns:
203            tuple or int: A tuple of integers if the input is a range, or an integer if it's a single number.
204        """
205        # convert range string to a string
206        range_str = str(range_str)
207        try:
208            match = re.match(r"\(([\d.]+)\s*,\s*([\d.]+)\)", range_str)
209            start, end = int(match.group(1)), int(match.group(2))
210            return (start, end)
211        except:
212            return int(range_str)
213
214    csv_data = pd.read_csv(f'{directory}/inputs/terminal_data.csv')
215    for column in csv_data.columns[3:]:
216        csv_data[column] = csv_data[column].apply(
217            lambda x: parse_range(x) if isinstance(x, str) else x)
218    return csv_data
219
220
221def create_terminal_data_cache(terminal_data, run_id, seed):
222    """
223    Create a cache dictionary from terminal data for quick access to resource values.
224    This function initializes a random number generator with a given seed,
225    iterates through the terminal data, and populates a cache dictionary with keys
226    based on cargo type, terminal ID, and resource name. If the resource value is a range,
227    it stores a random integer from that range; otherwise, it stores the value directly.
228    Args:
229        terminal_data (pd.DataFrame): The DataFrame containing terminal data.
230        run_id (str): The identifier for the current run, used for logging.
231        seed (int): The seed value for the random number generator.
232    Returns:
233        dict: A dictionary where keys are tuples of (cargo_type, terminal_id, resource_name)
234              and values are the corresponding resource values or random integers from ranges.
235    """
236    # Create the cache dictionary
237    initialize_rng(seed)
238    cache = {}
239    # Populate the cache
240    for index, row in terminal_data.iterrows():
241        cargo_type = row['Cargo']
242        terminal_id = row['Terminal']
243        # Skip 'Cargo' and 'Terminal' columns
244        for resource_name in row.index[2:]:
245            value = row[resource_name]
246            key = (cargo_type, terminal_id, resource_name)
247            # If value is a tuple-like string, convert it to tuple and store a random range value; otherwise, store as int
248            if isinstance(value, tuple):
249                # Convert to tuple and store a random value from range
250                cache[key] = rng_random.randint(value[0], value[1])
251            else:
252                if resource_name in ['train arrival rate', 'truck arrival rate']:
253                    cache[key] = value
254                # Store integer or directly as it is
255                else:
256                    cache[key] = int(value)
257
258    return cache
259
260
261def get_value_by_terminal(terminal_data_cache, cargo_type, terminal_id, resource_name):
262    """
263    Retrieve a value from the terminal data cache based on cargo type, terminal ID, and resource name.
264    This function checks the cache for a specific key and returns the corresponding value.
265    Args:
266        terminal_data_cache (dict): The cache dictionary containing terminal data.
267        cargo_type (str): The type of cargo.
268        terminal_id (str): The ID of the terminal.
269        resource_name (str): The name of the resource.
270    Returns:        
271        The value associated with the specified cargo type, terminal ID, and resource name,
272        or None if the key does not exist in the cache.
273    """
274    key = (cargo_type, terminal_id, resource_name)
275    return terminal_data_cache.get(key)
276
277
278def create_terminal_tuple_cache(terminal_data, run_id, seed):
279    """
280    Create a cache dictionary from terminal data for quick access to resource values as tuples.
281    This function initializes a random number generator with a given seed,
282    iterates through the terminal data, and populates a cache dictionary with keys
283    based on cargo type, terminal ID, and resource name. If the resource value is a range,
284    it stores the range as a tuple; otherwise, it stores the value as a tuple of itself.
285    Args:
286        terminal_data (pd.DataFrame): The DataFrame containing terminal data.   
287        run_id (str): The identifier for the current run, used for logging.
288        seed (int): The seed value for the random number generator.
289    Returns:
290        dict: A dictionary where keys are tuples of (cargo_type, terminal_id, resource_name)
291              and values are tuples of the corresponding resource values or ranges.
292    """
293    # Create the cache dictionary
294    initialize_rng(seed)
295    truck_load_cache = {}
296
297    # Populate the cache
298    for index, row in terminal_data.iterrows():
299        cargo_type = row['Cargo']
300        terminal_id = row['Terminal']
301        # Skip 'Cargo' and 'Terminal' columns
302        for resource_name in row.index[2:]:
303            value = row[resource_name]
304            key = (cargo_type, terminal_id, resource_name)
305            # If value is a tuple-like string, convert it to tuple and store a random range value; otherwise, store as int
306            if isinstance(value, tuple):
307                # Convert to tuple and store a random value from range
308                truck_load_cache[key] = (value[0], value[1])
309            else:
310                if resource_name in ['train arrival rate', 'truck arrival rate']:
311                    truck_load_cache[key] = (value, value)
312                # Store integer or directly as it is
313                else:
314                    truck_load_cache[key] = (int(value), int(value))
315
316    return truck_load_cache
317
318
319def get_value_from_terminal_tuple(terminal_tuple_cache, cargo_type, terminal_id, resource_name):
320    """
321    Retrieve a value from the terminal tuple cache based on cargo type, terminal ID, and resource name.
322    This function checks the cache for a specific key and returns a random integer from the range stored in the tuple.
323    Args:
324        terminal_tuple_cache (dict): The cache dictionary containing terminal data as tuples.
325        cargo_type (str): The type of cargo.        
326        terminal_id (str): The ID of the terminal.
327        resource_name (str): The name of the resource.
328    Returns:
329        int: A random integer from the range stored in the tuple for the specified cargo type, terminal ID, and resource name.
330        If the key does not exist in the cache, it returns None.
331    """
332    # initialize_rng(seed)
333    key = (cargo_type, terminal_id, resource_name)
334    tups = terminal_tuple_cache.get(key)
335    return rng_random.randint(tups[0], tups[1])
336
337
338def get_values_by_terminal_random_sample(terminal_data, cargo_type, terminal_id, resource_name_input, num_samples, seed):
339    """
340    Get a list of random samples from the terminal data based on cargo type and terminal ID.
341    This function retrieves a specific resource value from the terminal data for a given cargo type and terminal ID,
342    and generates a list of random samples from that value. If the value is a range, it samples from that range;
343    otherwise, it returns a list with the value repeated for the specified number of samples.  
344    Args:
345        terminal_data (pd.DataFrame): The DataFrame containing terminal data.
346        cargo_type (str): The type of cargo.
347        terminal_id (str): The ID of the terminal.
348        resource_name_input (str): The name of the resource to sample from.
349        num_samples (int): The number of random samples to generate.
350        seed (int): The seed value for the random number generator.
351    Returns:
352        list: A list of random samples from the specified resource value.
353        If the resource value is a range, it returns random integers from that range;
354        otherwise, it returns a list with the value repeated for the specified number of samples.
355    """
356    random.seed(seed)
357
358    row = terminal_data[(terminal_data['Terminal'] == terminal_id) &
359                        (terminal_data['Cargo'] == cargo_type)].iloc[0]
360
361    value = row[resource_name_input]
362
363    try:
364        return [random.randint(int(value[0]), int(value[1])) for _ in range(num_samples)]
365    except:
366        return [value] * num_samples
367
368
369#######################
370# Helper functions
371#######################
372
373def is_daytime(time, start=6, end=18):
374    """
375    Check if the given time is within the daytime range.
376    Args:
377        time (int or float): The time to check, in hours (0-23).
378        start (int): The start of the daytime range (default is 6).
379        end (int): The end of the daytime range (default is 18).
380    Returns:
381        bool: True if the time is within the daytime range, False otherwise.
382    """
383    return start <= time % 24 <= end
384
385#######################
386# Random
387#######################
388
389
390def normal_random_with_sd(mu, sigma, seed, scale_factor=1):
391    """
392    Generate a random value from a truncated normal distribution with specified mean and standard deviation.
393    The distribution is truncated to ensure the value is within a specified range based on the mean and standard deviation.
394    Args:
395        mu (float): The mean of the normal distribution.
396        sigma (float): The standard deviation of the normal distribution.
397        seed (int): Random seed for reproducibility.
398        scale_factor (float): Factor to scale the standard deviation for truncation (default is 1).
399    Returns:
400        float: A random value from the truncated normal distribution, ensuring it is within the range [a, b].
401    """
402    np.random.seed(seed)
403    a = max(0, mu - scale_factor * sigma)
404    b = max(0, mu + scale_factor * sigma)
405    if a <= 0:
406        a = 0
407    lower, upper = (a - mu) / sigma, (b - mu) / sigma
408    distribution = truncnorm(lower, upper, loc=mu, scale=sigma)
409    val = distribution.rvs()
410    if val < a:
411        return a
412    elif val > b:
413        return b
414    return distribution.rvs()
415
416
417def normal_random_with_limit(a, b, seed):
418    """
419    Generate a random value from a truncated normal distribution between a and b.
420    Args:
421        a (float): Lower limit of the range.
422        b (float): Upper limit of the range.
423        seed (int): Random seed for reproducibility.
424    Returns:
425        float: A random value from the truncated normal distribution.
426    """
427    np.random.seed(seed)
428    mean = (a + b) / 2
429    std_dev = (b - a) / 6
430    lower_bound = (a - mean) / std_dev
431    upper_bound = (b - mean) / std_dev
432    distribution = truncnorm(lower_bound, upper_bound, loc=mean, scale=std_dev)
433    return distribution.rvs()
def log_line(run_id, filename, line):
26def log_line(run_id, filename, line):
27    """
28    Logs a line to a specified file in the run's logs directory.
29    Args:
30        run_id (str): Unique identifier for the run to save the log.
31        filename (str): Name of the file to log the line.
32        line (str): The line to log.
33    Returns:
34        None: Appends the line to the specified log file.
35    """
36    try:
37        with open(f'.{run_id}/logs/{filename}', 'a') as f:
38            f.write(line + '\n')
39    except FileNotFoundError:
40        with open(f'.{run_id}/logs/{filename}', 'w') as f:
41            f.write(line + '\n')

Logs a line to a specified file in the run's logs directory.

Arguments:
  • run_id (str): Unique identifier for the run to save the log.
  • filename (str): Name of the file to log the line.
  • line (str): The line to log.
Returns:

None: Appends the line to the specified log file.

def save_warning(run_id, message, print_val=True):
44def save_warning(run_id, message, print_val=True):
45    """
46    Save a warning message to the logs directory for the current run.
47    If the logs directory does not exist, it will be created.
48    Args:
49        run_id (str): The identifier for the current run.
50        message (str): The warning message to be saved.
51        print_val (bool): Whether to print the message to the console. Default is True.
52    Returns:
53        None
54    """
55    if not os.path.exists(f'.{run_id}/logs/warnings.txt'):
56        with open(f'.{run_id}/logs/warnings.txt', 'w') as f:
57            f.write('Warnings:\n')
58        with open(f'.{run_id}/logs/warnings.txt', 'a') as f:
59            f.write(f'{message}\n')
60    else:
61        with open(f'.{run_id}/logs/warnings.txt', 'a') as f:
62            f.write(f'{message}\n')
63    if print_val:
64        print(f"{message}")

Save a warning message to the logs directory for the current run. If the logs directory does not exist, it will be created.

Arguments:
  • run_id (str): The identifier for the current run.
  • message (str): The warning message to be saved.
  • print_val (bool): Whether to print the message to the console. Default is True.
Returns:

None

def clear_env(env, ship_proc, truck_proc, train_proc, data_taker_proc):
67def clear_env(env, ship_proc, truck_proc, train_proc, data_taker_proc):
68    """
69    Clear the environment and interrupt all processes.
70    Args:
71        env (Simpy.Environment): The simulation environment to be cleared.
72        ship_proc (Simpy.Process): The process for handling ships.
73        truck_proc (Simpy.Process): The process for handling trucks.
74        train_proc (Simpy.Process): The process for handling trains.
75        data_taker_proc: The process for taking data.
76    Returns:
77        None
78    """
79    try:
80        ship_proc.interrupt()
81    except:
82        pass
83    try:
84        truck_proc.interrupt()
85    except:
86        pass
87    try:
88        train_proc.interrupt()
89    except:
90        pass
91
92    try:
93        data_taker_proc.interrupt()
94    except:
95        pass
96
97    env._queue.clear()

Clear the environment and interrupt all processes.

Arguments:
  • env (Simpy.Environment): The simulation environment to be cleared.
  • ship_proc (Simpy.Process): The process for handling ships.
  • truck_proc (Simpy.Process): The process for handling trucks.
  • train_proc (Simpy.Process): The process for handling trains.
  • data_taker_proc: The process for taking data.
Returns:

None

def clear_logs(run_id):
100def clear_logs(run_id):
101    """
102    Clear the logs directory for the current run by removing all files and subdirectories.
103    Then create the necessary subdirectories again.
104    This function is useful for resetting the logs directory before starting a new simulation run.
105    Args:
106        run_id (str): The identifier for the current run.
107    Returns:
108        None
109    """
110
111    if constants.DELETE_RESULTS_FOLDER == False:
112        shutil.rmtree(f'.{run_id}/logs', ignore_errors=True)
113        shutil.rmtree(f'.{run_id}/plots', ignore_errors=True)
114        # shutil.rmtree(f'.{run_id}/animations', ignore_errors=True)
115        shutil.rmtree(f'.{run_id}/bottlenecks', ignore_errors=True)
116
117    # os.makedirs(f'.{run_id}/animations')
118
119    os.makedirs(f'.{run_id}/logs')
120    os.makedirs(f'.{run_id}/logs/availability')
121    os.makedirs(f'.{run_id}/plots')
122    os.makedirs(f'.{run_id}/bottlenecks')
123    os.makedirs(f'.{run_id}/bottlenecks/chassisBays/')
124    os.makedirs(f'.{run_id}/plots/TerminalProcessCharts/')
125    os.makedirs(f'.{run_id}/plots/TerminalProcessDist/')
126    os.makedirs(f'.{run_id}/plots/DwellTimes/')
127    os.makedirs(f'.{run_id}/plots/TurnTimes/')
128    os.makedirs(f'.{run_id}/plots/DwellTimesDist/')
129    os.makedirs(f'.{run_id}/plots/TurnTimesDist/')
130    os.makedirs(f'.{run_id}/plots/scenarios')
131    os.makedirs(f'.{run_id}/plots/shipDistribution')
132
133    os.makedirs(f'.{run_id}/plots/truckDistribution')
134    os.makedirs(f'.{run_id}/plots/TruckDwellByCargo')
135    os.makedirs(f'.{run_id}/plots/TruckDwellByTerminal')
136    os.makedirs(f'.{run_id}/plots/TruckArrivalByCargo')
137    os.makedirs(f'.{run_id}/plots/TruckArrivalByTerminal')
138
139    # Create a force_action.txt file under logs
140    with open(f'.{run_id}/logs/force_action.txt', 'w') as f:
141        f.write('Force Actions')
142        f.write('\n')

Clear the logs directory for the current run by removing all files and subdirectories. Then create the necessary subdirectories again. This function is useful for resetting the logs directory before starting a new simulation run.

Arguments:
  • run_id (str): The identifier for the current run.
Returns:

None

def clean_results_directory():
145def clean_results_directory():
146    """
147    Remove existing result files and directories matching the pattern `.Results*`.
148    This function is useful for cleaning up the results directory before starting a new simulation run.
149    Args:
150        None
151    Returns:
152        None
153"""
154    for file_path in glob.glob(".Results*"):
155        try:
156            if os.path.isfile(file_path):
157                os.remove(file_path)
158            elif os.path.isdir(file_path):
159                for root, dirs, files in os.walk(file_path, topdown=False):
160                    for name in files:
161                        os.remove(os.path.join(root, name))
162                    for name in dirs:
163                        os.rmdir(os.path.join(root, name))
164                os.rmdir(file_path)
165        except OSError as e:
166            print(f"Error removing {file_path}: {e}")

Remove existing result files and directories matching the pattern .Results*. This function is useful for cleaning up the results directory before starting a new simulation run.

Arguments:
  • None
Returns:

None

def initialize_rng(seed):
173def initialize_rng(seed):
174    """
175    Initialize random state objects for repeatable randomness across runs.
176    Args:
177        seed (int): The seed value for the random number generator.
178    Returns:
179        None
180    """
181    global rng_random, rng_numpy
182    rng_random = random.Random(seed)
183    rng_numpy = np.random.default_rng(seed)

Initialize random state objects for repeatable randomness across runs.

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

None

def clean_data(directory):
186def clean_data(directory):
187    """
188    Clean the terminal data by parsing ranges and converting them to tuples.
189    This function reads a CSV file containing terminal data, processes the range strings,
190    and converts them into tuples of integers or floats.
191    Args:
192        directory (str): The directory where the terminal data CSV file is located.
193    Returns:
194        pd.DataFrame: A DataFrame containing the cleaned terminal data with ranges parsed into tuples.
195    """
196
197    def parse_range(range_str):
198        """
199        Parse a range in the format '(a,b)' from a string into a tuple of integers or floats.
200        If the input is a single number, it will be returned as an integer.
201        Args:
202            range_str (str): The range string to be parsed.
203        Returns:
204            tuple or int: A tuple of integers if the input is a range, or an integer if it's a single number.
205        """
206        # convert range string to a string
207        range_str = str(range_str)
208        try:
209            match = re.match(r"\(([\d.]+)\s*,\s*([\d.]+)\)", range_str)
210            start, end = int(match.group(1)), int(match.group(2))
211            return (start, end)
212        except:
213            return int(range_str)
214
215    csv_data = pd.read_csv(f'{directory}/inputs/terminal_data.csv')
216    for column in csv_data.columns[3:]:
217        csv_data[column] = csv_data[column].apply(
218            lambda x: parse_range(x) if isinstance(x, str) else x)
219    return csv_data

Clean the terminal data by parsing ranges and converting them to tuples. This function reads a CSV file containing terminal data, processes the range strings, and converts them into tuples of integers or floats.

Arguments:
  • directory (str): The directory where the terminal data CSV file is located.
Returns:

pd.DataFrame: A DataFrame containing the cleaned terminal data with ranges parsed into tuples.

def create_terminal_data_cache(terminal_data, run_id, seed):
222def create_terminal_data_cache(terminal_data, run_id, seed):
223    """
224    Create a cache dictionary from terminal data for quick access to resource values.
225    This function initializes a random number generator with a given seed,
226    iterates through the terminal data, and populates a cache dictionary with keys
227    based on cargo type, terminal ID, and resource name. If the resource value is a range,
228    it stores a random integer from that range; otherwise, it stores the value directly.
229    Args:
230        terminal_data (pd.DataFrame): The DataFrame containing terminal data.
231        run_id (str): The identifier for the current run, used for logging.
232        seed (int): The seed value for the random number generator.
233    Returns:
234        dict: A dictionary where keys are tuples of (cargo_type, terminal_id, resource_name)
235              and values are the corresponding resource values or random integers from ranges.
236    """
237    # Create the cache dictionary
238    initialize_rng(seed)
239    cache = {}
240    # Populate the cache
241    for index, row in terminal_data.iterrows():
242        cargo_type = row['Cargo']
243        terminal_id = row['Terminal']
244        # Skip 'Cargo' and 'Terminal' columns
245        for resource_name in row.index[2:]:
246            value = row[resource_name]
247            key = (cargo_type, terminal_id, resource_name)
248            # If value is a tuple-like string, convert it to tuple and store a random range value; otherwise, store as int
249            if isinstance(value, tuple):
250                # Convert to tuple and store a random value from range
251                cache[key] = rng_random.randint(value[0], value[1])
252            else:
253                if resource_name in ['train arrival rate', 'truck arrival rate']:
254                    cache[key] = value
255                # Store integer or directly as it is
256                else:
257                    cache[key] = int(value)
258
259    return cache

Create a cache dictionary from terminal data for quick access to resource values. This function initializes a random number generator with a given seed, iterates through the terminal data, and populates a cache dictionary with keys based on cargo type, terminal ID, and resource name. If the resource value is a range, it stores a random integer from that range; otherwise, it stores the value directly.

Arguments:
  • terminal_data (pd.DataFrame): The DataFrame containing terminal data.
  • run_id (str): The identifier for the current run, used for logging.
  • seed (int): The seed value for the random number generator.
Returns:

dict: A dictionary where keys are tuples of (cargo_type, terminal_id, resource_name) and values are the corresponding resource values or random integers from ranges.

def get_value_by_terminal(terminal_data_cache, cargo_type, terminal_id, resource_name):
262def get_value_by_terminal(terminal_data_cache, cargo_type, terminal_id, resource_name):
263    """
264    Retrieve a value from the terminal data cache based on cargo type, terminal ID, and resource name.
265    This function checks the cache for a specific key and returns the corresponding value.
266    Args:
267        terminal_data_cache (dict): The cache dictionary containing terminal data.
268        cargo_type (str): The type of cargo.
269        terminal_id (str): The ID of the terminal.
270        resource_name (str): The name of the resource.
271    Returns:        
272        The value associated with the specified cargo type, terminal ID, and resource name,
273        or None if the key does not exist in the cache.
274    """
275    key = (cargo_type, terminal_id, resource_name)
276    return terminal_data_cache.get(key)

Retrieve a value from the terminal data cache based on cargo type, terminal ID, and resource name. This function checks the cache for a specific key and returns the corresponding value.

Arguments:
  • terminal_data_cache (dict): The cache dictionary containing terminal data.
  • cargo_type (str): The type of cargo.
  • terminal_id (str): The ID of the terminal.
  • resource_name (str): The name of the resource.

Returns:
The value associated with the specified cargo type, terminal ID, and resource name, or None if the key does not exist in the cache.

def create_terminal_tuple_cache(terminal_data, run_id, seed):
279def create_terminal_tuple_cache(terminal_data, run_id, seed):
280    """
281    Create a cache dictionary from terminal data for quick access to resource values as tuples.
282    This function initializes a random number generator with a given seed,
283    iterates through the terminal data, and populates a cache dictionary with keys
284    based on cargo type, terminal ID, and resource name. If the resource value is a range,
285    it stores the range as a tuple; otherwise, it stores the value as a tuple of itself.
286    Args:
287        terminal_data (pd.DataFrame): The DataFrame containing terminal data.   
288        run_id (str): The identifier for the current run, used for logging.
289        seed (int): The seed value for the random number generator.
290    Returns:
291        dict: A dictionary where keys are tuples of (cargo_type, terminal_id, resource_name)
292              and values are tuples of the corresponding resource values or ranges.
293    """
294    # Create the cache dictionary
295    initialize_rng(seed)
296    truck_load_cache = {}
297
298    # Populate the cache
299    for index, row in terminal_data.iterrows():
300        cargo_type = row['Cargo']
301        terminal_id = row['Terminal']
302        # Skip 'Cargo' and 'Terminal' columns
303        for resource_name in row.index[2:]:
304            value = row[resource_name]
305            key = (cargo_type, terminal_id, resource_name)
306            # If value is a tuple-like string, convert it to tuple and store a random range value; otherwise, store as int
307            if isinstance(value, tuple):
308                # Convert to tuple and store a random value from range
309                truck_load_cache[key] = (value[0], value[1])
310            else:
311                if resource_name in ['train arrival rate', 'truck arrival rate']:
312                    truck_load_cache[key] = (value, value)
313                # Store integer or directly as it is
314                else:
315                    truck_load_cache[key] = (int(value), int(value))
316
317    return truck_load_cache

Create a cache dictionary from terminal data for quick access to resource values as tuples. This function initializes a random number generator with a given seed, iterates through the terminal data, and populates a cache dictionary with keys based on cargo type, terminal ID, and resource name. If the resource value is a range, it stores the range as a tuple; otherwise, it stores the value as a tuple of itself.

Arguments:
  • terminal_data (pd.DataFrame): The DataFrame containing terminal data.
  • run_id (str): The identifier for the current run, used for logging.
  • seed (int): The seed value for the random number generator.
Returns:

dict: A dictionary where keys are tuples of (cargo_type, terminal_id, resource_name) and values are tuples of the corresponding resource values or ranges.

def get_value_from_terminal_tuple(terminal_tuple_cache, cargo_type, terminal_id, resource_name):
320def get_value_from_terminal_tuple(terminal_tuple_cache, cargo_type, terminal_id, resource_name):
321    """
322    Retrieve a value from the terminal tuple cache based on cargo type, terminal ID, and resource name.
323    This function checks the cache for a specific key and returns a random integer from the range stored in the tuple.
324    Args:
325        terminal_tuple_cache (dict): The cache dictionary containing terminal data as tuples.
326        cargo_type (str): The type of cargo.        
327        terminal_id (str): The ID of the terminal.
328        resource_name (str): The name of the resource.
329    Returns:
330        int: A random integer from the range stored in the tuple for the specified cargo type, terminal ID, and resource name.
331        If the key does not exist in the cache, it returns None.
332    """
333    # initialize_rng(seed)
334    key = (cargo_type, terminal_id, resource_name)
335    tups = terminal_tuple_cache.get(key)
336    return rng_random.randint(tups[0], tups[1])

Retrieve a value from the terminal tuple cache based on cargo type, terminal ID, and resource name. This function checks the cache for a specific key and returns a random integer from the range stored in the tuple.

Arguments:
  • terminal_tuple_cache (dict): The cache dictionary containing terminal data as tuples.
  • cargo_type (str): The type of cargo.
  • terminal_id (str): The ID of the terminal.
  • resource_name (str): The name of the resource.
Returns:

int: A random integer from the range stored in the tuple for the specified cargo type, terminal ID, and resource name. If the key does not exist in the cache, it returns None.

def get_values_by_terminal_random_sample( terminal_data, cargo_type, terminal_id, resource_name_input, num_samples, seed):
339def get_values_by_terminal_random_sample(terminal_data, cargo_type, terminal_id, resource_name_input, num_samples, seed):
340    """
341    Get a list of random samples from the terminal data based on cargo type and terminal ID.
342    This function retrieves a specific resource value from the terminal data for a given cargo type and terminal ID,
343    and generates a list of random samples from that value. If the value is a range, it samples from that range;
344    otherwise, it returns a list with the value repeated for the specified number of samples.  
345    Args:
346        terminal_data (pd.DataFrame): The DataFrame containing terminal data.
347        cargo_type (str): The type of cargo.
348        terminal_id (str): The ID of the terminal.
349        resource_name_input (str): The name of the resource to sample from.
350        num_samples (int): The number of random samples to generate.
351        seed (int): The seed value for the random number generator.
352    Returns:
353        list: A list of random samples from the specified resource value.
354        If the resource value is a range, it returns random integers from that range;
355        otherwise, it returns a list with the value repeated for the specified number of samples.
356    """
357    random.seed(seed)
358
359    row = terminal_data[(terminal_data['Terminal'] == terminal_id) &
360                        (terminal_data['Cargo'] == cargo_type)].iloc[0]
361
362    value = row[resource_name_input]
363
364    try:
365        return [random.randint(int(value[0]), int(value[1])) for _ in range(num_samples)]
366    except:
367        return [value] * num_samples

Get a list of random samples from the terminal data based on cargo type and terminal ID. This function retrieves a specific resource value from the terminal data for a given cargo type and terminal ID, and generates a list of random samples from that value. If the value is a range, it samples from that range; otherwise, it returns a list with the value repeated for the specified number of samples.

Arguments:
  • terminal_data (pd.DataFrame): The DataFrame containing terminal data.
  • cargo_type (str): The type of cargo.
  • terminal_id (str): The ID of the terminal.
  • resource_name_input (str): The name of the resource to sample from.
  • num_samples (int): The number of random samples to generate.
  • seed (int): The seed value for the random number generator.
Returns:

list: A list of random samples from the specified resource value. If the resource value is a range, it returns random integers from that range; otherwise, it returns a list with the value repeated for the specified number of samples.

def is_daytime(time, start=6, end=18):
374def is_daytime(time, start=6, end=18):
375    """
376    Check if the given time is within the daytime range.
377    Args:
378        time (int or float): The time to check, in hours (0-23).
379        start (int): The start of the daytime range (default is 6).
380        end (int): The end of the daytime range (default is 18).
381    Returns:
382        bool: True if the time is within the daytime range, False otherwise.
383    """
384    return start <= time % 24 <= end

Check if the given time is within the daytime range.

Arguments:
  • time (int or float): The time to check, in hours (0-23).
  • start (int): The start of the daytime range (default is 6).
  • end (int): The end of the daytime range (default is 18).
Returns:

bool: True if the time is within the daytime range, False otherwise.

def normal_random_with_sd(mu, sigma, seed, scale_factor=1):
391def normal_random_with_sd(mu, sigma, seed, scale_factor=1):
392    """
393    Generate a random value from a truncated normal distribution with specified mean and standard deviation.
394    The distribution is truncated to ensure the value is within a specified range based on the mean and standard deviation.
395    Args:
396        mu (float): The mean of the normal distribution.
397        sigma (float): The standard deviation of the normal distribution.
398        seed (int): Random seed for reproducibility.
399        scale_factor (float): Factor to scale the standard deviation for truncation (default is 1).
400    Returns:
401        float: A random value from the truncated normal distribution, ensuring it is within the range [a, b].
402    """
403    np.random.seed(seed)
404    a = max(0, mu - scale_factor * sigma)
405    b = max(0, mu + scale_factor * sigma)
406    if a <= 0:
407        a = 0
408    lower, upper = (a - mu) / sigma, (b - mu) / sigma
409    distribution = truncnorm(lower, upper, loc=mu, scale=sigma)
410    val = distribution.rvs()
411    if val < a:
412        return a
413    elif val > b:
414        return b
415    return distribution.rvs()

Generate a random value from a truncated normal distribution with specified mean and standard deviation. The distribution is truncated to ensure the value is within a specified range based on the mean and standard deviation.

Arguments:
  • mu (float): The mean of the normal distribution.
  • sigma (float): The standard deviation of the normal distribution.
  • seed (int): Random seed for reproducibility.
  • scale_factor (float): Factor to scale the standard deviation for truncation (default is 1).
Returns:

float: A random value from the truncated normal distribution, ensuring it is within the range [a, b].

def normal_random_with_limit(a, b, seed):
418def normal_random_with_limit(a, b, seed):
419    """
420    Generate a random value from a truncated normal distribution between a and b.
421    Args:
422        a (float): Lower limit of the range.
423        b (float): Upper limit of the range.
424        seed (int): Random seed for reproducibility.
425    Returns:
426        float: A random value from the truncated normal distribution.
427    """
428    np.random.seed(seed)
429    mean = (a + b) / 2
430    std_dev = (b - a) / 6
431    lower_bound = (a - mean) / std_dev
432    upper_bound = (b - mean) / std_dev
433    distribution = truncnorm(lower_bound, upper_bound, loc=mean, scale=std_dev)
434    return distribution.rvs()

Generate a random value from a truncated normal distribution between a and b.

Arguments:
  • a (float): Lower limit of the range.
  • b (float): Upper limit of the range.
  • seed (int): Random seed for reproducibility.
Returns:

float: A random value from the truncated normal distribution.