Skip to content

Commit 91d3340

Browse files
committed
TST: fix tests for new drag implementation
1 parent 22204d9 commit 91d3340

File tree

4 files changed

+282
-4
lines changed

4 files changed

+282
-4
lines changed

tests/acceptance/test_bella_lui_rocket.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def drogue_trigger(p, h, y):
141141
)
142142

143143
# Define aerodynamic drag coefficients
144-
BellaLui.power_off_drag = Function(
144+
power_off_drag_by_mach = Function(
145145
[
146146
(0.01, 0.51),
147147
(0.02, 0.46),
@@ -156,7 +156,7 @@ def drogue_trigger(p, h, y):
156156
"linear",
157157
"constant",
158158
)
159-
BellaLui.power_on_drag = Function(
159+
power_on_drag_by_mach = Function(
160160
[
161161
(0.01, 0.51),
162162
(0.02, 0.46),
@@ -171,8 +171,50 @@ def drogue_trigger(p, h, y):
171171
"linear",
172172
"constant",
173173
)
174-
BellaLui.power_off_drag *= parameters.get("power_off_drag")[0]
175-
BellaLui.power_on_drag *= parameters.get("power_on_drag")[0]
174+
BellaLui.power_off_drag_7d = Function(
175+
lambda alpha,
176+
beta,
177+
mach,
178+
reynolds,
179+
pitch_rate,
180+
yaw_rate,
181+
roll_rate: power_off_drag_by_mach.get_value_opt(mach),
182+
[
183+
"alpha",
184+
"beta",
185+
"mach",
186+
"reynolds",
187+
"pitch_rate",
188+
"yaw_rate",
189+
"roll_rate",
190+
],
191+
["Drag Coefficient with Power Off"],
192+
interpolation="linear",
193+
extrapolation="constant",
194+
)
195+
BellaLui.power_on_drag_7d = Function(
196+
lambda alpha,
197+
beta,
198+
mach,
199+
reynolds,
200+
pitch_rate,
201+
yaw_rate,
202+
roll_rate: power_on_drag_by_mach.get_value_opt(mach),
203+
[
204+
"alpha",
205+
"beta",
206+
"mach",
207+
"reynolds",
208+
"pitch_rate",
209+
"yaw_rate",
210+
"roll_rate",
211+
],
212+
["Drag Coefficient with Power On"],
213+
interpolation="linear",
214+
extrapolation="constant",
215+
)
216+
BellaLui.power_off_drag_7d *= parameters.get("power_off_drag")[0]
217+
BellaLui.power_on_drag_7d *= parameters.get("power_on_drag")[0]
176218

177219
# Flight
178220
test_flight = Flight(

tests/integration/simulation/test_flight_3dof.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,72 @@ def test_weathercock_coeff_default(flight_3dof):
200200
assert flight_3dof.weathercock_coeff == 0.0
201201

202202

203+
def test_point_mass_rocket_3dof_uses_7d_drag_inputs(example_plain_env, point_mass_motor):
204+
"""Ensure PointMassRocket uses the 7D drag interface in 3-DOF dynamics."""
205+
206+
def drag_7d(alpha, beta, mach, reynolds, pitch_rate, yaw_rate, roll_rate):
207+
return (
208+
0.2
209+
+ 0.01 * abs(alpha)
210+
+ 0.01 * abs(beta)
211+
+ 1e-7 * reynolds
212+
+ 0.001 * (abs(pitch_rate) + abs(yaw_rate) + abs(roll_rate))
213+
+ 0.01 * mach
214+
)
215+
216+
rocket = PointMassRocket(
217+
radius=0.05,
218+
mass=2.0,
219+
center_of_mass_without_motor=0.1,
220+
power_off_drag=drag_7d,
221+
power_on_drag=drag_7d,
222+
)
223+
rocket.add_motor(point_mass_motor, position=0)
224+
225+
flight = Flight(
226+
rocket=rocket,
227+
environment=example_plain_env,
228+
rail_length=1,
229+
simulation_mode="3 DOF",
230+
)
231+
232+
t = 10.0
233+
u = [0, 0, 100, 50, 5, 0, 1, 0, 0, 0, 0.3, -0.2, 0.1]
234+
u_dot = flight.u_dot_generalized_3dof(t, u)
235+
236+
z = u[2]
237+
vx, vy, vz = u[3], u[4], u[5]
238+
omega1, omega2, omega3 = u[10], u[11], u[12]
239+
240+
rho = flight.env.density.get_value_opt(z)
241+
dynamic_viscosity = flight.env.dynamic_viscosity.get_value_opt(z)
242+
wind_vx = flight.env.wind_velocity_x.get_value_opt(z)
243+
wind_vy = flight.env.wind_velocity_y.get_value_opt(z)
244+
speed_of_sound = flight.env.speed_of_sound.get_value_opt(z)
245+
gravity = flight.env.gravity.get_value_opt(z)
246+
247+
free_stream_velocity = np.array([wind_vx - vx, wind_vy - vy, -vz])
248+
free_stream_speed = np.linalg.norm(free_stream_velocity)
249+
mach = free_stream_speed / speed_of_sound
250+
251+
stream_velocity_body = free_stream_velocity
252+
aerodynamic_stream_velocity = -stream_velocity_body
253+
alpha = np.arctan2(aerodynamic_stream_velocity[1], aerodynamic_stream_velocity[2])
254+
beta = np.arctan2(aerodynamic_stream_velocity[0], aerodynamic_stream_velocity[2])
255+
256+
reynolds = (
257+
rho * free_stream_speed * (2 * rocket.radius) / dynamic_viscosity
258+
if dynamic_viscosity > 0
259+
else 0
260+
)
261+
262+
cd_expected = drag_7d(alpha, beta, mach, reynolds, omega1, omega2, omega3)
263+
r3_expected = -0.5 * rho * free_stream_speed**2 * rocket.area * cd_expected
264+
az_expected = (r3_expected - rocket.total_mass.get_value_opt(t) * gravity) / rocket.total_mass.get_value_opt(t)
265+
266+
assert u_dot[5] == pytest.approx(az_expected)
267+
268+
203269
def test_weathercock_zero_gives_fixed_attitude(flight_weathercock_zero):
204270
"""Tests that weathercock_coeff=0 results in fixed attitude (no quaternion change).
205271
When weathercock_coeff is 0, the quaternion derivatives should be zero,

tests/unit/rocket/aero_surface/test_generic_surfaces.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,32 @@ def test_valid_initialization_from_csv(filename_valid_coeff):
7474
)
7575

7676

77+
def test_csv_independent_variables_accept_any_order(tmp_path):
78+
"""Checks if GenericSurface correctly maps CSV columns by header names,
79+
regardless of independent variable column order."""
80+
filename = tmp_path / "valid_coefficients_shuffled_order.csv"
81+
filename.write_text(
82+
"mach,alpha,cL\n0,0,0\n0,1,10\n2,0,2\n2,1,12\n",
83+
encoding="utf-8",
84+
)
85+
86+
generic_surface = GenericSurface(
87+
reference_area=REFERENCE_AREA,
88+
reference_length=REFERENCE_LENGTH,
89+
coefficients={"cL": str(filename)},
90+
)
91+
92+
closure = generic_surface.cL.source.__closure__
93+
csv_function = next(
94+
cell.cell_contents
95+
for cell in closure
96+
if isinstance(cell.cell_contents, Function)
97+
)
98+
99+
assert generic_surface.cL(1, 0, 2, 0, 0, 0, 0) == pytest.approx(12)
100+
assert csv_function.get_interpolation_method() == "regular_grid"
101+
102+
77103
def test_compute_forces_and_moments():
78104
"""Checks if there are not logical errors in
79105
compute forces and moments"""

tests/unit/rocket/test_rocket.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import warnings
2+
from itertools import product
23
from unittest.mock import patch
34

45
import numpy as np
@@ -689,3 +690,146 @@ def test_coordinate_system_orientation(
689690
static_margin_nose_to_tail = rocket_nose_to_tail.static_margin
690691

691692
assert np.array_equal(static_margin_tail_to_nose, static_margin_nose_to_tail)
693+
694+
695+
def test_drag_csv_header_order_independent_for_multivariable_input(tmp_path):
696+
"""Ensure drag CSV independent-variable columns are interpreted by name.
697+
698+
This test checks that swapping the order of header-defined variables
699+
(mach and reynolds) yields equivalent drag interpolation results.
700+
"""
701+
702+
ordered_csv = tmp_path / "drag_mach_reynolds.csv"
703+
ordered_csv.write_text(
704+
"mach,reynolds,cd\n0.5,0.1,0.6\n1.0,0.1,1.1\n0.5,0.2,0.7\n1.0,0.2,1.2\n",
705+
encoding="utf-8",
706+
)
707+
708+
swapped_csv = tmp_path / "drag_reynolds_mach.csv"
709+
swapped_csv.write_text(
710+
"reynolds,mach,cd\n0.1,0.5,0.6\n0.1,1.0,1.1\n0.2,0.5,0.7\n0.2,1.0,1.2\n",
711+
encoding="utf-8",
712+
)
713+
714+
rocket_ordered = Rocket(
715+
radius=0.05,
716+
mass=1.0,
717+
inertia=(1.0, 1.0, 1.0),
718+
power_off_drag=str(ordered_csv),
719+
power_on_drag=str(ordered_csv),
720+
center_of_mass_without_motor=0.0,
721+
)
722+
723+
rocket_swapped = Rocket(
724+
radius=0.05,
725+
mass=1.0,
726+
inertia=(1.0, 1.0, 1.0),
727+
power_off_drag=str(swapped_csv),
728+
power_on_drag=str(swapped_csv),
729+
center_of_mass_without_motor=0.0,
730+
)
731+
732+
drag_ordered = rocket_ordered.power_off_drag_7d(0, 0, 0.8, 0.15, 0, 0, 0)
733+
drag_swapped = rocket_swapped.power_off_drag_7d(0, 0, 0.8, 0.15, 0, 0, 0)
734+
735+
ordered_closure = rocket_ordered.power_off_drag_7d.source.__closure__
736+
swapped_closure = rocket_swapped.power_off_drag_7d.source.__closure__
737+
ordered_csv_function = next(
738+
cell.cell_contents
739+
for cell in ordered_closure
740+
if isinstance(cell.cell_contents, Function)
741+
)
742+
swapped_csv_function = next(
743+
cell.cell_contents
744+
for cell in swapped_closure
745+
if isinstance(cell.cell_contents, Function)
746+
)
747+
748+
assert drag_ordered == pytest.approx(0.95)
749+
assert drag_swapped == pytest.approx(0.95)
750+
assert drag_swapped == pytest.approx(drag_ordered)
751+
assert ordered_csv_function.get_interpolation_method() == "regular_grid"
752+
assert swapped_csv_function.get_interpolation_method() == "regular_grid"
753+
754+
755+
def test_drag_input_types_supported_for_power_on_and_power_off(tmp_path):
756+
"""Ensure drag input processing accepts all supported input types.
757+
758+
This test validates that both ``power_off_drag`` and ``power_on_drag``
759+
accept and correctly evaluate all supported input categories.
760+
"""
761+
query = (1.0, 0.0, 0.8, 0.15, 0.0, 0.0, 0.0)
762+
763+
csv_drag = tmp_path / "drag_mach_reynolds.csv"
764+
csv_drag.write_text(
765+
"mach,reynolds,cd\n0.5,0.1,0.6\n1.0,0.1,1.1\n0.5,0.2,0.7\n1.0,0.2,1.2\n",
766+
encoding="utf-8",
767+
)
768+
769+
txt_drag = tmp_path / "drag_curve.txt"
770+
txt_drag.write_text("0.0,0.2\n1.0,0.4\n", encoding="utf-8")
771+
772+
function_1d = Function(
773+
lambda mach: 0.2 + mach,
774+
inputs=["mach"],
775+
outputs=["cd"],
776+
interpolation="linear",
777+
)
778+
function_7d = Function(
779+
lambda alpha, beta, mach, reynolds, pitch_rate, yaw_rate, roll_rate: mach
780+
+ reynolds,
781+
inputs=[
782+
"alpha",
783+
"beta",
784+
"mach",
785+
"reynolds",
786+
"pitch_rate",
787+
"yaw_rate",
788+
"roll_rate",
789+
],
790+
outputs=["cd"],
791+
interpolation="linear",
792+
)
793+
794+
drag_7d_table = [
795+
(*coords, float(sum(coords))) for coords in product((0.0, 1.0), repeat=7)
796+
]
797+
drag_7d_query = (1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0)
798+
799+
test_cases = [
800+
("int", 1, query, 1.0),
801+
("float", 0.37, query, 0.37),
802+
("csv_path", str(csv_drag), query, 0.95),
803+
("txt_path", str(txt_drag), query, 0.36),
804+
("function_1d", function_1d, query, 1.0),
805+
("function_7d", function_7d, query, 0.95),
806+
(
807+
"callable_1d",
808+
lambda mach: 0.2 + mach,
809+
query,
810+
1.0,
811+
),
812+
(
813+
"callable_7d",
814+
lambda alpha, beta, mach, reynolds, pitch_rate, yaw_rate, roll_rate: mach
815+
+ reynolds,
816+
query,
817+
0.95,
818+
),
819+
("list_pairs", [[0.0, 0.2], [1.0, 0.4]], query, 0.36),
820+
("tuple_pairs", ((0.0, 0.2), (1.0, 0.4)), query, 0.36),
821+
("list_7d_entries", drag_7d_table, drag_7d_query, 4.0),
822+
]
823+
824+
for _, drag_input, query_point, expected in test_cases:
825+
rocket = Rocket(
826+
radius=0.05,
827+
mass=1.0,
828+
inertia=(1.0, 1.0, 1.0),
829+
power_off_drag=drag_input,
830+
power_on_drag=drag_input,
831+
center_of_mass_without_motor=0.0,
832+
)
833+
834+
assert rocket.power_off_drag_7d(*query_point) == pytest.approx(expected)
835+
assert rocket.power_on_drag_7d(*query_point) == pytest.approx(expected)

0 commit comments

Comments
 (0)