Skip to content

Commit 335834d

Browse files
authored
BUG: rocket with a late-starting thrust curve never leaves the rail (#411) (#1085)
A motor whose thrust curve starts at t > 0 never left the rail: the rail phase's only time nodes were [t=0, max_time], so LSODA (max_step defaults to inf) took one huge step over the entire burn and the rocket never accelerated. __setup_phase_time_nodes now forces solver stops at ignition and burn-out when burn_start_time > 0, leaving ordinary motors byte-for-byte unchanged. Also moves Codecov's coverage config out of .github/workflows/codecov.yml (where Actions was trying to execute it as a workflow, failing on every run) to .codecov.yml. Closes #411
1 parent f40f18e commit 335834d

4 files changed

Lines changed: 76 additions & 1 deletion

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ Attention: The newest changes should be on top -->
4242

4343
### Fixed
4444

45+
- BUG: rocket with a late-starting thrust curve never leaves the rail [#1085](https://github.com/RocketPy-Team/RocketPy/pull/1085)
46+
4547
## [v1.13.0] - 2026-07-21
4648

4749
### Added

‎rocketpy/simulation/flight.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,18 @@ def __setup_phase_time_nodes(self, phase):
848848
# Add last time node
849849
phase.time_nodes.add_node(phase.time_bound, [], [], [])
850850

851+
# A thrust curve that starts at t > 0 leaves the rocket stationary at
852+
# ignition-minus, so the solver (max_step defaults to inf) can take one
853+
# huge step clean over the burn and the rocket never lifts off (#411).
854+
# Force solver stops at ignition and burn-out so the burn is always
855+
# sampled. Guarded to burn_start > 0, so ordinary motors are untouched.
856+
motor = self.rocket.motor
857+
burn_start = getattr(motor, "burn_start_time", 0) or 0
858+
if burn_start > 0:
859+
for t_burn in (motor.burn_start_time, motor.burn_out_time):
860+
if phase.t < t_burn < phase.time_bound:
861+
phase.time_nodes.add_node(t_burn, [], [], [])
862+
851863
# Organize time nodes
852864
phase.time_nodes.sort()
853865
phase.time_nodes.merge()

‎tests/integration/simulation/test_flight.py‎

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,72 @@
55
import numpy.testing as npt
66
import pytest
77

8-
from rocketpy import Flight
8+
from rocketpy import Flight, Rocket, SolidMotor
99

1010
plt.rcParams.update({"figure.max_open_warning": 0})
1111

1212

13+
def test_flight_with_delayed_burn_leaves_rail(example_plain_env):
14+
"""Regression test for #411: a motor whose thrust curve starts at t > 0 must
15+
still lift the rocket off the rail. Previously the solver stepped over the
16+
burn (``max_step`` defaults to ``inf``) so the rocket never left the pad
17+
(``out_of_rail_time`` came out as 0).
18+
19+
Parameters
20+
----------
21+
example_plain_env : rocketpy.Environment
22+
A plain environment object, this is a pytest fixture.
23+
"""
24+
motor = SolidMotor(
25+
thrust_source=[(8.0, 1500.0), (9.0, 2000.0), (14.0, 2000.0), (20.0, 0.0)],
26+
burn_time=(8, 20),
27+
dry_mass=1.815,
28+
dry_inertia=(0.125, 0.125, 0.002),
29+
nozzle_radius=33 / 1000,
30+
grain_number=5,
31+
grain_density=1815,
32+
grain_outer_radius=33 / 1000,
33+
grain_initial_inner_radius=15 / 1000,
34+
grain_initial_height=120 / 1000,
35+
grain_separation=5 / 1000,
36+
grains_center_of_mass_position=0.397,
37+
center_of_dry_mass_position=0.317,
38+
nozzle_position=0,
39+
throat_radius=11 / 1000,
40+
coordinate_system_orientation="nozzle_to_combustion_chamber",
41+
)
42+
rocket = Rocket(
43+
radius=127 / 2000,
44+
mass=14.426,
45+
inertia=(6.321, 6.321, 0.034),
46+
power_off_drag=0.5,
47+
power_on_drag=0.5,
48+
center_of_mass_without_motor=0,
49+
coordinate_system_orientation="tail_to_nose",
50+
)
51+
rocket.add_motor(motor, position=-1.255)
52+
rocket.add_nose(length=0.55829, kind="vonKarman", position=1.278)
53+
rocket.add_trapezoidal_fins(
54+
n=4, root_chord=0.120, tip_chord=0.060, span=0.110, position=-1.04956
55+
)
56+
rocket.set_rail_buttons(0.082, -0.618)
57+
58+
flight = Flight(
59+
rocket=rocket,
60+
environment=example_plain_env,
61+
rail_length=5.2,
62+
inclination=85,
63+
heading=0,
64+
max_time=60,
65+
)
66+
67+
# Ignition is at t = 8 s; the rocket must leave the rail shortly after,
68+
# not remain at the pad the whole flight.
69+
assert flight.out_of_rail_time > 8.0
70+
assert flight.out_of_rail_velocity > 10.0
71+
assert flight.apogee - example_plain_env.elevation > 1000.0
72+
73+
1374
@pytest.mark.parametrize(
1475
"flight_fixture", ["flight_calisto_robust", "flight_calisto_robust_solid_eom"]
1576
)

0 commit comments

Comments
 (0)