STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
thermal_analysis.py
Go to the documentation of this file.
1"""Comprehensive thermal analysis for multi-layer ablative/graphite system.
2
3This module provides:
41. Multi-layer thermal conduction (stainless steel + phenolic + graphite)
52. Pyrolysis modeling with char layer formation
63. Vaporization at high temperatures
74. Transient thermal response
85. Temperature profiles through wall thickness
96. Back-face temperature limits for structural integrity
10"""
11
12from __future__ import annotations
13
14from typing import Dict, Tuple, Optional, List
15from dataclasses import dataclass
16import numpy as np
17
18from .config_schemas import AblativeCoolingConfig, GraphiteInsertConfig
19
20SIGMA = 5.670374419e-8 # Stefan-Boltzmann constant
21
22
23@dataclass
25 """Properties of a single material layer."""
26 name: str
27 thickness: float # [m]
28 thermal_conductivity: float # [W/(m·K)]
29 density: float # [kg/m³]
30 specific_heat: float # [J/(kg·K)]
31 emissivity: float = 0.8 # Surface emissivity
32 pyrolysis_temp: Optional[float] = None # [K] - For ablative materials
33 vaporization_temp: Optional[float] = None # [K] - For high-temp materials
34
35
36@dataclass
38 """Boundary conditions for thermal analysis."""
39 T_hot_gas: float # [K] - Hot gas temperature
40 h_hot_gas: float # [W/(m²·K)] - Hot gas convective coefficient
41 q_rad_hot: float # [W/m²] - Radiative heat flux from hot gas
42 T_ambient: float = 300.0 # [K] - Ambient temperature
43 h_ambient: float = 10.0 # [W/(m²·K)] - Ambient convective coefficient (natural convection)
44 q_rad_ambient: float = 0.0 # [W/m²] - Radiative to ambient (usually negligible)
45
46
48 layers: List[MaterialLayer],
49 bc: ThermalBoundaryConditions,
50 n_points_per_layer: int = 10,
51) -> Dict[str, np.ndarray]:
52 """
53 Calculate steady-state temperature profile through multi-layer wall.
54
55 Uses thermal resistance network:
56 q = (T_hot - T_cold) / R_total
57 R_total = R_conv_hot + R_cond_1 + R_cond_2 + ... + R_conv_cold
58
59 For each layer:
60 R_cond = t / (k * A) (1D conduction)
61
62 Parameters:
63 -----------
64 layers : List[MaterialLayer]
65 Material layers from hot side to cold side
66 bc : ThermalBoundaryConditions
67 Boundary conditions
68 n_points_per_layer : int
69 Number of temperature points per layer
70
71 Returns:
72 --------
73 dict
74 - positions: np.ndarray [m] - Distance from hot surface
75 - temperatures: np.ndarray [K] - Temperature at each position
76 - heat_flux: float [W/m²] - Steady-state heat flux
77 - T_surface_hot: float [K] - Hot surface temperature
78 - T_surface_cold: float [K] - Cold surface temperature
79 - layer_boundaries: List[float] - Positions of layer boundaries
80 """
81 if not layers:
82 raise ValueError("layers list cannot be empty")
83
84 # Calculate total thermal resistance
85 R_conv_hot = 1.0 / max(bc.h_hot_gas, 1e-6)
86
87 R_cond_total = 0.0
88 for layer in layers:
89 if layer.thickness <= 0:
90 continue
91 R_cond_layer = layer.thickness / max(layer.thermal_conductivity, 1e-6)
92 R_cond_total += R_cond_layer
93
94 R_conv_cold = 1.0 / max(bc.h_ambient, 1e-6)
95 R_total = R_conv_hot + R_cond_total + R_conv_cold
96
97 # Calculate total heat flux (convective + radiative)
98 # Iterative: need to know surface temp for radiation
99 # Start with convective only, then iterate
100 T_surface_hot_guess = bc.T_hot_gas * 0.8 # Initial guess
101
102 for _ in range(10): # Iterate for radiation coupling
103 q_conv = bc.h_hot_gas * (bc.T_hot_gas - T_surface_hot_guess)
104 q_rad = bc.q_rad_hot # Assume constant for now
105 q_total = q_conv + q_rad
106
107 # Temperature drop across each resistance
108 delta_T_hot = q_total * R_conv_hot
109 T_surface_hot = bc.T_hot_gas - delta_T_hot
110
111 if abs(T_surface_hot - T_surface_hot_guess) < 1.0: # Converged
112 break
113 T_surface_hot_guess = T_surface_hot
114
115 # Cold surface temperature
116 delta_T_cold = q_total * R_conv_cold
117 T_surface_cold = bc.T_ambient + delta_T_cold
118
119 # Build temperature profile through layers
120 positions = []
121 temperatures = []
122 layer_boundaries = [0.0]
123
124 current_pos = 0.0
125 current_temp = T_surface_hot
126
127 for i, layer in enumerate(layers):
128 if layer.thickness <= 0:
129 continue
130
131 # Temperature drop across this layer
132 R_layer = layer.thickness / max(layer.thermal_conductivity, 1e-6)
133 delta_T_layer = q_total * R_layer
134
135 # Points within this layer
136 x_layer = np.linspace(0, layer.thickness, n_points_per_layer)
137 T_layer = current_temp - (delta_T_layer / layer.thickness) * x_layer
138
139 positions.extend(current_pos + x_layer)
140 temperatures.extend(T_layer)
141
142 current_pos += layer.thickness
143 current_temp -= delta_T_layer
144 layer_boundaries.append(current_pos)
145
146 positions = np.array(positions)
147 temperatures = np.array(temperatures)
148
149 return {
150 "positions": positions,
151 "temperatures": temperatures,
152 "heat_flux": float(q_total),
153 "T_surface_hot": float(T_surface_hot),
154 "T_surface_cold": float(T_surface_cold),
155 "layer_boundaries": layer_boundaries,
156 "R_total": float(R_total),
157 }
158
159
161 surface_temperature: float,
162 ablative_config: AblativeCoolingConfig,
163 heat_flux: float,
164 time_step: float = 0.01,
165) -> Dict[str, float]:
166 """
167 Calculate pyrolysis response including char layer formation.
168
169 Pyrolysis occurs when T > T_pyrolysis:
170 - Material decomposes into char + pyrolysis gases
171 - Char layer forms on surface (lower conductivity)
172 - Pyrolysis gases flow outward (blowing effect)
173
174 Parameters:
175 -----------
176 surface_temperature : float
177 Surface temperature [K]
178 ablative_config : AblativeCoolingConfig
179 Ablative material configuration
180 heat_flux : float
181 Incident heat flux [W/m²]
182 time_step : float
183 Time step [s] for transient calculations
184
185 Returns:
186 --------
187 dict
188 - pyrolysis_rate: float [kg/(m²·s)] - Mass flux of pyrolysis gases
189 - char_formation_rate: float [m/s] - Char layer growth rate
190 - char_thickness: float [m] - Current char layer thickness
191 - pyrolysis_active: bool - Whether pyrolysis is occurring
192 - energy_consumed: float [J/kg] - Energy consumed by pyrolysis
193 """
194 if surface_temperature < ablative_config.pyrolysis_temperature:
195 return {
196 "pyrolysis_rate": 0.0,
197 "char_formation_rate": 0.0,
198 "char_thickness": 0.0,
199 "pyrolysis_active": False,
200 "energy_consumed": 0.0,
201 }
202
203 # Pyrolysis rate increases with temperature above threshold
204 # Arrhenius-like behavior: rate ∝ exp(-E_a / (R * T))
205 T_excess = surface_temperature - ablative_config.pyrolysis_temperature
206 T_normalized = T_excess / (ablative_config.surface_temperature_limit - ablative_config.pyrolysis_temperature)
207 T_normalized = np.clip(T_normalized, 0.0, 1.0)
208
209 # Pyrolysis rate (empirical model)
210 # Typical: 0.1-1.0 kg/(m²·s) at high temperatures
211 pyrolysis_rate_max = 1.0 # kg/(m²·s)
212 pyrolysis_rate = pyrolysis_rate_max * (T_normalized ** 2.0)
213
214 # Char formation rate (char density typically 800-1200 kg/m³)
215 char_density = 1000.0 # kg/m³ (typical)
216 virgin_density = ablative_config.material_density
217 char_formation_rate = pyrolysis_rate * (virgin_density / char_density - 1.0) / virgin_density
218
219 # Char thickness (grows over time)
220 # Simplified: assume steady-state char thickness based on heat flux
221 # In reality, this would be time-integrated
222 char_thickness = ablative_config.char_layer_thickness # Use config value
223
224 # Energy consumed by pyrolysis (latent heat + sensible heat)
225 # Typical: 1-3 MJ/kg for phenolic pyrolysis
226 delta_h_pyrolysis = 2.0e6 # J/kg (typical)
227 energy_consumed = pyrolysis_rate * delta_h_pyrolysis
228
229 return {
230 "pyrolysis_rate": float(pyrolysis_rate),
231 "char_formation_rate": float(char_formation_rate),
232 "char_thickness": float(char_thickness),
233 "pyrolysis_active": True,
234 "energy_consumed": float(energy_consumed),
235 }
236
237
239 surface_temperature: float,
240 material_config: AblativeCoolingConfig | GraphiteInsertConfig,
241 heat_flux: float,
242 pressure: float = 1e6, # [Pa]
243) -> Dict[str, float]:
244 """
245 Calculate material vaporization rate at high temperatures.
246
247 Vaporization occurs when T > T_vaporization:
248 - Material directly sublimates/vaporizes
249 - No char formation (unlike pyrolysis)
250 - High energy consumption
251
252 Parameters:
253 -----------
254 surface_temperature : float
255 Surface temperature [K]
256 material_config : AblativeCoolingConfig | GraphiteInsertConfig
257 Material configuration
258 heat_flux : float
259 Incident heat flux [W/m²]
260 pressure : float
261 Local pressure [Pa]
262
263 Returns:
264 --------
265 dict
266 - vaporization_rate: float [kg/(m²·s)] - Mass flux of vaporized material
267 - vaporization_active: bool - Whether vaporization is occurring
268 - energy_consumed: float [J/kg] - Energy consumed by vaporization
269 """
270 # Determine vaporization temperature
271 if isinstance(material_config, AblativeCoolingConfig):
272 T_vap = material_config.surface_temperature_limit * 1.1 # ~10% above limit
273 else: # GraphiteInsertConfig
274 T_vap = material_config.surface_temperature_limit * 0.95 # Graphite can handle higher temps
275
276 if surface_temperature < T_vap:
277 return {
278 "vaporization_rate": 0.0,
279 "vaporization_active": False,
280 "energy_consumed": 0.0,
281 }
282
283 # Vaporization rate (Clausius-Clapeyron-like)
284 # Higher temperature and lower pressure increase rate
285 T_excess = surface_temperature - T_vap
286 P_effect = (1e6 / max(pressure, 1e3)) ** 0.5 # Lower pressure = higher rate
287
288 # Vaporization rate (empirical)
289 # Typical: 0.01-0.1 kg/(m²·s) at very high temperatures
290 vaporization_rate_max = 0.1 # kg/(m²·s)
291 vaporization_rate = vaporization_rate_max * np.exp(-5000.0 / max(surface_temperature, 1000.0)) * P_effect
292
293 # Energy consumed (latent heat of vaporization)
294 # Typical: 5-15 MJ/kg for high-temperature materials
295 delta_h_vaporization = 10.0e6 # J/kg (typical)
296 energy_consumed = vaporization_rate * delta_h_vaporization
297
298 return {
299 "vaporization_rate": float(vaporization_rate),
300 "vaporization_active": True,
301 "energy_consumed": float(energy_consumed),
302 }
303
304
306 layers: List[MaterialLayer],
307 bc: ThermalBoundaryConditions,
308 ablative_config: Optional[AblativeCoolingConfig] = None,
309 graphite_config: Optional[GraphiteInsertConfig] = None,
310) -> Dict[str, any]:
311 """
312 Comprehensive thermal analysis of multi-layer system.
313
314 Combines:
315 - Steady-state temperature profile
316 - Pyrolysis response (if ablative)
317 - Vaporization (if high temperature)
318 - Back-face temperature check
319
320 Parameters:
321 -----------
322 layers : List[MaterialLayer]
323 Material layers (hot to cold)
324 bc : ThermalBoundaryConditions
325 Boundary conditions
326 ablative_config : Optional[AblativeCoolingConfig]
327 Ablative configuration (if applicable)
328 graphite_config : Optional[GraphiteInsertConfig]
329 Graphite configuration (if applicable)
330
331 Returns:
332 --------
333 dict
334 Comprehensive thermal analysis results
335 """
336 # Steady-state temperature profile
337 temp_profile = calculate_steady_state_temperature_profile(layers, bc)
338
339 T_surface_hot = temp_profile["T_surface_hot"]
340 q_total = temp_profile["heat_flux"]
341
342 # Pyrolysis response (if ablative)
343 pyrolysis_results = {}
344 if ablative_config is not None:
345 pyrolysis_results = calculate_pyrolysis_response(
346 T_surface_hot,
347 ablative_config,
348 q_total,
349 )
350
351 # Vaporization (if high temperature)
352 vaporization_results = {}
353 if ablative_config is not None and T_surface_hot > ablative_config.surface_temperature_limit * 1.05:
354 vaporization_results = calculate_vaporization_rate(
355 T_surface_hot,
356 ablative_config,
357 q_total,
358 )
359 elif graphite_config is not None and T_surface_hot > graphite_config.surface_temperature_limit * 0.9:
360 vaporization_results = calculate_vaporization_rate(
361 T_surface_hot,
362 graphite_config,
363 q_total,
364 )
365
366 # Back-face temperature (last layer cold surface)
367 T_backface = temp_profile["T_surface_cold"]
368
369 # Compile results
370 results = {
371 "temperature_profile": temp_profile,
372 "T_surface_hot": T_surface_hot,
373 "T_backface": T_backface,
374 "heat_flux": q_total,
375 "pyrolysis": pyrolysis_results,
376 "vaporization": vaporization_results,
377 }
378
379 return results
380
381
383 heat_flux: float,
384 burn_time: float,
385 ablative_config: AblativeCoolingConfig,
386 backface_temp_limit: float = 500.0, # [K] - Max backface temp for stainless steel
387 stainless_thickness: float = 0.002, # [m] - 2mm stainless wall
388 stainless_k: float = 15.0, # [W/(m·K)] - Stainless steel conductivity
389) -> Dict[str, float]:
390 """
391 Calculate required ablative thickness to keep backface below limit.
392
393 This is a sizing function to determine how much ablative material is needed.
394
395 Parameters:
396 -----------
397 heat_flux : float
398 Design heat flux [W/m²]
399 burn_time : float
400 Burn time [s]
401 ablative_config : AblativeCoolingConfig
402 Ablative material configuration
403 backface_temp_limit : float
404 Maximum backface temperature [K]
405 stainless_thickness : float
406 Stainless steel wall thickness [m]
407 stainless_k : float
408 Stainless steel thermal conductivity [W/(m·K)]
409
410 Returns:
411 --------
412 dict
413 - required_thickness: float [m] - Required ablative thickness
414 - recession_allowance: float [m] - Material lost to recession
415 - conduction_thickness: float [m] - Thickness for thermal protection
416 - safety_margin: float [m] - Recommended safety margin
417 """
418 # Estimate surface temperature (iterative, simplified here)
419 T_surface_estimate = ablative_config.pyrolysis_temperature * 1.1
420
421 # Recession allowance (material consumed during burn)
422 # From ablative response model
423 delta_T_pyro = max(T_surface_estimate - ablative_config.pyrolysis_temperature, 0.0)
424 energy_per_mass = ablative_config.heat_of_ablation + ablative_config.specific_heat * delta_T_pyro
425 mass_flux = heat_flux / max(energy_per_mass, 1e-6)
426 recession_rate = mass_flux / ablative_config.material_density
427 recession_allowance = recession_rate * burn_time
428
429 # Conduction thickness (to keep backface cool)
430 # Solve: T_backface = T_surface - (q * t_ablative / k_ablative) - (q * t_stainless / k_stainless)
431 # Rearrange: t_ablative = (k_ablative / q) * (T_surface - T_backface - q * t_stainless / k_stainless)
432 R_stainless = stainless_thickness / stainless_k
433 delta_T_stainless = heat_flux * R_stainless
434 delta_T_ablative_max = T_surface_estimate - backface_temp_limit - delta_T_stainless
435
436 if delta_T_ablative_max <= 0:
437 conduction_thickness = 0.0
438 else:
439 conduction_thickness = (ablative_config.thermal_conductivity / max(heat_flux, 1e-6)) * delta_T_ablative_max
440
441 # Safety margin (30% of total)
442 total_thickness = recession_allowance + conduction_thickness
443 safety_margin = 0.3 * total_thickness
444
445 required_thickness = recession_allowance + conduction_thickness + safety_margin
446
447 return {
448 "required_thickness": float(required_thickness),
449 "recession_allowance": float(recession_allowance),
450 "conduction_thickness": float(conduction_thickness),
451 "safety_margin": float(safety_margin),
452 "recession_rate": float(recession_rate),
453 }
454
#define max(a, b)
Definition Arduino.h:43
Dict[str, float] calculate_vaporization_rate(float surface_temperature, AblativeCoolingConfig|GraphiteInsertConfig material_config, float heat_flux, float pressure=1e6)
Dict[str, float] calculate_pyrolysis_response(float surface_temperature, AblativeCoolingConfig ablative_config, float heat_flux, float time_step=0.01)
Dict[str, float] calculate_required_ablative_thickness(float heat_flux, float burn_time, AblativeCoolingConfig ablative_config, float backface_temp_limit=500.0, float stainless_thickness=0.002, float stainless_k=15.0)
Dict[str, np.ndarray] calculate_steady_state_temperature_profile(List[MaterialLayer] layers, ThermalBoundaryConditions bc, int n_points_per_layer=10)
Dict[str, any] analyze_multi_layer_system(List[MaterialLayer] layers, ThermalBoundaryConditions bc, Optional[AblativeCoolingConfig] ablative_config=None, Optional[GraphiteInsertConfig] graphite_config=None)