The following script runs standalone to run a scenario:
The results are printed first, followed by the plot is closed with an animation.
import numpy as np
import matplotlib.pyplot as plt
import tkinter as tk
from tkinter import ttk
from matplotlib.animation import FuncAnimation
def run_model():
# Input parameters (example values)
frequency_per_year = 365
exposure_duration = 240 # minutes
product_amount = 5 # g
weight_fraction_substance = 0.6 # fraction
room_volume = 58 # m³
ventilation_rate = 0.5 # per hour
inhalation_rate = 10 # m³/hr
body_weight_kg = 65 # kg
# Advanced parameters
vapour_pressure = 2 # Pa
application_temperature = 18 # °C
molecular_weight = 100 # g/mol
# Conversion functions
def convert_to_seconds(duration):
return duration * 60 # minutes to seconds
def convert_to_kg(amount):
return amount / 1000 # g to kg
def convert_to_per_second(rate):
return rate / 3600 # per hour to per second
def convert_inhalation_rate(rate):
return rate # m³/hr
def convert_pressure(value):
return value # Pa
def convert_temperature(value):
return value + 273.15 # °C to K
def convert_molecular_weight(value):
return value # g/mol
# Convert units
exposure_duration_seconds = convert_to_seconds(exposure_duration)
product_amount_kg = convert_to_kg(product_amount)
ventilation_rate_per_second = convert_to_per_second(ventilation_rate)
inhalation_rate_m3_hr = convert_inhalation_rate(inhalation_rate)
temperature_k = convert_temperature(application_temperature)
vapour_pressure_pa = convert_pressure(vapour_pressure)
molecular_weight_kg_mol = convert_molecular_weight(molecular_weight)
# Universal gas constant
R = 8.314 # J/mol/K
# Time points (in seconds)
time_points = np.linspace(0, exposure_duration_seconds, 500)
# Calculate C_air over time
C_air_over_time = (product_amount_kg * weight_fraction_substance / room_volume) * np.exp(-ventilation_rate_per_second * time_points)
# Calculate C_sat
C_sat = (molecular_weight_kg_mol * vapour_pressure_pa) / (R * temperature_k)
# Convert C_air_over_time to mg/m^3 for plotting
C_air_over_time_mg_m3 = C_air_over_time * 1e6
# Calculate the total inhaled dose
total_inhaled_volume_m3 = inhalation_rate_m3_hr * (exposure_duration_seconds / 3600) # m^3
total_inhaled_dose_kg = np.trapz(C_air_over_time, time_points) * inhalation_rate_m3_hr / 3600 # kg
total_inhaled_dose_mg = total_inhaled_dose_kg * 1e6 # mg
# Calculate the external event dose
external_event_dose_mg_kg_bw = total_inhaled_dose_mg / body_weight_kg # mg/kg bw
# Calculate mean event concentration
mean_event_concentration = np.mean(C_air_over_time_mg_m3)
# Calculate peak concentration (TWA 15 min)
time_interval_15min = 15 * 60 # 15 minutes in seconds
if exposure_duration_seconds >= time_interval_15min:
time_points_15min = time_points[time_points <= time_interval_15min]
C_air_15min = C_air_over_time[time_points <= time_interval_15min]
TWA_15_min = np.mean(C_air_15min) * 1e6 # Convert to mg/m³
else:
TWA_15_min = mean_event_concentration
# Calculate mean concentration on day of exposure
mean_concentration_day_of_exposure = mean_event_concentration * (exposure_duration_seconds / 86400)
# Calculate year average concentration
year_average_concentration = mean_concentration_day_of_exposure * frequency_per_year / 365
# Calculate cumulative dose over time and convert to mg/kg body weight
time_step = time_points[1] - time_points[0]
cumulative_dose_kg = np.cumsum(C_air_over_time) * time_step * inhalation_rate_m3_hr / 3600 # kg
cumulative_dose_mg = cumulative_dose_kg * 1e6 # mg
cumulative_dose_mg_kg_bw = cumulative_dose_mg / body_weight_kg # mg/kg bw
# Final values for annotations
final_air_concentration = C_air_over_time_mg_m3[-1]
final_external_event_dose = cumulative_dose_mg_kg_bw[-1]
# Prepare results text
result_text = (
f"Mean event concentration: {mean_event_concentration:.1e} mg/m³\n"
f"Peak concentration (TWA 15 min): {TWA_15_min:.1e} mg/m³\n"
f"Mean concentration on day of exposure: {mean_concentration_day_of_exposure:.1e} mg/m³\n"
f"Year average concentration: {year_average_concentration:.1e} mg/m³\n"
f"External event dose: {external_event_dose_mg_kg_bw:.1f} mg/kg bw\n"
f"External dose on day of exposure: {external_event_dose_mg_kg_bw:.1f} mg/kg bw"
)
# Display results in a Tkinter window
result_window = tk.Tk()
result_window.title("Model Results")
ttk.Label(result_window, text=result_text, justify=tk.LEFT).grid(column=0, row=0, padx=10, pady=10)
ttk.Button(result_window, text="Close", command=result_window.destroy).grid(column=0, row=1, pady=(0, 10))
result_window.mainloop()
# Plotting with animation
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
# Initialize lines
line1, = ax1.plot([], [], lw=2)
line2, = ax2.plot([], [], lw=2)
# Plot air concentration over time
def init_air_concentration():
ax1.set_xlim(0, exposure_duration)
ax1.set_ylim(0, np.max(C_air_over_time_mg_m3) * 1.1)
ax1.set_title('Inhalation - Air concentration')
ax1.set_xlabel('Time (min)')
ax1.set_ylabel('Concentration (mg/m³)')
ax1.grid(True)
return line1,
def update_air_concentration(frame):
line1.set_data(time_points[:frame] / 60, C_air_over_time_mg_m3[:frame])
return line1,
# Plot external event dose over time
def init_external_dose():
ax2.set_xlim(0, exposure_duration)
ax2.set_ylim(0, np.max(cumulative_dose_mg_kg_bw) * 1.1)
ax2.set_title('Inhalation - External event dose over time')
ax2.set_xlabel('Time (min)')
ax2.set_ylabel('Dose (mg/kg bw)')
ax2.grid(True)
return line2,
def update_external_dose(frame):
line2.set_data(time_points[:frame] / 60, cumulative_dose_mg_kg_bw[:frame])
return line2,
ani1 = FuncAnimation(fig, update_air_concentration, frames=len(time_points), init_func=init_air_concentration, blit=True, interval=300/len(time_points), repeat=False)
ani2 = FuncAnimation(fig, update_external_dose, frames=len(time_points), init_func=init_external_dose, blit=True, interval=300/len(time_points), repeat=False)
plt.tight_layout()
plt.show()
if __name__ == "__main__":
run_model()
However, I would like them to appear simultaneously. Or, if not, that one immediately after the other without having to close the results window first.
Various attempts at this cause this issue:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSApplication macOSVersion]: unrecognized selector sent to instance 0x13616f730' First throw call stack: