|
| 1 | +import numpy as np |
| 2 | +import matplotlib.pyplot as plt |
| 3 | +from scipy.integrate import solve_ivp |
| 4 | +import os |
| 5 | +import sys |
| 6 | + |
| 7 | +# Add path to Verification/Pyrolysis |
| 8 | +sys.path.append('../../Verification/Pyrolysis') |
| 9 | + |
| 10 | +# Define global variables |
| 11 | +dTdt = None |
| 12 | +R0 = None |
| 13 | +E = None |
| 14 | +A = None |
| 15 | +residue = None |
| 16 | + |
| 17 | +def plot_style(): |
| 18 | + # Define plot style settings (placeholder) |
| 19 | + plt.style.use('seaborn-darkgrid') |
| 20 | + global Plot_Units, Plot_X, Plot_Y, Plot_Width, Plot_Height |
| 21 | + global Font_Name, Label_Font_Size, Font_Interpreter |
| 22 | + global Figure_Visibility, Paper_Units, Paper_Width, Paper_Height |
| 23 | + Plot_Units = 'normalized' |
| 24 | + Plot_X = 0.1 |
| 25 | + Plot_Y = 0.1 |
| 26 | + Plot_Width = 0.8 |
| 27 | + Plot_Height = 0.8 |
| 28 | + Font_Name = 'Arial' |
| 29 | + Label_Font_Size = 12 |
| 30 | + Font_Interpreter = 'latex' |
| 31 | + Figure_Visibility = 'on' |
| 32 | + Paper_Units = 'inches' |
| 33 | + Paper_Width = 6 |
| 34 | + Paper_Height = 4 |
| 35 | + |
| 36 | +def addverstr(ax, filename, mode): |
| 37 | + # Placeholder for addverstr function |
| 38 | + # In Python, you might read the version string from the file and add text to the plot |
| 39 | + if os.path.exists(filename): |
| 40 | + with open(filename, 'r') as file: |
| 41 | + ver_str = file.read().strip() |
| 42 | + ax.text(0.05, 0.95, ver_str, transform=ax.transAxes, fontsize=8, |
| 43 | + verticalalignment='top') |
| 44 | + |
| 45 | +def reaction_rate(T, Y): |
| 46 | + """ |
| 47 | + Defines the ODE system dY/dt = -A * Y * exp(-E / (R * T)) |
| 48 | + """ |
| 49 | + global A, E, R0 |
| 50 | + return -A * Y * np.exp(-E / (R0 * T)) |
| 51 | + |
| 52 | +def main(): |
| 53 | + global dTdt, R0, E, A, residue |
| 54 | + |
| 55 | + plt.close('all') |
| 56 | + plt.figure() |
| 57 | + |
| 58 | + plot_style() |
| 59 | + |
| 60 | + show_fds = True |
| 61 | + |
| 62 | + for i_plot in range(1, 3): |
| 63 | + fig, ax1 = plt.subplots() |
| 64 | + fig.set_size_inches(Paper_Width, Paper_Height) |
| 65 | + ax1.set_position([Plot_X, Plot_Y, Plot_Width, Plot_Height]) |
| 66 | + |
| 67 | + dTdt = 5.0 / 60.0 # degrees C per second |
| 68 | + R0 = 8314.3 # J/(kmol*K) |
| 69 | + |
| 70 | + if i_plot == 1: |
| 71 | + n_components = 3 |
| 72 | + T_p = np.array([100.0 + 273.0, 300.0 + 273.0]) |
| 73 | + Y_0 = np.array([0.00, 1.00, 0.00]) |
| 74 | + delta_T = np.array([10.0, -80.0]) |
| 75 | + r_p = np.zeros(n_components - 1) |
| 76 | + residue = np.array([0.0, 0.0]) |
| 77 | + else: |
| 78 | + n_components = 3 |
| 79 | + T_p = np.array([100.0 + 273.0, 300.0 + 273.0]) |
| 80 | + Y_0 = np.array([0.1, 0.9, 0.0]) |
| 81 | + delta_T = np.array([10.0, 80.0]) |
| 82 | + residue = np.array([0.0, 0.2]) |
| 83 | + r_p = np.zeros(n_components - 1) |
| 84 | + |
| 85 | + # Calculate A and E |
| 86 | + E = np.zeros(n_components - 1) |
| 87 | + A = np.zeros(n_components - 1) |
| 88 | + for i in range(n_components - 1): |
| 89 | + if delta_T[i] > 0: |
| 90 | + r_p[i] = 2.0 * dTdt * (1.0 - residue[i]) / delta_T[i] |
| 91 | + E[i] = np.exp(1.0) * r_p[i] * R0 * T_p[i] ** 2 / dTdt |
| 92 | + A[i] = np.exp(1.0) * r_p[i] * np.exp(E[i] / (R0 * T_p[i])) |
| 93 | + |
| 94 | + # Solve the ODE dY/dT = f(T, Y) |
| 95 | + # Since dY/dt = f(T, Y) and dT/dt is constant, dY/dT = f(T, Y) / dTdt |
| 96 | + def dydT(T, Y): |
| 97 | + return reaction_rate(T, Y) / dTdt |
| 98 | + |
| 99 | + sol = solve_ivp(dydT, [273, 673], Y_0, method='BDF', |
| 100 | + rtol=1e-10, atol=1e-8) |
| 101 | + T = sol.t |
| 102 | + Y = sol.y.T |
| 103 | + |
| 104 | + # Compute dm/dT |
| 105 | + m = np.sum(Y, axis=1) |
| 106 | + dmdT = np.gradient(m, T) |
| 107 | + dmdT[0] = 0.0 |
| 108 | + dmdT[-1] = 0.0 |
| 109 | + |
| 110 | + # Plot the solution |
| 111 | + TC = T - 273.0 |
| 112 | + ax2 = ax1.twinx() |
| 113 | + line1, = ax1.plot(TC, m, label='Normalized Mass') |
| 114 | + line2, = ax2.plot(TC, -dmdT * dTdt * 1000.0, label='Normalized Mass Loss Rate × 10³ (s⁻¹)', color='orange') |
| 115 | + ax1.set_xlabel('Temperature (°C)', fontsize=Label_Font_Size, fontname=Font_Name, |
| 116 | + fontfamily=Font_Interpreter) |
| 117 | + ax1.set_ylabel('Normalized Mass', fontsize=Label_Font_Size, fontname=Font_Name, |
| 118 | + fontfamily=Font_Interpreter) |
| 119 | + ax2.set_ylabel('Normalized Mass Loss Rate × 10³ (s⁻¹)', fontsize=Label_Font_Size, fontname=Font_Name, |
| 120 | + fontfamily='LaTeX') |
| 121 | + ax1.set_ylim([0, 1.1]) |
| 122 | + ax2.set_ylim([0, 2.2]) |
| 123 | + ax1.set_yticks([0, 0.2, 0.4, 0.6, 0.8, 1.0]) |
| 124 | + ax2.set_yticks([0, 0.4, 0.8, 1.2, 1.6, 2.0]) |
| 125 | + line1.set_linestyle('-') |
| 126 | + line2.set_linestyle('-') |
| 127 | + |
| 128 | + # Add the FDS solution |
| 129 | + if show_fds: |
| 130 | + filename = f'pyrolysis_{i_plot}_devc.csv' |
| 131 | + if not os.path.exists(filename): |
| 132 | + print(f'Error: File {filename} does not exist. Skipping case.') |
| 133 | + continue |
| 134 | + FDS = np.genfromtxt(filename, delimiter=',', skip_header=2) |
| 135 | + ax1.plot(FDS[:, 3], FDS[:, 1], 'b--', label='FDS Mass') |
| 136 | + ax2.plot(FDS[:, 3], FDS[:, 2] * 500.0, 'r--', label='FDS Mass Loss Rate × 500') |
| 137 | + |
| 138 | + # Add vertical lines to indicate the temperature peaks |
| 139 | + for i in range(n_components - 1): |
| 140 | + ax2.axvline(x=T_p[i] - 273.0, color='black', linewidth=1) |
| 141 | + |
| 142 | + # Add version string if file is available |
| 143 | + chid = f'pyrolysis_{i_plot}' |
| 144 | + git_filename = f'{chid}_git.txt' |
| 145 | + addverstr(ax1, git_filename, 'linear') |
| 146 | + |
| 147 | + # Create the PDF files |
| 148 | + fig.set_visible(True if Figure_Visibility == 'on' else False) |
| 149 | + fig.set_size_inches(Paper_Width * 1.1, Paper_Height) |
| 150 | + pdf_filename = f'../../Manuals/FDS_User_Guide/SCRIPT_FIGURES/{chid}.pdf' |
| 151 | + plt.savefig(pdf_filename, format='pdf') |
| 152 | + |
| 153 | + plt.show() |
| 154 | + |
| 155 | +if __name__ == "__main__": |
| 156 | + main() |
| 157 | + |
0 commit comments