Skip to content

Add rotation matrix helpers - #290

Open
asTejaswinis wants to merge 4 commits into
camUrban:mainfrom
asTejaswinis:feature/add_rotation_matrix_helpers
Open

Add rotation matrix helpers#290
asTejaswinis wants to merge 4 commits into
camUrban:mainfrom
asTejaswinis:feature/add_rotation_matrix_helpers

Conversation

@asTejaswinis

@asTejaswinis asTejaswinis commented Aug 30, 2026

Copy link
Copy Markdown

Description

Add 3x3 rotation matrix helpers and adopt them at the MuJoCo boundary

Motivation

_transformations.py provides homogeneous 4x4 transform helpers (generate_rot_T, invert_T_pas, invert_T_act, and apply_T_to_vectors) but no equivalents for bare 3x3 rotation matrices, so the MuJoCo boundary code does rotation math inline with raw NumPy. The bare operations that need covering are: inverting a passive rotation (currently a .T; the TODO comment in MuJoCoModel.get_state already proposes naming this invert_R_pas), inverting an active rotation (the np.linalg.inv followed by .T in the MuJoCoModel constructor), extracting the 3x3 rotation block from a 4x4 transform (currently T[:3, :3]), and applying a rotation matrix to vectors (currently xmat @ qvel and R @ omega, the 3x3 analog of apply_T_to_vectors). This is correctness-neutral consistency work: the inline math is right, it is just unnamed, unvalidated, and repeated.

Relevant Issues

Closes #249.

Changes

  • Added invert_R_pas, invert_R_act, apply_R_to_vectors, and extract_R_from_T functions to pterasoftware/_transformations.py along with reStructuredText docstrings.
  • Routed inline NumPy math in pterasoftware/_mujoco_model.py constructor and get_state through the new helpers.
  • Deleted the stale rotation inversion TODO comment in pterasoftware/_mujoco_model.py.
  • Updated math snippets to use the new helpers in docs/MUJOCO_CONVENTIONS.md.
  • Updated the 3x3 array operations to use the new helpers in tests/unit/test_mujoco_model.py.

Dependency Updates

None.

Change Magnitude

Minor: Small change such as a bug fix, small enhancement, or documentation update.

Checklist (check each item when completed or not applicable)

  • I am familiar with the current contribution guidelines.
  • PR description links all relevant issues and follows this template.
  • My branch is based on main and is up to date with the upstream main branch.
  • All calculations use S.I. units.
  • Code is formatted with black (line length = 88).
  • Code is well documented with block comments where appropriate.
  • Any external code, algorithms, or equations used have been cited in comments or docstrings.
  • All new modules, classes, functions, and methods have docstrings in reStructuredText format, and are formatted using docformatter (--in-place --black). See the style guide for type hints and docstrings for more details.
  • All new classes, functions, and methods in the pterasoftware package use type hints. See the style guide for type hints and docstrings for more details.
  • If any major functionality was added or significantly changed, I have added or updated tests in the tests package.
  • Code locally passes all tests in the tests package.
  • This PR passes the ReadTheDocs build check (this runs automatically with the other workflows).
  • This PR passes the ascii-only, pre-commit-hooks, and zizmor GitHub actions.
  • This PR passes the lint job of the CI GitHub action.
  • This PR passes the test jobs of the CI GitHub action.

@asTejaswinis
asTejaswinis marked this pull request as ready for review September 7, 2026 07:34
@camUrban camUrban added the maintenance Improvements or additions to documentation, testing, robustness, or tooling label Sep 8, 2026
@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.40%. Comparing base (c29d12a) to head (974bd80).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #290   +/-   ##
=======================================
  Coverage   94.39%   94.40%           
=======================================
  Files          47       47           
  Lines        9694     9701    +7     
=======================================
+ Hits         9151     9158    +7     
  Misses        543      543           

☔ 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.

@camUrban camUrban changed the title Feature/add rotation matrix helpers Add rotation matrix helpers Sep 8, 2026

@camUrban camUrban left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @asTejaswinis, thanks so much for your hard work on this! The results look really good so far. There are a few changes I requested. Once you've addressed them, feel free to request another review and we can get this merged! 😃

Also, as you are a first-time contributor, please also add yourself to the bottom of the list of contributors in the README.md (follow one of the pre-existing entries for formatting).

R_act_BP1_to_E = np.linalg.inv(R_pas_BP1_to_E)

R_act_E_to_BP1 = R_act_BP1_to_E.T
R_act_E_to_BP1 = _transformations.invert_R_act(R_pas_BP1_to_E)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mathematically correct, but I think we want to break this into two steps. First, converting the passive rotation to an active rotation, and then inverting the active rotation to convert "body to earth axes" to "earth to body axes".

The reason is ease of interpret-ability and consistency with the rest of the codebase, as we already have the functions _transformations.convert_T_pas_to_T_act and _transformations.convert_T_act_to_T_pas. We could complement them with two additional functions: _transformations.convert_R_pas_to_R_act and transformations.convert_R_act_to_R_pas.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_transformations.convert_R_pas_to_R_act and _transformations.convert_R_pas_to_R_act would also benefit from some new unit test classes (tests/unit/transformations_tests.TestConvertRPasToRAct and tests/unit/transformations_tests.TestConvertRActToPas).

R = R_act
if passive:
R = R.T
R = invert_R_pas(R)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should switch this to R = convert_R_act_to_R_pas(R), once it exists.

return alpha, beta


def invert_R_pas(R_pas: np.ndarray) -> np.ndarray:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should get some unit tests, perhaps in a new class: tests/unit/transformations_tests.TestInvertRPas. You can get some ideas of the number and shape of tests to include by looking at tests/unit/transformations_tests.TestInvertTPas.

return R_pas.T


def invert_R_act(R_act: np.ndarray) -> np.ndarray:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should get some unit tests, perhaps in a new class: tests/unit/transformations_tests.TestInvertRAct. You can get some ideas of the number and shape of tests to include by looking at tests/unit/transformations_tests.TestInvertTAct.

return np.linalg.inv(R_act).T


def apply_R_to_vectors(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should get some unit tests, perhaps in a new class: tests/unit/transformations_tests.TestApplyRToVectors. You can get some ideas of the number and shape of tests to include by looking at tests/unit/transformations_tests.TestApplyTToVectors.

return np.asarray(np.einsum("ij,...j->...i", R, vectors_A), dtype=float)


def extract_R_from_T(T: np.ndarray) -> np.ndarray:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should get some unit tests, perhaps in a new class: tests/unit/transformations_tests.TestExtractRFromT.

@camUrban

camUrban commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Also, I merged in some updates from main, so please remember to git pull before continuing work on your local branch!

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

Labels

maintenance Improvements or additions to documentation, testing, robustness, or tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add rotation-matrix helpers and adopt them at the MuJoCo boundary

2 participants