ENH: Implement 3-DOF Single Rail Button Flight Phase (Tip-off Analysis) - #920
Conversation
|
Hey @Rafit345 Thanks for this PR! You have done some solid work here. I am still reviewing your code changes. But I'll suggest to run make lint and make format with your commits! |
| print(f"Current Simulation Time: {self.t:3.4f} s", end="\r") | ||
|
|
||
| # check for the first time the rocket is between the two rail buttons for tip-off analysis | ||
| if len(self.between_rails_state) == 1 and ( |
There was a problem hiding this comment.
The condition len(self.betweenrailsstate) == 1 checks whether the state has been set once, not whether the rocket is currently between the buttons. Once betweenrailsstate is populated, subsequent integration steps will not re-enter this block, so the phase is only added at the exact instant the lower button clears, not maintained throughout the tip-off interval. This means the udot_rail2 phase may execute for only a single time step or not propagate correctly.
aZira371
left a comment
There was a problem hiding this comment.
I have the following comments for the preliminary implementation. In general looks to be going in a good direction as of now. Will wait for u_dotrail2 implementation to be fully complete in order to review the tests and involved physics.
|
@Rafit345 thank you for your submission! Please address all the comments and ask for a re-review whenever this PR is ready again. We look forwarding to receiving updates from you soon! |
There was a problem hiding this comment.
Pull request overview
This PR implements a preliminary 3-DOF single rail button flight phase (tip-off analysis) for RocketPy, introducing an intermediate udot_rail2 phase that operates between the initial 1-DOF rail phase and the 6-DOF free flight phase. The implementation adds a feature flag use_udot_rail2 to enable/disable this behavior, includes a Hermite-root fallback for numerical robustness, and provides comprehensive unit tests to verify correctness.
- Adds
udot_rail2method implementing 3-DOF equations of motion (linear motion along rail + pitch/yaw, enforcing zero roll) - Introduces
use_udot_rail2parameter (default True) to control the intermediate rail phase activation - Implements between-rails event detection and smooth phase transitions from 1-DOF → 3-DOF → 6-DOF
- Adds fallback handling for edge cases where rail-exit root filtering returns no valid roots
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 15 comments.
| File | Description |
|---|---|
| rocketpy/simulation/flight.py | Implements udot_rail2 3-DOF rail phase with equations of motion, adds use_udot_rail2 parameter, implements between-rails event detection, adds fallback for root finding failures, initializes attitude_unit and between_rails_state tracking |
| tests/unit/simulation/test_udot_rail2_feature.py | Adds unit tests verifying phase insertion order, zero roll enforcement, rail alignment constraints, and CSV output generation for enabled/disabled comparison |
90813ac to
a2c5b42
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #920 +/- ##
===========================================
+ Coverage 91.41% 91.49% +0.07%
===========================================
Files 132 132
Lines 18133 18241 +108
===========================================
+ Hits 16577 16689 +112
+ Misses 1556 1552 -4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…is)" Refs RocketPy-Team#28. This commit was made as a submission to the selective process deliverables challenge. The method udot_rail2 functions as an intermediate flight phase before the rocket has fully left the guide rail, allowing for 3 degrees of freedom (linear motion along the rail, pitch and yaw). Flight init includes a feature to run a simulation without udot_rail2. Numerical values enabling udot_rail2 are very close to 1 DOF flight. Flight phase transitions smoothly from 1 DOF rail phase to 3DOF and from 3 DOF to 6 DOF free flight. Current equations of motion inside udot_rail2 rely heavily on udot_generalized, ensuring 3 DOF through vector operations. Still working on the implementation of proper lagrangean expansion /derivation of equations of motion. Articles "Tip-off effect analysis of a vehicle moving along an inclined guideway by considering dynamic interactions" by Chou et al and "ANALYSIS OF MISSILE LAUNCHERS PART Q Tipoff Effects in Helical Rail Launchers" by Hosken et al are proving useful. --Summary-- Add preliminary udot_rail2 (3-DOF tip-off) support and safe, deterministic phase-insertion handling during rail → 6DOF transitions. Add a feature flag to enable/disable udot_rail2 on Flight init. Add a Hermite-root fallback to avoid hard failures when rail-exit root filtering returns no valid root (warn + midpoint fallback). Add comprehensive unit tests (alignment, no-roll, insertion-order, CSV comparisons) and sample CSV output for comparison runs with udot_rail2 enabled vs disabled.
Complete the 3-DOF single-rail-button (tip-off) phase from issue RocketPy-Team#28. - Fix the phase transition ordering: rail1 -> udot_rail2 (at effective_1rl, upper button exit) -> u_dot_generalized (at effective_2rl, lower button exit). Previously the thresholds were swapped, so udot_rail2 was inserted after free flight and never exited. - Replace the placeholder udot_rail2 (which reused free-flight dynamics with an ad-hoc velocity projection) with rigorous constrained dynamics: the lower button slides along the fixed rail while roll is suppressed. The reaction wrench (normal force + roll moment) is solved from a 3x3 linear system so the button's perpendicular acceleration and the roll acceleration vanish, derived in the true body frame on top of the validated u_dot_generalized solution. - Make the feature opt-in (use_udot_rail2 defaults to False); disabled runs are bit-for-bit identical to previous behavior. - Factor the rail-exit root finding into a shared helper. - Rewrite the unit tests to check phase ordering, the opt-in default, the on-rail constraint (button stays on the rail to machine precision), zero roll, and the gravity tip-off direction. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Define the rail axis (`attitude_unit`) for every Flight, not only the ones that start on the rail. It depends solely on the launch inclination and heading, so `udot_rail2` no longer raises `AttributeError` when an `initial_solution` skips the rail phase. Verified equal to the previous quaternion-derived vector to 3e-16 across inclinations, headings and rolls. - Compute the squared distance from the launch point once and share it between the two rail button exit checks. - Rename `r_B` -> `r_button` and `I_CM_inv` -> `inv_inertia_cm`, drop the unused unpacking in `udot_rail2` and the now-dead `K_init`, so pylint is clean without relaxing `.pylintrc`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The udot_rail2 docstring pointed at a derivation that lived in an untracked scratch file, so the reference was dead for anyone reading the code. Move the derivation into the technical documentation, where the other equations of motion are documented, and cite the two tip-off papers it follows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
a2c5b42 to
80ff5da
Compare
Fold the two initial-solution branches of __init_flight_state into one, as the review asked: they set the same monitors, and the Flight-object branch differed only by *not* assigning t_initial. That omission raised `AttributeError: 'Flight' object has no attribute 't_initial'` whenever the continued rocket carried sensors or controllers, since post-processing the initial state reads it. The bug predates this branch; merging the branches fixes it. Covered by a regression test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@aZira371 @Rafit345 this PR is ready for another look. Since the last review the The two blocking problems1. The phase transition was inverted. The thresholds were swapped: 2. The dynamics had no reaction force, which is the substance of your review
The derivation is now in the technical documentation, Your other comments
Two Copilot comments I deliberately left alone: the Validation
Known limitationThe button is modelled on the rocket axis, so the small roll coupling through its |
|
Status check on this one: it went conflicting with @aZira371 the |
I'll review this soon! |
# Conflicts: # CHANGELOG.md # rocketpy/simulation/flight.py # tests/unit/simulation/test_flight.py
Picks up the four unresolved review threads on RocketPy-Team#920, on top of develop. Blocking: * Move ``use_udot_rail2`` to the end of ``Flight.__init__``. It sat between ``equations_of_motion`` and ``ode_solver``, so any caller passing ``ode_solver``, ``simulation_mode`` or ``post_step_callback`` positionally silently got the wrong value. The docstring entry moves with it. Correctness: * Refuse ``use_udot_rail2=True`` together with ``simulation_mode="3 DOF"`` or ``equations_of_motion="solid_propulsion"``. The phase patches the generalized 6-DOF solution with a constraint wrench built from the full inertia tensor, but those options rebind ``u_dot_generalized`` to reduced formulations that do not carry that state -- ``u_dot_generalized_3dof`` models no attitude at all. The combination was neither guarded nor tested; it now raises ValueError. Note this also covers point-mass motors, which force "3 DOF". * Give ``use_udot_rail2`` a class-level default. Flights restored from a ``.rpy`` written before this feature are rebuilt without ``__init__``, so reading the flag raised AttributeError (caught by test_load_from_rpy). Reporting: * ``between_rails_time`` and ``between_rails_state`` were tracked and serialized, but never surfaced: studying tip-off for dispersion meant digging them out of the raw solution. Adds ``between_rails_velocity`` and ``tip_off_duration``, a "Tip-Off State" section in ``Flight.info()``, and shading of the window in the attitude plots. All of it is inert when the phase is off, so existing output is unchanged. Performance: * ``udot_rail2`` re-interpolated the total mass and the inertia tensor that ``u_dot_generalized`` had just computed for the same t, on every solver evaluation inside the window. The generalized equations now expose both. Verified to leave the trajectory bit-for-bit identical. Adds eight tests covering the guard, the reporting, and the restored-object path. Documents the mode restrictions and the reported attributes in the technical docs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pull request type
Checklist
ruff check,ruff format --check,pylint) has passed locallyCHANGELOG.mdhas been updated (if relevant)Current behavior
Refs #28.
The rail phase ends the moment the upper rail button reaches the end of the rail,
at
effective_1rl, and the simulation jumps straight from 1-DOF rail motion to6-DOF free flight. The interval in which the rocket is still guided by the lower
button alone — the tip-off phase — is not modelled, so the rocket begins free
flight with no angular rate and with exactly the rail's attitude.
New behavior
Adds
Flight.udot_rail2, an intermediate 3-DOF flight phase covering the intervalbetween the two rail buttons leaving the rail:
The phase models a constrained rigid body: the lower button slides along the fixed
inertial rail axis and roll is suppressed, leaving translation along the rail, pitch
and yaw. It reuses
u_dot_generalizedfor the free solution and adds a reactionwrench — a normal force at the button (perpendicular to the rail, 2 DOF) plus a roll
reaction moment (1 DOF) — solved from a 3x3 linear system built from the three
constraints: the button's perpendicular acceleration vanishes (2) and the roll angular
acceleration vanishes (1). Aerodynamics, thrust, body-frame gravity and the evolving
inertia tensor therefore come along by construction.
The derivation is documented in
docs/technical/tip_off.rst.Enabled with
Flight(..., use_udot_rail2=True).Breaking change
use_udot_rail2defaults toFalse, and a disabled run is bit-for-bit identicalto
develop— verified by comparing the full final state against a cleandevelopcheckout, not just the apogee.
Additional information
Validation
~1e-13 m; roll rate and roll acceleration stay at zero.
pivot); with a crosswind the rocket weathercocks into the wind before fully leaving
the rail, which is the acceptance criterion in ENH: Implement 3-DOF Single Rail Button Flight Phase (Tip-off Analysis) #28. Apogee shifts about -2 m on
Calisto.
Also fixed here: a
Flightcontinued from anotherFlightobject never sett_initial, which raisedAttributeErrorfor any continued flight carrying sensorsor controllers. The bug predates this branch and is fixed by merging the two
initial-solution branches (a refactor the review asked for). Covered by a regression
test.
Known limitation: the button is modelled on the rocket axis, so the small roll
coupling through its radial standoff is not represented — roll is instead suppressed
explicitly by the reaction moment. Documented as a modelling assumption; a natural
follow-up.
References: "Tip-off effect analysis of a vehicle moving along an inclined
guideway by considering dynamic interactions" (Chou et al.) and "Analysis of Missile
Launchers Part Q: Tipoff Effects in Helical Rail Launchers" (Hosken et al.).