Skip to content

Commit e3fcad1

Browse files
committed
ENH: Added alpha-sensitive flight fixtures to flight_fixtures.py
ENH: Used shared flight fixtures and simplify multi-drag integration tests in test_multidim_drag.py ENH: Exposes multidimensionality and validated grid extrapolation in function.py
1 parent ecb90ed commit e3fcad1

3 files changed

Lines changed: 119 additions & 79 deletions

File tree

rocketpy/mathutils/function.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ def is_multidimensional(self):
376376
"""
377377
try:
378378
return int(self.__dom_dim__) > 1
379-
except Exception:
379+
except (AttributeError, TypeError):
380380
return False
381381

382382
def __set_interpolation_func(self): # pylint: disable=too-many-statements

tests/fixtures/flight/flight_fixtures.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import numpy as np
12
import pytest
23

3-
from rocketpy import Flight
4+
from rocketpy import Flight, Function, Rocket
45

56

67
@pytest.fixture
@@ -273,3 +274,90 @@ def flight_calisto_with_sensors(calisto_with_sensors, example_plain_env):
273274
time_overshoot=False,
274275
terminate_on_apogee=True,
275276
)
277+
278+
279+
@pytest.fixture
280+
def flight_alpha(example_plain_env, cesaroni_m1670):
281+
"""Fixture that returns a Flight using an alpha-dependent 3D Cd function."""
282+
# Create grid data
283+
mach = np.array([0.0, 0.5, 1.0, 1.5])
284+
reynolds = np.array([1e5, 1e6])
285+
alpha = np.array([0.0, 5.0, 10.0, 15.0])
286+
M, _, A = np.meshgrid(mach, reynolds, alpha, indexing="ij")
287+
cd_data = 0.3 + 0.05 * M + 0.03 * A
288+
cd_data = np.clip(cd_data, 0.2, 2.0)
289+
290+
drag_func = Function.from_grid(
291+
cd_data,
292+
[mach, reynolds, alpha],
293+
inputs=["Mach", "Reynolds", "Alpha"],
294+
outputs="Cd",
295+
)
296+
297+
env = example_plain_env
298+
env.set_atmospheric_model(type="standard_atmosphere")
299+
300+
# Build rocket and flight
301+
rocket = Rocket(
302+
radius=0.0635,
303+
mass=16.24,
304+
inertia=(6.321, 6.321, 0.034),
305+
power_off_drag=drag_func,
306+
power_on_drag=drag_func,
307+
center_of_mass_without_motor=0,
308+
coordinate_system_orientation="tail_to_nose",
309+
)
310+
rocket.set_rail_buttons(0.2, -0.5, 30)
311+
rocket.add_motor(cesaroni_m1670, position=-1.255)
312+
313+
return Flight(
314+
rocket=rocket,
315+
environment=env,
316+
rail_length=5.2,
317+
inclination=85,
318+
heading=0,
319+
)
320+
321+
322+
@pytest.fixture
323+
def flight_flat(example_plain_env, cesaroni_m1670):
324+
"""Fixture that returns a Flight using an alpha-averaged (flat) Cd function."""
325+
# Create grid data
326+
mach = np.array([0.0, 0.5, 1.0, 1.5])
327+
reynolds = np.array([1e5, 1e6])
328+
alpha = np.array([0.0, 5.0, 10.0, 15.0])
329+
M, _, A = np.meshgrid(mach, reynolds, alpha, indexing="ij")
330+
cd_data = 0.3 + 0.05 * M + 0.03 * A
331+
cd_data = np.clip(cd_data, 0.2, 2.0)
332+
333+
cd_flat = cd_data.mean(axis=2)
334+
drag_flat = Function.from_grid(
335+
cd_flat,
336+
[mach, reynolds],
337+
inputs=["Mach", "Reynolds"],
338+
outputs="Cd",
339+
)
340+
341+
env = example_plain_env
342+
env.set_atmospheric_model(type="standard_atmosphere")
343+
344+
# Build rocket and flight
345+
rocket = Rocket(
346+
radius=0.0635,
347+
mass=16.24,
348+
inertia=(6.321, 6.321, 0.034),
349+
power_off_drag=drag_flat,
350+
power_on_drag=drag_flat,
351+
center_of_mass_without_motor=0,
352+
coordinate_system_orientation="tail_to_nose",
353+
)
354+
rocket.set_rail_buttons(0.2, -0.5, 30)
355+
rocket.add_motor(cesaroni_m1670, position=-1.255)
356+
357+
return Flight(
358+
rocket=rocket,
359+
environment=env,
360+
rail_length=5.2,
361+
inclination=85,
362+
heading=0,
363+
)

tests/integration/test_multidim_drag.py

Lines changed: 29 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,13 @@ def test_flight_with_3d_drag_basic(example_plain_env, cesaroni_m1670):
7474
assert hasattr(flight, "angle_of_attack")
7575

7676

77-
def test_3d_drag_with_varying_alpha(example_plain_env, cesaroni_m1670):
78-
"""Test that 3D drag responds to angle of attack changes."""
77+
def test_3d_drag_with_varying_alpha():
78+
"""Test that 3D drag responds to angle of attack changes.
79+
80+
This test only verifies the Function mapping from alpha -> Cd. The
81+
integration-level comparison is placed in a separate test to keep each
82+
test function small and easier to lint/maintain.
83+
"""
7984
# Create drag function with strong alpha dependency
8085
mach = np.array([0.0, 0.5, 1.0, 1.5])
8186
reynolds = np.array([1e5, 1e6])
@@ -102,103 +107,50 @@ def test_3d_drag_with_varying_alpha(example_plain_env, cesaroni_m1670):
102107
assert cd_10 > cd_0
103108
assert cd_10 - cd_0 > 0.2 # Should show significant difference
104109

105-
# --- Integration test: verify flight uses alpha-dependent Cd ---
106-
# Create a flat (alpha-agnostic) drag by averaging over alpha
107-
cd_flat = cd_data.mean(axis=2)
108-
drag_flat = Function.from_grid(
109-
cd_flat,
110-
[mach, reynolds],
111-
inputs=["Mach", "Reynolds"],
112-
outputs="Cd",
113-
)
114-
115-
# Use fixtures for environment and motor
116-
env = example_plain_env
117-
env.set_atmospheric_model(type="standard_atmosphere")
118-
motor = cesaroni_m1670
119-
120-
# Build two rockets: one that uses alpha-sensitive drag, one flat
121-
rocket_alpha = Rocket(
122-
radius=0.0635,
123-
mass=16.24,
124-
inertia=(6.321, 6.321, 0.034),
125-
power_off_drag=drag_func,
126-
power_on_drag=drag_func,
127-
center_of_mass_without_motor=0,
128-
coordinate_system_orientation="tail_to_nose",
129-
)
130-
rocket_alpha.set_rail_buttons(0.2, -0.5, 30)
131-
rocket_alpha.add_motor(motor, position=-1.255)
132-
133-
rocket_flat = Rocket(
134-
radius=0.0635,
135-
mass=16.24,
136-
inertia=(6.321, 6.321, 0.034),
137-
power_off_drag=drag_flat,
138-
power_on_drag=drag_flat,
139-
center_of_mass_without_motor=0,
140-
coordinate_system_orientation="tail_to_nose",
141-
)
142-
rocket_flat.set_rail_buttons(0.2, -0.5, 30)
143-
rocket_flat.add_motor(motor, position=-1.255)
144-
145-
# Run flights
146-
flight_alpha = Flight(
147-
rocket=rocket_alpha,
148-
environment=env,
149-
rail_length=5.2,
150-
inclination=85,
151-
heading=0,
152-
)
153110

154-
flight_flat = Flight(
155-
rocket=rocket_flat,
156-
environment=env,
157-
rail_length=5.2,
158-
inclination=85,
159-
heading=0,
160-
)
111+
def test_flight_apogee_diff(flight_alpha, flight_flat):
112+
"""Run paired flights (fixtures) and assert their apogees differ."""
161113

162114
# Flights should both launch
163115
assert flight_alpha.apogee > 100
164116
assert flight_flat.apogee > 100
165117

166-
# The two rockets should behave differently since one depends on alpha
167-
# while the other uses a flat-averaged Cd. Do not assume which direction
168-
# is larger (depends on encountered alpha vs averaged alpha) but ensure
169-
# the apogees differ.
118+
# Apogees should differ
170119
assert flight_alpha.apogee != flight_flat.apogee
171120

172-
# Additionally, sample Cd during flight from aerodynamic state and
173-
# compare values computed from each rocket's drag function at the
174-
# same time index to ensure alpha actually affects the evaluated Cd.
175-
# Use mid-ascent index (avoid t=0). Find a time index where speed > 5 m/s
121+
122+
def test_flight_cd_sample_consistency(flight_alpha, flight_flat):
123+
"""Sample Cd during a flight and ensure Cd difference matches apogee ordering.
124+
125+
Uses the `flight_alpha` and `flight_flat` fixtures which provide paired
126+
flights constructed with alpha-dependent and alpha-averaged Cd functions.
127+
"""
128+
129+
# Sample a mid-ascent time and compare Cd evaluations
176130
speeds = flight_alpha.free_stream_speed[:, 1]
177-
times = flight_alpha.time
178131
idx_candidates = np.where(speeds > 5)[0]
179132
assert idx_candidates.size > 0
180133
idx = idx_candidates[len(idx_candidates) // 2]
181-
t_sample = times[idx]
134+
t_sample = flight_alpha.time[idx]
182135

183136
mach_sample = flight_alpha.mach_number.get_value_opt(t_sample)
184-
rho_sample = flight_alpha.density.get_value_opt(t_sample)
185-
mu_sample = flight_alpha.dynamic_viscosity.get_value_opt(t_sample)
186-
V_sample = flight_alpha.free_stream_speed.get_value_opt(t_sample)
187-
reynolds_sample = rho_sample * V_sample * (2 * rocket_alpha.radius) / mu_sample
137+
v_sample = flight_alpha.free_stream_speed.get_value_opt(t_sample)
138+
reynolds_sample = (
139+
flight_alpha.density.get_value_opt(t_sample)
140+
* v_sample
141+
* (2 * flight_alpha.rocket.radius)
142+
/ flight_alpha.dynamic_viscosity.get_value_opt(t_sample)
143+
)
188144
alpha_sample = flight_alpha.angle_of_attack.get_value_opt(t_sample)
189145

190-
cd_alpha_sample = rocket_alpha.power_on_drag.get_value_opt(
146+
cd_alpha_sample = flight_alpha.rocket.power_on_drag.get_value_opt(
191147
mach_sample, reynolds_sample, alpha_sample
192148
)
193-
cd_flat_sample = rocket_flat.power_on_drag.get_value_opt(
149+
cd_flat_sample = flight_flat.rocket.power_on_drag.get_value_opt(
194150
mach_sample, reynolds_sample
195151
)
196152

197-
# Alpha-sensitive Cd should differ from the flat Cd at the sampled time
198153
assert cd_alpha_sample != cd_flat_sample
199-
200-
# Ensure the sign of the Cd difference is consistent with the apogee
201-
# ordering: larger Cd -> lower apogee
202154
if cd_alpha_sample > cd_flat_sample:
203155
assert flight_alpha.apogee < flight_flat.apogee
204156
else:

0 commit comments

Comments
 (0)