From 71d1014e700362150a87b2e0d66b5e9fdd5b415b Mon Sep 17 00:00:00 2001 From: Yuval Tassa Date: Sun, 17 May 2026 16:47:20 -0700 Subject: [PATCH] Add mjENBL_DIAGEXACT for exact constraint diagonal. Fixes #2472 PiperOrigin-RevId: 916932908 Change-Id: Id23ac39b5cd996afc52990719a4e07c0cc7de600 --- doc/XMLreference.rst | 13 +++ doc/XMLschema.rst | 3 + doc/changelog.rst | 6 ++ doc/computation/index.rst | 14 ++++ doc/includes/references.h | 4 +- doc/modeling.rst | 19 +++++ include/mujoco/mjmodel.h | 4 +- python/mujoco/bindings_test.py | 2 +- python/mujoco/introspect/enums.py | 3 +- python/mujoco/introspect/enums_test.py | 3 +- src/engine/engine_core_constraint.c | 109 +++++++++++++++++++------ src/engine/engine_forward.c | 8 +- src/engine/engine_inverse.c | 7 ++ src/engine/engine_memory.h | 1 + src/engine/engine_support.c | 3 +- src/xml/xml_native_reader.cc | 4 +- src/xml/xml_native_writer.cc | 1 + test/engine/engine_forward_test.cc | 37 +++++++++ test/engine/engine_inverse_test.cc | 46 +++++++---- unity/Runtime/Bindings/MjBindings.cs | 3 +- wasm/codegen/generated/bindings.cc | 1 + 21 files changed, 234 insertions(+), 57 deletions(-) diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index d136291a..75d6fe03 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -697,6 +697,19 @@ from its default. order for the :ref:`sleep-init` policy to take effect. Second, it must be set in order for static quantities to be computed. See :ref:`implementation notes` for more details. +.. _option-flag-diagexact: + +:at:`diagexact`: :at-val:`[disable, enable], "disable"` + This flag enables computation of the exact diagonal of the constraint-space inertia matrix :math:`A = J M^{-1} J^T`, + replacing the body-based approximation normally used. The exact diagonal is computed from the whitened Jacobian + :math:`Y = J M^{-1/2}` as :math:`A_{ii} = \|Y_i\|^2`. This provides a more accurate + :ref:`impedance` computation, which can improve solver quality for models with complex kinematic + coupling. See :ref:`Diagonal approximation ` for details on the approximation errors that this flag + eliminates. The cost is one back-substitution with the Cholesky factor of the mass matrix per active constraint row; + if dual solvers are used (:ref:`PGS` or :ref:`NoSlip`), the cost is + negligible since :math:`Y` is computed anyway. Consider enabling this flag when observing divergence or poor + constraint quality, particularly in models with highly anisotropic body inertias or bodies operating far from the + initial configuration ``qpos0``. .. _compiler: diff --git a/doc/XMLschema.rst b/doc/XMLschema.rst index f394f2ac..6c9bb2a8 100755 --- a/doc/XMLschema.rst +++ b/doc/XMLschema.rst @@ -175,6 +175,9 @@ .. grid-item:: :ref:`sleep` + .. grid-item:: + :ref:`diagexact` + .. dropdown:: :ref:`compiler` |*| diff --git a/doc/changelog.rst b/doc/changelog.rst index 1f36fe6a..b05d0c2f 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -7,6 +7,12 @@ Upcoming version (not yet released) General ^^^^^^^ +- Added ``mjData.efc_Y``, the whitened constraint Jacobian :math:`Y = J M^{-1/2}`, allocated in the arena when + dual solvers (PGS or NoSlip) are used or when :ref:`diagexact` is enabled. +- Added the :ref:`diagexact` enable flag, which computes the exact diagonal of the + constraint-space inertia matrix at the current configuration, replacing the default compile-time approximation. + This improves solver quality for models with anisotropic inertias or complex kinematic coupling. See + :ref:`Exact diagonal ` for details. - The pseudo-random constraint visitation order in the :ref:`PGS solver`, introduced in the previous release, now uses a fixed seed. The previous implementation seeded with ``mjData.time``, which introduced subtle yet undesirable time dependence. diff --git a/doc/computation/index.rst b/doc/computation/index.rst index 059558ee..72c71410 100644 --- a/doc/computation/index.rst +++ b/doc/computation/index.rst @@ -1514,6 +1514,20 @@ constraint would satisfy and so we would achieve the desired interpolation effect. This of course does not hold exactly in general, but the goal here is to construct a sensible and intuitive parameterization of the constraint model and get the scaling right. +.. _soExactDiag: + +**Diagonal approximation:** The approximation has three sources of error: (i) it is frozen at ``qpos0`` rather than +evaluated at the current configuration; (ii) it averages the directional inverse inertia into a scalar, assuming +isotropy; and (iii) it treats the contributions of different bodies as independent, ignoring kinematic coupling through +shared DOFs. These errors are usually modest, but can become significant for models with highly anisotropic inertias or +long kinematic chains that operate far from ``qpos0``. In severe cases — particularly when the averaged inertia becomes +near-zero despite finite directional inertia — the regularizer :math:`R` becomes near-zero, making constraints +infinitely hard and causing divergence. The :ref:`diagexact` flag replaces the approximation with +the exact diagonal :math:`A_{ii} = \|Y_i\|^2`, where :math:`Y = J M^{-1/2}` is the whitened Jacobian, computed at the +current configuration. This eliminates all three sources of error at a modest runtime cost: computing :math:`Y` requires +a back-substitution with the Cholesky factor of the mass matrix for each active constraint row; if +:ref:`dual solvers` are used (PGS or NoSlip), the cost is negligible since :math:`Y` is computed anyway. + Next we explain how the reference acceleration is computed. As already mentioned, we use a spring-damper model parameterized by *damping* and *stiffness* coefficients element-wise: diff --git a/doc/includes/references.h b/doc/includes/references.h index b1c1f0df..84d244c4 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -508,10 +508,10 @@ typedef enum mjtEnableBit_ { // enable optional feature bitflags mjENBL_ENERGY = 1<<1, // energy computation mjENBL_FWDINV = 1<<2, // record solver statistics mjENBL_INVDISCRETE = 1<<3, // discrete-time inverse dynamics - // experimental features: mjENBL_SLEEP = 1<<4, // sleeping + mjENBL_DIAGEXACT = 1<<5, // exact diagonal of constraint inertia - mjNENABLE = 5 // number of enable flags + mjNENABLE = 6 // number of enable flags } mjtEnableBit; typedef enum mjtJoint_ { // type of degree of freedom mjJNT_FREE = 0, // global position and orientation (quat) (7) diff --git a/doc/modeling.rst b/doc/modeling.rst index 52a3ff75..5492c407 100644 --- a/doc/modeling.rst +++ b/doc/modeling.rst @@ -1855,6 +1855,25 @@ in a visible way, and the energy fluctuates around the initial value instead of +.. _CConstraintImpedance: + +Constraint accuracy +~~~~~~~~~~~~~~~~~~~ +MuJoCo's :ref:`constraint impedance` computation relies on an approximate diagonal of the constraint-space +inertia matrix, computed once at compile time from the initial configuration ``qpos0``. + +In the vast majority of models this approximation is entirely adequate. However, in certain situations—such as models +with highly anisotropic inertias, complex kinematic chains, or bodies operating far from ``qpos0``—the approximation +may become inaccurate. This can occasionally manifest as unexplained solver divergence (``badqacc`` warnings), +excessive penetration, unrealistic slip, or poor solver convergence. A useful diagnostic is the +:ref:`fwdinv` flag: if the forward-inverse discrepancy is large, inaccurate constraint scaling may +be a contributing factor. + +If you suspect that the compile-time approximation is insufficient for your model, you can enable the +:ref:`diagexact` flag to compute the exact diagonal at runtime. See :ref:`Diagonal approximation +` for details on the underlying mechanics and performance implications. + + .. |image3| image:: images/modeling/tendonwraps.png :width: 500px .. |image4| image:: images/modeling/particle.png diff --git a/include/mujoco/mjmodel.h b/include/mujoco/mjmodel.h index 62de7144..6e4ddb5d 100644 --- a/include/mujoco/mjmodel.h +++ b/include/mujoco/mjmodel.h @@ -81,10 +81,10 @@ typedef enum mjtEnableBit_ { // enable optional feature bitflags mjENBL_ENERGY = 1<<1, // energy computation mjENBL_FWDINV = 1<<2, // record solver statistics mjENBL_INVDISCRETE = 1<<3, // discrete-time inverse dynamics - // experimental features: mjENBL_SLEEP = 1<<4, // sleeping + mjENBL_DIAGEXACT = 1<<5, // exact diagonal of constraint inertia - mjNENABLE = 5 // number of enable flags + mjNENABLE = 6 // number of enable flags } mjtEnableBit; diff --git a/python/mujoco/bindings_test.py b/python/mujoco/bindings_test.py index 8df15728..a79b2d1a 100644 --- a/python/mujoco/bindings_test.py +++ b/python/mujoco/bindings_test.py @@ -966,7 +966,7 @@ Euler integrator, semi-implicit in velocity. self.assertEqual(mujoco.mjtEnableBit.mjENBL_OVERRIDE, 1 << 0) self.assertEqual(mujoco.mjtEnableBit.mjENBL_ENERGY, 1 << 1) self.assertEqual(mujoco.mjtEnableBit.mjENBL_FWDINV, 1 << 2) - self.assertEqual(mujoco.mjtEnableBit.mjNENABLE, 5) + self.assertEqual(mujoco.mjtEnableBit.mjNENABLE, 6) self.assertEqual(mujoco.mjtGeom.mjGEOM_PLANE, 0) self.assertEqual(mujoco.mjtGeom.mjGEOM_HFIELD, 1) self.assertEqual(mujoco.mjtGeom.mjGEOM_SPHERE, 2) diff --git a/python/mujoco/introspect/enums.py b/python/mujoco/introspect/enums.py index 1ec77cb1..6518b2dc 100644 --- a/python/mujoco/introspect/enums.py +++ b/python/mujoco/introspect/enums.py @@ -60,7 +60,8 @@ ENUMS: Mapping[str, EnumDecl] = dict([ ('mjENBL_FWDINV', 4), ('mjENBL_INVDISCRETE', 8), ('mjENBL_SLEEP', 16), - ('mjNENABLE', 5), + ('mjENBL_DIAGEXACT', 32), + ('mjNENABLE', 6), ]), )), ('mjtJoint', diff --git a/python/mujoco/introspect/enums_test.py b/python/mujoco/introspect/enums_test.py index e373034b..67b51961 100644 --- a/python/mujoco/introspect/enums_test.py +++ b/python/mujoco/introspect/enums_test.py @@ -43,7 +43,8 @@ class EnumsTest(absltest.TestCase): ('mjENBL_FWDINV', 1<<2), ('mjENBL_INVDISCRETE', 1<<3), ('mjENBL_SLEEP', 1<<4), - ('mjNENABLE', 5))) + ('mjENBL_DIAGEXACT', 1<<5), + ('mjNENABLE', 6))) # values mostly increment by one with occasional overrides def test_mjtGeom(self): # pylint: disable=invalid-name diff --git a/src/engine/engine_core_constraint.c b/src/engine/engine_core_constraint.c index 428c6fdc..6600c183 100644 --- a/src/engine/engine_core_constraint.c +++ b/src/engine/engine_core_constraint.c @@ -2812,15 +2812,10 @@ void mj_makeConstraint(const mjModel* m, mjData* d) { } -// compute efc_AR -void mj_projectConstraint(const mjModel* m, mjData* d) { +// compute Y = J*M^{-1/2}; if flg_diagexact, overwrite efc_diagApprox with ||Y_i||^2 +static void mj_makeY(const mjModel* m, mjData* d, int flg_diagexact) { int nefc = d->nefc, nv = m->nv; - // nothing to do - if (nefc == 0 || !mj_isDual(m)) { - return; - } - mj_markStack(d); // inverse square root of D from inertia LDL decomposition @@ -2843,10 +2838,8 @@ void mj_projectConstraint(const mjModel* m, mjData* d) { return; } - // markers for merged dofs, initialized to -1 - int* marker = mjSTACKALLOC(d, nv, int); - // pre-count Y_rownnz, Y_rowadr, nY (total nonzeros) + int* marker = mjSTACKALLOC(d, nv, int); d->nY = computeY_precount(d->efc_Y_rownnz, d->efc_Y_rowadr, nefc, nv, d->efc_J_rownnz, d->efc_J_rowadr, d->efc_J_colind, m->M_rownnz, m->M_rowadr, m->M_colind, marker); @@ -2867,12 +2860,57 @@ void mj_projectConstraint(const mjModel* m, mjData* d) { d->efc_J, d->efc_J_rownnz, d->efc_J_rowadr, d->efc_J_colind, m->dof_parentid); - // in-place sparse back-substitution: Y <- Y * M^-1/2 computeY_backsub(d->efc_Y, d->efc_Y_rownnz, d->efc_Y_rowadr, d->efc_Y_colind, nefc, d->qLD, m->M_rownnz, m->M_rowadr, m->M_colind, sqrtInvD); + // overwrite diagApprox with exact diagonal: diagApprox[i] = ||Y_i||^2 + if (flg_diagexact) { + for (int i=0; i < nefc; i++) { + int adr = d->efc_Y_rowadr[i]; + int nnz = d->efc_Y_rownnz[i]; + d->efc_diagApprox[i] = mju_dot(d->efc_Y+adr, d->efc_Y+adr, nnz); + } + } + } + + // dense Y = backsubM2(J')' and its transpose + else { + // arena-allocate efc_Y + d->nY = nefc * nv; + d->efc_Y = mj_arenaAllocByte(d, sizeof(mjtNum) * d->nY, _Alignof(mjtNum)); + if (!d->efc_Y) { + mj_warning(d, mjWARN_CNSTRFULL, d->narena); + mj_clearEfc(d); + d->parena = d->ncon * sizeof(mjContact); + mj_freeStack(d); + return; + } + + // Y = backsubM2(J')' + mj_solveM2(m, d, d->efc_Y, d->efc_J, sqrtInvD, nefc); + + // overwrite diagApprox with exact diagonal: diagApprox[i] = ||Y_i||^2 + if (flg_diagexact) { + for (int i=0; i < nefc; i++) { + d->efc_diagApprox[i] = mju_dot(d->efc_Y+i*nv, d->efc_Y+i*nv, nv); + } + } + } + + mj_freeStack(d); +} + + +// assemble AR = Y*Y' + diag(R) for dual solver +static void mj_makeAR(const mjModel* m, mjData* d) { + int nefc = d->nefc, nv = m->nv; + + mj_markStack(d); + + // sparse + if (mj_isSparse(m)) { // Y supernodes are identical to J supernodes const int* Y_rowsuper = d->efc_J_rowsuper; @@ -2934,20 +2972,6 @@ void mj_projectConstraint(const mjModel* m, mjData* d) { // dense Y = backsubM2(J')' and its transpose else { - // arena-allocate efc_Y - d->nY = nefc * nv; - d->efc_Y = mj_arenaAllocByte(d, sizeof(mjtNum) * d->nY, _Alignof(mjtNum)); - if (!d->efc_Y) { - mj_warning(d, mjWARN_CNSTRFULL, d->narena); - mj_clearEfc(d); - d->parena = d->ncon * sizeof(mjContact); - mj_freeStack(d); - return; - } - - // Y = backsubM2(J')' - mj_solveM2(m, d, d->efc_Y, d->efc_J, sqrtInvD, nefc); - // arena-allocate efc_AR d->nA = nefc * nefc; d->efc_AR = mj_arenaAllocByte(d, sizeof(mjtNum) * d->nA, _Alignof(mjtNum)); @@ -2976,6 +3000,41 @@ void mj_projectConstraint(const mjModel* m, mjData* d) { } +// compute efc_Y, optionally efc_diagApprox, optionally efc_AR +void mj_projectConstraint(const mjModel* m, mjData* d) { + int nefc = d->nefc; + + // nothing to do + if (!nefc) { + return; + } + + int isDual = mj_isDual(m); + int diagexact = mjENABLED(mjENBL_DIAGEXACT); + + // compute Y = J*M^{-1/2}; overwrite diagApprox if diagexact + if (isDual || diagexact) { + mj_makeY(m, d, diagexact); + } + + // recompute impedance from exact diagonal + if (diagexact && d->nefc) { + mj_makeImpedance(m, d); + + // re-gather island D/R + if (d->nisland) { + mju_gather(d->iefc_D, d->efc_D, d->map_iefc2efc, d->nefc); + mju_gather(d->iefc_R, d->efc_R, d->map_iefc2efc, d->nefc); + } + } + + // assemble AR for dual solver + if (isDual && d->nefc) { + mj_makeAR(m, d); + } +} + + // compute efc_vel, efc_aref void mj_referenceConstraint(const mjModel* m, mjData* d) { int nefc = d->nefc; diff --git a/src/engine/engine_forward.c b/src/engine/engine_forward.c index 450c628f..f129e8d8 100644 --- a/src/engine/engine_forward.c +++ b/src/engine/engine_forward.c @@ -207,14 +207,14 @@ void mj_fwdPosition(const mjModel* m, mjData* d) { mj_island(m, d); TM_END(mjTIMER_POS_MAKE); - TM_RESTART; - mj_transmission(m, d); - TM_ADD(mjTIMER_POS_KINEMATICS); - TM_RESTART; mj_projectConstraint(m, d); TM_END(mjTIMER_POS_PROJECT); + TM_RESTART; + mj_transmission(m, d); + TM_ADD(mjTIMER_POS_KINEMATICS); + TM_END1(mjTIMER_POSITION); } diff --git a/src/engine/engine_inverse.c b/src/engine/engine_inverse.c index f76a9eca..2bfbe3c0 100644 --- a/src/engine/engine_inverse.c +++ b/src/engine/engine_inverse.c @@ -59,6 +59,13 @@ void mj_invPosition(const mjModel* m, mjData* d) { mj_makeConstraint(m, d); TM_END(mjTIMER_POS_MAKE); + // compute exact diagonal if enabled + if (mjENABLED(mjENBL_DIAGEXACT)) { + TM_RESTART; + mj_projectConstraint(m, d); + TM_END(mjTIMER_POS_PROJECT); + } + TM_RESTART; mj_transmission(m, d); TM_ADD(mjTIMER_POS_KINEMATICS); diff --git a/src/engine/engine_memory.h b/src/engine/engine_memory.h index 6c408e5b..11459a79 100644 --- a/src/engine/engine_memory.h +++ b/src/engine/engine_memory.h @@ -76,6 +76,7 @@ static inline void mj_clearEfc(mjData* d) { #undef X d->nefc = 0; d->nisland = 0; + d->nJ = d->nY = d->nA = 0; d->contact = (mjContact*) d->arena; // if any contacts are allocated, clear their efc_address diff --git a/src/engine/engine_support.c b/src/engine/engine_support.c index 18edd0de..6a247f09 100644 --- a/src/engine/engine_support.c +++ b/src/engine/engine_support.c @@ -77,7 +77,8 @@ const char* mjENABLESTRING[mjNENABLE] = { "Energy", "Fwdinv", "InvDiscrete", - "Sleep" + "Sleep", + "DiagExact" }; diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index f249df3f..9b7ee999 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -118,7 +118,8 @@ std::vector MJCF[nMJCF] = { {"flag", "?", "constraint", "equality", "frictionloss", "limit", "contact", "spring", "damper", "gravity", "clampctrl", "warmstart", "filterparent", "actuation", "refsafe", "sensor", "midphase", "eulerdamp", "autoreset", "nativeccd", "island", - "override", "energy", "fwdinv", "invdiscrete", "multiccd", "sleep"}, + "override", "energy", "fwdinv", "invdiscrete", "multiccd", "sleep", + "diagexact"}, {">"}, {"size", "*", "memory", "njmax", "nconmax", "nstack", "nuserdata", "nkey", @@ -1297,6 +1298,7 @@ void mjXReader::Option(XMLElement* section, mjOption* opt) { READENBL("fwdinv", mjENBL_FWDINV) READENBL("invdiscrete", mjENBL_INVDISCRETE) READENBL("sleep", mjENBL_SLEEP) + READENBL("diagexact", mjENBL_DIAGEXACT) #undef READENBL } } diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index ff17f141..d1dda462 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -1126,6 +1126,7 @@ void mjXWriter::Option(XMLElement* root) { WRITEENBL("fwdinv", mjENBL_FWDINV) WRITEENBL("invdiscrete", mjENBL_INVDISCRETE) WRITEENBL("sleep", mjENBL_SLEEP) + WRITEENBL("diagexact", mjENBL_DIAGEXACT) #undef WRITEENBL } diff --git a/test/engine/engine_forward_test.cc b/test/engine/engine_forward_test.cc index 63e1c2f7..bfcba7b6 100644 --- a/test/engine/engine_forward_test.cc +++ b/test/engine/engine_forward_test.cc @@ -917,6 +917,43 @@ TEST_F(ImplicitIntegratorTest, MidpointEligibility) { mj_deleteModel(m); } +// model with degenerate translational inertia +TEST_F(ForwardTest, DegenerateInertia) { + static constexpr char xml[] = R"( + + + + + + + + + + + + + + )"; + char error[1024]; + mjModel* model = LoadModelFromString(xml, error, sizeof(error)); + ASSERT_THAT(model, NotNull()) << error; + mjData* data = mj_makeData(model); + + for (int i = 0; i < 1000; i++) { + mj_step(model, data); + EXPECT_EQ(data->warning[mjWARN_BADQACC].number, 0) + << "divergence at timestep " << i; + if (data->warning[mjWARN_BADQACC].number != 0) { + break; + } + } + + mj_deleteData(data); + mj_deleteModel(model); +} + TEST_F(ForwardTest, ControlClamping) { static constexpr char xml[] = R"( diff --git a/test/engine/engine_inverse_test.cc b/test/engine/engine_inverse_test.cc index 97a48e2d..b698a00b 100644 --- a/test/engine/engine_inverse_test.cc +++ b/test/engine/engine_inverse_test.cc @@ -48,28 +48,38 @@ TEST_F(InverseTest, ForwardInverseMatch) { // solver names for diagnostics const char* solver_name[] = {"PGS", "CG", "Newton"}; - for (mjtSolver solver : {mjSOL_PGS, mjSOL_CG, mjSOL_NEWTON}) { - model->opt.solver = solver; - mj_resetData(model, data); - - // simulate, call mj_forward - for (int i = 0; i < kSteps; ++i) { - mj_step(model, data); + for (int diagexact = 0; diagexact < 2; diagexact++) { + if (diagexact) { + model->opt.enableflags |= mjENBL_DIAGEXACT; + } else { + model->opt.enableflags &= ~mjENBL_DIAGEXACT; } - mj_forward(model, data); - // call built-in testing function - mj_compareFwdInv(model, data); + for (mjtSolver solver : {mjSOL_PGS, mjSOL_CG, mjSOL_NEWTON}) { + model->opt.solver = solver; + mj_resetData(model, data); - // per-solver tolerances - mjtNum epsilon; - switch (solver) { - case mjSOL_PGS: epsilon = MjTol(1e-6, 1e-2); break; - case mjSOL_CG: epsilon = MjTol(1e-3, 1e-1); break; - case mjSOL_NEWTON: epsilon = MjTol(1e-10, 5e-3); break; + // simulate, call mj_forward + for (int i = 0; i < kSteps; ++i) { + mj_step(model, data); + } + mj_forward(model, data); + + // call built-in testing function + mj_compareFwdInv(model, data); + + // per-solver tolerances + mjtNum epsilon; + switch (solver) { + case mjSOL_PGS: epsilon = MjTol(1e-6, 1e-2); break; + case mjSOL_CG: epsilon = MjTol(1e-3, 1e0); break; + case mjSOL_NEWTON: epsilon = MjTol(1e-10, 1e-2); break; + } + EXPECT_LT(data->solver_fwdinv[0], epsilon) + << solver_name[solver] << " diagexact=" << diagexact; + EXPECT_LT(data->solver_fwdinv[1], epsilon) + << solver_name[solver] << " diagexact=" << diagexact; } - EXPECT_LT(data->solver_fwdinv[0], epsilon) << solver_name[solver]; - EXPECT_LT(data->solver_fwdinv[1], epsilon) << solver_name[solver]; } mj_deleteData(data); diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index a85fd5bc..8b66b5a5 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -146,7 +146,8 @@ public enum mjtEnableBit : int{ mjENBL_FWDINV = 4, mjENBL_INVDISCRETE = 8, mjENBL_SLEEP = 16, - mjNENABLE = 5, + mjENBL_DIAGEXACT = 32, + mjNENABLE = 6, } public enum mjtJoint : int{ mjJNT_FREE = 0, diff --git a/wasm/codegen/generated/bindings.cc b/wasm/codegen/generated/bindings.cc index 1ee87963..21d0565c 100644 --- a/wasm/codegen/generated/bindings.cc +++ b/wasm/codegen/generated/bindings.cc @@ -11009,6 +11009,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { .value("mjENBL_FWDINV", mjENBL_FWDINV) .value("mjENBL_INVDISCRETE", mjENBL_INVDISCRETE) .value("mjENBL_SLEEP", mjENBL_SLEEP) + .value("mjENBL_DIAGEXACT", mjENBL_DIAGEXACT) .value("mjNENABLE", mjNENABLE); enum_("mjtEq") .value("mjEQ_CONNECT", mjEQ_CONNECT)