Add support for joint and tendon frictionloss to MJX.

PiperOrigin-RevId: 670679777
Change-Id: I2eb56d013c11d4e1851f1c2edd6d98faaf854db9
This commit is contained in:
Baruch Tabanpour
2024-09-03 13:16:27 -07:00
committed by Copybara-Service
parent 2d3d5415b7
commit 49711fa14f
8 changed files with 143 additions and 44 deletions
+1
View File
@@ -45,6 +45,7 @@ MJX
- Added support for :ref:`implicitfast integration<geIntegration>` for all cases except
:doc:`fluid drag <computation/fluid>`.
- Fixed a bug where ``qLDiagInv`` had the wrong size for sparse mass matrices.
- Added support for joint and tendon :ref:`frictionloss <coFriction>`.
Bug fixes
^^^^^^^^^
+1 -3
View File
@@ -199,7 +199,7 @@ The following features are **fully supported** in MJX:
- ``PLANE``, ``HFIELD``, ``SPHERE``, ``CAPSULE``, ``BOX``, ``MESH`` are fully implemented. ``ELLIPSOID`` and
``CYLINDER`` are implemented but only collide with other primitives, note that ``BOX`` is implemented as a mesh.
* - :ref:`Constraint <mjtConstraint>`
- ``EQUALITY``, ``LIMIT_JOINT``, ``CONTACT_FRICTIONLESS``, ``CONTACT_PYRAMIDAL``, ``CONTACT_ELLIPTIC``
- ``EQUALITY``, ``LIMIT_JOINT``, ``CONTACT_FRICTIONLESS``, ``CONTACT_PYRAMIDAL``, ``CONTACT_ELLIPTIC``, ``FRICTION_DOF``, ``FRICTION_TENDON``
* - :ref:`Equality <mjtEq>`
- ``CONNECT``, ``WELD``, ``JOINT``, ``TENDON``
* - :ref:`Integrator <mjtIntegrator>`
@@ -228,8 +228,6 @@ The following features are **in development** and coming soon:
* - :ref:`Geom <mjtGeom>`
- ``SDF``. Collisions between (``SPHERE``, ``BOX``, ``MESH``, ``HFIELD``) and ``CYLINDER``. Collisions between
(``BOX``, ``MESH``, ``HFIELD``) and ``ELLIPSOID``.
* - :ref:`Constraint <mjtConstraint>`
- :ref:`Frictionloss <coFriction>`, ``FRICTION_DOF``
* - :ref:`Integrator <mjtIntegrator>`
- ``IMPLICIT``
* - Dynamics
+65 -14
View File
@@ -45,6 +45,7 @@ class _Efc(PyTreeNode):
solref: jax.Array
solimp: jax.Array
margin: jax.Array
frictionloss: jax.Array
def _kbi(
@@ -119,8 +120,9 @@ def _efc_equality_connect(m: Model, d: Data) -> Optional[_Efc]:
j = (jacp1 - jacp2).T
pos_imp = math.norm(pos)
invweight = m.body_invweight0[obj1id, 0] + m.body_invweight0[obj2id, 0]
zero = jp.zeros_like(pos)
return _row(j, pos, pos_imp, invweight, solref, solimp, jp.zeros_like(pos))
return _row(j, pos, pos_imp, invweight, solref, solimp, zero, zero)
args = (m.eq_obj1id, m.eq_obj2id, m.eq_data, m.eq_solref, m.eq_solimp)
args = jax.tree_util.tree_map(lambda x: x[eq_id], args)
@@ -165,8 +167,9 @@ def _efc_equality_weld(m: Model, d: Data) -> Optional[_Efc]:
pos_imp = math.norm(pos)
invweight = m.body_invweight0[obj1id] + m.body_invweight0[obj2id]
invweight = jp.repeat(invweight, 3, axis=0)
zero = jp.zeros_like(pos)
return _row(j, pos, pos_imp, invweight, solref, solimp, jp.zeros_like(pos))
return _row(j, pos, pos_imp, invweight, solref, solimp, zero, zero)
args = (m.eq_obj1id, m.eq_obj2id, m.eq_data, m.eq_solref, m.eq_solimp)
args = jax.tree_util.tree_map(lambda x: x[eq_id], args)
@@ -194,8 +197,9 @@ def _efc_equality_joint(m: Model, d: Data) -> Optional[_Efc]:
j = jp.zeros((m.nv)).at[dofadr2].set(-deriv).at[dofadr1].set(1.0)
invweight = m.dof_invweight0[dofadr1]
invweight += m.dof_invweight0[dofadr2] * (obj2id > -1)
zero = jp.zeros_like(pos)
return _row(j, pos, pos, invweight, solref, solimp, jp.zeros_like(pos))
return _row(j, pos, pos, invweight, solref, solimp, zero, zero)
args = (m.eq_obj1id, m.eq_obj2id, m.eq_data, m.eq_solref, m.eq_solimp)
args = jax.tree_util.tree_map(lambda x: x[eq_id], args)
@@ -232,8 +236,9 @@ def _efc_equality_tendon(m: Model, d: Data) -> Optional[_Efc]:
pos = pos1 - jp.dot(data[:5], dif_power)
deriv = jp.dot(data[1:5], dif_power[:4] * jp.arange(1, 5)) * (obj2id > -1)
j = jac1 + jac2 * -deriv
zero = jp.zeros_like(pos)
return _row(j, pos, pos, invweight, solref, solimp, jp.zeros_like(pos))
return _row(j, pos, pos, invweight, solref, solimp, zero, zero)
inv1, inv2 = m.tendon_invweight0[obj1id], m.tendon_invweight0[obj2id]
jac1, jac2 = d.ten_J[obj1id], d.ten_J[obj2id]
@@ -245,9 +250,32 @@ def _efc_equality_tendon(m: Model, d: Data) -> Optional[_Efc]:
def _efc_friction(m: Model, d: Data) -> Optional[_Efc]:
# TODO(robotics-team): implement _instantiate_friction
del m, d
return None
"""Calculates constraint rows for dof frictionloss."""
dof_id = np.nonzero(m.dof_hasfrictionloss)[0]
tendon_id = np.nonzero(m.tendon_hasfrictionloss)[0]
size = dof_id.size + tendon_id.size
if (m.opt.disableflags & DisableBit.FRICTIONLOSS) or (size == 0):
return None
args_dof = (jp.eye(m.nv), m.dof_frictionloss, m.dof_invweight0, m.dof_solref)
args_dof += (m.dof_solimp,)
args_dof = jax.tree_util.tree_map(lambda x: x[dof_id], args_dof)
args_ten = (d.ten_J, m.tendon_frictionloss, m.tendon_invweight0)
args_ten += (m.tendon_solref_fri, m.tendon_solimp_fri)
args_ten = jax.tree_util.tree_map(lambda x: x[tendon_id], args_ten)
args = jax.tree_util.tree_map(
lambda *x: jp.concatenate(x), args_dof, args_ten
)
@jax.vmap
def rows(j, frictionloss, invweight, solref, solimp):
z = jp.zeros_like(frictionloss)
return _row(j, z, z, invweight, solref, solimp, z, frictionloss)
return rows(*args)
def _efc_limit_ball(m: Model, d: Data) -> Optional[_Efc]:
@@ -267,9 +295,10 @@ def _efc_limit_ball(m: Model, d: Data) -> Optional[_Efc]:
active = pos < 0
j = jp.zeros(m.nv).at[jp.arange(3) + dofadr].set(-axis)
invweight = m.dof_invweight0[dofadr]
z = jp.zeros_like(pos)
return _row(
j * active, pos * active, pos, invweight, solref, solimp, jnt_margin
j * active, pos * active, pos, invweight, solref, solimp, jnt_margin, z
)
args = (m.jnt_qposadr, m.jnt_dofadr, m.jnt_range, m.jnt_margin, m.jnt_solref)
@@ -296,9 +325,10 @@ def _efc_limit_slide_hinge(m: Model, d: Data) -> Optional[_Efc]:
active = pos < 0
j = jp.zeros(m.nv).at[dofadr].set((dist_min < dist_max) * 2 - 1)
invweight = m.dof_invweight0[dofadr]
z = jp.zeros_like(pos)
return _row(
j * active, pos * active, pos, invweight, solref, solimp, jnt_margin
j * active, pos * active, pos, invweight, solref, solimp, jnt_margin, z
)
args = (m.jnt_qposadr, m.jnt_dofadr, m.jnt_range, m.jnt_margin, m.jnt_solref)
@@ -332,8 +362,11 @@ def _efc_limit_tendon(m: Model, d: Data) -> Optional[_Efc]:
pos = jp.minimum(dist_min, dist_max) - margin
active = pos < 0
j = jax.vmap(jp.multiply)(j, ((dist_min < dist_max) * 2 - 1) * active)
zero = jp.zeros_like(pos)
return jax.vmap(_row)(j, pos * active, pos, invweight, solref, solimp, margin)
return jax.vmap(_row)(
j, pos * active, pos, invweight, solref, solimp, margin, zero
)
def _efc_contact_frictionless(m: Model, d: Data) -> Optional[_Efc]:
@@ -362,6 +395,7 @@ def _efc_contact_frictionless(m: Model, d: Data) -> Optional[_Efc]:
c.solref,
c.solimp,
c.includemargin,
jp.zeros_like(pos),
)
contact = jax.tree_util.tree_map(lambda x: x[con_id], d.contact)
@@ -406,6 +440,7 @@ def _efc_contact_pyramidal(m: Model, d: Data, condim: int) -> Optional[_Efc]:
c.solref,
c.solimp,
c.includemargin,
jp.zeros_like(pos),
)
contact = jax.tree_util.tree_map(lambda x: x[con_id], d.contact)
@@ -450,6 +485,7 @@ def _efc_contact_elliptic(m: Model, d: Data, condim: int) -> Optional[_Efc]:
solref,
c.solimp,
c.includemargin,
jp.zeros_like(pos),
)
contact = jax.tree_util.tree_map(lambda x: x[con_id], d.contact)
@@ -460,7 +496,8 @@ def _efc_contact_elliptic(m: Model, d: Data, condim: int) -> Optional[_Efc]:
def counts(efc_type: np.ndarray) -> Tuple[int, int, int, int]:
"""Returns equality, friction, limit, and contact constraint counts."""
ne = (efc_type == ConstraintType.EQUALITY).sum()
nf = 0 # no support for friction loss yet
nf = (efc_type == ConstraintType.FRICTION_DOF).sum()
nf += (efc_type == ConstraintType.FRICTION_TENDON).sum()
nl = (efc_type == ConstraintType.LIMIT_JOINT).sum()
nl += (efc_type == ConstraintType.LIMIT_TENDON).sum()
nc_f = (efc_type == ConstraintType.CONTACT_FRICTIONLESS).sum()
@@ -488,6 +525,20 @@ def make_efc_type(
num_rows += (m.eq_type == EqType.TENDON).sum()
efc_types += [ConstraintType.EQUALITY] * num_rows
if not m.opt.disableflags & DisableBit.FRICTIONLOSS:
nf_dof = (
m.dof_hasfrictionloss.sum()
if isinstance(m, Model)
else (m.dof_frictionloss > 0).sum()
)
efc_types += [ConstraintType.FRICTION_DOF] * nf_dof
nf_tendon = (
m.tendon_hasfrictionloss.sum()
if isinstance(m, Model)
else (m.tendon_frictionloss > 0).sum()
)
efc_types += [ConstraintType.FRICTION_TENDON] * nf_tendon
if not m.opt.disableflags & DisableBit.LIMIT:
efc_types += [ConstraintType.LIMIT_JOINT] * m.jnt_limited.sum()
efc_types += [ConstraintType.LIMIT_TENDON] * m.tendon_limited.sum()
@@ -570,12 +621,12 @@ def make_constraint(m: Model, d: Data) -> Data:
k, b, imp = _kbi(m, efc.solref, efc.solimp, efc.pos_imp)
r = jp.maximum(efc.invweight * (1 - imp) / imp, mujoco.mjMINVAL)
aref = -b * (efc.J @ d.qvel) - k * imp * efc.pos_aref
return aref, r, efc.pos_aref + efc.margin, efc.margin
return aref, r, efc.pos_aref + efc.margin, efc.margin, efc.frictionloss
aref, r, pos, margin = fn(efc)
aref, r, pos, margin, frictionloss = fn(efc)
d = d.replace(
efc_J=efc.J, efc_D=1 / r, efc_aref=aref, efc_pos=pos, efc_margin=margin
)
d = d.replace(efc_frictionloss=jp.zeros_like(r))
d = d.replace(efc_frictionloss=frictionloss)
return d
+26 -10
View File
@@ -60,12 +60,17 @@ class ConstraintTest(parameterized.TestCase):
order = test_util.efc_order(m, d, dx)
d_efc_j = d.efc_J.reshape((-1, m.nv))
_assert_eq(d_efc_j, dx.efc_J[order][:d.nefc], 'efc_J')
_assert_eq(0, dx.efc_J[order][d.nefc:], 'efc_J')
_assert_eq(d.efc_aref, dx.efc_aref[order][:d.nefc], 'efc_aref')
_assert_eq(0, dx.efc_aref[order][d.nefc:], 'efc_aref')
_assert_eq(d.efc_D, dx.efc_D[order][:d.nefc], 'efc_D')
_assert_eq(d.efc_pos, dx.efc_pos[order][:d.nefc], 'efc_pos')
_assert_eq(d_efc_j, dx.efc_J[order][: d.nefc], 'efc_J')
_assert_eq(0, dx.efc_J[order][d.nefc :], 'efc_J')
_assert_eq(d.efc_aref, dx.efc_aref[order][: d.nefc], 'efc_aref')
_assert_eq(0, dx.efc_aref[order][d.nefc :], 'efc_aref')
_assert_eq(d.efc_D, dx.efc_D[order][: d.nefc], 'efc_D')
_assert_eq(d.efc_pos, dx.efc_pos[order][: d.nefc], 'efc_pos')
_assert_eq(
d.efc_frictionloss,
dx.efc_frictionloss[order][: d.nefc],
'efc_frictionloss',
)
def test_disable_refsafe(self):
m = test_util.load_test_file('constraints.xml')
@@ -96,22 +101,33 @@ class ConstraintTest(parameterized.TestCase):
m.opt.disableflags = m.opt.disableflags | mjx.DisableBit.EQUALITY
ne, nf, nl, nc = constraint.counts(constraint.make_efc_type(m))
self.assertEqual(ne, 0)
self.assertEqual(nf, 0)
self.assertEqual(nf, 2)
self.assertEqual(nl, 5)
self.assertEqual(nc, 148)
dx = constraint.make_constraint(mjx.put_model(m), mjx.make_data(m))
self.assertEqual(dx.efc_J.shape[0], 153) # only joint/tendon limit, contact
self.assertEqual(dx.efc_J.shape[0], 155) # only joint/tendon limit, contact
def test_disable_contact(self):
m = test_util.load_test_file('constraints.xml')
m.opt.disableflags = m.opt.disableflags | mjx.DisableBit.CONTACT
ne, nf, nl, nc = constraint.counts(constraint.make_efc_type(m))
self.assertEqual(ne, 11)
self.assertEqual(nf, 0)
self.assertEqual(nf, 2)
self.assertEqual(nl, 5)
self.assertEqual(nc, 0)
dx = constraint.make_constraint(mjx.put_model(m), mjx.make_data(m))
self.assertEqual(dx.efc_J.shape[0], 16) # only equality, joint/tendon limit
self.assertEqual(dx.efc_J.shape[0], 18) # only equality, joint/tendon limit
def test_disable_frictionloss(self):
m = test_util.load_test_file('constraints.xml')
m.opt.disableflags = m.opt.disableflags | mjx.DisableBit.FRICTIONLOSS
ne, nf, nl, nc = constraint.counts(constraint.make_efc_type(m))
self.assertEqual(ne, 11)
self.assertEqual(nf, 0)
self.assertEqual(nl, 5)
self.assertEqual(nc, 148)
dx = constraint.make_constraint(mjx.put_model(m), mjx.make_data(m))
self.assertEqual(dx.efc_J.shape[0], 164)
def test_margin(self):
"""Test margin."""
+2 -3
View File
@@ -126,15 +126,14 @@ def put_model(
f'{[mj_type(m) for m in missing]} not supported'
)
if not np.allclose(m.dof_frictionloss, 0) and not _full_compat:
raise NotImplementedError('dof_frictionloss is not implemented.')
mj_field_names = {
f.name
for f in types.Model.fields()
if f.metadata.get('restricted_to') != 'mjx'
}
fields = {f: getattr(m, f) for f in mj_field_names}
fields['dof_hasfrictionloss'] = fields['dof_frictionloss'] > 0
fields['tendon_hasfrictionloss'] = fields['tendon_frictionloss'] > 0
fields['geom_rbound_hfield'] = fields['geom_rbound']
fields['cam_mat0'] = fields['cam_mat0'].reshape((-1, 3, 3))
fields['opt'] = _make_option(m.opt)
+38 -10
View File
@@ -145,9 +145,23 @@ class _LSPoint(PyTreeNode):
"""Creates a linesearch point with first and second derivatives."""
# roughly corresponds to CGEval in mujoco/src/engine/engine_solver.c
# TODO(robotics-team): change this to support friction constraints
cost, deriv_0, deriv_1 = 0.0, 0.0, 0.0
quad_total = quad_gauss
x = ctx.Jaref + alpha * jv
active = (x < 0).at[: d.ne + d.nf].set(True)
dof_fl, ten_fl = m.dof_hasfrictionloss, m.tendon_hasfrictionloss
if (dof_fl.any() or ten_fl.any()) and not (
m.opt.disableflags & DisableBit.FRICTIONLOSS
):
f = d.efc_frictionloss
r = 1.0 / (d.efc_D + (d.efc_D == 0.0) * mujoco.mjMINVAL)
rf, z = r * f, jp.zeros_like(f)
linear_neg = (x <= -rf)[:, None]
linear_pos = (x >= rf)[:, None]
qf = linear_neg * jp.array([f * (-0.5 * rf - ctx.Jaref), -f * jv, z]).T
qf += linear_pos * jp.array([f * (-0.5 * rf + ctx.Jaref), f * jv, z]).T
quad = jp.where(f[:, None] > 0, qf, quad)
if m.opt.cone == ConeType.ELLIPTIC:
mu, u0 = ctx.fri[:, 0], ctx.u[:, 0]
@@ -161,7 +175,6 @@ class _LSPoint(PyTreeNode):
# quadratic cost for equality, friction, limits, frictionless contacts
dim1 = d.contact.efc_address[d.contact.dim == 1]
nefl = d.ne + d.nf + d.nl
active = ((ctx.Jaref + alpha * jv) < 0).at[:d.ne + d.nf].set(True)
active = active.at[nefl:].set(False).at[dim1].set(active[dim1])
quad_efld = jax.vmap(jp.multiply)(quad, active)
quad_total += jp.sum(quad_efld, axis=0)
@@ -181,7 +194,6 @@ class _LSPoint(PyTreeNode):
deriv_0 = jp.sum(dm * nmt * (n1 - mu * t1))
deriv_1 = jp.sum(dm * (jp.square(n1 - mu * t1) - nmt * mu * t2))
elif m.opt.cone == ConeType.PYRAMIDAL:
active = ((ctx.Jaref + alpha * jv) < 0).at[:d.ne + d.nf].set(True)
quad = jax.vmap(jp.multiply)(quad, active) # only active
quad_total += jp.sum(quad, axis=0)
else:
@@ -228,7 +240,7 @@ def _while_loop_scan(cond_fun, body_fun, init_val, max_iter):
def _update_constraint(m: Model, d: Data, ctx: _Context) -> _Context:
"""Updates constraint force and resulting cost given latst solver iteration.
"""Updates constraint force and resulting cost given last solver iteration.
Corresponds to CGupdateConstraint in mujoco/src/engine/engine_solver.c
@@ -240,10 +252,27 @@ def _update_constraint(m: Model, d: Data, ctx: _Context) -> _Context:
Returns:
context with new constraint force and costs
"""
# ne constraints are always active, nf are conditionally active, others are
# non-negative constraints.
active = (ctx.Jaref < 0).at[: d.ne + d.nf].set(True)
floss_force, floss_cost = jp.zeros(d.nefc), 0.0
dof_fl, ten_fl = m.dof_hasfrictionloss, m.tendon_hasfrictionloss
if (dof_fl.any() or ten_fl.any()) and not (
m.opt.disableflags & DisableBit.FRICTIONLOSS
):
f = d.efc_frictionloss
r = 1.0 / (d.efc_D + (d.efc_D == 0.0) * mujoco.mjMINVAL)
linear_neg = (ctx.Jaref <= -r * f) * (f > 0)
linear_pos = (ctx.Jaref >= r * f) * (f > 0)
active = active & ~linear_neg & ~linear_pos
floss_force = linear_neg * f + linear_pos * -f
floss_cost = linear_neg * (-0.5 * r * f * f - f * ctx.Jaref)
floss_cost += linear_pos * (-0.5 * r * f * f + f * ctx.Jaref)
floss_cost = floss_cost.sum()
if m.opt.cone == ConeType.PYRAMIDAL:
# ne/nf constraints are always active, rest are non-negative constraints
active = (ctx.Jaref < 0).at[:d.ne + d.nf].set(True)
efc_force = d.efc_D * -ctx.Jaref * active
efc_force = d.efc_D * -ctx.Jaref * active + floss_force
cost = 0.5 * jp.sum(d.efc_D * ctx.Jaref * ctx.Jaref * active)
dm, u, h = 0.0, 0.0, 0.0
elif m.opt.cone == ConeType.ELLIPTIC:
@@ -256,13 +285,12 @@ def _update_constraint(m: Model, d: Data, ctx: _Context) -> _Context:
# bottom zone: quadratic
bottom_zone = ((t <= 0) & (n < 0)) | ((t > 0) & ((mu * n + t) <= 0))
active = (ctx.Jaref < 0).at[:d.ne + d.nf].set(True)
adr_i, adr_j = [], []
for i, (condim, addr) in enumerate(zip(dim, efc_address)):
adr_i.extend(range(addr, addr + condim))
adr_j.extend([i] * condim)
active = active.at[jp.array(adr_i)].set(bottom_zone[jp.array(adr_j)])
efc_force = d.efc_D * -ctx.Jaref * active
efc_force = d.efc_D * -ctx.Jaref * active + floss_force
cost = 0.5 * jp.sum(d.efc_D * ctx.Jaref * ctx.Jaref * active)
# middle zone: cone
@@ -309,7 +337,7 @@ def _update_constraint(m: Model, d: Data, ctx: _Context) -> _Context:
ctx = ctx.replace(
qfrc_constraint=qfrc_constraint,
gauss=gauss,
cost=cost + gauss,
cost=cost + gauss + floss_cost,
prev_cost=ctx.cost,
efc_force=efc_force,
active=active,
+8 -2
View File
@@ -49,6 +49,7 @@ class DisableBit(enum.IntFlag):
"""
CONSTRAINT = mujoco.mjtDisableBit.mjDSBL_CONSTRAINT
EQUALITY = mujoco.mjtDisableBit.mjDSBL_EQUALITY
FRICTIONLOSS = mujoco.mjtDisableBit.mjDSBL_FRICTIONLOSS
LIMIT = mujoco.mjtDisableBit.mjDSBL_LIMIT
CONTACT = mujoco.mjtDisableBit.mjDSBL_CONTACT
PASSIVE = mujoco.mjtDisableBit.mjDSBL_PASSIVE
@@ -60,7 +61,7 @@ class DisableBit(enum.IntFlag):
SENSOR = mujoco.mjtDisableBit.mjDSBL_SENSOR
EULERDAMP = mujoco.mjtDisableBit.mjDSBL_EULERDAMP
FILTERPARENT = mujoco.mjtDisableBit.mjDSBL_FILTERPARENT
# unsupported: FRICTIONLOSS, MIDPHASE
# unsupported: MIDPHASE
class JointType(enum.IntEnum):
@@ -265,7 +266,8 @@ class ConstraintType(enum.IntEnum):
CONTACT_PYRAMIDAL: frictional contact, pyramidal friction cone
"""
EQUALITY = mujoco.mjtConstraint.mjCNSTR_EQUALITY
# unsupported: FRICTION_DOF, FRICTION_TENDON
FRICTION_DOF = mujoco.mjtConstraint.mjCNSTR_FRICTION_DOF
FRICTION_TENDON = mujoco.mjtConstraint.mjCNSTR_FRICTION_TENDON
LIMIT_JOINT = mujoco.mjtConstraint.mjCNSTR_LIMIT_JOINT
LIMIT_TENDON = mujoco.mjtConstraint.mjCNSTR_LIMIT_TENDON
CONTACT_FRICTIONLESS = mujoco.mjtConstraint.mjCNSTR_CONTACT_FRICTIONLESS
@@ -557,6 +559,7 @@ class Model(PyTreeNode):
dof_solref: constraint solver reference:frictionloss (nv, mjNREF)
dof_solimp: constraint solver impedance:frictionloss (nv, mjNIMP)
dof_frictionloss: dof friction loss (nv,)
dof_hasfrictionloss: dof has >0 frictionloss (MJX) (nv,)
dof_armature: dof armature inertia/mass (nv,)
dof_damping: damping coefficient (nv,)
dof_invweight0: diag. inverse inertia in qpos0 (nv,)
@@ -697,6 +700,7 @@ class Model(PyTreeNode):
tendon_lengthspring: spring resting length range (ntendon, 2)
tendon_length0: tendon length in qpos0 (ntendon,)
tendon_invweight0: inv. weight in qpos0 (ntendon,)
tendon_hasfrictionloss: tendon has >0 frictionloss (MJX) (ntendon,)
wrap_type: wrap object type (mjtWrap) (nwrap,)
wrap_objid: object id: geom, site, joint (nwrap,)
wrap_prm: divisor, joint coef, or site id (nwrap,)
@@ -860,6 +864,7 @@ class Model(PyTreeNode):
dof_solref: jax.Array
dof_solimp: jax.Array
dof_frictionloss: jax.Array
dof_hasfrictionloss: np.ndarray = _restricted_to('mjx')
dof_armature: jax.Array
dof_damping: jax.Array
dof_invweight0: jax.Array
@@ -1005,6 +1010,7 @@ class Model(PyTreeNode):
tendon_lengthspring: jax.Array
tendon_length0: jax.Array
tendon_invweight0: jax.Array
tendon_hasfrictionloss: np.ndarray = _restricted_to('mjx')
wrap_type: np.ndarray = _restricted_to('mujoco')
wrap_objid: np.ndarray = _restricted_to('mujoco')
wrap_prm: np.ndarray = _restricted_to('mujoco')
+2 -2
View File
@@ -29,7 +29,7 @@
</body>
<body name="beam3" pos="1 0 0">
<joint name="joint3" axis="1 0 0" type="hinge" range="-20 20"/>
<joint name="joint3" axis="1 0 0" type="hinge" range="-20 20" frictionloss="0.1"/>
<geom class="box"/>
</body>
@@ -65,7 +65,7 @@
</worldbody>
<tendon>
<fixed name="tendon_1" limited="true" range="-0.3 0.1" stiffness=".1" damping=".2">
<fixed name="tendon_1" limited="true" range="-0.3 0.1" stiffness=".1" damping=".2" frictionloss="0.1">
<joint joint="joint3" coef=".1"/>
<joint joint="joint4" coef="-.2"/>
</fixed>