Skip to content

Conversation

Will-Schwend
Copy link
Contributor

  • Review: By commit
  • Merge strategy: Merge (no squash)

Description

This PR adds the ability to extract the system mass matrix and number of degrees of freedom from a Mujoco scene with a single spacecraft. This enables the use of these values throughout other Basilisk modules.

Verification

A new unit test test_MJSystemMassMatrix.py was created which verifies the degrees of freedom and mass matrix extracted in the module

Documentation

New documentation was made for the MJSystemMassMatrix module. Reslease notes were updated to include the new module.

Future work

N/A

@Will-Schwend Will-Schwend self-assigned this Sep 22, 2025
@Will-Schwend Will-Schwend requested a review from a team as a code owner September 22, 2025 16:29
@Will-Schwend Will-Schwend added documentation Improvements or additions to documentation enhancement New feature or request labels Sep 22, 2025
@Will-Schwend Will-Schwend moved this to 👀 In review in Basilisk Sep 22, 2025
@Will-Schwend Will-Schwend force-pushed the feature/Mujoco-System-Mass-Matrix branch from cbcdddc to 0eea9ec Compare September 23, 2025 19:02
Copy link
Contributor

@schaubh schaubh left a comment

Choose a reason for hiding this comment

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

Two small comments to address. Otherwise good to go.

@Will-Schwend Will-Schwend force-pushed the feature/Mujoco-System-Mass-Matrix branch from 0eea9ec to f07d39e Compare September 26, 2025 17:06
@schaubh
Copy link
Contributor

schaubh commented Sep 29, 2025

@Will-Schwend , feel free to push this PR if @juan-g-bonilla has no issues with your implementation to interface with his code.

@Will-Schwend Will-Schwend force-pushed the feature/Mujoco-System-Mass-Matrix branch from f07d39e to debf501 Compare September 29, 2025 17:25
Copy link
Contributor

@juan-g-bonilla juan-g-bonilla left a comment

Choose a reason for hiding this comment

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

Great PR, thanks for putting in the work to make this a general useful component.


#include "architecture/utilities/macroDefinitions.h"

#define MJ_MASSMATRIX_DIM (6 + MAX_EFF_CNT) // row/col size
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we defining MJ_MASSMATRIX_DIM but then using 6 + MAX_EFF_CNT explicitly for the MassMatrix size?

typedef struct {
int nbase; //!< [-] number of base DOFs (6 if free base, else 0)
int nj; //!< [-] number of joint DOFs populated in mass matrix
double MassMatrix[6 + MAX_EFF_CNT][6 + MAX_EFF_CNT]; //!< [varies by component] system mass matrix in generalized coordinates
Copy link
Contributor

Choose a reason for hiding this comment

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

Should MassMatrix not be massMatrix following the camelCase convention?

Copy link
Contributor

Choose a reason for hiding this comment

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

good catch on not using lower came case!

BSKLogger bskLogger; //!< BSK Logging
private:

int nDOF{0}; // number of total DOF
Copy link
Contributor

Choose a reason for hiding this comment

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

A bit pedantic, but maybe these should be size_t or another unsigned integer.

Copy link
Contributor

Choose a reason for hiding this comment

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

please make sure there are no new implicit type conversion warnings.

Comment on lines +36 to +41
MJSystemMassMatrix();
~MJSystemMassMatrix() = default;

void SelfInit();
void Reset(uint64_t CurrentSimNanos);
void UpdateState(uint64_t CurrentSimNanos);
Copy link
Contributor

Choose a reason for hiding this comment

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

I like including the doxygen method comments in the .h instead of the .cpp file. That way, you have the class, variable, and method descriptions all in the same file, instead of having to go to the .cpp to read the documentation. Just a style choice, not required.

Copy link
Contributor

Choose a reason for hiding this comment

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

agree, this is a personal choice of the person writing the module :-)

bodies: [{'m':float,'r':(3,), 'Icom':(3,3)}, ...]
Return (M6, rC).
"""
m_tot = sum(b['m'] for b in bodies)
Copy link
Contributor

Choose a reason for hiding this comment

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

camelCase mTot please!

Comment on lines +154 to +191
def add_allclose(testFailCount, testMessages, name, A, B, rtol=0.0, atol=1e-10):
try:
assert_allclose(A, B, rtol=rtol, atol=atol)
except AssertionError as e:
testFailCount += 1
testMessages.append(f"{name}: {str(e)}\n")
return testFailCount, testMessages

def add_equal(testFailCount, testMessages, name, a, b):
try:
assert_equal(a, b)
except AssertionError as e:
testFailCount += 1
testMessages.append(f"{name}: {str(e)}\n")
return testFailCount, testMessages

def add_symmetry(testFailCount, testMessages, name, M, tol=1e-12):
return add_allclose(testFailCount, testMessages, name, M, M.T, rtol=0.0, atol=tol)

def add_psd(testFailCount, testMessages, name, M, tol=-1e-12):
try:
w = np.linalg.eigvalsh(0.5*(M+M.T))
# require every eigenvalue >= tol (allow tiny negative for roundoff)
assert_array_less(np.full_like(w, tol), w)
except AssertionError as e:
testFailCount += 1
testMessages.append(f"{name}: not PSD; {str(e)}; eigvals={w}\n")
return testFailCount, testMessages

def add_padding_zero(testFailCount, testMessages, name, M, used, atol=1e-14):
try:
if used < M.shape[0]:
assert_allclose(M[used:, :], 0.0, rtol=0.0, atol=atol)
assert_allclose(M[:, used:], 0.0, rtol=0.0, atol=atol)
except AssertionError as e:
testFailCount += 1
testMessages.append(f"{name}: {str(e)}\n")
return testFailCount, testMessages
Copy link
Contributor

Choose a reason for hiding this comment

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

Some time ago, we abandoned the pattern:

    try:
        assert ...
    except AssertionError as e:
        testFailCount += 1
        testMessages.append(f"{name}: {str(e)}\n")
    return testFailCount, testMessages

In fact newer tests don't use testFailCount or testMessages at all. Instead, let the actual assertion error propagate, since pytest will capture it and print nicer errors when the test fails.

testProc.addTask(unitTestSim.CreateNewTask(unitTaskName, testProcessRate))

# create the Mujoco scene
scene = mujoco.MJScene.fromFile(xml_path)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
scene = mujoco.MJScene.fromFile(xml_path)
scene = mujoco.MJScene(xml_fn())

if you already have the XML as a string, you can just pass it to the constructor.

Remember to remove _write_tmp!

Comment on lines +240 to +241
testFailCount = 0
testMessages = []
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove

import numpy as np
from numpy.testing import assert_allclose, assert_equal, assert_array_less
import pytest
import tempfile
Copy link
Contributor

Choose a reason for hiding this comment

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

Remember to remove unused import.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation enhancement New feature or request
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

3 participants