Skip to content

Commit 35dc460

Browse files
committed
TST: add relevant tests
1 parent 0ed8189 commit 35dc460

2 files changed

Lines changed: 355 additions & 0 deletions

File tree

tests/fixtures/surfaces/surface_fixtures.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import pytest
22

33
from rocketpy.rocket.aero_surface import (
4+
EllipticalFin,
45
EllipticalFins,
6+
FreeFormFin,
57
FreeFormFins,
68
NoseCone,
79
RailButtons,
810
Tail,
11+
TrapezoidalFin,
912
TrapezoidalFins,
1013
)
1114

@@ -69,6 +72,29 @@ def calisto_trapezoidal_fins():
6972
)
7073

7174

75+
@pytest.fixture
76+
def calisto_trapezoidal_fin():
77+
"""A single trapezoidal fin based on Calisto dimensions.
78+
79+
Returns
80+
-------
81+
rocketpy.TrapezoidalFin
82+
A single trapezoidal fin.
83+
"""
84+
return TrapezoidalFin(
85+
angular_position=0,
86+
span=0.100,
87+
root_chord=0.120,
88+
tip_chord=0.040,
89+
rocket_radius=0.0635,
90+
name="calisto_trapezoidal_fin",
91+
cant_angle=0,
92+
sweep_length=None,
93+
sweep_angle=None,
94+
airfoil=None,
95+
)
96+
97+
7298
@pytest.fixture
7399
def calisto_trapezoidal_fins_custom_sweep_length():
74100
"""The trapezoidal fins of the Calisto rocket with
@@ -130,6 +156,23 @@ def calisto_free_form_fins():
130156
)
131157

132158

159+
@pytest.fixture
160+
def calisto_free_form_fin():
161+
"""A single free-form fin based on Calisto-like dimensions.
162+
163+
Returns
164+
-------
165+
rocketpy.FreeFormFin
166+
A single free-form fin.
167+
"""
168+
return FreeFormFin(
169+
angular_position=0,
170+
shape_points=[(0, 0), (0.08, 0.1), (0.12, 0.1), (0.12, 0)],
171+
rocket_radius=0.0635,
172+
name="calisto_free_form_fin",
173+
)
174+
175+
133176
@pytest.fixture
134177
def calisto_rail_buttons():
135178
"""The rail buttons of the Calisto rocket.
@@ -157,3 +200,23 @@ def elliptical_fin_set():
157200
airfoil=None,
158201
name="Test Elliptical Fins",
159202
)
203+
204+
205+
@pytest.fixture
206+
def calisto_elliptical_fin():
207+
"""A single elliptical fin based on Calisto-like dimensions.
208+
209+
Returns
210+
-------
211+
rocketpy.EllipticalFin
212+
A single elliptical fin.
213+
"""
214+
return EllipticalFin(
215+
angular_position=0,
216+
span=0.100,
217+
root_chord=0.120,
218+
rocket_radius=0.0635,
219+
cant_angle=0,
220+
airfoil=None,
221+
name="calisto_elliptical_fin",
222+
)
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
"""Unit tests for individual fin classes."""
2+
3+
from unittest.mock import patch
4+
5+
import numpy as np
6+
import pytest
7+
8+
from rocketpy import (
9+
EllipticalFin,
10+
FreeFormFin,
11+
Rocket,
12+
TrapezoidalFin,
13+
TrapezoidalFins,
14+
)
15+
16+
17+
@pytest.mark.parametrize(
18+
"fixture_name,expected_class",
19+
[
20+
("calisto_trapezoidal_fin", TrapezoidalFin),
21+
("calisto_elliptical_fin", EllipticalFin),
22+
("calisto_free_form_fin", FreeFormFin),
23+
],
24+
)
25+
def test_individual_fin_info_returns_none(request, fixture_name, expected_class):
26+
"""Ensure info() executes for all individual fin classes."""
27+
# Arrange
28+
fin = request.getfixturevalue(fixture_name)
29+
30+
# Act
31+
result = fin.info()
32+
33+
# Assert
34+
assert isinstance(fin, expected_class)
35+
assert result is None
36+
37+
38+
@patch("matplotlib.pyplot.show")
39+
@pytest.mark.parametrize(
40+
"fixture_name",
41+
[
42+
"calisto_trapezoidal_fin",
43+
"calisto_elliptical_fin",
44+
"calisto_free_form_fin",
45+
],
46+
)
47+
def test_individual_fin_draw_returns_none(mock_show, request, fixture_name):
48+
"""Ensure draw() executes for all individual fin classes."""
49+
# Arrange
50+
fin = request.getfixturevalue(fixture_name)
51+
52+
# Act
53+
result = fin.draw(filename=None)
54+
55+
# Assert
56+
assert result is None
57+
58+
59+
@pytest.mark.parametrize(
60+
"fixture_name",
61+
[
62+
"calisto_trapezoidal_fin",
63+
"calisto_elliptical_fin",
64+
"calisto_free_form_fin",
65+
],
66+
)
67+
def test_individual_fin_angular_position_updates_radians(request, fixture_name):
68+
"""Ensure angular_position setter updates angular_position_rad."""
69+
# Arrange
70+
fin = request.getfixturevalue(fixture_name)
71+
72+
# Act
73+
fin.angular_position = 45
74+
75+
# Assert
76+
assert fin.angular_position == 45
77+
assert fin.angular_position_rad == pytest.approx(np.pi / 4)
78+
79+
80+
def test_trapezoidal_fin_setters_update_geometry(calisto_trapezoidal_fin):
81+
"""Ensure trapezoidal fin geometry setters update exposed values."""
82+
# Arrange
83+
fin = calisto_trapezoidal_fin
84+
85+
# Act
86+
fin.tip_chord = 0.05
87+
fin.sweep_angle = 12.0
88+
fin.sweep_length = 0.03
89+
90+
# Assert
91+
assert fin.tip_chord == pytest.approx(0.05)
92+
assert fin.sweep_angle == pytest.approx(12.0)
93+
assert fin.sweep_length == pytest.approx(0.03)
94+
95+
96+
def test_trapezoidal_fin_rejects_inconsistent_sweep_inputs():
97+
"""Ensure trapezoidal fin rejects sweep_length with sweep_angle together."""
98+
# Arrange / Act / Assert
99+
with pytest.raises(ValueError, match="Cannot use sweep_length and sweep_angle together"):
100+
TrapezoidalFin(
101+
angular_position=0,
102+
root_chord=0.12,
103+
tip_chord=0.04,
104+
span=0.1,
105+
rocket_radius=0.0635,
106+
sweep_length=0.02,
107+
sweep_angle=10.0,
108+
)
109+
110+
111+
def test_free_form_fin_shape_points_property(calisto_free_form_fin):
112+
"""Ensure free-form fin exposes the original shape points."""
113+
# Arrange
114+
fin = calisto_free_form_fin
115+
116+
# Act
117+
shape_points = fin.shape_points
118+
119+
# Assert
120+
assert shape_points == [(0, 0), (0.08, 0.1), (0.12, 0.1), (0.12, 0)]
121+
122+
123+
@pytest.mark.parametrize(
124+
"fixture_name,required_keys",
125+
[
126+
(
127+
"calisto_trapezoidal_fin",
128+
{
129+
"angular_position",
130+
"root_chord",
131+
"span",
132+
"rocket_radius",
133+
"cant_angle",
134+
"airfoil",
135+
"name",
136+
"tip_chord",
137+
"sweep_length",
138+
"sweep_angle",
139+
},
140+
),
141+
(
142+
"calisto_elliptical_fin",
143+
{
144+
"angular_position",
145+
"root_chord",
146+
"span",
147+
"rocket_radius",
148+
"cant_angle",
149+
"airfoil",
150+
"name",
151+
},
152+
),
153+
(
154+
"calisto_free_form_fin",
155+
{
156+
"angular_position",
157+
"rocket_radius",
158+
"cant_angle",
159+
"airfoil",
160+
"name",
161+
"shape_points",
162+
},
163+
),
164+
],
165+
)
166+
def test_individual_fin_to_dict_contains_expected_keys(
167+
request, fixture_name, required_keys
168+
):
169+
"""Ensure to_dict for each individual fin exposes expected input keys."""
170+
# Arrange
171+
fin = request.getfixturevalue(fixture_name)
172+
173+
# Act
174+
data = fin.to_dict()
175+
176+
# Assert
177+
assert required_keys.issubset(data.keys())
178+
179+
180+
@pytest.mark.parametrize(
181+
"fixture_name,fin_class,comparisons",
182+
[
183+
(
184+
"calisto_trapezoidal_fin",
185+
TrapezoidalFin,
186+
["angular_position", "root_chord", "tip_chord", "span", "rocket_radius"],
187+
),
188+
(
189+
"calisto_elliptical_fin",
190+
EllipticalFin,
191+
["angular_position", "root_chord", "span", "rocket_radius"],
192+
),
193+
(
194+
"calisto_free_form_fin",
195+
FreeFormFin,
196+
["angular_position", "rocket_radius"],
197+
),
198+
],
199+
)
200+
def test_individual_fin_from_dict_roundtrip(request, fixture_name, fin_class, comparisons):
201+
"""Ensure each individual fin can be reconstructed with from_dict."""
202+
# Arrange
203+
fin = request.getfixturevalue(fixture_name)
204+
data = fin.to_dict()
205+
206+
# Act
207+
reconstructed = fin_class.from_dict(data)
208+
209+
# Assert
210+
assert isinstance(reconstructed, fin_class)
211+
for field in comparisons:
212+
assert getattr(reconstructed, field) == pytest.approx(getattr(fin, field))
213+
214+
if fin_class is FreeFormFin:
215+
assert reconstructed.shape_points == fin.shape_points
216+
217+
218+
def test_calisto_finset_vs_four_individual_fins_close():
219+
"""Ensure a 4-fin set and 4 individual fins produce close aerodynamics.
220+
221+
Notes
222+
-----
223+
A fin set model includes finite-set lift correction for the number of fins.
224+
For 4 fins, this correction is equivalent to scaling the sum of 4
225+
individual-fin lift derivatives by 1/2.
226+
"""
227+
# Arrange
228+
finset_rocket = Rocket(
229+
radius=0.0635,
230+
mass=14.426,
231+
inertia=(6.321, 6.321, 0.034),
232+
power_off_drag="data/rockets/calisto/powerOffDragCurve.csv",
233+
power_on_drag="data/rockets/calisto/powerOnDragCurve.csv",
234+
center_of_mass_without_motor=0,
235+
coordinate_system_orientation="tail_to_nose",
236+
)
237+
finset_rocket.add_surfaces(
238+
TrapezoidalFins(
239+
n=4,
240+
span=0.100,
241+
root_chord=0.120,
242+
tip_chord=0.040,
243+
rocket_radius=0.0635,
244+
name="calisto_trapezoidal_fins",
245+
cant_angle=0,
246+
sweep_length=None,
247+
sweep_angle=None,
248+
airfoil=None,
249+
),
250+
-1.168,
251+
)
252+
253+
individual_fins_rocket = Rocket(
254+
radius=0.0635,
255+
mass=14.426,
256+
inertia=(6.321, 6.321, 0.034),
257+
power_off_drag="data/rockets/calisto/powerOffDragCurve.csv",
258+
power_on_drag="data/rockets/calisto/powerOnDragCurve.csv",
259+
center_of_mass_without_motor=0,
260+
coordinate_system_orientation="tail_to_nose",
261+
)
262+
263+
individual_fins = [
264+
TrapezoidalFin(
265+
angular_position=angle,
266+
root_chord=0.120,
267+
tip_chord=0.040,
268+
span=0.100,
269+
rocket_radius=0.0635,
270+
name=f"calisto_trapezoidal_fin_{i}",
271+
cant_angle=0,
272+
sweep_length=None,
273+
sweep_angle=None,
274+
airfoil=None,
275+
)
276+
for i, angle in enumerate((0, 90, 180, 270), start=1)
277+
]
278+
individual_fins_rocket.add_surfaces(individual_fins, [-1.168] * 4)
279+
280+
mach_grid = np.linspace(0, 2, 21)
281+
282+
# Act
283+
cp_finset = finset_rocket.cp_position(mach_grid)
284+
cp_individual = individual_fins_rocket.cp_position(mach_grid)
285+
clalpha_finset = finset_rocket.total_lift_coeff_der(mach_grid)
286+
clalpha_individual = individual_fins_rocket.total_lift_coeff_der(mach_grid)
287+
lift_correction = TrapezoidalFins.fin_num_correction(4) / 4
288+
clalpha_individual_corrected = np.array(clalpha_individual) * lift_correction
289+
290+
# Assert
291+
np.testing.assert_allclose(cp_individual, cp_finset, rtol=1e-6, atol=1e-6)
292+
np.testing.assert_allclose(clalpha_individual_corrected, clalpha_finset)

0 commit comments

Comments
 (0)