STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_layer2_required_impulse.py
Go to the documentation of this file.
1"""Layer 2's required impulse is a vertical flight, not a formula (FLT-7).
2
3It was (1.15 sqrt(2 g h) + 0.5 g t_b)(m_dry + m_p): 27,094 N s for the 6.5 kN vehicle against
422,537 N s with no drag (Sutton) and 25,025 N s at the flight sim's old Cd 0.45, with errors
5that cancelled only near that point (+18 % at 40 kg / 40 kg / 15 s).
6
7References: Sutton & Biblarz's closed form for vertical flight without drag (ch. 4), and an
8independent point-mass integration (scipy solve_ivp, fluids' 1976 atmosphere) with drag.
9"""
10
11from __future__ import annotations
12
13import math
14
15import pytest
16from scipy.optimize import brentq
17
18from engine.optimizer.layers.layer2_pressure import calculate_required_impulse_from_mass
19
20G0 = 9.80665
21
22
23def _sutton_impulse(m0, mp, tb, h):
24 def apogee(c):
25 R = m0 / (m0 - mp)
26 u = c * math.log(R) - G0 * tb
27 hp = c * tb * (1 - math.log(R) / (R - 1)) - 0.5 * G0 * tb**2
28 return hp + u * u / (2 * G0)
29
30 return brentq(lambda c: apogee(c) - h, 100.0, 20000.0) * mp
31
32
33def _ivp_impulse(m0, mp, tb, h, cd, area, elev):
34 """Flat mdot, constant exhaust velocity, constant Cd, 1976 atmosphere: impulse to apogee h."""
35 fluids = pytest.importorskip("fluids.atmosphere")
36 from scipy.integrate import solve_ivp
37
38 md = mp / tb
39
40 def apogee(c):
41 def rhs(t, y):
42 z, v, m = y
43 atm = fluids.ATMOSPHERE_1976(elev + max(z, 0.0))
44 T = c * md if t < tb else 0.0
45 return [v, (T - 0.5 * atm.rho * v * abs(v) * cd * area) / m - G0, -md if t < tb else 0.0]
46
47 burn = solve_ivp(rhs, (0, tb), [0, 0, m0], max_step=0.005, rtol=1e-10, atol=1e-9)
48 top = lambda t, y: y[1] # noqa: E731
49 top.terminal, top.direction = True, -1
50 coast = solve_ivp(rhs, (tb, 400), burn.y[:, -1], events=top, max_step=0.05, rtol=1e-10, atol=1e-9)
51 return coast.y_events[0][0][0]
52
53 return brentq(lambda c: apogee(c) - h, 500.0, 6000.0, xtol=1e-4) * mp
54
55
57 I = calculate_required_impulse_from_mass(3890.7, 81.647 - 11.015, 11.015, 3.8978)
58 assert I == pytest.approx(_sutton_impulse(81.647, 11.015, 3.8978, 3890.7), rel=1e-4)
59 assert I == pytest.approx(22537.0, rel=5e-3) # the audit's number
60
61
62@pytest.mark.parametrize("m_dry,mp,tb,h", [(70.632, 11.015, 3.8978, 3890.7), (40.0, 40.0, 15.0, 9000.0)])
64 from engine.pipeline.vehicle_drag import DragCurves
65
66 area, elev = math.pi * 0.078359**2, 626.67
67 const = DragCurves((0.0, 5.0), (0.45, 0.45), (0.45, 0.45), "table", "constant 0.45")
68 vehicle = {"flight": {"area_m2": area, "drag": const, "elevation_m": elev}, "gas_on_board_kg": 0.0}
69 I = calculate_required_impulse_from_mass(h, m_dry, mp, tb, vehicle=vehicle)
70 assert I == pytest.approx(_ivp_impulse(m_dry + mp, mp, tb, h, 0.45, area, elev), rel=2e-3)
71
72
74 base = calculate_required_impulse_from_mass(3890.7, 69.320, 11.015, 3.8978)
75 gas = calculate_required_impulse_from_mass(3890.7, 69.320, 11.015, 3.8978, vehicle={"gas_on_board_kg": 1.312})
76 assert gas == pytest.approx(_sutton_impulse(69.320 + 1.312 + 11.015, 11.015, 3.8978, 3890.7), rel=1e-4)
77 assert gas > base
78
79
81 """The same curve, drag and pad: the 1-DOF apogee and RocketPy's within 1 %."""
82 pytest.importorskip("rocketpy")
83 from engine.optimizer.layers.layer2_pressure import layer2_flight_vehicle
84 from engine.pipeline.flight_1dof import vertical_apogee_agl
85 from flight_cases import fly, header_curve, shipped
86
87 cfg = shipped()
88 res = fly(cfg)
89 vehicle = layer2_flight_vehicle(cfg)
90 t, F, mO, mF = header_curve()
91 h = vertical_apogee_agl(t, F, mO + mF, res["flight_report"]["wet_mass_kg"], **vehicle["flight"])
92 assert h == pytest.approx(res["apogee"], rel=0.01)
93 assert vehicle["gas_on_board_kg"] == pytest.approx(cfg.press_tank.initial_gas_mass)
#define max(a, b)
Definition Arduino.h:43
static void fly(world_t *w)
Definition flight_e2e.c:369
*brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer *sa const json_pointer & rhs
Definition json.hpp:13930
_ivp_impulse(m0, mp, tb, h, cd, area, elev)
test_with_drag_matches_an_independent_integration(m_dry, mp, tb, h)