Skip to content

Commit 80ff5da

Browse files
DOC: document the tip-off equations of motion
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>
1 parent f0808be commit 80ff5da

4 files changed

Lines changed: 252 additions & 2 deletions

File tree

‎docs/technical/index.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ in their code.
1212

1313
Equations of Motion v0 <equations_of_motion.rst>
1414
Equations of Motion v1 <equations_of_motion_v1.rst>
15+
Tip-off <tip_off.rst>
1516
Elliptical Fins <aerodynamics/elliptical_fins.rst>
1617
Individual Fin <aerodynamics/individual_fins.rst>
1718
Roll Moment <aerodynamics/roll_equations.rst>

‎docs/technical/references.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ References
66
.. [Niskanen] Niskanen, S. (2013). *Development of an Open Source model rocket simulation software*.
77
88
.. [Model] Barrowman, James S.. (1970). *Model Rocketry*.
9+
10+
.. [Chou] Chou, P.-C., et al. *Tip-off effect analysis of a vehicle moving along an inclined guideway by considering dynamic interactions*.
11+
12+
.. [Hosken] Hosken, R. W., et al. *Analysis of missile launchers, part Q: tip-off effects in helical rail launchers*.

‎docs/technical/tip_off.rst‎

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
.. _tipoff:
2+
3+
===========================================
4+
Tip-off: the 3-DOF Single Rail Button Phase
5+
===========================================
6+
7+
Introduction
8+
------------
9+
10+
Between the moment the *upper* rail button leaves the launch rail and the moment
11+
the *lower* button follows it, the rocket is still guided --- but only at one
12+
point. It slides along the rail while free to pitch and yaw about that remaining
13+
button. This short interval is what the literature calls **tip-off**, and it sets
14+
the attitude and angular rate with which the rocket begins free flight.
15+
16+
This document derives the equations of motion used by
17+
:meth:`rocketpy.Flight.udot_rail2`, the flight phase that models this interval.
18+
It is enabled with ``Flight(..., use_udot_rail2=True)``; when disabled (the
19+
default) the simulation transitions straight from the 1-DOF rail phase to the
20+
generalized 6-DOF equations, exactly as it did before this phase existed.
21+
22+
The three flight phases around rail departure are, in order of the distance
23+
``d`` travelled from the launch point:
24+
25+
.. math::
26+
27+
\begin{aligned}
28+
\texttt{udot_rail1} \quad & \text{for } d < \ell_1
29+
&& \text{(both buttons engaged, 1 DOF)} \\
30+
\texttt{udot_rail2} \quad & \text{for } \ell_1 \le d < \ell_2
31+
&& \text{(upper button gone, lower engaged, 3 DOF)} \\
32+
\texttt{u_dot_generalized} \quad & \text{for } d \ge \ell_2
33+
&& \text{(free flight, 6 DOF)}
34+
\end{aligned}
35+
36+
where :math:`\ell_1` and :math:`\ell_2` are the ``effective_1rl`` and
37+
``effective_2rl`` attributes of :class:`rocketpy.Flight` --- the distances at
38+
which the upper and the lower button reach the end of the rail. Their difference
39+
is the button-to-button distance, so the phase has zero length for a rocket with
40+
a single rail button and is skipped in that case.
41+
42+
Frames and conventions
43+
----------------------
44+
45+
The solver integrates the same 13-element state vector used by the other
46+
right-hand sides,
47+
48+
.. math::
49+
50+
\mathbf{u} = [\,x,\ y,\ z,\ v_x,\ v_y,\ v_z,\ e_0,\ e_1,\ e_2,\ e_3,\
51+
\omega_1,\ \omega_2,\ \omega_3\,]
52+
53+
with :math:`\mathbf{r} = [x, y, z]` the inertial position of the **center of dry
54+
mass** (CDM, the tracked point), :math:`\mathbf{v}` its inertial velocity,
55+
:math:`\mathbf{e}` the attitude quaternion and :math:`\boldsymbol{\omega}` the
56+
angular velocity in the **body** frame. The matrix
57+
:math:`\mathbf{K} = \texttt{Matrix.transformation}(\mathbf{e})` rotates body
58+
components into inertial ones, and :math:`\hat{\mathbf{z}}_b = [0, 0, 1]` is the
59+
body roll (symmetry) axis.
60+
61+
The relevant mass geometry at time :math:`t`, in the body frame, is the total
62+
mass :math:`m`, the CDM-to-center-of-mass offset :math:`\mathbf{r}_{CM}`, and the
63+
inertia tensor about the CDM, :math:`\mathbf{I}`. Shifting the latter to the
64+
instantaneous center of mass gives
65+
66+
.. math::
67+
68+
\mathbf{I}_{CM} = \mathbf{I} - m\left(|\mathbf{r}_{CM}|^2 \mathbb{1}
69+
- \mathbf{r}_{CM}\mathbf{r}_{CM}^{\mathsf T}\right).
70+
71+
**Rail geometry.** The rail is a line fixed in the inertial frame, set by the
72+
launch inclination and heading. Its unit vector is the ``attitude_unit``
73+
attribute of :class:`rocketpy.Flight`; the constrained body point is the lower
74+
rail button, at the fixed body position :math:`\mathbf{r}_{B}` relative to the
75+
CDM.
76+
77+
The constraint
78+
--------------
79+
80+
A free rigid body has six degrees of freedom. The single engaged button removes
81+
three of them:
82+
83+
#. **The button stays on the rail line.** Its position may only vary along the
84+
rail, so the component of its acceleration perpendicular to the rail
85+
vanishes. That is **two** scalar constraints.
86+
#. **Roll is suppressed** by the button in its rail slot:
87+
:math:`\dot{\boldsymbol{\omega}} \cdot \hat{\mathbf{z}}_b = 0`. That is
88+
**one** more.
89+
90+
Three constraints leave :math:`6 - 3 = 3` degrees of freedom: translation along
91+
the rail, pitch and yaw --- the 3 DOF this phase is named for.
92+
93+
The forces that enforce them are the unknowns of the problem:
94+
95+
* a **normal reaction** at the button, perpendicular to the rail because a
96+
frictionless slot can neither pull nor push along it. Writing an orthonormal
97+
body triad :math:`\{\hat{\mathbf{e}}_1, \hat{\mathbf{e}}_2, \hat{\mathbf{n}}_b\}`
98+
around the body-frame rail direction
99+
:math:`\hat{\mathbf{n}}_b = \mathbf{K}^{\mathsf T}\hat{\mathbf{n}}`, it has two
100+
components: :math:`\mathbf{N}_b = \lambda_1 \hat{\mathbf{e}}_1 + \lambda_2
101+
\hat{\mathbf{e}}_2`;
102+
* a **roll reaction moment** :math:`\mu \hat{\mathbf{z}}_b` about the body axis.
103+
104+
Three unknowns, three constraints: a :math:`3\times3` linear system, solved once
105+
per evaluation of the right-hand side.
106+
107+
The initial conditions are consistent with the constraint. The phase is entered
108+
from ``udot_rail1``, where the rocket has no angular velocity and its velocity is
109+
along the rail, so the button's perpendicular *velocity* is already zero.
110+
Enforcing zero perpendicular *acceleration* therefore keeps it on the rail.
111+
112+
Augmented dynamics
113+
------------------
114+
115+
The generalized equations of motion assemble a total force
116+
:math:`\mathbf{T}_{20}` and a total moment about the CDM :math:`\mathbf{T}_{21}`,
117+
both in the body frame, and solve
118+
119+
.. math::
120+
121+
\dot{\boldsymbol{\omega}}_{\text{free}} = \mathbf{I}_{CM}^{-1}
122+
\left(\mathbf{T}_{21} + \mathbf{T}_{20} \times \mathbf{r}_{CM}\right),
123+
\qquad
124+
\mathbf{a}_{\text{free}} = \frac{\mathbf{T}_{20}}{m}
125+
- \mathbf{r}_{CM} \times \dot{\boldsymbol{\omega}}_{\text{free}}.
126+
127+
Both totals are *sums of external contributions*, which is what makes the
128+
constraint clean to add: the reaction wrench simply enters the sums,
129+
130+
.. math::
131+
132+
\mathbf{T}_{20}' = \mathbf{T}_{20} + \mathbf{N}_b,
133+
\qquad
134+
\mathbf{T}_{21}' = \mathbf{T}_{21} + \mathbf{r}_{B} \times \mathbf{N}_b
135+
+ \mu \hat{\mathbf{z}}_b,
136+
137+
after which the same two lines apply. Since the solve is linear in the totals,
138+
the result splits into the free solution plus a response to the unknowns. Using
139+
:math:`\mathbf{r}_B \times \mathbf{N} + \mathbf{N} \times \mathbf{r}_{CM}
140+
= (\mathbf{r}_B - \mathbf{r}_{CM}) \times \mathbf{N}` and writing
141+
:math:`\mathbf{d} = \mathbf{r}_{B} - \mathbf{r}_{CM}` for the center-of-mass-to-button
142+
vector,
143+
144+
.. math::
145+
146+
\Delta\dot{\boldsymbol{\omega}} = \mathbf{I}_{CM}^{-1}
147+
\left(\mathbf{d} \times \mathbf{N}_b + \mu \hat{\mathbf{z}}_b\right),
148+
\qquad
149+
\Delta\mathbf{a} = \frac{\mathbf{N}_b}{m}
150+
- \mathbf{r}_{CM} \times \Delta\dot{\boldsymbol{\omega}}.
151+
152+
The button is body-fixed, so its acceleration in body components is
153+
154+
.. math::
155+
156+
\mathbf{A} = \mathbf{A}_{\text{free}} + \Delta\mathbf{a}
157+
+ \Delta\dot{\boldsymbol{\omega}} \times \mathbf{r}_{B},
158+
\qquad
159+
\mathbf{A}_{\text{free}} = \mathbf{K}^{\mathsf T}\mathbf{a}_{\text{free}}
160+
+ \dot{\boldsymbol{\omega}}_{\text{free}} \times \mathbf{r}_{B}
161+
+ \boldsymbol{\omega} \times
162+
\left(\boldsymbol{\omega} \times \mathbf{r}_{B}\right).
163+
164+
The linear solve
165+
----------------
166+
167+
Collect the unknowns in :math:`\boldsymbol{\chi} = [\lambda_1, \lambda_2, \mu]`.
168+
The three constraints read
169+
170+
.. math::
171+
172+
\mathbf{A} \cdot \hat{\mathbf{e}}_1 = 0,
173+
\qquad
174+
\mathbf{A} \cdot \hat{\mathbf{e}}_2 = 0,
175+
\qquad
176+
\dot{\boldsymbol{\omega}} \cdot \hat{\mathbf{z}}_b = 0,
177+
178+
and because :math:`\mathbf{A}` and :math:`\dot{\boldsymbol{\omega}}` are linear
179+
in :math:`\boldsymbol{\chi}`, they form the system
180+
:math:`\mathbf{J}\boldsymbol{\chi} = -\mathbf{g}_{\text{free}}` with
181+
182+
.. math::
183+
184+
\mathbf{g}_{\text{free}} =
185+
\begin{bmatrix}
186+
\mathbf{A}_{\text{free}} \cdot \hat{\mathbf{e}}_1 \\
187+
\mathbf{A}_{\text{free}} \cdot \hat{\mathbf{e}}_2 \\
188+
\dot{\boldsymbol{\omega}}_{\text{free}} \cdot \hat{\mathbf{z}}_b
189+
\end{bmatrix}.
190+
191+
Each column of :math:`\mathbf{J}` is obtained by evaluating the response above at
192+
one of the three unit inputs :math:`(\mathbf{N}_b, \mu) =
193+
(\hat{\mathbf{e}}_1, 0)`, :math:`(\hat{\mathbf{e}}_2, 0)`,
194+
:math:`(\mathbf{0}, 1)`. Solving for :math:`\boldsymbol{\chi}` and substituting
195+
back gives the constrained accelerations, which override the free ones in the
196+
returned derivative:
197+
198+
.. math::
199+
200+
\dot{\boldsymbol{\omega}} = \dot{\boldsymbol{\omega}}_{\text{free}}
201+
+ \Delta\dot{\boldsymbol{\omega}},
202+
\qquad
203+
\mathbf{a}_{CDM} = \mathbf{a}_{\text{free}}
204+
+ \mathbf{K}\,\Delta\mathbf{a}.
205+
206+
The position and quaternion derivatives are the ordinary kinematic ones. In
207+
particular :math:`\dot{\mathbf{r}} = \mathbf{v}`: the velocity is **not**
208+
projected onto the rail. The constraint acts at the acceleration level on the
209+
*button*, and the CDM legitimately acquires a small perpendicular velocity as the
210+
rocket pitches about that button.
211+
212+
Modelling assumptions and edge cases
213+
------------------------------------
214+
215+
* **Roll axis.** The constraint suppresses roll about the *body* axis. The rocket
216+
travels only the button-to-button distance during this phase, so the tip-off
217+
angle is small and the body axis stays close to the rail direction; the
218+
difference between body roll and rail-axis roll is of that order. This is
219+
consistent with ``udot_rail1``, which freezes rotation entirely.
220+
* **Radial button offset.** The button is modelled on the rocket axis. The roll
221+
constraint is enforced explicitly by :math:`\mu`, so what is lost is only the
222+
(small) roll coupling through the button's radial standoff.
223+
* **Zero-length phase.** A rocket with a single rail button has
224+
:math:`\ell_1 = \ell_2`, and the phase is skipped.
225+
* **Singular system.** Should the geometry make :math:`\mathbf{J}` singular, the
226+
step falls back to the unconstrained dynamics and warns.
227+
228+
Expected behaviour
229+
------------------
230+
231+
The phase reproduces the two effects tip-off is modelled for. With no wind, the
232+
center of mass sits ahead of the button that the rocket now pivots about, so
233+
gravity pitches the nose down by a fraction of a degree. With a crosswind, the
234+
aerodynamic moment turns the rocket into the wind before it is fully free ---
235+
the weathercock effect --- and the rocket therefore leaves the rail with a small
236+
angular rate rather than none.
237+
238+
References
239+
----------
240+
241+
The tip-off phase and its effect on the initial conditions of free flight are
242+
treated in the launcher dynamics literature: [Chou]_ studies a vehicle moving
243+
along an inclined guideway with dynamic interactions, and [Hosken]_ analyses
244+
tip-off effects in rail launchers.

‎rocketpy/simulation/flight.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2030,8 +2030,9 @@ def udot_rail2(self, t, u, post_processing=False):
20302030
to the rail, 2 DOF) plus a roll reaction moment ``mu`` (1 DOF). The
20312031
three unknowns are found from three constraints -- the button's
20322032
acceleration perpendicular to the rail is zero (2) and the roll angular
2033-
acceleration is zero (1). See ``scratch/pr920_tipoff_derivation.md`` for
2034-
the full derivation. All reaction quantities are expressed in the "true"
2033+
acceleration is zero (1). The full derivation is in the technical
2034+
documentation, :ref:`Tip-off <tipoff>`. All reaction quantities are
2035+
expressed in the "true"
20352036
body frame (the one used by ``surfaces_cp_to_cdm``, body-z toward the
20362037
nose), so ``r_CM`` and the button position are taken with that sign
20372038
convention -- independent of the internal (point-to-CDM) convention used

0 commit comments

Comments
 (0)