STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
recession_animation_simple.py
Go to the documentation of this file.
1"""Simple recession animation: just shows diameter evolution over time.
2
3This is a much simpler approach:
41. Plot recession vs time
52. Show cross-section evolving where diameter changes over time
6No complex multi-layer stuff - just diameter evolution.
7"""
8
9from __future__ import annotations
10
11from typing import Dict, List, Optional
12import numpy as np
13import plotly.graph_objects as go
14from plotly.subplots import make_subplots
15from engine.pipeline.config_schemas import PintleEngineConfig
16
17
19 time_history: np.ndarray,
20 recession_history: np.ndarray,
21 D_chamber_initial: float,
22 D_throat_initial: float,
23 L_chamber: float,
24 max_frames: int = 20,
25) -> go.Figure:
26 """
27 Create simple animation showing diameter evolution over time.
28
29 Shows:
30 1. Recession vs time (plot)
31 2. Cross-section evolving (diameter changes over time)
32
33 Parameters:
34 -----------
35 time_history : np.ndarray
36 Time array [s]
37 recession_history : np.ndarray
38 Recession array [m] (chamber recession)
39 D_chamber_initial : float
40 Initial chamber diameter [m]
41 D_throat_initial : float
42 Initial throat diameter [m]
43 L_chamber : float
44 Chamber length [m]
45 max_frames : int
46 Maximum number of frames
47
48 Returns:
49 --------
50 fig : go.Figure
51 Plotly figure with animation
52 """
53 n_steps = len(time_history)
54
55 # Limit frames
56 if n_steps > max_frames:
57 step = max(1, n_steps // max_frames)
58 indices = np.arange(0, n_steps, step)
59 time_history = time_history[indices]
60 recession_history = recession_history[indices]
61 n_steps = len(time_history)
62
63 if n_steps == 0:
64 raise ValueError("No time steps")
65
66 # Create positions along chamber
67 n_points = 50
68 positions = np.linspace(0.0, L_chamber, n_points)
69 throat_pos = L_chamber
70
71 # Calculate diameters at each time step
72 all_D_chamber = []
73 all_D_throat = []
74
75 for i in range(n_steps):
76 recession = recession_history[i]
77
78 # Chamber diameter grows with recession
79 D_chamber = D_chamber_initial + 2.0 * recession
80
81 # Throat diameter (assume constant for now, or can change)
82 D_throat = D_throat_initial # Graphite keeps it constant
83
84 all_D_chamber.append(D_chamber)
85 all_D_throat.append(D_throat)
86
87 # Create figure with subplots
88 fig = make_subplots(
89 rows=2, cols=1,
90 subplot_titles=("Recession vs Time", "Chamber Cross-Section Evolution"),
91 vertical_spacing=0.15,
92 row_heights=[0.3, 0.7],
93 )
94
95 # Plot 1: Recession vs time
96 fig.add_trace(go.Scatter(
97 x=time_history,
98 y=recession_history * 1000, # Convert to mm
99 mode='lines+markers',
100 name='Chamber Recession',
101 line=dict(color='red', width=2),
102 marker=dict(size=5),
103 ), row=1, col=1)
104
105 # Plot 2: Cross-section evolution
106 # Initial state
107 D_chamber_0 = all_D_chamber[0]
108 D_throat_0 = all_D_throat[0]
109
110 # Create radius profile
111 R_chamber = D_chamber_0 / 2.0
112 R_throat = D_throat_0 / 2.0
113
114 # Simple profile: constant chamber, converging to throat
115 # Find throat position (where diameter is minimum)
116 throat_idx = np.argmin([D_chamber_0, D_throat_0])
117 throat_pos_actual = L_chamber # Throat at end of chamber
118
119 R_profile = np.zeros(n_points)
120 for i, x in enumerate(positions):
121 if x < throat_pos_actual * 0.8: # Most of chamber
122 R_profile[i] = R_chamber
123 else: # Converging section
124 x_conv = (x - throat_pos_actual * 0.8) / (throat_pos_actual * 0.2 + 1e-10)
125 x_conv = np.clip(x_conv, 0.0, 1.0)
126 R_profile[i] = R_chamber + (R_throat - R_chamber) * x_conv
127
128 # Initial cross-section (top)
129 fig.add_trace(go.Scatter(
130 x=positions,
131 y=R_profile,
132 mode='lines',
133 name='Gas Boundary (t=0)',
134 line=dict(color='orange', width=3),
135 ), row=2, col=1)
136
137 # Initial cross-section (bottom)
138 fig.add_trace(go.Scatter(
139 x=positions,
140 y=-R_profile,
141 mode='lines',
142 name='Gas Boundary Lower',
143 line=dict(color='orange', width=3),
144 showlegend=False,
145 ), row=2, col=1)
146
147 # Create frames
148 frames = []
149 for i in range(n_steps):
150 D_chamber_i = all_D_chamber[i]
151 D_throat_i = all_D_throat[i]
152 recession_i = recession_history[i]
153
154 # Update radius profile
155 R_chamber_i = D_chamber_i / 2.0
156 R_throat_i = D_throat_i / 2.0
157
158 # Throat position (same as initial)
159 throat_pos_actual = L_chamber
160
161 R_profile_i = np.zeros(n_points)
162 for j, x in enumerate(positions):
163 if x < throat_pos_actual * 0.8:
164 R_profile_i[j] = R_chamber_i
165 else:
166 x_conv = (x - throat_pos_actual * 0.8) / (throat_pos_actual * 0.2 + 1e-10)
167 x_conv = np.clip(x_conv, 0.0, 1.0)
168 R_profile_i[j] = R_chamber_i + (R_throat_i - R_chamber_i) * x_conv
169
170 # Create frame data - update existing traces
171 frames.append(go.Frame(
172 data=[
173 go.Scatter(x=time_history[:i+1], y=recession_history[:i+1] * 1000,
174 mode='lines+markers', line=dict(color='red', width=2),
175 marker=dict(size=5), name='Chamber Recession'),
176 go.Scatter(x=positions, y=R_profile_i, mode='lines',
177 line=dict(color='orange', width=3), name='Gas Boundary'),
178 go.Scatter(x=positions, y=-R_profile_i, mode='lines',
179 line=dict(color='orange', width=3), showlegend=False),
180 ],
181 name=str(i),
182 traces=[0, 1, 2]
183 ))
184
185 # Add frames
186 fig.frames = frames
187
188 # Update layout with animation controls
189 fig.update_layout(
190 title="Recession Evolution Over Time",
191 height=800,
192 updatemenus=[{
193 "type": "buttons",
194 "showactive": False,
195 "x": 0.1,
196 "y": 0,
197 "buttons": [
198 {
199 "label": "▶ Play",
200 "method": "animate",
201 "args": [None, {
202 "frame": {"duration": 500, "redraw": True},
203 "fromcurrent": True,
204 "transition": {"duration": 0}
205 }]
206 },
207 {
208 "label": "⏸ Pause",
209 "method": "animate",
210 "args": [[None], {
211 "frame": {"duration": 0, "redraw": False},
212 "mode": "immediate",
213 "transition": {"duration": 0}
214 }]
215 }
216 ],
217 }],
218 sliders=[{
219 "active": 0,
220 "currentvalue": {"prefix": "Time: ", "suffix": " s", "visible": True},
221 "x": 0.1,
222 "len": 0.9,
223 "xanchor": "left",
224 "y": 0,
225 "yanchor": "top",
226 "steps": [
227 {
228 "args": [[str(i)], {
229 "frame": {"duration": 300, "redraw": True},
230 "mode": "immediate",
231 "transition": {"duration": 0}
232 }],
233 "label": f"{t:.2f}",
234 "method": "animate"
235 }
236 for i, t in enumerate(time_history)
237 ]
238 }]
239 )
240
241 fig.update_xaxes(title_text="Time [s]", row=1, col=1)
242 fig.update_yaxes(title_text="Recession [mm]", row=1, col=1)
243 fig.update_xaxes(title_text="Axial Position [m]", row=2, col=1)
244 fig.update_yaxes(title_text="Radius [m]", row=2, col=1, scaleanchor="x", scaleratio=1)
245
246 return fig
247
#define max(a, b)
Definition Arduino.h:43
go.Figure create_simple_recession_animation(np.ndarray time_history, np.ndarray recession_history, float D_chamber_initial, float D_throat_initial, float L_chamber, int max_frames=20)