16 time_history: np.ndarray,
17 geometry_history: List[Dict],
18 config: PintleEngineConfig,
19 impingement_data: Optional[Dict] =
None,
23 Create animated plotly figure showing recession over time.
26 - Properly handles all data structures
27 - Creates working animations without crashes
28 - Shows all layers (gas, ablative, graphite, stainless)
30 if len(geometry_history) == 0:
31 raise ValueError(
"No geometry history provided")
33 n_steps = len(time_history)
36 if n_steps > max_frames:
37 step =
max(1, n_steps // max_frames)
38 indices = np.arange(0, n_steps, step)
39 time_history = time_history[indices]
40 geometry_history = [geometry_history[i]
for i
in indices
if i < len(geometry_history)]
41 n_steps = len(time_history)
43 if n_steps == 0
or len(geometry_history) == 0:
44 raise ValueError(
"No valid time steps or geometry data")
48 for geom
in geometry_history:
49 pos = geom.get(
"positions", [])
51 positions = np.array(pos)
54 if positions
is None or len(positions) == 0:
55 raise ValueError(
"No positions found in geometry data")
60 subplot_titles=(
"Chamber Cross-Section",
"Cumulative Recession"),
61 vertical_spacing=0.15,
62 row_heights=[0.7, 0.3],
68 for i, geometry
in enumerate(geometry_history):
71 pos = np.array(geometry.get(
"positions", positions))
72 if len(pos) != len(positions):
75 pos = np.interp(positions, np.linspace(0, 1, len(pos)), pos)
80 D_gas_list = geometry.get(
"D_gas_chamber", [])
81 if isinstance(D_gas_list, (list, np.ndarray)):
82 D_gas = np.array(D_gas_list)
84 D_gas = np.full_like(positions, float(D_gas[0]))
85 elif len(D_gas) != len(positions):
87 D_gas = np.interp(positions, np.linspace(0, 1, len(D_gas)), D_gas)
89 D_gas = np.full_like(positions, float(D_gas[0])
if len(D_gas) > 0
else 0.1)
91 D_gas = np.full_like(positions, float(D_gas_list)
if D_gas_list
else 0.1)
93 D_ablative_list = geometry.get(
"D_ablative_outer", D_gas)
94 if isinstance(D_ablative_list, (list, np.ndarray)):
95 D_ablative = np.array(D_ablative_list)
96 if len(D_ablative) == 1:
97 D_ablative = np.full_like(positions, float(D_ablative[0]))
98 elif len(D_ablative) != len(positions):
99 if len(D_ablative) > 1:
100 D_ablative = np.interp(positions, np.linspace(0, 1, len(D_ablative)), D_ablative)
104 D_ablative = np.full_like(positions, float(D_ablative_list)
if D_ablative_list
else D_gas[0])
107 recession_list = geometry.get(
"recession", [])
108 if isinstance(recession_list, (list, np.ndarray)):
109 recession = np.array(recession_list)
110 if len(recession) == 1:
111 recession = np.full_like(positions, float(recession[0]))
112 elif len(recession) != len(positions):
113 if len(recession) > 1:
114 recession = np.interp(positions, np.linspace(0, 1, len(recession)), recession)
116 recession = np.zeros_like(positions)
118 recession = np.full_like(positions, float(recession_list)
if recession_list
else 0.0)
122 R_ablative = D_ablative / 2.0
126 "R_ablative": R_ablative,
127 "recession": recession,
129 except Exception
as e:
133 if len(all_data) == 0:
134 raise ValueError(
"No valid geometry data after processing")
137 n_pos = len(positions)
138 for data
in all_data:
139 if len(data[
"R_gas"]) != n_pos:
140 data[
"R_gas"] = np.full(n_pos, data[
"R_gas"][0]
if len(data[
"R_gas"]) > 0
else 0.05)
141 if len(data[
"R_ablative"]) != n_pos:
142 data[
"R_ablative"] = np.full(n_pos, data[
"R_ablative"][0]
if len(data[
"R_ablative"]) > 0
else 0.06)
143 if len(data[
"recession"]) != n_pos:
144 data[
"recession"] = np.full(n_pos, data[
"recession"][0]
if len(data[
"recession"]) > 0
else 0.0)
147 first_data = all_data[0]
148 fig.add_trace(go.Scatter(
150 y=first_data[
"R_gas"],
153 line=dict(color=
'orange', width=3),
156 fig.add_trace(go.Scatter(
158 y=-first_data[
"R_gas"],
160 name=
'Gas Boundary (Lower)',
161 line=dict(color=
'orange', width=3),
165 fig.add_trace(go.Scatter(
167 y=first_data[
"R_ablative"],
169 name=
'Ablative Outer',
170 line=dict(color=
'brown', width=2, dash=
'dash'),
172 fillcolor=
'rgba(139, 69, 19, 0.3)',
175 fig.add_trace(go.Scatter(
177 y=-first_data[
"R_ablative"],
179 name=
'Ablative Outer (Lower)',
180 line=dict(color=
'brown', width=2, dash=
'dash'),
182 fillcolor=
'rgba(139, 69, 19, 0.3)',
187 fig.add_trace(go.Scatter(
189 y=first_data[
"recession"] * 1e6,
191 name=
'Cumulative Recession',
192 line=dict(color=
'red', width=2),
197 for i
in range(
min(len(all_data), n_steps)):
198 if i >= len(all_data):
204 go.Scatter(x=positions, y=data[
"R_gas"], mode=
'lines',
205 line=dict(color=
'orange', width=3)),
206 go.Scatter(x=positions, y=-data[
"R_gas"], mode=
'lines',
207 line=dict(color=
'orange', width=3), showlegend=
False),
208 go.Scatter(x=positions, y=data[
"R_ablative"], mode=
'lines',
209 line=dict(color=
'brown', width=2, dash=
'dash'),
210 fill=
'tonexty', fillcolor=
'rgba(139, 69, 19, 0.3)'),
211 go.Scatter(x=positions, y=-data[
"R_ablative"], mode=
'lines',
212 line=dict(color=
'brown', width=2, dash=
'dash'),
213 fill=
'tonexty', fillcolor=
'rgba(139, 69, 19, 0.3)', showlegend=
False),
214 go.Scatter(x=positions, y=data[
"recession"] * 1e6, mode=
'lines',
215 line=dict(color=
'red', width=2)),
218 frames.append(go.Frame(
221 traces=[0, 1, 2, 3, 4]
225 raise ValueError(
"No valid frames created")
231 title=
"Ablative Recession Over Time",
241 "frame": {
"duration": 300,
"redraw":
True},
243 "transition": {
"duration": 150}
250 "frame": {
"duration": 0,
"redraw":
False},
252 "transition": {
"duration": 0}
266 "frame": {
"duration": 150,
"redraw":
True},
268 "transition": {
"duration": 150}
273 for i, t
in enumerate(time_history[:len(frames)])
279 fig.update_xaxes(title_text=
"Axial Position [m]", row=1, col=1)
280 fig.update_yaxes(title_text=
"Radius [m]", row=1, col=1, scaleanchor=
"x", scaleratio=1)
281 fig.update_xaxes(title_text=
"Axial Position [m]", row=2, col=1)
282 fig.update_yaxes(title_text=
"Recession [mm]", row=2, col=1)