Skip to content

Commit a2c617c

Browse files
MateusStanoclaude
andcommitted
TST: rebuild calisto_linear_generic from extracted Barrowman curves
Replace the poorly defined calisto_linear_generic fixture, which kept the Barrowman nose cone and tail and swapped only the fins for a LinearGenericSurface with arbitrary made-up coefficients. The new fixture is a standalone Calisto whose nose cone, tail and fins are all LinearGenericSurfaces built from coefficient curves extracted off the standard Barrowman surfaces (normal-force-curve slope, center of pressure, fin roll damping). Each linear surface applies its force at its own origin and is placed at the source surface's center-of-pressure station, so both the static-margin path and the flight-moment path land at the same point as calisto_robust. The resulting flight matches the standard Calisto (identical apogee, out-of-rail time and ascent angle of attack), so the fixture now exercises the linear generic-surface path against a known-good reference. Also fix test_linear_generic_surface_flight_is_stable to check the angle of attack only during the ascent off the rail: on the rail the freestream speed is ~0 and the angle of attack is reported as a degenerate 90 degrees for any launcher, which previously failed the < 45 assertion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent f6dd040 commit a2c617c

2 files changed

Lines changed: 177 additions & 2 deletions

File tree

tests/fixtures/rockets/rocket_fixtures.py

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,59 @@
11
import numpy as np
22
import pytest
33

4-
from rocketpy import Rocket
4+
from rocketpy import LinearGenericSurface, Rocket
5+
6+
# TODO: review note: gotta test execution speed of changes in this branch
7+
8+
def _linear_surface_from_barrowman(surface):
9+
"""Build a LinearGenericSurface that reproduces a Barrowman surface's aero.
10+
11+
Reads the coefficient curves off a standard (Barrowman) aerodynamic surface
12+
-- its normal-force-curve slope ``clalpha`` as a function of Mach and, for fin
13+
sets, its roll cant and damping coefficients -- and packs them into the
14+
body-frame coefficient derivatives of an equivalent
15+
:class:`LinearGenericSurface`. The pitch- and yaw-plane slopes follow the
16+
Barrowman sign convention (``cN_alpha = clalpha`` and ``cY_beta = -clalpha``).
17+
18+
The returned surface applies its force at its own origin (center of pressure
19+
``(0, 0, 0)``); the caller is expected to add it at the surface's center of
20+
pressure station (``barrowman_station - clalpha_cp``) so that its force
21+
lands, and its static margin reads, at the same point as the Barrowman
22+
surface. Its ``cpz`` (the distance from the surface origin to its center of
23+
pressure) is exposed on the returned object as ``barrowman_cpz`` to make that
24+
offset easy for the caller.
25+
26+
Parameters
27+
----------
28+
surface : rocketpy.NoseCone, rocketpy.Tail or rocketpy fin set
29+
A standard Barrowman aerodynamic surface to copy the aero curves from.
30+
31+
Returns
32+
-------
33+
rocketpy.LinearGenericSurface
34+
A linear generic surface with the same normal force and (for fins) roll
35+
behaviour as ``surface``, carrying the source surface's ``cpz`` as
36+
``barrowman_cpz``.
37+
"""
38+
clalpha = surface.clalpha # normal-force-curve slope, a Function of Mach
39+
coefficients = {
40+
"cN_alpha": clalpha,
41+
"cY_beta": lambda mach: -clalpha.get_value_opt(mach),
42+
}
43+
# Fin sets carry roll coefficients: cant forcing (zero when uncanted) and
44+
# roll-rate damping. Other surfaces have no roll_parameters.
45+
if getattr(surface, "roll_parameters", None) is not None:
46+
coefficients["cl_0"] = surface.cl_0
47+
coefficients["cl_p"] = surface.cl_p
48+
linear_surface = LinearGenericSurface(
49+
reference_area=surface.reference_area,
50+
reference_length=surface.reference_length,
51+
coefficients=coefficients,
52+
center_of_pressure=(0, 0, 0),
53+
name=f"{surface.name}_linear",
54+
)
55+
linear_surface.barrowman_cpz = surface.cpz
56+
return linear_surface
557

658

759
@pytest.fixture
@@ -184,6 +236,89 @@ def calisto_robust(
184236
return calisto
185237

186238

239+
@pytest.fixture
240+
def calisto_linear_generic(
241+
cesaroni_m1670,
242+
calisto_nose_cone,
243+
calisto_tail,
244+
calisto_trapezoidal_fins,
245+
calisto_main_chute,
246+
calisto_drogue_chute,
247+
):
248+
"""Calisto built entirely from LinearGenericSurfaces instead of Barrowman ones.
249+
250+
This is the same rocket as ``calisto_robust`` -- same body, motor, rail
251+
buttons and parachutes at the same stations -- but its nose cone, tail and
252+
fin set are each replaced by a body-frame ``LinearGenericSurface``. The
253+
coefficient curves of those linear surfaces are extracted from the matching
254+
standard (Barrowman) surfaces: each linear surface reuses the standard
255+
surface's normal-force-curve slope, center of pressure and (for the fins)
256+
roll damping. Because the aero data is identical, this rocket's flight
257+
closely tracks the standard Calisto's while exercising the linear
258+
generic-surface aerodynamic path -- the one whose forces and moments are
259+
built directly in the body frame from the coefficient derivatives, with no
260+
wind-to-body rotation. It is a standalone rocket (it does not reuse the
261+
shared ``calisto`` fixture), so a test may build both it and ``calisto_robust``
262+
and compare their flights.
263+
264+
Parameters
265+
----------
266+
cesaroni_m1670 : rocketpy.SolidMotor
267+
The Calisto motor. This is a pytest fixture too.
268+
calisto_nose_cone : rocketpy.NoseCone
269+
The standard nose cone whose aero curves are copied. This is a pytest
270+
fixture too.
271+
calisto_tail : rocketpy.Tail
272+
The standard boat tail whose aero curves are copied. This is a pytest
273+
fixture too.
274+
calisto_trapezoidal_fins : rocketpy.TrapezoidalFins
275+
The standard fin set whose aero curves are copied. This is a pytest
276+
fixture too.
277+
calisto_main_chute : rocketpy.Parachute
278+
The main parachute of the Calisto rocket. This is a pytest fixture too.
279+
calisto_drogue_chute : rocketpy.Parachute
280+
The drogue parachute of the Calisto rocket. This is a pytest fixture too.
281+
282+
Returns
283+
-------
284+
rocketpy.Rocket
285+
The Calisto rocket whose nose cone, tail and fins are all
286+
LinearGenericSurfaces.
287+
"""
288+
calisto = Rocket(
289+
radius=0.0635,
290+
mass=14.426,
291+
inertia=(6.321, 6.321, 0.034),
292+
power_off_drag="data/rockets/calisto/powerOffDragCurve.csv",
293+
power_on_drag="data/rockets/calisto/powerOnDragCurve.csv",
294+
center_of_mass_without_motor=0,
295+
coordinate_system_orientation="tail_to_nose",
296+
)
297+
calisto.add_motor(cesaroni_m1670, position=-1.373)
298+
# Replace each Barrowman surface with an equivalent LinearGenericSurface. A
299+
# Barrowman surface is placed by its origin and carries its center of
300+
# pressure at ``cpz`` aft of that origin; a generic surface applies its force
301+
# at its own origin. So each linear surface is added at the Barrowman
302+
# surface's center-of-pressure station (station - cpz, with tail_to_nose
303+
# csys = +1), which lands its force -- and its static margin -- at the same
304+
# point as calisto_robust.
305+
for surface, station in (
306+
(calisto_nose_cone, 1.160),
307+
(calisto_tail, -1.313),
308+
(calisto_trapezoidal_fins, -1.168),
309+
):
310+
linear_surface = _linear_surface_from_barrowman(surface)
311+
calisto.add_surfaces(linear_surface, station - linear_surface.barrowman_cpz)
312+
calisto.set_rail_buttons(
313+
upper_button_position=0.082,
314+
lower_button_position=-0.618,
315+
angular_position=0,
316+
)
317+
calisto.parachutes.append(calisto_main_chute)
318+
calisto.parachutes.append(calisto_drogue_chute)
319+
return calisto
320+
321+
187322
@pytest.fixture
188323
def calisto_nose_to_tail_robust(
189324
calisto_nose_to_tail,

tests/unit/simulation/test_flight.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pytest
88
from scipy import optimize
99

10-
from rocketpy import Components, Flight, Function, Rocket
10+
from rocketpy import Components, Flight, Function, LinearGenericSurface, Rocket
1111

1212
plt.rcParams.update({"figure.max_open_warning": 0})
1313

@@ -648,6 +648,46 @@ def test_stability_static_margins(
648648
assert np.all(np.abs(moments) <= 1e-10)
649649

650650

651+
def test_linear_generic_surface_flight_is_stable(
652+
calisto_linear_generic, example_plain_env
653+
):
654+
"""A Calisto whose fin set is a body-frame LinearGenericSurface flies stably.
655+
656+
The linear surface builds its forces and moments directly in the body frame
657+
from the coefficient derivatives (no wind-to-body rotation). With a positive
658+
normal-force slope placed aft it must give a positive static margin and the
659+
rocket must reach a finite apogee while staying aligned with the flow (a
660+
small angle of attack, i.e. no tumbling).
661+
"""
662+
rocket = calisto_linear_generic
663+
assert any(
664+
isinstance(surface, LinearGenericSurface)
665+
for surface, _ in rocket.aerodynamic_surfaces
666+
)
667+
assert rocket.static_margin(0) > 0
668+
669+
test_flight = Flight(
670+
environment=example_plain_env,
671+
rocket=rocket,
672+
rail_length=5.2,
673+
inclination=85,
674+
heading=0,
675+
terminate_on_apogee=True,
676+
)
677+
678+
assert test_flight.apogee_time > test_flight.out_of_rail_time
679+
assert np.isfinite(test_flight.apogee)
680+
assert test_flight.apogee > example_plain_env.elevation
681+
# A stable rocket keeps a small angle of attack throughout the ascent. Only
682+
# the ascent off the rail is checked: while the rocket is still on the rail
683+
# its speed is ~0, so the angle of attack is reported as a degenerate 90
684+
# degrees (arccos of 0) for every launcher, stable or not.
685+
aoa_source = test_flight.angle_of_attack.get_source()
686+
ascent = aoa_source[:, 0] > test_flight.out_of_rail_time
687+
angle_of_attack = aoa_source[ascent, 1]
688+
assert np.nanmax(np.abs(angle_of_attack)) < 45
689+
690+
651691
def test_max_acceleration_power_off_time_with_controllers(
652692
flight_calisto_air_brakes,
653693
):

0 commit comments

Comments
 (0)