16 time_history: np.ndarray,
17 geometry_history: List[Dict],
18 config: PintleEngineConfig,
19 impingement_data: Optional[Dict] =
None,
23 Create stable animated figure showing recession over time.
25 Uses static figure with time slider instead of frames to avoid crashes.
27 if len(geometry_history) == 0:
28 raise ValueError(
"No geometry history provided")
30 n_steps = len(time_history)
33 if n_steps > max_frames:
34 step =
max(1, n_steps // max_frames)
35 indices = np.arange(0, n_steps, step)
36 time_history = time_history[indices]
37 geometry_history = [geometry_history[i]
for i
in indices
if i < len(geometry_history)]
38 n_steps = len(time_history)
40 if n_steps == 0
or len(geometry_history) == 0:
41 raise ValueError(
"No valid time steps or geometry data")
45 for geom
in geometry_history:
46 pos = geom.get(
"positions", [])
48 positions = np.array(pos)
51 if positions
is None or len(positions) == 0:
52 raise ValueError(
"No positions found in geometry data")
59 for geometry
in geometry_history:
61 pos = np.array(geometry.get(
"positions", positions))
62 if len(pos) != len(positions):
64 pos = np.interp(positions, np.linspace(0, 1, len(pos)), pos)
69 D_gas_list = geometry.get(
"D_gas_chamber", [])
70 if isinstance(D_gas_list, (list, np.ndarray)):
71 D_gas = np.array(D_gas_list)
73 D_gas = np.full_like(positions, float(D_gas[0]))
74 elif len(D_gas) != len(positions):
75 D_gas = np.full_like(positions, float(D_gas[0])
if len(D_gas) > 0
else 0.1)
77 D_gas = np.full_like(positions, float(D_gas_list)
if D_gas_list
else 0.1)
79 D_ablative_list = geometry.get(
"D_ablative_outer", D_gas)
80 if isinstance(D_ablative_list, (list, np.ndarray)):
81 D_ablative = np.array(D_ablative_list)
82 if len(D_ablative) == 1:
83 D_ablative = np.full_like(positions, float(D_ablative[0]))
84 elif len(D_ablative) != len(positions):
85 D_ablative = np.full_like(positions, float(D_ablative[0])
if len(D_ablative) > 0
else D_gas[0])
87 D_ablative = np.full_like(positions, float(D_ablative_list)
if D_ablative_list
else D_gas[0])
89 recession_list = geometry.get(
"recession", [])
90 if isinstance(recession_list, (list, np.ndarray)):
91 recession = np.array(recession_list)
92 if len(recession) == 1:
93 recession = np.full_like(positions, float(recession[0]))
94 elif len(recession) != len(positions):
95 recession = np.full_like(positions, float(recession[0])
if len(recession) > 0
else 0.0)
97 recession = np.full_like(positions, float(recession_list)
if recession_list
else 0.0)
100 R_ablative = D_ablative / 2.0
102 all_R_gas.append(R_gas)
103 all_R_ablative.append(R_ablative)
104 all_recession.append(recession)
108 if len(all_R_gas) == 0:
109 raise ValueError(
"No valid geometry data after processing")
114 subplot_titles=(
"Chamber Cross-Section",
"Cumulative Recession"),
115 vertical_spacing=0.15,
116 row_heights=[0.7, 0.3],
121 "R_gas": all_R_gas[0]
if len(all_R_gas) > 0
else np.zeros_like(positions),
122 "R_ablative": all_R_ablative[0]
if len(all_R_ablative) > 0
else np.zeros_like(positions),
123 "recession": all_recession[0]
if len(all_recession) > 0
else np.zeros_like(positions),
126 fig.add_trace(go.Scatter(
128 y=first_data[
"R_gas"],
131 line=dict(color=
'orange', width=3),
134 fig.add_trace(go.Scatter(
136 y=-first_data[
"R_gas"],
138 name=
'Gas Boundary (Lower)',
139 line=dict(color=
'orange', width=3),
143 fig.add_trace(go.Scatter(
145 y=first_data[
"R_ablative"],
147 name=
'Ablative Outer',
148 line=dict(color=
'brown', width=2, dash=
'dash'),
150 fillcolor=
'rgba(139, 69, 19, 0.3)',
153 fig.add_trace(go.Scatter(
155 y=-first_data[
"R_ablative"],
157 name=
'Ablative Outer (Lower)',
158 line=dict(color=
'brown', width=2, dash=
'dash'),
160 fillcolor=
'rgba(139, 69, 19, 0.3)',
164 fig.add_trace(go.Scatter(
166 y=first_data[
"recession"] * 1e6,
168 name=
'Cumulative Recession',
169 line=dict(color=
'red', width=2),
174 n_frames =
min(len(all_R_gas), n_steps, max_frames)
176 for i
in range(n_frames):
177 if i >= len(all_R_gas)
or i >= len(all_R_ablative)
or i >= len(all_recession):
181 go.Scatter(x=positions, y=all_R_gas[i], mode=
'lines', line=dict(color=
'orange', width=3)),
182 go.Scatter(x=positions, y=-all_R_gas[i], mode=
'lines', line=dict(color=
'orange', width=3), showlegend=
False),
183 go.Scatter(x=positions, y=all_R_ablative[i], mode=
'lines', line=dict(color=
'brown', width=2, dash=
'dash'),
184 fill=
'tonexty', fillcolor=
'rgba(139, 69, 19, 0.3)'),
185 go.Scatter(x=positions, y=-all_R_ablative[i], mode=
'lines', line=dict(color=
'brown', width=2, dash=
'dash'),
186 fill=
'tonexty', fillcolor=
'rgba(139, 69, 19, 0.3)', showlegend=
False),
187 go.Scatter(x=positions, y=all_recession[i] * 1e6, mode=
'lines', line=dict(color=
'red', width=2)),
190 frames.append(go.Frame(
193 traces=[0, 1, 2, 3, 4]
199 title=
"Ablative Recession (Static View)",
206 title=
"Ablative Recession Over Time",
216 "frame": {
"duration": 500,
"redraw":
True},
224 "frame": {
"duration": 0,
"redraw":
False},
232 "currentvalue": {
"prefix":
"Time: ",
"suffix":
" s"},
235 "args": [[str(i)], {
"frame": {
"duration": 300,
"redraw":
True},
"mode":
"immediate"}],
239 for i, t
in enumerate(time_history[:len(frames)])
244 fig.update_xaxes(title_text=
"Axial Position [m]", row=1, col=1)
245 fig.update_yaxes(title_text=
"Radius [m]", row=1, col=1, scaleanchor=
"x", scaleratio=1)
246 fig.update_xaxes(title_text=
"Axial Position [m]", row=2, col=1)
247 fig.update_yaxes(title_text=
"Recession [mm]", row=2, col=1)