Skip to content

Commit d4421e1

Browse files
authored
Merge pull request #13936 from rmcdermo/master
Python: add prof1d.py
2 parents 94892cb + 83d3430 commit d4421e1

File tree

1 file changed

+180
-0
lines changed

1 file changed

+180
-0
lines changed

Utilities/Python/scripts/prof1d.py

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

0 commit comments

Comments
 (0)