Add error handling to mj_recompile.

This CL adds error handling to the mj_recompile function. If the recompile fails, the function will return -1 and set the error message in the spec. The Python wrapper for mj_recompile will catch the error and raise a ValueError exception.

PiperOrigin-RevId: 666784579
Change-Id: I225eca1769ea839c782be3c03fc4ff1ea5885a48
This commit is contained in:
Alessio Quaglino
2024-08-23 07:17:26 -07:00
committed by Copybara-Service
parent 088079eff0
commit 505d01a1c1
10 changed files with 77 additions and 20 deletions
+5 -5
View File
@@ -47,7 +47,9 @@ mj_compile
.. mujoco-include:: mj_compile .. mujoco-include:: mj_compile
Compile spec to model. Compile :ref:`mjSpec` to :ref:`mjModel`. A spec can be edited and compiled multiple times, returning a new
:ref:`mjModel` instance that takes the edits into account.
If compilation fails, :ref:`mj_compile` returns ``NULL``; the error can be read with :ref:`mjs_getError`.
.. _mj_recompile: .. _mj_recompile:
@@ -63,10 +65,8 @@ reallocate existing :ref:`mjModel` and :ref:`mjData` instances in-place. Second,
newly added or removed degrees of freedom. This allows the user to continue simulation with the same model and data newly added or removed degrees of freedom. This allows the user to continue simulation with the same model and data
struct pointers while editing the model programmatically. struct pointers while editing the model programmatically.
.. admonition:: Incomplete implementation :ref:`mj_recompile` returns 0 if compilation succeed. In the case of failure, the given :ref:`mjModel` and :ref:`mjData`
:class: attention instances will be deleted; as in :ref:`mj_compile`, the compilation error can be read with :ref:`mjs_getError`.
This function is currently incomplete, preserving only ``mjData.qpos`` and ``mjData.qvel``.
.. _mj_saveLastXML: .. _mj_saveLastXML:
+8 -4
View File
@@ -29,6 +29,12 @@ The key function here is :ref:`mj_loadXML`. It invokes the built-in parser and c
a valid mjModel, or NULL - in which case the user should check the error information in the user-provided string. a valid mjModel, or NULL - in which case the user should check the error information in the user-provided string.
The model and all files referenced in it can be loaded from disk or from a VFS when provided. The model and all files referenced in it can be loaded from disk or from a VFS when provided.
.. _mj_compile:
Compile :ref:`mjSpec` to :ref:`mjModel`. A spec can be edited and compiled multiple times, returning a new
:ref:`mjModel` instance that takes the edits into account.
If compilation fails, :ref:`mj_compile` returns ``NULL``; the error can be read with :ref:`mjs_getError`.
.. _mj_recompile: .. _mj_recompile:
Recompile spec to model, preserving the state. Like :ref:`mj_compile`, this function compiles an :ref:`mjSpec` to an Recompile spec to model, preserving the state. Like :ref:`mj_compile`, this function compiles an :ref:`mjSpec` to an
@@ -38,10 +44,8 @@ reallocate existing :ref:`mjModel` and :ref:`mjData` instances in-place. Second,
newly added or removed degrees of freedom. This allows the user to continue simulation with the same model and data newly added or removed degrees of freedom. This allows the user to continue simulation with the same model and data
struct pointers while editing the model programmatically. struct pointers while editing the model programmatically.
.. admonition:: Incomplete implementation :ref:`mj_recompile` returns 0 if compilation succeed. In the case of failure, the given :ref:`mjModel` and :ref:`mjData`
:class: attention instances will be deleted; as in :ref:`mj_compile`, the compilation error can be read with :ref:`mjs_getError`.
This function is currently incomplete, preserving only ``mjData.qpos`` and ``mjData.qvel``.
.. _Mainsimulation: .. _Mainsimulation:
+1 -1
View File
@@ -3103,7 +3103,7 @@ mjModel* mj_loadXML(const char* filename, const mjVFS* vfs, char* error, int err
mjSpec* mj_parseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz); mjSpec* mj_parseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz);
mjSpec* mj_parseXMLString(const char* xml, const mjVFS* vfs, char* error, int error_sz); mjSpec* mj_parseXMLString(const char* xml, const mjVFS* vfs, char* error, int error_sz);
mjModel* mj_compile(mjSpec* s, const mjVFS* vfs); mjModel* mj_compile(mjSpec* s, const mjVFS* vfs);
void mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d); int mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d);
int mj_saveLastXML(const char* filename, const mjModel* m, char* error, int error_sz); int mj_saveLastXML(const char* filename, const mjModel* m, char* error, int error_sz);
void mj_freeLastXML(void); void mj_freeLastXML(void);
void mj_copyBack(mjSpec* s, const mjModel* m); void mj_copyBack(mjSpec* s, const mjModel* m);
+2 -2
View File
@@ -106,8 +106,8 @@ MJAPI mjSpec* mj_parseXMLString(const char* xml, const mjVFS* vfs, char* error,
// Compile spec to model. // Compile spec to model.
MJAPI mjModel* mj_compile(mjSpec* s, const mjVFS* vfs); MJAPI mjModel* mj_compile(mjSpec* s, const mjVFS* vfs);
// Recompile spec to model, preserving the state. // Recompile spec to model, preserving the state, return 0 on success.
MJAPI void mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d); MJAPI int mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d);
// Update XML data structures with info from low-level model, save as MJCF. // Update XML data structures with info from low-level model, save as MJCF.
// If error is not NULL, it must have size error_sz. // If error is not NULL, it must have size error_sz.
+2 -2
View File
@@ -251,7 +251,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
('mj_recompile', ('mj_recompile',
FunctionDecl( FunctionDecl(
name='mj_recompile', name='mj_recompile',
return_type=ValueType(name='void'), return_type=ValueType(name='int'),
parameters=( parameters=(
FunctionParameterDecl( FunctionParameterDecl(
name='s', name='s',
@@ -278,7 +278,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
), ),
), ),
), ),
doc='Recompile spec to model, preserving the state.', doc='Recompile spec to model, preserving the state, return 0 on success.', # pylint: disable=line-too-long
)), )),
('mj_saveLastXML', ('mj_saveLastXML',
FunctionDecl( FunctionDecl(
+24
View File
@@ -352,5 +352,29 @@ class SpecsTest(absltest.TestCase):
self.assertEqual(model.nplugin, 1) self.assertEqual(model.nplugin, 1)
self.assertEqual(model.body_plugin[1], 0) self.assertEqual(model.body_plugin[1], 0)
def test_recompile_error(self):
main_xml = """
<mujoco>
<worldbody>
<body>
<geom size="0.1"/>
</body>
</worldbody>
</mujoco>
"""
spec = mujoco.MjSpec()
spec.from_string(main_xml)
model = spec.compile()
data = mujoco.MjData(model)
spec.add_material().name = 'yellow'
spec.add_material().name = 'yellow'
with self.assertRaisesRegex(
ValueError, "Error: repeated name 'yellow' in material"
):
spec.recompile(model, data)
if __name__ == '__main__': if __name__ == '__main__':
absltest.main() absltest.main()
+3 -1
View File
@@ -456,7 +456,9 @@ py::tuple RecompileSpec(raw::MjSpec* spec, const MjModelWrapper& old_m,
raw::MjModel* m = static_cast<raw::MjModel*>(mju_malloc(sizeof(mjModel))); raw::MjModel* m = static_cast<raw::MjModel*>(mju_malloc(sizeof(mjModel)));
m->buffer = nullptr; m->buffer = nullptr;
raw::MjData* d = mj_copyData(nullptr, old_m.get(), old_d.get()); raw::MjData* d = mj_copyData(nullptr, old_m.get(), old_d.get());
mj_recompile(spec, nullptr, m, d); if (mj_recompile(spec, nullptr, m, d)) {
throw py::value_error(mjs_getError(spec));
}
py::object m_pyobj = py::cast((MjModelWrapper(m))); py::object m_pyobj = py::cast((MjModelWrapper(m)));
py::object d_pyobj = py::object d_pyobj =
+9 -3
View File
@@ -90,8 +90,8 @@ mjModel* mj_compile(mjSpec* s, const mjVFS* vfs) {
// recompile spec into existing model and data while preserving the state // recompile spec to model, preserving the state, return 0 on success
void mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d) { [[nodiscard]] int mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d) {
mjCModel* modelC = static_cast<mjCModel*>(s->element); mjCModel* modelC = static_cast<mjCModel*>(s->element);
std::string state_name = "state"; std::string state_name = "state";
mjtNum time = 0; mjtNum time = 0;
@@ -99,13 +99,19 @@ void mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d) {
time = d->time; time = d->time;
modelC->SaveState(state_name, d->qpos, d->qvel, d->act, d->ctrl, d->mocap_pos, d->mocap_quat); modelC->SaveState(state_name, d->qpos, d->qvel, d->act, d->ctrl, d->mocap_pos, d->mocap_quat);
} }
modelC->Compile(vfs, &m); if (!modelC->Compile(vfs, &m)) {
if (d) {
mj_deleteData(d);
}
return -1;
};
if (d) { if (d) {
modelC->MakeData(m, &d); modelC->MakeData(m, &d);
modelC->RestoreState(state_name, m->qpos0, m->body_pos, m->body_quat, d->qpos, d->qvel, modelC->RestoreState(state_name, m->qpos0, m->body_pos, m->body_quat, d->qpos, d->qvel,
d->act, d->ctrl, d->mocap_pos, d->mocap_quat); d->act, d->ctrl, d->mocap_pos, d->mocap_quat);
d->time = time; d->time = time;
} }
return 0;
} }
+2 -2
View File
@@ -42,8 +42,8 @@ MJAPI mjSpec* mj_makeSpec(void);
// Compile spec to model. // Compile spec to model.
MJAPI mjModel* mj_compile(mjSpec* s, const mjVFS* vfs); MJAPI mjModel* mj_compile(mjSpec* s, const mjVFS* vfs);
// Recompile spec to model preserving the current state. // Recompile spec to model, preserving the state, return 0 on success.
MJAPI void mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d); MJAPI int mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d);
// Copy spec. // Copy spec.
MJAPI mjSpec* mj_copySpec(const mjSpec* s); MJAPI mjSpec* mj_copySpec(const mjSpec* s);
+21
View File
@@ -140,6 +140,27 @@ TEST_F(PluginTest, ActivatePlugin) {
mj_deleteModel(model); mj_deleteModel(model);
} }
TEST_F(MujocoTest, RecompileFails) {
mjSpec* spec = mj_makeSpec();
mjsBody* body = mjs_addBody(mjs_findBody(spec, "world"), 0);
mjsGeom* geom = mjs_addGeom(body, 0);
geom->type = mjGEOM_SPHERE;
geom->size[0] = 1;
mjModel* model = mj_compile(spec, 0);
mjData* data = mj_makeData(model);
mjsMaterial* mat1 = mjs_addMaterial(spec, 0);
mjsMaterial* mat2 = mjs_addMaterial(spec, 0);
mjs_setString(mat1->name, "yellow");
mjs_setString(mat2->name, "yellow");
EXPECT_EQ(mj_recompile(spec, 0, model, data), -1);
EXPECT_STREQ(mjs_getError(spec), "Error: repeated name 'yellow' in material");
mj_deleteSpec(spec);
}
// ------------------- test recompilation multiple files ----------------------- // ------------------- test recompilation multiple files -----------------------
TEST_F(PluginTest, RecompileCompare) { TEST_F(PluginTest, RecompileCompare) {
mjtNum tol = 0; mjtNum tol = 0;