1"""Validate chamber intrinsics against Huzel and Huang reference data"""
4import matplotlib.pyplot
as plt
6from pathlib
import Path
8sys.path.insert(0, str(Path(__file__).parent.parent))
15 calculate_actual_chamber_temp,
16 calculate_frozen_flow_correction
20config_path = Path(__file__).parent.parent /
"configs" /
"default.yaml"
21config = load_config(str(config_path))
27print(
"VALIDATING CHAMBER INTRINSICS vs HUZEL & HUANG")
31chamber_pressures = [300, 500, 700]
34cache = runner.solver.cea_cache
37print(f
"Cache MR range: {MR_min:.2f} to {MR_max:.2f}")
40MR_range = np.linspace(MR_min, MR_max, 30)
43R_UNIVERSAL = 8314.462618
47cg = ensure_chamber_geometry(config)
48Lstar = calculate_Lstar(
51 Lstar_override=cg.Lstar
53print(f
"L* = {Lstar:.3f} m (for chamber-driven corrections)")
54if cg.Lstar
is not None:
55 print(f
" (Using L* from config: {cg.Lstar:.3f} m)")
57 print(f
" (Calculated from V={cg.volume:.6f} m³, At={cg.A_throat:.6f} m²)")
62for Pc_psia
in chamber_pressures:
63 Pc_Pa = Pc_psia * 6894.76
67 cstar_ideal_values = []
68 cstar_actual_values = []
69 gamma_ideal_values = []
70 gamma_actual_values = []
75 print(f
"\nEvaluating at Pc = {Pc_psia} psia ({Pc_Pa/1e6:.2f} MPa)...")
80 cea_props = runner.solver.cea_cache.eval(MR, Pc_Pa)
82 Tc_ideal = cea_props[
'Tc']
83 cstar_ideal = cea_props[
'cstar_ideal']
84 gamma_ideal = cea_props[
'gamma']
94 "cstar_ideal": cstar_ideal,
100 "chamber_length": cg.length,
103 "spray_diagnostics":
None,
104 "turbulence_intensity": DEFAULT_TURBULENCE_INTENSITY_ND,
108 config.combustion.efficiency,
109 cooling_efficiency=1.0,
110 advanced_params=advanced_params,
114 cstar_actual = eta * cstar_ideal
117 Tc_actual = calculate_actual_chamber_temp(
124 gamma_frozen_factor = calculate_frozen_flow_correction(Lstar, gamma_ideal)
125 gamma_actual = gamma_ideal * gamma_frozen_factor
128 Tc_ideal_values.append(Tc_ideal)
129 Tc_actual_values.append(Tc_actual)
130 cstar_ideal_values.append(cstar_ideal)
131 cstar_actual_values.append(cstar_actual)
132 gamma_ideal_values.append(gamma_ideal)
133 gamma_actual_values.append(gamma_actual)
135 eta_values.append(eta)
142 except Exception
as e:
143 print(f
" Warning: Failed at MR={MR:.2f}: {e}")
144 Tc_ideal_values.append(np.nan)
145 Tc_actual_values.append(np.nan)
146 cstar_ideal_values.append(np.nan)
147 cstar_actual_values.append(np.nan)
148 gamma_ideal_values.append(np.nan)
149 gamma_actual_values.append(np.nan)
150 R_values.append(np.nan)
151 M_values.append(np.nan)
152 eta_values.append(np.nan)
154 results_by_Pc[Pc_psia] = {
155 'Tc_ideal': np.array(Tc_ideal_values),
156 'Tc_actual': np.array(Tc_actual_values),
157 'cstar_ideal': np.array(cstar_ideal_values),
158 'cstar_actual': np.array(cstar_actual_values),
159 'gamma_ideal': np.array(gamma_ideal_values),
160 'gamma_actual': np.array(gamma_actual_values),
161 'R': np.array(R_values),
162 'M': np.array(M_values),
163 'eta': np.array(eta_values)
167print(
"\nConverting to imperial units...")
169for Pc_psia
in chamber_pressures:
170 results = results_by_Pc[Pc_psia]
173 results[
'Tc_ideal_F'] = (results[
'Tc_ideal'] - 273.15) * 9/5 + 32
174 results[
'Tc_actual_F'] = (results[
'Tc_actual'] - 273.15) * 9/5 + 32
177 results[
'cstar_ideal_fps'] = results[
'cstar_ideal'] * 3.28084
178 results[
'cstar_actual_fps'] = results[
'cstar_actual'] * 3.28084
181 results[
'M_lb_lbmol'] = results[
'M']
185 results[
'R_imperial'] = results[
'R'] * 0.0053566
188fig, axes = plt.subplots(3, 2, figsize=(16, 14))
189fig.suptitle(
"Chamber Intrinsics Validation vs Huzel & Huang (LOX/RP-1)",
190 fontsize=16, fontweight=
"bold")
194for Pc_psia
in chamber_pressures:
195 results = results_by_Pc[Pc_psia]
197 valid_mask = ~np.isnan(results[
'Tc_actual_F'])
198 if np.any(valid_mask):
199 ax.plot(MR_range[valid_mask], results[
'Tc_actual_F'][valid_mask],
200 linewidth=2, marker=
'o', markersize=4,
201 label=f
'Pc = {Pc_psia} psia (actual)')
203 ideal_mask = ~np.isnan(results[
'Tc_ideal_F'])
204 if np.any(ideal_mask):
205 ax.plot(MR_range[ideal_mask], results[
'Tc_ideal_F'][ideal_mask],
206 linewidth=1, linestyle=
'--', alpha=0.5,
207 label=f
'Pc = {Pc_psia} psia (ideal CEA)')
208ax.set_xlabel(
'Mixture Ratio (O/F)')
209ax.set_ylabel(
'Chamber Temperature [°F]')
210ax.set_title(
'Chamber Temperature (ACTUAL - chamber-driven)')
211ax.grid(
True, alpha=0.3)
213ax.set_xlim(MR_min, MR_max)
214ax.set_ylim(3000, 7000)
216ax.text(0.05, 0.95,
'H&H Peak: ~6000°F @ MR≈2.75',
217 transform=ax.transAxes, fontsize=10, verticalalignment=
'top',
218 bbox=dict(boxstyle=
'round', facecolor=
'wheat', alpha=0.5))
222for Pc_psia
in chamber_pressures:
223 results = results_by_Pc[Pc_psia]
225 valid_mask = ~np.isnan(results[
'cstar_actual_fps'])
226 if np.any(valid_mask):
227 ax.plot(MR_range[valid_mask], results[
'cstar_actual_fps'][valid_mask],
228 linewidth=2, marker=
'o', markersize=4,
229 label=f
'Pc = {Pc_psia} psia (actual)')
231 ideal_mask = ~np.isnan(results[
'cstar_ideal_fps'])
232 if np.any(ideal_mask):
233 ax.plot(MR_range[ideal_mask], results[
'cstar_ideal_fps'][ideal_mask],
234 linewidth=1, linestyle=
'--', alpha=0.5,
235 label=f
'Pc = {Pc_psia} psia (ideal CEA)')
236ax.set_xlabel(
'Mixture Ratio (O/F)')
237ax.set_ylabel(
'Characteristic Velocity [ft/s]')
238ax.set_title(
'Characteristic Velocity (c* - ACTUAL)')
239ax.grid(
True, alpha=0.3)
241ax.set_xlim(MR_min, MR_max)
242ax.set_ylim(5000, 6000)
243ax.text(0.05, 0.95,
'H&H Peak: ~5850 ft/s @ MR≈2.25',
244 transform=ax.transAxes, fontsize=10, verticalalignment=
'top',
245 bbox=dict(boxstyle=
'round', facecolor=
'wheat', alpha=0.5))
249for Pc_psia
in chamber_pressures:
250 results = results_by_Pc[Pc_psia]
252 valid_mask = ~np.isnan(results[
'gamma_actual'])
253 if np.any(valid_mask):
254 ax.plot(MR_range[valid_mask], results[
'gamma_actual'][valid_mask],
255 linewidth=2, marker=
'o', markersize=4,
256 label=f
'Pc = {Pc_psia} psia (actual)')
258 ideal_mask = ~np.isnan(results[
'gamma_ideal'])
259 if np.any(ideal_mask):
260 ax.plot(MR_range[ideal_mask], results[
'gamma_ideal'][ideal_mask],
261 linewidth=1, linestyle=
'--', alpha=0.5,
262 label=f
'Pc = {Pc_psia} psia (ideal CEA)')
263ax.set_xlabel(
'Mixture Ratio (O/F)')
264ax.set_ylabel(
'Specific Heat Ratio (gamma)')
265ax.set_title(
'Specific Heat Ratio (gamma - ACTUAL with frozen flow)')
266ax.grid(
True, alpha=0.3)
268ax.set_xlim(MR_min, MR_max)
269ax.set_ylim(1.15, 1.30)
270ax.text(0.05, 0.95,
'H&H Range: 1.28→1.21 (decreasing)',
271 transform=ax.transAxes, fontsize=10, verticalalignment=
'top',
272 bbox=dict(boxstyle=
'round', facecolor=
'wheat', alpha=0.5))
276for Pc_psia
in chamber_pressures:
277 results = results_by_Pc[Pc_psia]
279 valid_mask = ~np.isnan(results[
'M_lb_lbmol'])
280 if np.any(valid_mask):
281 ax.plot(MR_range[valid_mask], results[
'M_lb_lbmol'][valid_mask],
282 linewidth=2, marker=
'o', markersize=4,
283 label=f
'Pc = {Pc_psia} psia')
284ax.set_xlabel(
'Mixture Ratio (O/F)')
285ax.set_ylabel(
'Molecular Weight [lb/lbmol]')
286ax.set_title(
'Molecular Weight of Combustion Products (M)')
287ax.grid(
True, alpha=0.3)
289ax.set_xlim(MR_min, MR_max)
291ax.text(0.05, 0.95,
'H&H Range: 17.5→24.5 (increasing)',
292 transform=ax.transAxes, fontsize=10, verticalalignment=
'top',
293 bbox=dict(boxstyle=
'round', facecolor=
'wheat', alpha=0.5))
297for Pc_psia
in chamber_pressures:
298 results = results_by_Pc[Pc_psia]
300 valid_mask = ~np.isnan(results[
'R'])
301 if np.any(valid_mask):
302 ax.plot(MR_range[valid_mask], results[
'R'][valid_mask],
303 linewidth=2, marker=
'o', markersize=4,
304 label=f
'Pc = {Pc_psia} psia')
305ax.set_xlabel(
'Mixture Ratio (O/F)')
306ax.set_ylabel(
'Gas Constant R [J/(kg·K)]')
307ax.set_title(
'Gas Constant (R = R_universal / M)')
308ax.grid(
True, alpha=0.3)
310ax.set_xlim(MR_min, MR_max)
318idx_target = np.argmin(np.abs(MR_range - MR_target))
321table_data.append([
'Parameter',
'Our Model',
'H&H Ref',
'Units'])
322table_data.append([
'─' * 15,
'─' * 12,
'─' * 12,
'─' * 10])
325results_500 = results_by_Pc[500]
326Tc_actual_F = results_500[
'Tc_actual_F'][idx_target]
327cstar_actual_fps = results_500[
'cstar_actual_fps'][idx_target]
328gamma_actual = results_500[
'gamma_actual'][idx_target]
329M = results_500[
'M_lb_lbmol'][idx_target]
330eta = results_500[
'eta'][idx_target]
332table_data.append([
'Tc (actual)', f
'{Tc_actual_F:.0f}',
'~5800',
'°F'])
333table_data.append([
'c* (actual)', f
'{cstar_actual_fps:.0f}',
'~5850',
'ft/s'])
334table_data.append([
'gamma (actual)', f
'{gamma_actual:.3f}',
'~1.23',
'-'])
335table_data.append([
'M', f
'{M:.1f}',
'~22',
'lb/lbmol'])
336table_data.append([
'eta_c*', f
'{eta:.3f}',
'-',
'-'])
339table_text =
'Comparison at MR = 2.2, Pc = 500 psia\n\n'
340for row
in table_data:
341 table_text += f
'{row[0]:15s} {row[1]:>12s} {row[2]:>12s} {row[3]:>10s}\n'
343ax.text(0.1, 0.5, table_text, fontsize=11, family=
'monospace',
344 verticalalignment=
'center',
345 bbox=dict(boxstyle=
'round', facecolor=
'lightblue', alpha=0.3))
348output_path = Path(__file__).parent /
"chamber_intrinsics_validation.png"
349plt.savefig(str(output_path), dpi=300, bbox_inches=
'tight')
350print(f
"\n[OK] Saved validation plots to {output_path}")
353print(
"\n" +
"=" * 70)
354print(
"VALIDATION SUMMARY (at Pc = 500 psia, MR = 2.2)")
357results_500 = results_by_Pc[500]
358idx = np.argmin(np.abs(MR_range - 2.2))
360print(f
"\nOur Model (CHAMBER-DRIVEN - actual):")
361print(f
" Tc_actual = {results_500['Tc_actual'][idx]:.1f} K ({results_500['Tc_actual_F'][idx]:.0f} °F)")
362print(f
" c*_actual = {results_500['cstar_actual'][idx]:.1f} m/s ({results_500['cstar_actual_fps'][idx]:.0f} ft/s)")
363print(f
" gamma_actual = {results_500['gamma_actual'][idx]:.4f}")
364print(f
" M = {results_500['M'][idx]:.2f} kg/kmol")
365print(f
" R = {results_500['R'][idx]:.2f} J/(kg·K)")
366print(f
" eta_c* = {results_500['eta'][idx]:.4f}")
367print(f
"\n (Ideal CEA for comparison:)")
368print(f
" Tc_ideal = {results_500['Tc_ideal'][idx]:.1f} K ({results_500['Tc_ideal_F'][idx]:.0f} °F)")
369print(f
" c*_ideal = {results_500['cstar_ideal'][idx]:.1f} m/s ({results_500['cstar_ideal_fps'][idx]:.0f} ft/s)")
370print(f
" gamma_ideal = {results_500['gamma_ideal'][idx]:.4f}")
372print(f
"\nHuzel & Huang Reference (approximate from graph):")
373print(f
" Tc approx 5800 degF (peak ~6000 degF @ MR approx 2.75)")
374print(f
" c* approx 5850 ft/s (peak @ MR approx 2.25)")
375print(f
" gamma approx 1.23 (decreasing with MR)")
376print(f
" M approx 22 lb/lbmol (increasing with MR)")
378print(
"\n[OK] Validation complete!")