以下脚本独立运行以运行场景:
首先打印结果,然后以动画结束绘图。
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()
但是,我希望它们同时出现。或者,如果不是,则该一个紧接着另一个,而不必先关闭结果窗口。
对此的各种尝试会导致此问题:
*** 由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“ -[NSApplication macOSVersion]:无法识别的选择器发送到实例 0x13616f730' *** 首先抛出调用堆栈:
任何帮助表示赞赏。
代码的问题在于,你试图从主线程以外的线程更新 Tkinter GUI。
matplotlib.animation.FuncAnimation
在后台线程中运行动画,当动画更新试图与主线程中的 Tkinter 窗口交互时,就会发生冲突。
以下是如何修改代码以使其正常工作的步骤:
- 将绘图逻辑与 GUI 逻辑分开: 将计算结果和更新绘图的代码移动到单独的函数中。
-
使用
matplotlib.figure.Figure
嵌入 Matplotlib 图表: 不用plt.show()
,而是创建一个Figure
和Canvas
并将其嵌入到 Tkinter 窗口中。 -
使用
after
方法进行动画: 使用 Tkinter 的after
方法更新动画,而不是FuncAnimation
,该方法允许你在不阻塞主循环的情况下安排函数在特定时间后运行。
以下是用这些更改修改后的代码:
import numpy as np
import matplotlib.pyplot as plt
import tkinter as tk
from tkinter import ttk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
def run_model():
# ...(你的计算代码,与之前相同)...
# --- GUI 代码 ---
root = tk.Tk()
root.title("Inhalation Model")
# 结果框架
result_frame = ttk.LabelFrame(root, text="Model Results")
result_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
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"
)
ttk.Label(result_frame, text=result_text, justify=tk.LEFT).grid(column=0, row=0, padx=10, pady=10)
# 绘图框架
plot_frame = ttk.LabelFrame(root, text="Concentration and Dose over Time")
plot_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
# 创建 Matplotlib 图表
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
canvas = FigureCanvasTkAgg(fig, master=plot_frame)
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# 初始化线
line1, = ax1.plot([], [], lw=2)
line2, = ax2.plot([], [], lw=2)
# 绘图配置
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)
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')
ax2.set_xlabel('Time (min)')
ax2.set_ylabel('Dose (mg/kg bw)')
ax2.grid(True)
# 动画函数
def animate(frame):
line1.set_data(time_points[:frame] / 60, C_air_over_time_mg_m3[:frame])
line2.set_data(time_points[:frame] / 60, cumulative_dose_mg_kg_bw[:frame])
canvas.draw()
if frame < len(time_points):
root.after(300 // len(time_points), animate, frame + 1)
# 启动动画
animate(0)
root.mainloop()
if __name__ == "__main__":
run_model()
在这个修改后的代码中:
-
我们创建了一个单独的
animate
函数来处理绘图更新。 -
我们使用
canvas.draw()
更新嵌入式 Matplotlib 图表。 -
我们使用
root.after()
来安排animate
函数在每个时间步长后调用自身,从而创建动画。
此方法可确保所有 GUI 更新都在主线程上进行,从而避免了冲突并按预期运行代码。
标签:python,matplotlib,tkinter,tkinter-canvas,matplotlib-animation From: 78772431