STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
animation_fixed.py
Go to the documentation of this file.
1"""Fixed recession animation that actually works.
2
3Simple, robust animation showing:
41. Recession vs time
52. Diameter evolution
6"""
7
8from __future__ import annotations
9
10from typing import Optional
11import numpy as np
12import plotly.graph_objects as go
13from plotly.subplots import make_subplots
14
15
17 time_history: np.ndarray,
18 recession_history: np.ndarray,
19 D_chamber_initial: float,
20 D_throat_initial: float,
21 L_chamber: float,
22 max_frames: int = 15,
23) -> go.Figure:
24 """
25 Create working animation.
26
27 Parameters:
28 -----------
29 time_history : np.ndarray
30 Time array [s]
31 recession_history : np.ndarray
32 Recession array [m]
33 D_chamber_initial : float
34 Initial chamber diameter [m]
35 D_throat_initial : float
36 Initial throat diameter [m]
37 L_chamber : float
38 Chamber length [m]
39 max_frames : int
40 Max frames
41
42 Returns:
43 --------
44 fig : go.Figure
45 Plotly figure
46 """
47 # Limit frames
48 n_steps = len(time_history)
49 if n_steps > max_frames:
50 step = max(1, n_steps // max_frames)
51 indices = np.arange(0, n_steps, step)
52 time_history = time_history[indices]
53 recession_history = recession_history[indices]
54 n_steps = len(time_history)
55
56 if n_steps == 0:
57 raise ValueError("No time steps")
58
59 # Create positions
60 n_points = 50
61 positions = np.linspace(0.0, L_chamber, n_points)
62 throat_pos = L_chamber
63
64 # Calculate diameters at each time
65 all_D_chamber = []
66 for recession in recession_history:
67 D_chamber = D_chamber_initial + 2.0 * recession
68 all_D_chamber.append(D_chamber)
69
70 # Create figure
71 fig = make_subplots(
72 rows=2, cols=1,
73 subplot_titles=("Recession vs Time", "Chamber Cross-Section"),
74 vertical_spacing=0.15,
75 row_heights=[0.3, 0.7],
76 )
77
78 # Initial traces
79 D_chamber_0 = all_D_chamber[0]
80 R_chamber_0 = D_chamber_0 / 2.0
81 R_throat_0 = D_throat_initial / 2.0
82
83 # Initial radius profile
84 R_profile_0 = np.zeros(n_points)
85 for i, x in enumerate(positions):
86 if x < throat_pos * 0.8:
87 R_profile_0[i] = R_chamber_0
88 else:
89 x_conv = (x - throat_pos * 0.8) / (throat_pos * 0.2 + 1e-10)
90 x_conv = np.clip(x_conv, 0.0, 1.0)
91 R_profile_0[i] = R_chamber_0 + (R_throat_0 - R_chamber_0) * x_conv
92
93 # Add initial traces (start with first point only)
94 fig.add_trace(go.Scatter(
95 x=[time_history[0]],
96 y=[recession_history[0] * 1000],
97 mode='lines+markers',
98 name='Recession',
99 line=dict(color='red', width=2),
100 marker=dict(size=5),
101 ), row=1, col=1)
102
103 fig.add_trace(go.Scatter(
104 x=positions,
105 y=R_profile_0,
106 mode='lines',
107 name='Gas Boundary',
108 line=dict(color='orange', width=3),
109 ), row=2, col=1)
110
111 fig.add_trace(go.Scatter(
112 x=positions,
113 y=-R_profile_0,
114 mode='lines',
115 name='Gas Boundary Lower',
116 line=dict(color='orange', width=3),
117 showlegend=False,
118 ), row=2, col=1)
119
120 # Create frames
121 frames = []
122 for i in range(n_steps):
123 D_chamber_i = all_D_chamber[i]
124 R_chamber_i = D_chamber_i / 2.0
125
126 # Update profile
127 R_profile_i = np.zeros(n_points)
128 for j, x in enumerate(positions):
129 if x < throat_pos * 0.8:
130 R_profile_i[j] = R_chamber_i
131 else:
132 x_conv = (x - throat_pos * 0.8) / (throat_pos * 0.2 + 1e-10)
133 x_conv = np.clip(x_conv, 0.0, 1.0)
134 R_profile_i[j] = R_chamber_i + (R_throat_0 - R_chamber_i) * x_conv
135
136 # Frame data: update all three traces
137 frames.append(go.Frame(
138 name=str(i),
139 traces=[0, 1, 2],
140 data=[
141 go.Scatter(
142 x=time_history[:i+1],
143 y=recession_history[:i+1] * 1000,
144 mode='lines+markers',
145 line=dict(color='red', width=2),
146 marker=dict(size=5)
147 ),
148 go.Scatter(
149 x=positions,
150 y=R_profile_i,
151 mode='lines',
152 line=dict(color='orange', width=3)
153 ),
154 go.Scatter(
155 x=positions,
156 y=-R_profile_i,
157 mode='lines',
158 line=dict(color='orange', width=3)
159 ),
160 ]
161 ))
162
163 fig.frames = frames
164
165 # Layout
166 fig.update_layout(
167 title="Recession Evolution",
168 height=800,
169 updatemenus=[{
170 "type": "buttons",
171 "showactive": False,
172 "buttons": [
173 {"label": "▶ Play", "method": "animate", "args": [None, {
174 "frame": {"duration": 500, "redraw": True},
175 "fromcurrent": True
176 }]},
177 {"label": "⏸ Pause", "method": "animate", "args": [[None], {
178 "frame": {"duration": 0, "redraw": False},
179 "mode": "immediate"
180 }]}
181 ]
182 }],
183 sliders=[{
184 "active": 0,
185 "currentvalue": {"prefix": "Time: ", "suffix": " s"},
186 "steps": [{
187 "args": [[str(i)], {
188 "frame": {"duration": 300, "redraw": True},
189 "mode": "immediate"
190 }],
191 "label": f"{time_history[i]:.2f}",
192 "method": "animate"
193 } for i in range(len(time_history))]
194 }]
195 )
196
197 fig.update_xaxes(title_text="Time [s]", row=1, col=1)
198 fig.update_yaxes(title_text="Recession [mm]", row=1, col=1)
199 fig.update_xaxes(title_text="Axial Position [m]", row=2, col=1)
200 fig.update_yaxes(title_text="Radius [m]", row=2, col=1)
201
202 return fig
203
#define max(a, b)
Definition Arduino.h:43
go.Figure create_working_animation(np.ndarray time_history, np.ndarray recession_history, float D_chamber_initial, float D_throat_initial, float L_chamber, int max_frames=15)