Add mjENBL_DIAGEXACT for exact constraint diagonal. Fixes #2472
PiperOrigin-RevId: 916932908 Change-Id: Id23ac39b5cd996afc52990719a4e07c0cc7de600
This commit is contained in:
committed by
Copybara-Service
parent
66d764a116
commit
71d1014e70
@@ -697,6 +697,19 @@ from its default.
|
||||
order for the :ref:`sleep-init<body-sleep>` policy to take effect. Second, it must be set in order for static
|
||||
quantities to be computed. See :ref:`implementation notes<siSleep>` 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<soParameters>` computation, which can improve solver quality for models with complex kinematic
|
||||
coupling. See :ref:`Diagonal approximation <soExactDiag>` 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<option-solver>` or :ref:`NoSlip<option-noslip_iterations>`), 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:
|
||||
|
||||
|
||||
@@ -175,6 +175,9 @@
|
||||
.. grid-item::
|
||||
:ref:`sleep<option-flag-sleep>`
|
||||
|
||||
.. grid-item::
|
||||
:ref:`diagexact<option-flag-diagexact>`
|
||||
|
||||
|
||||
.. dropdown:: :ref:`compiler<compiler>` |*|
|
||||
|
||||
|
||||
@@ -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<option-flag-diagexact>` is enabled.
|
||||
- Added the :ref:`diagexact<option-flag-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 <soExactDiag>` for details.
|
||||
- The pseudo-random constraint visitation order in the :ref:`PGS solver<soAlgorithms>`, introduced in the previous
|
||||
release, now uses a fixed seed. The previous implementation seeded with ``mjData.time``, which introduced subtle yet
|
||||
undesirable time dependence.
|
||||
|
||||
@@ -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<option-flag-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<soAlgorithms>` 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:
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1855,6 +1855,25 @@ in a visible way, and the energy fluctuates around the initial value instead of
|
||||
</worldbody>
|
||||
|
||||
|
||||
.. _CConstraintImpedance:
|
||||
|
||||
Constraint accuracy
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
MuJoCo's :ref:`constraint impedance<soParameters>` 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<option-flag-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<option-flag-diagexact>` flag to compute the exact diagonal at runtime. See :ref:`Diagonal approximation
|
||||
<soExactDiag>` for details on the underlying mechanics and performance implications.
|
||||
|
||||
|
||||
.. |image3| image:: images/modeling/tendonwraps.png
|
||||
:width: 500px
|
||||
.. |image4| image:: images/modeling/particle.png
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -77,7 +77,8 @@ const char* mjENABLESTRING[mjNENABLE] = {
|
||||
"Energy",
|
||||
"Fwdinv",
|
||||
"InvDiscrete",
|
||||
"Sleep"
|
||||
"Sleep",
|
||||
"DiagExact"
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -118,7 +118,8 @@ std::vector<const char*> 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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"(
|
||||
<mujoco>
|
||||
<option integrator="implicitfast" cone="elliptic">
|
||||
<flag gravity="disable" diagexact="enable"/>
|
||||
</option>
|
||||
<worldbody>
|
||||
<body name="1" pos="0.05 0.3 0">
|
||||
<joint name="1" axis="0 1 0"/>
|
||||
<geom type="capsule" size="0.1 0.5"/>
|
||||
</body>
|
||||
<body name="2">
|
||||
<joint name="2" axis="1 0 0" stiffness="1" springref="90"/>
|
||||
<geom type="capsule" size="0.1 0.5"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
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"(
|
||||
<mujoco>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>("mjtEq")
|
||||
.value("mjEQ_CONNECT", mjEQ_CONNECT)
|
||||
|
||||
Reference in New Issue
Block a user