Skip to content

Commit e48a442

Browse files
ENH: Update flight tests to avoid unstable rocket errors
1 parent 05c4f89 commit e48a442

File tree

7 files changed

+177
-224
lines changed

7 files changed

+177
-224
lines changed
Lines changed: 47 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import numpy as np
21
import pandas as pd
32
import pytest
43
from scipy.signal import savgol_filter
54

6-
from rocketpy import Environment, Flight, Rocket, SolidMotor
5+
from rocketpy import Environment, Flight
76

87

98
@pytest.mark.parametrize(
@@ -13,63 +12,18 @@
1312
"data/weather/ndrt_2020_weather_data_ERA5_new.nc",
1413
],
1514
)
16-
def test_ndrt_2020_rocket_data_asserts_acceptance(env_file):
17-
# Notre Dame Rocket Team 2020 Flight
18-
# Launched at 19045-18879 Avery Rd, Three Oaks, MI 49128
19-
# Permission to use flight data given by Brooke Mumma, 2020
20-
#
21-
# IMPORTANT RESULTS (23rd feb)
22-
# Measured Stability Margin 2.875 cal
23-
# Official Target Altitude 4,444 ft
24-
# Measured Altitude 4,320 ft or 1316.736 m
25-
# Drift: 2275 ft
26-
27-
# Importing libraries
28-
29-
# Defining all parameters
30-
parameters = {
31-
# Mass Details
32-
"rocket_mass": (23.321 - 2.475 - 1, 0.010),
33-
# propulsion details
34-
"impulse": (4895.050, 0.033 * 4895.050),
35-
"burn_time": (3.45, 0.1),
36-
"nozzle_radius": (49.5 / 2000, 0.001),
37-
"throat_radius": (21.5 / 2000, 0.001),
38-
"grain_separation": (3 / 1000, 0.001),
39-
"grain_density": (1519.708, 30),
40-
"grain_outer_radius": (33 / 1000, 0.001),
41-
"grain_initial_inner_radius": (15 / 1000, 0.002),
42-
"grain_initial_height": (120 / 1000, 0.001),
43-
# aerodynamic details
44-
"drag_coefficient": (0.44, 0.1),
45-
"inertia_i": (83.351, 0.3 * 83.351),
46-
"inertia_z": (0.15982, 0.3 * 0.15982),
47-
"radius": (203 / 2000, 0.001),
48-
"distance_rocket_nozzle": (-1.255, 0.100),
49-
"distance_rocket_propellant": (-0.85704, 0.100),
50-
"power_off_drag": (1, 0.033),
51-
"power_on_drag": (1, 0.033),
52-
"nose_length": (0.610, 0.001),
53-
"nose_distance_to_cm": (0.71971, 0.100),
54-
"fin_span": (0.165, 0.001),
55-
"fin_root_chord": (0.152, 0.001),
56-
"fin_tip_chord": (0.0762, 0.001),
57-
"fin_distance_to_cm": (-1.04956, 0.100),
58-
"transition_top_radius": (203 / 2000, 0.010),
59-
"transition_bottom_radius": (155 / 2000, 0.010),
60-
"transition_length": (0.127, 0.010),
61-
"transition_distance_to_cm": (-1.194656, 0.010),
62-
# launch and environment details
63-
"wind_direction": (0, 3),
64-
"wind_speed": (1, 0.30),
65-
"inclination": (90, 1),
66-
"heading": (181, 3),
67-
"rail_length": (3.353, 0.001),
68-
# parachute details
69-
"cd_s_drogue": (1.5 * np.pi * (24 * 25.4 / 1000) * (24 * 25.4 / 1000) / 4, 0.1),
70-
"cd_s_main": (2.2 * np.pi * (120 * 25.4 / 1000) * (120 * 25.4 / 1000) / 4, 0.1),
71-
"lag_rec": (1, 0.5),
72-
}
15+
def test_ndrt_2020_rocket_data_asserts_acceptance(env_file, ndrt_2020_rocket):
16+
"""
17+
Notre Dame Rocket Team 2020 Flight
18+
- Launched at 19045-18879 Avery Rd, Three Oaks, MI 49128
19+
- Permission to use flight data given by Brooke Mumma, 2020
20+
21+
IMPORTANT RESULTS (23rd feb)
22+
- Measured Stability Margin 2.875 cal
23+
- Official Target Altitude 4,444 ft
24+
- Measured Altitude 4,320 ft or 1316.736 m
25+
- Drift: 2275 ft
26+
"""
7327

7428
# Environment conditions
7529
env = Environment(
@@ -86,117 +40,27 @@ def test_ndrt_2020_rocket_data_asserts_acceptance(env_file):
8640
)
8741
env.max_expected_height = 2000
8842

89-
# motor information
90-
L1395 = SolidMotor(
91-
thrust_source="data/motors/cesaroni/Cesaroni_4895L1395-P.eng",
92-
burn_time=parameters.get("burn_time")[0],
93-
dry_mass=1,
94-
dry_inertia=(0, 0, 0),
95-
center_of_dry_mass_position=0,
96-
grains_center_of_mass_position=parameters.get("distance_rocket_propellant")[0],
97-
grain_number=5,
98-
grain_separation=parameters.get("grain_separation")[0],
99-
grain_density=parameters.get("grain_density")[0],
100-
grain_outer_radius=parameters.get("grain_outer_radius")[0],
101-
grain_initial_inner_radius=parameters.get("grain_initial_inner_radius")[0],
102-
grain_initial_height=parameters.get("grain_initial_height")[0],
103-
nozzle_radius=parameters.get("nozzle_radius")[0],
104-
throat_radius=parameters.get("throat_radius")[0],
105-
interpolation_method="linear",
106-
nozzle_position=parameters.get("distance_rocket_nozzle")[0],
107-
)
108-
109-
# Rocket information
110-
NDRT2020 = Rocket(
111-
radius=parameters.get("radius")[0],
112-
mass=parameters.get("rocket_mass")[0],
113-
inertia=(
114-
parameters.get("inertia_i")[0],
115-
parameters.get("inertia_i")[0],
116-
parameters.get("inertia_z")[0],
117-
),
118-
power_off_drag=parameters.get("drag_coefficient")[0],
119-
power_on_drag=parameters.get("drag_coefficient")[0],
120-
center_of_mass_without_motor=0,
121-
)
122-
NDRT2020.set_rail_buttons(0.2, -0.5, 45)
123-
NDRT2020.add_motor(L1395, parameters.get("distance_rocket_nozzle")[0])
124-
NDRT2020.add_nose(
125-
length=parameters.get("nose_length")[0],
126-
kind="tangent",
127-
position=parameters.get("nose_distance_to_cm")[0]
128-
+ parameters.get("nose_length")[0],
129-
)
130-
NDRT2020.add_trapezoidal_fins(
131-
3,
132-
span=parameters.get("fin_span")[0],
133-
root_chord=parameters.get("fin_root_chord")[0],
134-
tip_chord=parameters.get("fin_tip_chord")[0],
135-
position=parameters.get("fin_distance_to_cm")[0],
136-
)
137-
NDRT2020.add_tail(
138-
top_radius=parameters.get("transition_top_radius")[0],
139-
bottom_radius=parameters.get("transition_bottom_radius")[0],
140-
length=parameters.get("transition_length")[0],
141-
position=parameters.get("transition_distance_to_cm")[0],
142-
)
143-
144-
# Parachute set-up
145-
def drogue_trigger(p, h, y): # pylint: disable=unused-argument
146-
# p = pressure
147-
# y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]
148-
# activate drogue when vz < 0 m/s.
149-
return True if y[5] < 0 else False
150-
151-
def main_trigger(p, h, y): # pylint: disable=unused-argument
152-
# p = pressure
153-
# y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]
154-
# activate main when vz < 0 m/s and z < 167.64 m (AGL) or 550 ft (AGL)
155-
return True if y[5] < 0 and h < 167.64 else False
156-
157-
NDRT2020.add_parachute(
158-
"Drogue",
159-
cd_s=parameters.get("cd_s_drogue")[0],
160-
trigger=drogue_trigger,
161-
sampling_rate=105,
162-
lag=parameters.get("lag_rec")[0],
163-
noise=(0, 8.3, 0.5),
164-
)
165-
NDRT2020.add_parachute(
166-
"Main",
167-
cd_s=parameters.get("cd_s_main")[0],
168-
trigger=main_trigger,
169-
sampling_rate=105,
170-
lag=parameters.get("lag_rec")[0],
171-
noise=(0, 8.3, 0.5),
172-
)
173-
17443
# Flight
17544
rocketpy_flight = Flight(
176-
rocket=NDRT2020,
45+
rocket=ndrt_2020_rocket,
17746
environment=env,
178-
rail_length=parameters.get("rail_length")[0],
179-
inclination=parameters.get("inclination")[0],
180-
heading=parameters.get("heading")[0],
181-
)
182-
df_ndrt_rocketpy = pd.DataFrame(
183-
rocketpy_flight.z[:, :], columns=["Time", "Altitude"]
47+
rail_length=3.353,
48+
inclination=90,
49+
heading=181,
18450
)
185-
df_ndrt_rocketpy["Vertical Velocity"] = rocketpy_flight.vz[:, 1]
186-
# df_ndrt_rocketpy["Vertical Acceleration"] = rocketpy_flight.az[:, 1]
187-
df_ndrt_rocketpy["Altitude"] -= env.elevation
18851

18952
# Reading data from the flightData (sensors: Raven)
190-
df_ndrt_raven = pd.read_csv("data/rockets/NDRT_2020/ndrt_2020_flight_data.csv")
53+
df = pd.read_csv("data/rockets/NDRT_2020/ndrt_2020_flight_data.csv")
54+
19155
# convert feet to meters
192-
df_ndrt_raven[" Altitude (m-AGL)"] = df_ndrt_raven[" Altitude (Ft-AGL)"] / 3.28084
56+
df[" Altitude (m-AGL)"] = df[" Altitude (Ft-AGL)"] / 3.28084
57+
19358
# Calculate the vertical velocity as a derivative of the altitude
19459
velocity_raven = [0]
195-
for i in range(1, len(df_ndrt_raven[" Altitude (m-AGL)"]), 1):
196-
v = (
197-
df_ndrt_raven[" Altitude (m-AGL)"][i]
198-
- df_ndrt_raven[" Altitude (m-AGL)"][i - 1]
199-
) / (df_ndrt_raven[" Time (s)"][i] - df_ndrt_raven[" Time (s)"][i - 1])
60+
for i in range(1, len(df[" Altitude (m-AGL)"]), 1):
61+
v = (df[" Altitude (m-AGL)"][i] - df[" Altitude (m-AGL)"][i - 1]) / (
62+
df[" Time (s)"][i] - df[" Time (s)"][i - 1]
63+
)
20064
if (
20165
v != 92.85844059786486
20266
and v != -376.85000927682034
@@ -208,21 +72,29 @@ def main_trigger(p, h, y): # pylint: disable=unused-argument
20872
velocity_raven.append(v)
20973
else:
21074
velocity_raven.append(velocity_raven[-1])
75+
76+
# Filter using Savitzky-Golay filter
21177
velocity_raven_filt = savgol_filter(velocity_raven, 51, 3)
21278

213-
apogee_time_measured = df_ndrt_raven.loc[
214-
df_ndrt_raven[" Altitude (Ft-AGL)"].idxmax(), " Time (s)"
215-
]
216-
apogee_time_simulated = rocketpy_flight.apogee_time
79+
# Apogee
21780

218-
assert (
219-
abs(max(df_ndrt_raven[" Altitude (m-AGL)"]) - max(df_ndrt_rocketpy["Altitude"]))
220-
/ max(df_ndrt_raven[" Altitude (m-AGL)"])
221-
< 0.015
222-
)
223-
assert (max(velocity_raven_filt) - rocketpy_flight.max_speed) / max(
224-
velocity_raven_filt
225-
) < 0.06
226-
assert (
227-
abs(apogee_time_measured - apogee_time_simulated) / apogee_time_simulated < 0.02
81+
apogee_measured = max(df[" Altitude (m-AGL)"])
82+
apogee_rocketpy = rocketpy_flight.apogee - rocketpy_flight.env.elevation
83+
apogee_error = abs(apogee_measured - apogee_rocketpy) / apogee_measured
84+
assert apogee_error < 0.02 # historical threshold for this flight
85+
86+
# Max Speed
87+
88+
max_speed_measured = max(velocity_raven_filt)
89+
max_speed_rocketpy = rocketpy_flight.max_speed
90+
max_speed_error = abs(max_speed_measured - max_speed_rocketpy) / max_speed_measured
91+
assert (max_speed_error) < 0.06
92+
93+
# Apogee Time
94+
95+
apogee_time_measured = df.loc[df[" Altitude (Ft-AGL)"].idxmax(), " Time (s)"]
96+
apogee_time_rocketpy = rocketpy_flight.apogee_time
97+
apogee_time_error = (
98+
abs(apogee_time_measured - apogee_time_rocketpy) / apogee_time_rocketpy
22899
)
100+
assert apogee_time_error < 0.025

tests/fixtures/flight/flight_fixtures.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
@pytest.fixture
7-
def flight_calisto(calisto, example_plain_env): # old name: flight
7+
def flight_calisto(calisto_robust, example_plain_env): # old name: flight
88
"""A rocketpy.Flight object of the Calisto rocket. This uses the calisto
99
without the aerodynamic surfaces and parachutes. The environment is the
1010
simplest possible, with no parameters set.
@@ -24,7 +24,7 @@ def flight_calisto(calisto, example_plain_env): # old name: flight
2424
"""
2525
return Flight(
2626
environment=example_plain_env,
27-
rocket=calisto,
27+
rocket=calisto_robust,
2828
rail_length=5.2,
2929
inclination=85,
3030
heading=0,

tests/fixtures/motor/solid_motor_fixtures.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@
66
## Motors and rockets
77

88

9+
@pytest.fixture
10+
def cesaroni_l1395():
11+
return SolidMotor(
12+
thrust_source="data/motors/cesaroni/Cesaroni_4895L1395-P.eng",
13+
burn_time=(0, 3.433),
14+
dry_mass=1.848,
15+
dry_inertia=(0, 0, 0),
16+
center_of_dry_mass_position=-0.35,
17+
grains_center_of_mass_position=-0.35,
18+
grain_number=5,
19+
grain_separation=0.003,
20+
grain_density=1519.708,
21+
grain_outer_radius=0.033,
22+
grain_initial_inner_radius=15 / 1000,
23+
grain_initial_height=120 / 1000,
24+
nozzle_radius=0.02475,
25+
throat_radius=0.01075,
26+
interpolation_method="linear",
27+
nozzle_position=0,
28+
coordinate_system_orientation="combustion_chamber_to_nozzle",
29+
)
30+
31+
932
@pytest.fixture
1033
def cesaroni_m1670(): # old name: solid_motor
1134
"""Create a simple object of the SolidMotor class to be used in the tests.

0 commit comments

Comments
 (0)