Skip to content

BUG: correct the position-vector convention in u_dot_generalized - #1196

Open
thc1006 wants to merge 3 commits into
RocketPy-Team:developfrom
thc1006:bug/generalized-eom-position-vector-convention
Open

thc1006 wants to merge 3 commits into
RocketPy-Team:developfrom
thc1006:bug/generalized-eom-position-vector-convention

Conversation

@thc1006

@thc1006 thc1006 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

u_dot_generalized reads two Rocket attributes as vectors from the center of dry mass, and both of them run the other way. Addresses #1186.

Pull request type

  • Code changes (bugfix, features)

Checklist

Current behavior

The v1 derivation takes the center of dry mass as its origin, so it wants a vector from the CDM to the instantaneous center of mass and one from the CDM to the nozzle exit. The two attributes it reads are defined the other way round:

  • evaluate_com_to_cdm_function documents the identity com_to_cdm_function + center_of_mass == center_of_dry_mass_position, which makes it CM to CDM, and it labels its own output "Z Coordinate COM to CDM".
  • nozzle_to_cdm is -(nozzle_position - center_of_dry_mass_position) * _csys, which is nozzle to CDM.

u_dot_generalized uses both without negating them, so r_CM and r_noz point the wrong way and the moment arm they form has the wrong sign. lateral_surface_wind, in the same file, does negate com_to_cdm_function before using it as a vector from the CDM, so the file is not consistent with itself.

New behavior

Both are negated where the derivation reads them, and the v_dot term that carries r_CM ^ w_dot changes sign with them.

docs/technical/equations_of_motion_v1.rst now states which origin it works in and that the two attributes are the opposite vectors, so the next reader does not have to infer it. Several typos in the derivation go with it: r_CM where the term is r_CM' or r_CM'', a missing plus before the aerodynamic force, the sign on the gravity term of T21, a square dropped from the H matrix and a brace misplaced around another.

Breaking change

  • Yes

Simulation results move. Nothing in the API changes, but any number that comes out of the generalized equations is different, so a study built on the old ones will not reproduce.

Additional information

The acceptance flight moves toward the measurement rather than away from it. tests/acceptance/test_defiance_rocket.py carries MEASURED_APOGEE_AGL = 9308.32 from the 2024 Defiance flight:

apogee AGL error against the measurement
develop at a50d07d5 9243.8350 m 0.6928 %
with this change 9247.9733 m 0.6483 %

I would not read one flight as proof on its own. It is one data point, and it is the only measured one in the suite.

What the new tests pin. tests/unit/simulation/test_generalized_eom.py is seven tests against a stand-in rocket, among them that the angular acceleration uses a lever arm measured from the center of mass, that an axial force through the axis produces no rotation, that the result does not depend on which reference origin the same physical setup is described in, and that the block system the documentation states is the one the code solves. Putting each half of the change back turns exactly the tests that half is about red, and leaves the rest green: the CM vector un-negated takes down six, the nozzle vector un-negated takes down the one named after it, and the v_dot sign alone takes down the reference-origin and block-system tests.

Expectations that move. Four parametrised values in tests/unit/simulation/test_flight.py change at apogee_time; that file says its expected values were copied from a run rather than derived, and are expected to change on a bug fix. The Defiance impact point moves further than the 3 m the acceptance test allows, so its two references follow the flight, x from 1609.40 to 1550.23 and y from 87.03 to 104.80. Max speed and max acceleration stay inside the 1 percent that test gives them.

Verified on a50d07d5:

pytest tests/                       2655 passed, 10 failed
  the same on develop a50d07d5      2644 passed, 10 failed
ruff check . / ruff format --check  clean
pylint rocketpy/ tests/ docs/       10.00/10, exit 0

The ten are the same ten on both, all of them optional dependencies this machine does not have.

The v1 derivation takes the center of dry mass as its origin, so it wants CDM
to CM and CDM to nozzle. Both Rocket attributes run the other way.
evaluate_com_to_cdm_function documents the identity com_to_cdm_function plus
center_of_mass equals center_of_dry_mass_position, which makes it CM to CDM,
and it labels its own output "Z Coordinate COM to CDM". nozzle_to_cdm is
-(nozzle_position - center_of_dry_mass_position) * _csys, which is nozzle to
CDM. u_dot_generalized reads both without negating; lateral_surface_wind, in
the same file, does negate com_to_cdm_function before using it as a vector
from the CDM. Both are negated here and the v_dot term that follows the sign
changes with them. Filed as RocketPy-Team#1186.

The equations of motion v1 page states the convention it works in and says
the two attributes are the opposite vectors. Several typos in the derivation
go with it: r_CM where the term is r_CM' or r_CM'', a missing plus before the
aerodynamic force, the sign on the gravity term of T21, a square dropped from
the H matrix and a brace misplaced around another.

tests/unit/simulation/test_generalized_eom.py is new and was untracked until
now: seven tests for what the block system claims, among them that the lever
arm comes from the center of mass, that an axial force through the axis makes
no rotation, and that the result does not depend on the reference origin.

The nozzle gyration tensor half of this reached develop as RocketPy-Team#1188, so it is
not here: rebasing dropped both of its hunks.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
Rebasing onto a50d07d dropped the nozzle gyration half, which reached
develop as RocketPy-Team#1188, and left the sign convention and its documentation. What
follows is measured on that base rather than carried over from the old one.

The stand-in rocket in test_generalized_eom.py yielded
(component, position) for aerodynamic_surfaces, and RocketPy-Team#1129 made that a triple.
Its factor is 1.0 here, which is what Components.add defaults to, and
u_dot_generalized binds it as unused, so it cannot move the result.

The Defiance acceptance flight, against the measured 9308.32 m AGL:

    develop a50d07d        9243.8350 m     0.6928 %
    with the convention     9247.9733 m     0.6483 %

Its impact point moves further than the 3 m the test allows, so the two
references follow the flight: x from 1609.40 to 1550.23 and y from 87.03 to
104.80. Max speed and max acceleration stay inside the 1 percent the test
gives them, at 443.99 against 444.24 and 10356.62 against 10400.76.

Four expectations in test_flight.py move at apogee, all of them values the
file says were copied from a run rather than derived. Measured again here:
moments (-0.652606, -0.734029, -2.115675e-14), forces (2.266124, -2.014696,
-0.818709), velocity (-11.634095, 16.695945, -0.000173) and acceleration
(0.073233, -0.062635, -9.614299). The first three are what this branch
recorded four weeks ago, so RocketPy-Team#1188 landing did not disturb them.

Whole suite on this branch: 2655 passed, 10 failed, against 2644 passed and
the same 10 on develop at a50d07d. Those ten want optional dependencies
this machine does not have. ruff and ruff format are clean and pylint is
10.00 with exit 0.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
Under Changed rather than Fixed: nothing about the API moves, but every
number out of the generalized equations of motion does, so a study built on
the old ones will not reproduce. The entry says that first and carries the
one measured comparison the suite has.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006
thc1006 requested a review from a team as a code owner September 16, 2026 07:42
@codecov

codecov Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.58%. Comparing base (a50d07d) to head (927e771).

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1196      +/-   ##
===========================================
- Coverage    91.58%   91.58%   -0.01%     
===========================================
  Files          132      132              
  Lines        18404    18403       -1     
===========================================
- Hits         16855    16854       -1     
  Misses        1549     1549              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant