Add muscle actuators to MJX.
PiperOrigin-RevId: 695334356 Change-Id: I6590ed6bdd4a8adc53d5c4e2641f472f7439cedc
This commit is contained in:
committed by
Copybara-Service
parent
414b677eb8
commit
0f381a9ebd
@@ -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
@@ -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
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user