Add muscle actuators to MJX.

PiperOrigin-RevId: 695334356
Change-Id: I6590ed6bdd4a8adc53d5c4e2641f472f7439cedc
This commit is contained in:
Taylor Howell
2024-11-11 07:45:11 -08:00
committed by Copybara-Service
parent 414b677eb8
commit 0f381a9ebd
10 changed files with 566 additions and 40 deletions
+4
View File
@@ -6,6 +6,10 @@ Changelog
Upcoming version (not yet released)
-----------------------------------
MJX
^^^
- Added muscle actuators.
Bug fixes
^^^^^^^^^
- Fixed :github:issue:`2212`, type error in ``mjx.get_data``.
+3 -9
View File
@@ -214,11 +214,11 @@ The following features are **fully supported** in MJX:
* - :ref:`Transmission <mjtTrn>`
- ``JOINT``, ``JOINTINPARENT``, ``SITE``, ``TENDON``
* - :ref:`Actuator Dynamics <mjtDyn>`
- ``NONE``, ``INTEGRATOR``, ``FILTER``, ``FILTEREXACT``
- ``NONE``, ``INTEGRATOR``, ``FILTER``, ``FILTEREXACT``, ``MUSCLE``
* - :ref:`Actuator Gain <mjtGain>`
- ``FIXED``, ``AFFINE``
- ``FIXED``, ``AFFINE``, ``MUSCLE``
* - :ref:`Actuator Bias <mjtBias>`
- ``NONE``, ``AFFINE``
- ``NONE``, ``AFFINE``, ``MUSCLE``
* - :ref:`Tendon Wrapping <mjtWrap>`
- ``JOINT``, ``SITE``, ``PULLEY``
* - :ref:`Geom <mjtGeom>`
@@ -264,12 +264,6 @@ The following features are **in development** and coming soon:
- ``IMPLICIT``
* - Dynamics
- :ref:`Inverse <mj_inverse>`
* - :ref:`Actuator Dynamics <mjtDyn>`
- ``MUSCLE``
* - :ref:`Actuator Gain <mjtGain>`
- ``MUSCLE``
* - :ref:`Actuator Bias <mjtBias>`
- ``MUSCLE``
* - :ref:`Tendon Wrapping <mjtWrap>`
- ``SPHERE``, ``CYLINDER``
* - Fluid Model
+10 -2
View File
@@ -115,6 +115,8 @@ def fwd_actuation(m: Model, d: Data) -> Data:
act_dot = ctrl
elif dyn_typ in (DynType.FILTER, DynType.FILTEREXACT):
act_dot = (ctrl - act) / jp.clip(dyn_prm[0], mujoco.mjMINVAL)
elif dyn_typ == DynType.MUSCLE:
act_dot = support.muscle_dynamics(ctrl, act, dyn_prm)
else:
raise NotImplementedError(f'dyntype {dyn_typ.name} not implemented.')
return act_dot
@@ -139,13 +141,15 @@ def fwd_actuation(m: Model, d: Data) -> Data:
ctrl_act = jp.where(m.actuator_actadr == -1, ctrl, act_last_dim)
def get_force(*args):
gain_t, gain_p, bias_t, bias_p, len_, vel, ctrl_act = args
gain_t, gain_p, bias_t, bias_p, len_, vel, ctrl_act, len_range, acc0 = args
typ, prm = GainType(gain_t), gain_p
if typ == GainType.FIXED:
gain = prm[0]
elif typ == GainType.AFFINE:
gain = prm[0] + prm[1] * len_ + prm[2] * vel
elif typ == GainType.MUSCLE:
gain = support.muscle_gain(len_, vel, len_range, acc0, prm)
else:
raise RuntimeError(f'unrecognized gaintype {typ.name}.')
@@ -153,13 +157,15 @@ def fwd_actuation(m: Model, d: Data) -> Data:
bias = jp.array(0.0)
if typ == BiasType.AFFINE:
bias = prm[0] + prm[1] * len_ + prm[2] * vel
elif typ == BiasType.MUSCLE:
bias = support.muscle_bias(len_, len_range, acc0, prm)
return gain * ctrl_act + bias
force = scan.flat(
m,
get_force,
'uuuuuuu',
'uuuuuuuuu',
'u',
m.actuator_gaintype,
m.actuator_gainprm,
@@ -168,6 +174,8 @@ def fwd_actuation(m: Model, d: Data) -> Data:
d.actuator_length,
d.actuator_velocity,
ctrl_act,
jp.array(m.actuator_lengthrange),
jp.array(m.actuator_acc0),
group_by='u',
)
forcerange = jp.where(
+15 -26
View File
@@ -15,6 +15,7 @@
"""Tests for forward functions."""
from absl.testing import absltest
from absl.testing import parameterized
import jax
import mujoco
from mujoco import mjx
@@ -167,40 +168,28 @@ class ForwardTest(absltest.TestCase):
np.testing.assert_allclose(dx.qvel, 1 + m.opt.timestep)
class ActuatorTest(absltest.TestCase):
_DYN_XML = """
<mujoco>
<compiler autolimits="true"/>
<worldbody>
<body name="box">
<joint name="slide1" type="slide" axis="1 0 0" />
<joint name="slide2" type="slide" axis="0 1 0" />
<joint name="slide3" type="slide" axis="0 0 1" />
<joint name="slide4" type="slide" axis="1 1 0" />
<geom type="box" size=".05 .05 .05" mass="1"/>
</body>
</worldbody>
<actuator>
<general joint="slide1" dynprm="0.1" gainprm="1.1" />
<general joint="slide2" dyntype="integrator" dynprm="0.1" gainprm="1.1" />
<general joint="slide3" dyntype="filter" dynprm="0.1" gainprm="1.1" />
<general joint="slide4" dyntype="filterexact" dynprm="0.1" gainprm="1.1" />
</actuator>
</mujoco>
"""
class ActuatorTest(parameterized.TestCase):
def test_dyntype(self):
m = mujoco.MjModel.from_xml_string(self._DYN_XML)
@parameterized.parameters(
'actuator/arm21.xml',
'actuator/arm26.xml',
'actuator/general_dyntype.xml',
)
def test_actuator(self, fname):
m = test_util.load_test_file(fname)
d = mujoco.MjData(m)
d.ctrl = np.array([1.5, 1.5, 1.5, 1.5])
d.act = np.array([0.5, 0.5, 0.5])
mujoco.mj_step(m, d)
d.ctrl = 1.5 * np.random.random(m.nu)
d.act = 0.5 * np.random.random(m.na)
mx = mjx.put_model(m)
dx = mjx.put_data(m, d)
mujoco.mj_fwdActuation(m, d)
dx = jax.jit(mjx.fwd_actuation)(mx, dx)
_assert_attr_eq(d, dx, 'act_dot')
_assert_attr_eq(d, dx, 'qfrc_actuator')
_assert_attr_eq(d, dx, 'actuator_force')
mujoco.mj_Euler(m, d)
dx = jax.jit(mjx.euler)(mx, dx)
+147
View File
@@ -560,3 +560,150 @@ def wrap(
wpnt1 = jp.where(invalid, jp.zeros(3), wpnt1)
return wlen, wpnt0, wpnt1
def muscle_gain_length(
length: jax.Array, lmin: jax.Array, lmax: jax.Array
) -> jax.Array:
"""Normalized muscle length-gain curve."""
# mid-ranges (maximum is at 1.0)
a = 0.5 * (lmin + 1)
b = 0.5 * (1 + lmax)
out0 = 0.5 * jp.square(
(length - lmin) / jp.maximum(mujoco.mjMINVAL, a - lmin)
)
out1 = 1 - 0.5 * jp.square((1 - length) / jp.maximum(mujoco.mjMINVAL, 1 - a))
out2 = 1 - 0.5 * jp.square((length - 1) / jp.maximum(mujoco.mjMINVAL, b - 1))
out3 = 0.5 * jp.square(
(lmax - length) / jp.maximum(mujoco.mjMINVAL, lmax - b)
)
out = jp.where(length <= b, out2, out3)
out = jp.where(length <= 1, out1, out)
out = jp.where(length <= a, out0, out)
out = jp.where((lmin <= length) & (length <= lmax), out, 0.0)
return out
def muscle_gain(
length: jax.Array,
vel: jax.Array,
lengthrange: jax.Array,
acc0: jax.Array,
prm: jax.Array,
) -> jax.Array:
"""Muscle active force."""
# unpack parameters
lrange = prm[:2]
force, scale, lmin, lmax, vmax, _, fvmax = prm[2:9]
force = jp.where(force < 0, scale / jp.maximum(mujoco.mjMINVAL, acc0), force)
# optimum length
L0 = (lengthrange[1] - lengthrange[0]) / jp.maximum( # pylint:disable=invalid-name
mujoco.mjMINVAL, lrange[1] - lrange[0]
)
# normalized length and velocity
L = lrange[0] + (length - lengthrange[0]) / jp.maximum(mujoco.mjMINVAL, L0) # pylint:disable=invalid-name
V = vel / jp.maximum(mujoco.mjMINVAL, L0 * vmax) # pylint:disable=invalid-name
# length curve
FL = muscle_gain_length(L, lmin, lmax) # pylint:disable=invalid-name
# velocity curve
y = fvmax - 1
FV = fvmax # pylint:disable=invalid-name
FV = jp.where( # pylint:disable=invalid-name
V <= y, fvmax - jp.square(y - V) / jp.maximum(mujoco.mjMINVAL, y), FV
)
FV = jp.where(V <= 0, jp.square(V + 1), FV) # pylint:disable=invalid-name
FV = jp.where(V <= -1, 0, FV) # pylint:disable=invalid-name
# compute FVL and scale, make it negative
return -force * FL * FV
def muscle_bias(
length: jax.Array, lengthrange: jax.Array, acc0: jax.Array, prm: jax.Array
) -> jax.Array:
"""Muscle passive force."""
# unpack parameters
lrange = prm[:2]
force, scale, _, lmax, _, fpmax = prm[2:8]
force = jp.where(force < 0, scale / jp.maximum(mujoco.mjMINVAL, acc0), force)
# optimum length
L0 = (lengthrange[1] - lengthrange[0]) / jp.maximum( # pylint:disable=invalid-name
mujoco.mjMINVAL, lrange[1] - lrange[0]
)
# normalized length
L = lrange[0] + (length - lengthrange[0]) / jp.maximum(mujoco.mjMINVAL, L0) # pylint:disable=invalid-name
# half-quadratic to (L0 + lmax) / 2, linear beyond
b = 0.5 * (1 + lmax)
out1 = (
-force
* fpmax
* 0.5
* jp.square((L - 1) / jp.maximum(mujoco.mjMINVAL, b - 1))
)
out2 = -force * fpmax * (0.5 + (L - b) / jp.maximum(mujoco.mjMINVAL, b - 1))
out = jp.where(L <= b, out1, out2)
out = jp.where(L <= 1, 0.0, out)
return out
def muscle_dynamics_timescale(
dctrl: jax.Array,
tau_act: jax.Array,
tau_deact: jax.Array,
smoothing_width: jax.Array,
) -> jax.Array:
"""Muscle time constant with optional smoothing."""
# hard switching
tau_hard = jp.where(dctrl > 0, tau_act, tau_deact)
def _sigmoid(x):
# sigmoid function over 0 <= x <= 1 using quintic polynomial
# sigmoid: f(x) = 6 * x^5 - 15 * x^4 + 10 * x^3
# solution of f(0) = f'(0) = f''(0) = 0, f(1) = 1, f'(1) = f''(1) = 0
return jp.clip(x**3 * (3 * x * (2 * x - 5) + 10), 0, 1)
# smooth switching
# scale by width, center around 0.5 midpoint, rescale to bounds
tau_smooth = tau_deact + (tau_act - tau_deact) * _sigmoid(
dctrl / smoothing_width + 0.5
)
return jp.where(smoothing_width < mujoco.mjMINVAL, tau_hard, tau_smooth)
def muscle_dynamics(
ctrl: jax.Array, act: jax.Array, prm: jax.Array
) -> jax.Array:
"""Muscle activation dynamics."""
# clamp control
ctrlclamp = jp.clip(ctrl, 0, 1)
# clamp activation
actclamp = jp.clip(act, 0, 1)
# compute timescales as in Millard et at. (2013)
# https://doi.org/10.1115/1.4023390
tau_act = prm[0] * (0.5 + 1.5 * actclamp) # activation timescale
tau_deact = prm[1] / (0.5 + 1.5 * actclamp) # deactivation timescale
smoothing_width = prm[2] # width of smoothing sigmoid
dctrl = ctrlclamp - act # excess excitation
tau = muscle_dynamics_timescale(dctrl, tau_act, tau_deact, smoothing_width)
# filter output
return dctrl / jp.maximum(mujoco.mjMINVAL, tau)
+205
View File
@@ -221,6 +221,211 @@ class SupportTest(parameterized.TestCase):
force = force.at[3:].set(dx.contact.frame[j] @ force[3:])
np.testing.assert_allclose(result, force, rtol=1e-5, atol=2)
def test_muscle_gain_length(self):
lmin = 0.5
lmax = 1.5
np.testing.assert_allclose(
support.muscle_gain_length(0, lmin, lmax),
jp.zeros(1),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(0.5, lmin, lmax),
jp.zeros(1),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(0.6, lmin, lmax),
jp.array([0.08]),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(0.75, lmin, lmax),
jp.array([0.5]),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(1.0, lmin, lmax),
jp.ones(1),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(1.25, lmin, lmax),
jp.array([0.5]),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(1.5, lmin, lmax),
jp.zeros(1),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(2.0, lmin, lmax),
jp.zeros(1),
rtol=1e-5,
atol=1e-5,
)
def test_muscle_gain(self):
length = jp.array([1.0])
lengthrange = jp.array([0.0, 1.0])
acc0 = jp.array([1.0])
prm = jp.array([0.0, 1.0, 1.0, 200.0, 0.5, 3.0, 1.0, 0.0, 2.0, 0.0])
# V <= -1
vel = jp.array([-1.5])
np.testing.assert_allclose(
support.muscle_gain(length, vel, lengthrange, acc0, prm),
jp.array([-0.0]),
rtol=1e-5,
atol=1e-5,
)
# V <= 0
vel = jp.array([-0.5])
np.testing.assert_allclose(
support.muscle_gain(length, vel, lengthrange, acc0, prm),
jp.array([-0.25]),
rtol=1e-5,
atol=1e-5,
)
# V <= y
vel = jp.array([0.5])
np.testing.assert_allclose(
support.muscle_gain(length, vel, lengthrange, acc0, prm),
jp.array([-1.75]),
rtol=1e-5,
atol=1e-5,
)
# V > y
vel = jp.array([1.5])
np.testing.assert_allclose(
support.muscle_gain(length, vel, lengthrange, acc0, prm),
jp.array([-2.0]),
rtol=1e-5,
atol=1e-5,
)
# force < 0
prm = prm.at[2].set(-1.0)
np.testing.assert_allclose(
support.muscle_gain(length, vel, lengthrange, acc0, prm),
jp.array([-400.0]),
rtol=1e-5,
atol=1e-5,
)
def test_muscle_bias(self):
lengthrange = jp.array([0.0, 1.0])
acc0 = jp.array([1.0])
prm = jp.array([0.0, 1.0, 1.0, 200.0, 0.5, 3.0, 1.5, 1.3, 1.2, 0.0])
# L <= 1
length = jp.array([0.5])
np.testing.assert_allclose(
support.muscle_bias(length, lengthrange, acc0, prm),
jp.array([0.0]),
rtol=1e-5,
atol=1e-5,
)
# L <= b
length = jp.array([1.5])
np.testing.assert_allclose(
support.muscle_bias(length, lengthrange, acc0, prm),
jp.array([-0.1625]),
rtol=1e-5,
atol=1e-5,
)
# L > b
length = jp.array([2.5])
np.testing.assert_allclose(
support.muscle_bias(length, lengthrange, acc0, prm),
jp.array([-1.3]),
rtol=1e-5,
atol=1e-5,
)
# force < 0
prm = prm.at[2].set(-1.0)
np.testing.assert_allclose(
support.muscle_bias(length, lengthrange, acc0, prm),
jp.array([-260.0]),
rtol=1e-5,
atol=1e-5,
)
def test_smooth_muscle_dynamics(self):
# compute time constant as in Millard et al. (2013)
# https://doi.org/10.1115/1.4023390
def _muscle_dynamics_millard(ctrl, act, prm):
ctrlclamp = jp.clip(ctrl, 0, 1)
actclamp = jp.clip(act, 0, 1)
tau0 = prm[0] * (0.5 + 1.5 * actclamp)
tau1 = prm[1] / (0.5 + 1.5 * actclamp)
tau = jp.where(ctrlclamp > act, tau0, tau1)
return (ctrlclamp - act) / jp.maximum(mujoco.mjMINVAL, tau)
prm = jp.array([0.01, 0.04, 0.0])
# exact equality if tau_smooth = 0
for ctrl in [-0.1, 0.0, 0.4, 0.5, 1.0, 1.0]:
for act in [-0.1, 0.0, 0.4, 0.5, 1.0, 1.1]:
actdot_old = _muscle_dynamics_millard(ctrl, act, prm)
actdot_new = support.muscle_dynamics(ctrl, act, prm)
np.testing.assert_allclose(actdot_old, actdot_new, rtol=1e-5, atol=1e-5)
# positive tau_smooth
tau_smooth = 0.2
prm = prm.at[2].set(tau_smooth)
act = 0.5
eps = 1.0e-6
ctrl = 0.4 - eps # smaller than act by just over 0.5 * tau_smooth
np.testing.assert_allclose(
_muscle_dynamics_millard(ctrl, act, prm),
support.muscle_dynamics(ctrl, act, prm),
rtol=1e-5,
atol=1e-5,
)
ctrl = 0.6 + eps # larger than act by just over 0.5 * tau_smooth
np.testing.assert_allclose(
_muscle_dynamics_millard(ctrl, act, prm),
support.muscle_dynamics(ctrl, act, prm),
rtol=1e-5,
atol=1e-5,
)
# right in the middle should give average of time constants
tau_act = 0.2
tau_deact = 0.3
for dctrl in [0.0, 0.1, 0.2, 1.0, 1.1]:
lower = support.muscle_dynamics_timescale(
-dctrl, tau_act, tau_deact, tau_smooth
)
upper = support.muscle_dynamics_timescale(
dctrl, tau_act, tau_deact, tau_smooth
)
np.testing.assert_allclose(
0.5 * (upper + lower),
0.5 * (tau_act + tau_deact),
rtol=1e-5,
atol=1e-5,
)
if __name__ == '__main__':
absltest.main()
+9 -3
View File
@@ -234,12 +234,14 @@ class DynType(enum.IntEnum):
INTEGRATOR: integrator: da/dt = u
FILTER: linear filter: da/dt = (u-a) / tau
FILTEREXACT: linear filter: da/dt = (u-a) / tau, with exact integration
MUSCLE: piece-wise linear filter with two time constants
"""
NONE = mujoco.mjtDyn.mjDYN_NONE
INTEGRATOR = mujoco.mjtDyn.mjDYN_INTEGRATOR
FILTER = mujoco.mjtDyn.mjDYN_FILTER
FILTEREXACT = mujoco.mjtDyn.mjDYN_FILTEREXACT
# unsupported: MUSCLE, USER
MUSCLE = mujoco.mjtDyn.mjDYN_MUSCLE
# unsupported: USER
class GainType(enum.IntEnum):
@@ -248,10 +250,12 @@ class GainType(enum.IntEnum):
Members:
FIXED: fixed gain
AFFINE: const + kp*length + kv*velocity
MUSCLE: muscle FLV curve computed by muscle_gain
"""
FIXED = mujoco.mjtGain.mjGAIN_FIXED
AFFINE = mujoco.mjtGain.mjGAIN_AFFINE
# unsupported: MUSCLE, USER
MUSCLE = mujoco.mjtGain.mjGAIN_MUSCLE
# unsupported: USER
class BiasType(enum.IntEnum):
@@ -260,10 +264,12 @@ class BiasType(enum.IntEnum):
Members:
NONE: no bias
AFFINE: const + kp*length + kv*velocity
MUSCLE: muscle passive force computed by muscle_bias
"""
NONE = mujoco.mjtBias.mjBIAS_NONE
AFFINE = mujoco.mjtBias.mjBIAS_AFFINE
# unsupported: MUSCLE, USER
MUSCLE = mujoco.mjtBias.mjBIAS_MUSCLE
# unsupported: USER
class ConstraintType(enum.IntEnum):
@@ -0,0 +1,37 @@
<!-- Copyright 2021 DeepMind Technologies Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mujoco model="2-link 1-muscle arm">
<worldbody>
<body>
<geom type="capsule" size="0.01" fromto="0 0 0 1 0 0"/>
<site name="s0" pos="0 0 0.01"/>
<joint type="slide" axis="1 0 0"/>
<body pos="1 0 0">
<geom type="capsule" size="0.01" fromto="0 0 0 1 0 0"/>
<site name="s1" pos="1 0 0.01"/>
</body>
</body>
</worldbody>
<tendon>
<spatial name="tendon" width="0.01">
<site site="s0"/>
<site site="s1"/>
</spatial>
</tendon>
<actuator>
<muscle tendon="tendon" lengthrange="0 2" force="1" ctrllimited="true" ctrlrange="0 1"/>
</actuator>
</mujoco>
+118
View File
@@ -0,0 +1,118 @@
<!-- Copyright 2021 DeepMind Technologies Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mujoco model="2-link 6-muscle arm">
<option timestep="0.005" iterations="50" solver="Newton" tolerance="1e-10"/>
<visual>
<rgba haze=".3 .3 .3 1"/>
</visual>
<default>
<joint type="hinge" pos="0 0 0" axis="0 0 1" limited="true" range="0 120" damping="0.1"/>
<muscle ctrllimited="true" ctrlrange="0 1"/>
</default>
<asset>
<texture type="skybox" builtin="gradient" rgb1="0.6 0.6 0.6" rgb2="0 0 0" width="512" height="512"/>
<texture name="texplane" type="2d" builtin="checker" rgb1=".25 .25 .25" rgb2=".3 .3 .3" width="512" height="512" mark="cross" markrgb=".8 .8 .8"/>
<material name="matplane" reflectance="0.3" texture="texplane" texrepeat="1 1" texuniform="true"/>
</asset>
<worldbody>
<geom name="floor" pos="0 0 -0.5" size="0 0 1" type="plane" material="matplane"/>
<light directional="true" diffuse=".8 .8 .8" specular=".2 .2 .2" pos="0 0 5" dir="0 0 -1"/>
<site name="s0" pos="-0.15 0 0" size="0.02"/>
<site name="x0" pos="0 -0.15 0" size="0.02" rgba="0 .7 0 1" group="1"/>
<body pos="0 0 0">
<geom name="upper arm" type="capsule" size="0.045" fromto="0 0 0 0.5 0 0" rgba=".5 .1 .1 1"/>
<joint name="shoulder"/>
<geom name="shoulder" type="cylinder" pos="0 0 0" size=".1 .05" rgba=".5 .1 .8 .5" mass="0" group="1"/>
<site name="s1" pos="0.15 0.06 0" size="0.02"/>
<site name="s2" pos="0.15 -0.06 0" size="0.02"/>
<site name="s3" pos="0.4 0.06 0" size="0.02"/>
<site name="s4" pos="0.4 -0.06 0" size="0.02"/>
<site name="s5" pos="0.25 0.1 0" size="0.02"/>
<site name="s6" pos="0.25 -0.1 0" size="0.02"/>
<site name="x1" pos="0.5 -0.15 0" size="0.02" rgba="0 .7 0 1" group="1"/>
<body pos="0.5 0 0">
<geom name="forearm" type="capsule" size="0.035" fromto="0 0 0 0.5 0 0" rgba=".5 .1 .1 1"/>
<joint name="elbow"/>
<geom name="elbow" type="cylinder" pos="0 0 0" size=".08 .05" rgba=".5 .1 .8 .5" mass="0" group="1"/>
<site name="s7" pos="0.11 0.05 0" size="0.02"/>
<site name="s8" pos="0.11 -0.05 0" size="0.02"/>
</body>
</body>
</worldbody>
<tendon>
<spatial name="SF" width="0.01">
<site site="s0"/>
<geom geom="shoulder"/>
<site site="s1"/>
</spatial>
<spatial name="SE" width="0.01">
<site site="s0"/>
<geom geom="shoulder" sidesite="x0"/>
<site site="s2"/>
</spatial>
<spatial name="EF" width="0.01">
<site site="s3"/>
<geom geom="elbow"/>
<site site="s7"/>
</spatial>
<spatial name="EE" width="0.01">
<site site="s4"/>
<geom geom="elbow" sidesite="x1"/>
<site site="s8"/>
</spatial>
<spatial name="BF" width="0.009" rgba=".4 .6 .4 1">
<site site="s0"/>
<geom geom="shoulder"/>
<site site="s5"/>
<geom geom="elbow"/>
<site site="s7"/>
</spatial>
<spatial name="BE" width="0.009" rgba=".4 .6 .4 1">
<site site="s0"/>
<geom geom="shoulder" sidesite="x0"/>
<site site="s6"/>
<geom geom="elbow" sidesite="x1"/>
<site site="s8"/>
</spatial>
</tendon>
<actuator>
<muscle name="SF" tendon="SF"/>
<muscle name="SE" tendon="SE"/>
<muscle name="EF" tendon="EF"/>
<muscle name="EE" tendon="EE"/>
<muscle name="BF" tendon="BF"/>
<muscle name="BE" tendon="BE"/>
</actuator>
</mujoco>
@@ -0,0 +1,18 @@
<mujoco>
<compiler autolimits="true"/>
<worldbody>
<body name="box">
<joint name="slide1" type="slide" axis="1 0 0" />
<joint name="slide2" type="slide" axis="0 1 0" />
<joint name="slide3" type="slide" axis="0 0 1" />
<joint name="slide4" type="slide" axis="1 1 0" />
<geom type="box" size=".05 .05 .05" mass="1"/>
</body>
</worldbody>
<actuator>
<general joint="slide1" dynprm="0.1" gainprm="1.1" />
<general joint="slide2" dyntype="integrator" dynprm="0.1" gainprm="1.1" />
<general joint="slide3" dyntype="filter" dynprm="0.1" gainprm="1.1" />
<general joint="slide4" dyntype="filterexact" dynprm="0.1" gainprm="1.1" />
</actuator>
</mujoco>