Skip to content

Commit a63332b

Browse files
committed
Python: add script to plot in depth profiles for char oxidation model
1 parent 94892cb commit a63332b

File tree

1 file changed

+185
-0
lines changed

1 file changed

+185
-0
lines changed

Utilities/Python/scripts/prof1d.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
"""
2+
McDermott
3+
12-20-2024
4+
5+
Animate a 1D plot of FDS in-depth profile (CHID_prof_n.csv)
6+
7+
Usage: Copy this script to your working directory and add your
8+
profiles to the filenames list.
9+
10+
Use option --with_slider to control the animation with a time slider
11+
Use option --save_animation to save the animation (no slider) to a movie file
12+
13+
Example:
14+
$ python prof1d.py --with_slider
15+
16+
Or you can specify a specific time
17+
18+
$ python prof1d.py --time=10 --prof='../Current_Results/pine_21O2_40_1C_cat_prof_4.csv'
19+
20+
will plot the profile at t=10 s.
21+
"""
22+
23+
import sys
24+
import csv
25+
import pandas as pd
26+
import matplotlib.pyplot as plt
27+
import numpy as np
28+
from matplotlib.animation import FuncAnimation
29+
from matplotlib.widgets import Slider
30+
import argparse
31+
32+
parser = argparse.ArgumentParser()
33+
parser.add_argument('--with_slider', action='store_true', help='Control animation with a time slider')
34+
parser.add_argument('--save_animation', action='store_true', help='Save animation')
35+
36+
args = parser.parse_args()
37+
38+
# if args.with_slider:
39+
# print("Flag is present")
40+
# else:
41+
# print("Flag is not present")
42+
43+
# sys.exit()
44+
45+
# Close all previously opened figures
46+
plt.close('all')
47+
48+
scalar_min = [20 , 0, 0, 0, 0, 0]
49+
scalar_max = [1000, 20, 400, 120, 10, 1]
50+
scalar_lab = ['T','H2O','PINE','CHAR','ASH','O2']
51+
52+
filenames = ['../Test/pine_21O2_40_1C_cat_prof_1.csv',
53+
'../Test/pine_21O2_40_1C_cat_prof_2.csv',
54+
'../Test/pine_21O2_40_1C_cat_prof_3.csv',
55+
'../Test/pine_21O2_40_1C_cat_prof_4.csv',
56+
'../Test/pine_21O2_40_1C_cat_prof_5.csv',
57+
'../Test/pine_21O2_40_1C_cat_prof_6.csv']
58+
59+
# create lists to store information about each profile
60+
IOR = []
61+
X = []
62+
Y = []
63+
Z = []
64+
df = {}
65+
66+
for i, filename in enumerate(filenames):
67+
68+
data = []
69+
max_cols = 0
70+
71+
# read header information
72+
73+
with open(filename,'r') as f:
74+
# Skip the first 1 lines
75+
for j in range(1):
76+
next(f)
77+
first_line = f.readline().strip('\n')
78+
79+
header=first_line.split(",")[1:5]
80+
IOR.append(int(header[0])) #; print(IOR)
81+
X.append(float(header[1])) #; print(X)
82+
Y.append(float(header[2])) #; print(Y)
83+
Z.append(float(header[3])) #; print(Z)
84+
next(f)
85+
86+
# Read lines one at a time
87+
while True:
88+
line = f.readline()
89+
if not line: # End of file
90+
break
91+
row = line.strip().split(',') # Adjust delimiter if needed
92+
93+
# Convert each element to float, handling errors gracefully
94+
try:
95+
row = [float(value) if value else None for value in row]
96+
except ValueError:
97+
# If a value cannot be converted, keep it as None
98+
row = [float(value) if value.replace('.', '', 1).isdigit() else None for value in row]
99+
100+
data.append(row)
101+
max_cols = max(max_cols, len(row)) # Track the maximum number of columns
102+
103+
# Normalize rows to have the same number of columns
104+
data = [row + [None] * (max_cols - len(row)) for row in data]
105+
106+
# Convert to a Pandas DataFrame
107+
df[i] = pd.DataFrame(data)
108+
109+
# sys.exit()
110+
111+
# take time values from first profile
112+
113+
t = df[0].values[:,0]
114+
time_diff = np.diff(t) # Calculate time difference between consecutive frames
115+
116+
n_points = len(t)
117+
118+
fig, ax = plt.subplots(3, 2, figsize=(12, 8))
119+
ax = ax.flatten() # Flatten the array for easier indexing
120+
121+
# Initialize the figure and axis
122+
# fig, ax = plt.subplots()
123+
for i in range(len(filenames)):
124+
line, = ax[i].plot([], [], marker='o', label="Scalar vs X")
125+
126+
time_text = fig.text(0.5, 0.95, "", ha="center", va="center", fontsize=14, weight="bold")
127+
128+
if args.with_slider:
129+
# Create a slider for controlling time
130+
axslider = plt.axes([0.1, 0.02, 0.8, 0.03])
131+
time_slider = Slider(axslider, 'Time', t[0], t[-1], valinit=t[0])
132+
133+
def update(frame):
134+
135+
for i in range(len(filenames)):
136+
n = int(df[i].values[frame,1])
137+
x = df[i].values[frame,2:2+n]*100. # in-depth positions (cm)
138+
scalar_values = df[i].values[frame,2+n:2+2*n]
139+
140+
# Clear the previous plot
141+
ax[i].cla()
142+
143+
# Plot the scatter plot for the current frame
144+
ax[i].plot(x, scalar_values, marker='o')
145+
if scalar_lab[i]=='O2' and np.max(scalar_values)>scalar_min[i]:
146+
ax[i].set_ylim(scalar_min[i], np.max(scalar_values)*1.1)
147+
else:
148+
ax[i].set_ylim(scalar_min[i], scalar_max[i])
149+
150+
# Set appropriate labels
151+
ax[i].set_ylabel(scalar_lab[i])
152+
153+
# Only show X-axis labels and ticks on the bottom row
154+
if i >= len(filenames) - 2: # Adjust based on layout (3 rows, 2 columns)
155+
ax[i].set_xlabel('x (cm)')
156+
else:
157+
ax[i].set_xticklabels([]) # Remove labels
158+
159+
# Update the shared time text
160+
time_text.set_text('Time: {:.2f}'.format(t[frame]))
161+
162+
if args.with_slider:
163+
# Function to update the plot when the slider is moved
164+
def update_slider(val):
165+
time = time_slider.val
166+
index = np.abs(t - time).argmin() # Find the index closest to the current time
167+
update(index)
168+
fig.canvas.draw_idle() # Redraw the plot
169+
170+
# Connect the slider to the update_slider function
171+
time_slider.on_changed(update_slider)
172+
else:
173+
# Create the animation
174+
interval = 200 # milliseconds
175+
ani = FuncAnimation(fig, update, frames=n_points, interval=interval, blit=False, repeat=False)
176+
177+
if args.save_animation:
178+
# Save the animation as an mp4 file
179+
ani.save('animation.mp4', writer='ffmpeg')
180+
181+
plt.show()
182+
183+
184+
185+

0 commit comments

Comments
 (0)