STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_flight_ullage_and_depletion.py
Go to the documentation of this file.
1"""The T-0 ullage and the depletion cut (FLT-1, FLT-3).
2
3FLT-1: both propellant tanks were seeded with 0.05 kg of gas at 50 kg/m3, 1.000 L whatever the
4tank. The shipped 6.5 kN tanks, loaded to 90 %, have 0.644 L and 0.620 L of ullage, so RocketPy
5refused both and the Flight tab could not fly the shipped vehicle at all. The gas that is there
6fills V - m/rho_liquid at tank pressure (CoolProp N2).
7
8FLT-3: a truncated burn lost 50 ms, and a fuel-only loop shrank loads within 1 mg of the
9integral to 98 % of the fuel; which fired was floating-point noise, a 143 m apogee spread for
10loads that differ by micrograms. The cut is now at the exact depletion.
11
12Expected values are hand geometry, CoolProp and the trapezoid of the thrust curve; none of them
13come from the flight sim.
14"""
15
16from __future__ import annotations
17
18import math
19
20import numpy as np
21import pytest
22
23pytest.importorskip("rocketpy")
24CP = pytest.importorskip("CoolProp.CoolProp")
25
26from flight_cases import curve_integrals, fly, fly_helpers, header_curve, roomy_tanks, shipped # noqa: E402
27
28PSI = 6894.757293168
29
30
31def _tank(res, name):
32 return next(p["tank"] for p in res["flight"].rocket.motor.positioned_tanks if p["tank"].name == name)
33
34
36 res = fly_helpers(shipped())
37 assert res.get("success"), res.get("error")
38 assert res["apogee"] > 1000.0
39
40
42 cfg = shipped()
43 res = fly(cfg)
44 for name, sec, rho_l in (("LOX Tank", cfg.lox_tank, 1140.0), ("Fuel Tank", cfg.fuel_tank, 789.0)):
45 V = sec.tank_volume_m3
46 V_ull = V - sec.mass / rho_l
47 rho_g = CP.PropsSI("D", "P", sec.initial_pressure_psi * PSI, "T", 293.15, "Nitrogen")
48 tank = _tank(res, name)
49 assert tank.gas_volume(0) == pytest.approx(0.999 * V_ull, abs=1e-9)
50 assert tank.gas_mass(0) == pytest.approx(0.999 * V_ull * rho_g, rel=1e-6)
51 # the hand numbers the finding quotes: 0.644 L and 0.620 L of ullage, ~30 g of N2 each
52 assert cfg.lox_tank.tank_volume_m3 - cfg.lox_tank.mass / 1140.0 == pytest.approx(0.6444e-3, abs=1e-6)
53 assert cfg.fuel_tank.tank_volume_m3 - cfg.fuel_tank.mass / 789.0 == pytest.approx(0.6202e-3, abs=1e-6)
54
55
57 """A 5 L LOX tank at 90 % fill: 0.5 L of ullage, half what the old seed demanded."""
58 cfg = roomy_tanks(shipped())
59 V = 5.0e-3
60 cfg.lox_tank.tank_volume_m3 = V
61 cfg.lox_tank.lox_h = V / (math.pi * cfg.lox_tank.lox_radius**2)
62 cfg.lox_tank.mass = 0.9 * V * 1140.0
63 res = fly(cfg)
64 assert _tank(res, "LOX Tank").gas_volume(0) == pytest.approx(0.999 * 0.1 * V, abs=1e-9)
65 assert res["truncation_info"]["reason"] == "LOX"
66
67
69 """An explicit capacity above what the tank holds lets 110 % of it through the cap (FLT-9);
70 the flight names the tank instead of failing inside RocketPy."""
71 cfg = shipped()
72 full = cfg.lox_tank.tank_volume_m3 * 1140.0
73 cfg.design_requirements.lox_tank_capacity_kg = 1.2 * full
74 cfg.lox_tank.mass = 1.1 * full
75 with pytest.raises(ValueError, match="LOX tank.*leaves no ullage"):
76 fly(cfg)
77
78
79@pytest.mark.parametrize("scale", [1 - 1e-9, 1.0, 1 + 1e-9, 1 + 1e-6])
81 """Loads at the curve's own integral burn the whole curve, whichever side of it they fall."""
82 I, IO, IF = curve_integrals()
83 cfg = roomy_tanks(shipped())
84 cfg.lox_tank.mass, cfg.fuel_tank.mass = IO * scale, IF * scale
85 res = fly(cfg)
86 motor = res["flight"].rocket.motor
87 assert motor.total_impulse / I - 1 == pytest.approx(0.0, abs=5e-4)
88 if res["truncation_info"]["truncated"]:
89 assert res["truncation_info"]["cutoff_time"] == pytest.approx(motor.burn_out_time, abs=1e-3)
90
91
93 """Was 3941 / 3890 / 4033 m for x(1, 1+1e-9, 1+1e-6). RocketPy's own integrator scatter on
94 this vehicle is under 1 m, so that is the bound."""
95 _, IO, IF = curve_integrals()
96 apogees = []
97 for scale in (1 - 1e-9, 1.0, 1 + 1e-9, 1 + 1e-6):
98 cfg = roomy_tanks(shipped())
99 cfg.lox_tank.mass, cfg.fuel_tank.mass = IO * scale, IF * scale
100 apogees.append(fly(cfg)["apogee"])
101 assert max(apogees) - min(apogees) < 1.0, apogees
102
103
105 I, IO, IF = curve_integrals()
106 cfg = roomy_tanks(shipped())
107 cfg.lox_tank.mass, cfg.fuel_tank.mass = IO * 0.99, IF * 0.99
108 res = fly(cfg)
109 motor = res["flight"].rocket.motor
110 assert motor.total_impulse / I - 1 == pytest.approx(-0.0100, abs=5e-4)
111 assert res["truncation_info"]["cutoff_time"] == pytest.approx(motor.burn_out_time, abs=1e-3)
112
113
115 """Vehicle mass falls by exactly the propellant burned: the refill is a transfer."""
116 t, F, mO, mF = header_curve()
117 res = fly(shipped())
118 rocket = res["flight"].rocket
119 tb = rocket.motor.burn_out_time
120 md = mO + mF
121 burned = float(np.interp(tb, t, np.concatenate([[0.0], np.cumsum(0.5 * (md[1:] + md[:-1]) * np.diff(t))])))
122 assert rocket.total_mass(0.0) - rocket.total_mass(tb + 5.0) == pytest.approx(burned, abs=1e-4)
123 copv = _tank(res, "Pressurant (Nā‚‚) Tank")
124 refill = copv.fluid_mass(0.0) - copv.fluid_mass(tb)
125 assert refill > 0.4, "the COPV should have refilled ~0.5 kg of ullage"
126 assert res["flight_report"]["copv_refill_kg"] == pytest.approx(refill, rel=2e-3)
#define min(a, b)
Definition Arduino.h:42
#define max(a, b)
Definition Arduino.h:43
static void fly(world_t *w)
Definition flight_e2e.c:369