STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_pressurant_is_not_propellant.py
Go to the documentation of this file.
1"""Pressurant gas is dead mass, not propellant.
2
3ui.flight_sim used to drain the entire COPV over the burn
4(``mdot = m_pressurant / burn_time``) and hand that to a RocketPy
5MassFlowRateBasedTank as ``liquid_mass_flow_rate_out``, which subtracts it from vehicle
6mass. But the gas moves from the COPV into the ullage the departing propellant leaves
7behind -- it is still on board at burnout. Flying it as propellant over-stated the mass
8ratio, and running the tank to exactly zero made RocketPy raise outright.
9"""
10import math
11import re
12from pathlib import Path
13
14import pytest
15
16FLIGHT_SIM = Path(__file__).resolve().parents[1] / "ui" / "flight_sim.py"
17
18
20 src = FLIGHT_SIM.read_text()
21 block = src[src.index("m_pressurant = getattr"):src.index("print(f\" Pressurant")]
22 assert "m_pressurant / effective_burn_time" not in block, (
23 "the COPV is being drained over the burn again; pressurant is not propellant"
24 )
25 assert re.search(r"mdot_pressurant_avg\s*=\s*0\.0", block), (
26 "mdot_pressurant_avg must be 0.0 -- the gas stays on the vehicle"
27 )
28
29
31 """Dead mass, not absent mass. It must still be in the initial mass sum."""
32 src = FLIGHT_SIM.read_text()
33 assert "total_initial_mass = rocket_mass + motor_dry_mass + m_lox0 + m_rp10 + m_pressurant" in src, (
34 "pressurant dropped out of the initial mass; it is carried, just not expelled"
35 )
36
37
39 """Quantifies the defect on the 180 lb / 11 L point, so the fix has a number on it.
40
41 Two different numbers, and they are not the same size -- worth keeping straight:
42
43 * the IDEAL rocket equation gains ~51 m/s from flying 1.551 kg of gas as propellant;
44 * the SIMULATED trajectory only gained 183 ft of apogee (12522 -> 12339 ft at
45 eta_c* 0.9315, measured A/B on this vehicle).
46
47 The trajectory effect is far smaller because the mass comes off gradually and the
48 flight is drag-dominated at Mach 0.87. Do not quote the 51 m/s as an apogee error.
49 """
50 m_wet, m_prop, m_gas = 180 * 0.45359237, 10.7375, 1.551
51 isp_eff = 238.72 * 9.80665
52 dv_wrong = isp_eff * math.log(m_wet / (m_wet - m_prop - m_gas)) # gas flown as propellant
53 dv_right = isp_eff * math.log(m_wet / (m_wet - m_prop)) # gas carried
54 assert dv_wrong > dv_right, "expelling the gas must inflate the mass ratio"
55 assert dv_wrong - dv_right == pytest.approx(51.0, abs=2.0), (
56 f"ideal-dv overstatement changed: {dv_wrong - dv_right:.1f} m/s"
57 )
58 # and the measured trajectory effect, which is the number that actually matters
59 apogee_bug_ft, apogee_fixed_ft = 12522.0, 12339.0
60 assert apogee_bug_ft > apogee_fixed_ft
61 assert (apogee_bug_ft / apogee_fixed_ft - 1) == pytest.approx(0.0148, abs=0.004), (
62 "the simulated apogee inflation was 1.5 %, not the 15 % the rocket equation implies"
63 )
64
65
67 """The old model hit exactly -0.000 kg at burnout and RocketPy raised on it."""
68 m_gas, burn = 1.551, 3.918
69 assert m_gas - (m_gas / burn) * burn == pytest.approx(0.0, abs=1e-12), "the old model ran it dry"
70 assert m_gas - 0.0 * burn == m_gas, "the fixed model leaves the COPV full"
71
72
73# ---------------------------------------------------------------------------------------
74# COPV volume and density are declared, not assumed
75# ---------------------------------------------------------------------------------------
76
78 """Fluid(density=200) was a number with no source on it.
79
80 Real GN2 at 4500 psi / 293 K is 310 kg/m3 (CoolProp, Z = 1.150). At 200 a 5 L COPV
81 caps at 1.0 kg, and the 1.551 kg a 5 L bottle actually holds made RocketPy refuse the
82 tank as "overfilled" -- so the flight sim failed outright on a correctly specified COPV.
83 """
84 src = FLIGHT_SIM.read_text()
85 assert 'Fluid(name="GN2_COPV", density=200)' not in src, "the invented 200 kg/m3 is back"
86 assert "density=m_pressurant/(V_copv*0.999)" in src, (
87 "COPV density must be mass/volume from the config, not a constant "
88 "(the 0.999 is solver ullage -- see test_solver_ullage_is_small_and_conserves_mass)"
89 )
90
91
93 """press_radius x press_h and free_volume_L could disagree; free_volume_L wins.
94
95 It is the number the operator specifies and the one a propellant-volume budget counts.
96 """
97 src = FLIGHT_SIM.read_text()
98 block = src[src.index("free_L = getattr"):src.index("press_geom = CylindricalTank", src.index("free_L = getattr"))]
99 assert "V_copv = float(free_L)/1000.0" in block
100 assert "press_h_eff = V_copv/(np.pi*config.press_tank.press_radius**2)" in block
101
102
104 """The case that broke it: 5 L, 1.551 kg of GN2 at 4500 psi."""
105 V, m = 0.005, 1.551
106 assert m / V == pytest.approx(310.2, abs=1.0), "5 L at 4500 psi is ~310 kg/m3"
107 assert m > V * 200.0, "at the old hardcoded 200 kg/m3 this tank reads as overfilled"
108 assert V * 200.0 == pytest.approx(1.0, abs=0.01), "the old cap was 1.0 kg"
109
110
112 """A flat 0.01 kg added ALONGSIDE the liquid over-filled the COPV.
113
114 With density derived as m_pressurant/V, the tank then held m_pressurant + 0.01 kg =
115 0.0050322 m3 in a 0.0050000 m3 bottle, and RocketPy rejected it:
116 "Input Function image (0.00503...) must be within the domain (0.0, 0.005)".
117 """
118 src = FLIGHT_SIM.read_text()
119 assert "initial_gas_mass=0.01," not in src, "the additive 0.01 kg stub is back"
120 assert "initial_liquid_mass=max(0.0, m_pressurant - 1.0e-4)," in src
121 assert "initial_gas_mass=1.0e-4," in src
122
123 # the arithmetic that broke it, on the 5 L / 1.551 kg COPV
124 V, m = 0.005, 1.551
125 rho = m / V
126 assert (m + 0.01) / rho == pytest.approx(0.0050322, abs=1e-6), "the overflow"
127 assert (m + 0.01) / rho > V, "additive stub must exceed the tank"
128 assert m / rho == pytest.approx(V, rel=1e-12), "taking the stub out of m conserves volume"
129
130
132 """The 0.1 % lives in the density, never in the mass."""
133 src = FLIGHT_SIM.read_text()
134 assert "density=m_pressurant/(V_copv*0.999)" in src
135 V, m = 0.005, 1.551
136 rho = m / (V * 0.999)
137 assert m / rho == pytest.approx(0.999 * V, rel=1e-12), "fluid sits just inside the domain"
138 assert rho * (0.999 * V) == pytest.approx(m, rel=1e-12), "mass is exact"