0

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:

1 Answer 1

0

I'm not sure if this is exactly what you want, but you could show the function animation on a Canvas within the tkinter window. This requires a couple extra imports, namely

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

From here you need to define your canvas. In your run_model() function you want to define the canvas above your animations like this:

canvas = FigureCanvasTkAgg(fig, master=result_window)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # you can edit this to your needs

It is important that you also move your code for the tkinter window down, so that it doesn't run before we create the animations. So, move lines 115-120 down to the end of the function. I removed the grid from your code and just used pack, but this can be changed. The frame is also not necessary, but better for organizational purposes. This will show both the information and your two plot animations on the screen. Note: In your animations, you originally had blit=True. For whatever reason this breaks the animations, and it must be set to false in order for this to work.

Here is the full code:

import numpy as np
import matplotlib.pyplot as plt
import tkinter as tk
from tkinter import ttk
from matplotlib.animation import FuncAnimation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


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"
    )

    # 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,


    # Display results in a Tkinter window
    result_window = tk.Tk()
    result_window.title("Model Results")
    result_window.geometry('1920x1080')

    canvas = FigureCanvasTkAgg(fig, master=result_window)
    canvas_widget = canvas.get_tk_widget()
    canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)

    frame = tk.Frame(result_window)
    frame.pack(side=tk.BOTTOM, fill=tk.X)

    label = ttk.Label(frame, text=result_text, justify=tk.LEFT)
    button = ttk.Button(frame, text="Close", command=result_window.destroy)
    label.pack(padx=10, pady=10)
    button.pack(padx=10, pady=10)

    ani1 = FuncAnimation(fig, update_air_concentration, frames=len(time_points), init_func=init_air_concentration, interval=300/len(time_points), repeat=False)
    ani2 = FuncAnimation(fig, update_external_dose, frames=len(time_points), init_func=init_external_dose, interval=300/len(time_points), repeat=False)

    result_window.mainloop()

if __name__ == "__main__":
    run_model()
Sign up to request clarification or add additional context in comments.

2 Comments

yep so when I run this I get: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSApplication macOSVersion]: unrecognized selector sent to instance 0x133638300' *** First throw call stack: Wonder if its a Mac issue
@Nick yeah that's weird I just copy pasted the code and it ran fine. I'm guessing it is a problem to do with the backend of one of the imports since nowhere in the code are you requesting the macOSVersion. Have you tried making sure all your imports are fully updated?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.