1- import numpy as np
21import pandas as pd
32import pytest
43from 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 (
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
0 commit comments