STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
pressure_sweep.py
Go to the documentation of this file.
1"""Example: Pressure sweep - evaluate performance at different tank pressures"""
2
3import numpy as np
4import matplotlib.pyplot as plt
5import sys
6from pathlib import Path
7
8sys.path.insert(0, str(Path(__file__).parent.parent))
9
10from engine.pipeline.io import load_config
11from engine.core.runner import PintleEngineRunner
12# from engine.pipeline.visualization import plot_all_validation # Not needed for this script
13
14# Load configuration
15config_path = Path(__file__).parent.parent / "configs" / "default.yaml"
16config = load_config(str(config_path))
17
18# Initialize runner
19runner = PintleEngineRunner(config)
20
21print("=" * 70)
22print("PRESSURE SWEEP: Evaluate Engine Performance at Different Tank Pressures")
23print("=" * 70)
24print("\nINPUT: Arrays of P_tank_O and P_tank_F")
25print("OUTPUT: Solved Pc, Thrust, and all performance parameters")
26print("\n" + "-" * 70)
27
28# Example 1: 2D Pressure Grid (like reference file)
29print("\n Example 1: 2D Pressure Grid (P_tank_O vs P_tank_F)")
30print("-" * 70)
31
32# Create 2D grid of tank pressures (like reference file)
33# Realistic ranges: Oxidizer 3-8 MPa, Fuel 2.7-7.6 MPa (90-95% of oxidizer)
34P_tank_O_grid = np.linspace(3.0e6, 8.0e6, 20) # 20 points
35P_tank_F_grid = np.linspace(2.7e6, 7.6e6, 20) # 20 points
36
37# Create meshgrid for all combinations
38P_O_mesh, P_F_mesh = np.meshgrid(P_tank_O_grid, P_tank_F_grid)
39
40# Flatten for evaluation
41P_O_flat = P_O_mesh.flatten()
42P_F_flat = P_F_mesh.flatten()
43
44print(f"Pressure Grid:")
45print(f" P_tank_O: {len(P_tank_O_grid)} points from {P_tank_O_grid[0]/6894.76:.0f} to {P_tank_O_grid[-1]/6894.76:.0f} psi")
46print(f" P_tank_F: {len(P_tank_F_grid)} points from {P_tank_F_grid[0]/6894.76:.0f} to {P_tank_F_grid[-1]/6894.76:.0f} psi")
47print(f" Total combinations: {len(P_O_flat)}")
48
49# Evaluate at all pressure combinations
50print(f"\nSolving for Pc and calculating thrust at each point...")
51results = runner.evaluate_arrays(P_O_flat, P_F_flat)
52
53# Reshape results for plotting (convert to psi)
54F_2d = results['F'].reshape(P_O_mesh.shape) / 1000 # kN
55Pc_2d = results['Pc'].reshape(P_O_mesh.shape) / 6894.76 # psi
56MR_2d = results['MR'].reshape(P_O_mesh.shape)
57Isp_2d = results['Isp'].reshape(P_O_mesh.shape)
58mdot_total_2d = (results['mdot_O'] + results['mdot_F']).reshape(P_O_mesh.shape)
59
60# Filter out NaN values for summary
61valid_mask = ~np.isnan(results['Pc'])
62print(f"\nResults Summary (valid points only):")
63print(f" Valid points: {np.sum(valid_mask)}/{len(P_O_flat)}")
64if np.sum(valid_mask) > 0:
65 print(f" Solved Pc range: {results['Pc'][valid_mask].min()/6894.76:.0f} - {results['Pc'][valid_mask].max()/6894.76:.0f} psi")
66 print(f" Thrust range: {results['F'][valid_mask].min()/1000:.2f} - {results['F'][valid_mask].max()/1000:.2f} kN")
67 print(f" MR range: {results['MR'][valid_mask].min():.2f} - {results['MR'][valid_mask].max():.2f}")
68 print(f" Isp range: {results['Isp'][valid_mask].min():.1f} - {results['Isp'][valid_mask].max():.1f} s")
69
70# Plot 2D contour maps
71fig, axes = plt.subplots(2, 2, figsize=(14, 12))
72fig.suptitle("2D Pressure Grid Results (P_tank_O vs P_tank_F)", fontsize=16, fontweight="bold")
73
74# Thrust map
75ax = axes[0, 0]
76contour = ax.contourf(P_O_mesh / 6894.76, P_F_mesh / 6894.76, F_2d, levels=20, cmap='viridis')
77ax.set_xlabel('Oxidizer Tank Pressure [psi]')
78ax.set_ylabel('Fuel Tank Pressure [psi]')
79ax.set_title('Thrust [kN]')
80plt.colorbar(contour, ax=ax, label='Thrust [kN]')
81
82# Chamber pressure map
83ax = axes[0, 1]
84contour = ax.contourf(P_O_mesh / 6894.76, P_F_mesh / 6894.76, Pc_2d, levels=20, cmap='plasma')
85ax.set_xlabel('Oxidizer Tank Pressure [psi]')
86ax.set_ylabel('Fuel Tank Pressure [psi]')
87ax.set_title('Chamber Pressure [psi] (SOLVED)')
88plt.colorbar(contour, ax=ax, label='Pc [psi]')
89
90# Mixture ratio map
91ax = axes[1, 0]
92contour = ax.contourf(P_O_mesh / 6894.76, P_F_mesh / 6894.76, MR_2d, levels=20, cmap='coolwarm')
93ax.set_xlabel('Oxidizer Tank Pressure [psi]')
94ax.set_ylabel('Fuel Tank Pressure [psi]')
95ax.set_title('Mixture Ratio (O/F)')
96plt.colorbar(contour, ax=ax, label='O/F Ratio')
97
98# Isp map
99ax = axes[1, 1]
100contour = ax.contourf(P_O_mesh / 6894.76, P_F_mesh / 6894.76, Isp_2d, levels=20, cmap='inferno')
101ax.set_xlabel('Oxidizer Tank Pressure [psi]')
102ax.set_ylabel('Fuel Tank Pressure [psi]')
103ax.set_title('Specific Impulse [s]')
104plt.colorbar(contour, ax=ax, label='Isp [s]')
105
106plt.tight_layout()
107output_path = Path(__file__).parent / "pressure_sweep_results.png"
108plt.savefig(str(output_path), dpi=300, bbox_inches='tight')
109print(f"\n[OK] Saved 2D pressure grid plots to {output_path}")
110
111# Example 2: 1D slices through the 2D grid (for comparison)
112print("\n Example 2: 1D Slices Through Pressure Grid")
113print("-" * 70)
114
115# Take slices: fix one pressure, vary the other
116# Slice 1: Fix fuel pressure, vary oxidizer
117P_tank_F_fixed = 5.0e6 # 5 MPa
118P_tank_O_slice = np.linspace(3.0e6, 8.0e6, 30)
119P_tank_F_slice1 = np.full_like(P_tank_O_slice, P_tank_F_fixed)
120
121# Slice 2: Fix oxidizer pressure, vary fuel
122P_tank_O_fixed = 6.0e6 # 6 MPa
123P_tank_F_slice = np.linspace(2.7e6, 7.6e6, 30)
124P_tank_O_slice2 = np.full_like(P_tank_F_slice, P_tank_O_fixed)
125
126print(f"Slice 1: P_tank_F fixed at {P_tank_F_fixed/1e6:.1f} MPa, varying P_tank_O")
127results_slice1 = runner.evaluate_arrays(P_tank_O_slice, P_tank_F_slice1)
128
129print(f"Slice 2: P_tank_O fixed at {P_tank_O_fixed/1e6:.1f} MPa, varying P_tank_F")
130results_slice2 = runner.evaluate_arrays(P_tank_O_slice2, P_tank_F_slice)
131
132# Plot slices
133fig, axes = plt.subplots(2, 2, figsize=(12, 10))
134fig.suptitle("1D Slices Through Pressure Grid", fontsize=16, fontweight="bold")
135
136# Thrust slices
137ax = axes[0, 0]
138ax.plot(P_tank_O_slice / 6894.76, results_slice1['F'] / 1000, 'b-', linewidth=2, marker='o', markersize=4,
139 label=f'P_tank_F = {P_tank_F_fixed/6894.76:.0f} psi')
140ax.plot(P_tank_F_slice / 6894.76, results_slice2['F'] / 1000, 'r--', linewidth=2, marker='s', markersize=4,
141 label=f'P_tank_O = {P_tank_O_fixed/6894.76:.0f} psi')
142ax.set_xlabel('Tank Pressure [psi]')
143ax.set_ylabel('Thrust [kN]')
144ax.legend()
145ax.grid(True, alpha=0.3)
146ax.set_title('Thrust vs Tank Pressure (Slices)')
147
148# Chamber pressure slices
149ax = axes[0, 1]
150ax.plot(P_tank_O_slice / 6894.76, results_slice1['Pc'] / 6894.76, 'b-', linewidth=2, marker='o', markersize=4,
151 label=f'P_tank_F = {P_tank_F_fixed/6894.76:.0f} psi')
152ax.plot(P_tank_F_slice / 6894.76, results_slice2['Pc'] / 6894.76, 'r--', linewidth=2, marker='s', markersize=4,
153 label=f'P_tank_O = {P_tank_O_fixed/6894.76:.0f} psi')
154ax.set_xlabel('Tank Pressure [psi]')
155ax.set_ylabel('Chamber Pressure [psi] (SOLVED)')
156ax.legend()
157ax.grid(True, alpha=0.3)
158ax.set_title('Pc vs Tank Pressure (Slices)')
159
160# Mixture ratio slices
161ax = axes[1, 0]
162ax.plot(P_tank_O_slice / 6894.76, results_slice1['MR'], 'b-', linewidth=2, marker='o', markersize=4,
163 label=f'P_tank_F = {P_tank_F_fixed/6894.76:.0f} psi')
164ax.plot(P_tank_F_slice / 6894.76, results_slice2['MR'], 'r--', linewidth=2, marker='s', markersize=4,
165 label=f'P_tank_O = {P_tank_O_fixed/6894.76:.0f} psi')
166ax.set_xlabel('Tank Pressure [psi]')
167ax.set_ylabel('Mixture Ratio (O/F)')
168ax.legend()
169ax.grid(True, alpha=0.3)
170ax.set_title('MR vs Tank Pressure (Slices)')
171
172# Isp slices
173ax = axes[1, 1]
174ax.plot(P_tank_O_slice / 6894.76, results_slice1['Isp'], 'b-', linewidth=2, marker='o', markersize=4,
175 label=f'P_tank_F = {P_tank_F_fixed/6894.76:.0f} psi')
176ax.plot(P_tank_F_slice / 6894.76, results_slice2['Isp'], 'r--', linewidth=2, marker='s', markersize=4,
177 label=f'P_tank_O = {P_tank_O_fixed/6894.76:.0f} psi')
178ax.set_xlabel('Tank Pressure [psi]')
179ax.set_ylabel('Specific Impulse [s]')
180ax.legend()
181ax.grid(True, alpha=0.3)
182ax.set_title('Isp vs Tank Pressure (Slices)')
183
184plt.tight_layout()
185output_path_slices = Path(__file__).parent / "pressure_slices.png"
186plt.savefig(str(output_path_slices), dpi=300, bbox_inches='tight')
187print(f"[OK] Saved 1D slice plots to {output_path_slices}")
188
189print("\n" + "=" * 70)
190print("[OK] Pressure sweep complete!")
191print("=" * 70)
192print("\nKey Points:")
193print(" • INPUT: Arrays of P_tank_O and P_tank_F")
194print(" • Pc is SOLVED at each point (not input!)")
195print(" • All performance parameters calculated from solved Pc")
196print(" • Can evaluate any number of pressure combinations")
197