-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
280 lines (218 loc) · 9.18 KB
/
main.py
File metadata and controls
280 lines (218 loc) · 9.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
"""
EnSim - Advanced Rocket Engine Simulation
Entry point for the application.
Usage:
python main.py # Launch GUI (when implemented)
python main.py --test # Run quick validation test
"""
import argparse
import sys
from typing import NoReturn
def run_gui() -> NoReturn:
"""Launch the PyQt6 GUI application."""
try:
import os
from pathlib import Path
from PyQt6.QtWidgets import QApplication
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QIcon
from src.ui.windows.main_window import MainWindow
from src.ui.splash_screen import EnSimSplashScreen
# Enable High DPI scaling for 4K/Retina displays
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1"
# Suppress Qt HiDPI warning spam (known PyQt6 bug)
os.environ["QT_LOGGING_RULES"] = "qt.qpa.window=false"
# Enable high DPI scaling
QApplication.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
)
app = QApplication(sys.argv)
app.setApplicationName("EnSim")
app.setOrganizationName("EnSim")
app.setApplicationVersion("1.0.0")
# Set app icon
icon_path = Path(__file__).parent / "assets" / "icon.png"
if icon_path.exists():
app.setWindowIcon(QIcon(str(icon_path)))
# Show splash screen
splash = EnSimSplashScreen()
splash.show()
app.processEvents()
# Pre-compile Numba functions with splash progress
def init_physics():
import numpy as np
splash.showMessage("Loading thermodynamics...")
from src.core.thermodynamics import cp_over_r
# Warm up JIT with raw Numba function
cp_over_r(1000.0, np.array([1.0, 1e-5, 1e-8, 1e-11, 1e-14, 1e4, 1.0]))
splash.showMessage("Loading propulsion...")
from src.core.propulsion import calculate_c_star
calculate_c_star(1.2, 500.0, 3000.0)
splash.showMessage("Ready...")
init_physics()
splash.showMessage("Starting application...")
app.processEvents()
window = MainWindow()
window.show()
splash.finish(window)
sys.exit(app.exec())
except ImportError as e:
print("EnSim - Rocket Engine Simulation")
print("=" * 40)
print(f"ERROR: PyQt6 not installed. Please run:")
print(" pip install PyQt6")
print(f"\nDetails: {e}")
sys.exit(1)
def run_validation_test() -> None:
"""Run a quick validation test of the core physics engine."""
print("EnSim - Core Physics Validation Test")
print("=" * 40)
from src.core.constants import GAS_CONSTANT
from src.core.thermodynamics import (
calculate_cp,
calculate_enthalpy,
calculate_entropy,
)
from src.utils.nasa_parser import create_sample_database
# Load sample database
db = create_sample_database()
print(f"\n✓ Loaded {len(db)} species from sample database")
# Test H2O properties at 1000K
h2o = db['H2O']
T = 1000.0
cp = calculate_cp(T, h2o)
h = calculate_enthalpy(T, h2o)
s = calculate_entropy(T, h2o)
print(f"\nH2O Properties at {T} K:")
print(f" Cp = {cp:.4f} J/(mol·K)")
print(f" H = {h/1000:.4f} kJ/mol")
print(f" S = {s:.4f} J/(mol·K)")
# Validate against known values (NIST/CEA reference)
# H2O at 1000K: Cp ≈ 41.3 J/(mol·K), H ≈ -215 kJ/mol from 298K
cp_expected = 41.3 # J/(mol·K) - approximate
error_pct = abs((cp - cp_expected) / cp_expected) * 100
print(f"\n Cp error vs NIST: {error_pct:.2f}%")
if error_pct < 1.0:
print("\n✓ Validation PASSED (error < 1%)")
else:
print(f"\n⚠ Validation WARNING: error = {error_pct:.2f}%")
# Test temperature range switching
print("\nTemperature range switching test:")
for T_test in [500.0, 999.0, 1000.0, 1001.0, 2000.0]:
cp_test = calculate_cp(T_test, h2o)
print(f" T = {T_test:6.0f} K -> Cp = {cp_test:.4f} J/(mol·K)")
# Coefficient continuity test at T_mid
from src.core.thermodynamics import cp_over_r
print("\n" + "-" * 40)
print("Coefficient Continuity Test at T_mid:")
T_mid = h2o.t_mid # Should be 1000.0 K
cp_r_low = cp_over_r(T_mid, h2o.coeffs_low)
cp_r_high = cp_over_r(T_mid, h2o.coeffs_high)
cp_low = cp_r_low * GAS_CONSTANT # J/(mol·K)
cp_high = cp_r_high * GAS_CONSTANT # J/(mol·K)
diff = abs(cp_low - cp_high)
print(f" T_mid = {T_mid:.1f} K")
print(f" Cp (low coeffs) = {cp_low:.6f} J/(mol·K)")
print(f" Cp (high coeffs) = {cp_high:.6f} J/(mol·K)")
print(f" Difference = {diff:.6f} J/(mol·K)")
if diff > 0.001:
print(f"\n⚠ WARNING: Cp discontinuity at T_mid > 0.001 J/(mol·K)!")
print(f" This may cause numerical issues in equilibrium calculations.")
else:
print(f"\n✓ Coefficients are continuous at T_mid (diff < 0.001)")
# Combustion equilibrium demo
print("\n" + "-" * 40)
print("Combustion Equilibrium Demo (H2 + O2):")
from src.core.chemistry import CombustionProblem
problem = CombustionProblem(db)
problem.add_fuel('H2', moles=2.0, temperature=298.15)
problem.add_oxidizer('O2', moles=1.0, temperature=298.15)
try:
result = problem.solve(pressure=1013250.0) # 10 atm
print(f"\n Reactants: 2 mol H2 + 1 mol O2 @ 10 atm")
print(f" Adiabatic Flame Temperature: {result.temperature:.1f} K")
print(f" Iterations: {result.iterations}")
print(f" Converged: {result.converged}")
print("\n Product Mole Fractions:")
for name, x in sorted(zip(result.species_names, result.mole_fractions),
key=lambda t: t[1], reverse=True):
if x > 0.001:
print(f" {name:8s}: {x:.4f}")
print(f"\n Mean MW: {result.mean_molecular_weight:.2f} g/mol")
print(f" Gamma: {result.gamma:.3f}")
if 2500 < result.temperature < 4500:
print("\n✓ Combustion calculation completed successfully!")
else:
print(f"\n⚠ Temperature outside expected range")
# Propulsion performance demo
print("\n" + "-" * 40)
print("Propulsion Performance (Frozen Flow):")
from src.core.propulsion import NozzleConditions, calculate_performance
# Vacuum performance (large expansion ratio)
nozzle_vac = NozzleConditions(
area_ratio=50.0,
chamber_pressure=1013250.0, # 10 atm
ambient_pressure=0.0 # Vacuum
)
perf_vac = calculate_performance(
T_chamber=result.temperature,
P_chamber=nozzle_vac.chamber_pressure,
gamma=result.gamma,
mean_molecular_weight=result.mean_molecular_weight,
nozzle=nozzle_vac
)
print(f"\n Vacuum Performance (ε = 50):")
print(f" C* = {perf_vac.c_star:.1f} m/s")
print(f" Ve = {perf_vac.exit_velocity:.1f} m/s")
print(f" Cf = {perf_vac.c_f:.3f}")
print(f" Isp = {perf_vac.isp:.1f} s")
print(f" Me = {perf_vac.exit_mach:.2f}")
# Sea level performance (higher chamber pressure, smaller expansion ratio)
nozzle_sl = NozzleConditions(
area_ratio=15.0,
chamber_pressure=68.0 * 101325.0, # 68 atm (typical for sea level engine)
ambient_pressure=101325.0 # 1 atm
)
perf_sl = calculate_performance(
T_chamber=result.temperature,
P_chamber=nozzle_sl.chamber_pressure,
gamma=result.gamma,
mean_molecular_weight=result.mean_molecular_weight,
nozzle=nozzle_sl
)
print(f"\n Sea Level Performance (ε = 15):")
print(f" Isp = {perf_sl.isp:.1f} s")
print(f" Ve = {perf_sl.exit_velocity:.1f} m/s")
if perf_vac.isp > 400 and perf_sl.isp > 300:
print("\n✓ Propulsion calculations completed successfully!")
else:
print("\n⚠ Isp outside expected range")
except Exception as e:
print(f"\n⚠ Combustion calculation failed: {e}")
print("\n" + "=" * 40)
print("✓ Core physics engine operational!")
print(" Run 'pytest tests/' for full test suite.\n")
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="EnSim - Rocket Engine Simulation",
prog="ensim"
)
parser.add_argument(
"--test",
action="store_true",
help="Run quick validation test"
)
parser.add_argument(
"--version",
action="version",
version="EnSim 0.1.0"
)
args = parser.parse_args()
if args.test:
run_validation_test()
else:
run_gui()
if __name__ == "__main__":
main()