Preparation for MIMO actuators: split actuator counts: nu (inputs), nactuator (objects), nout (outputs).

An actuator now owns a block of consecutive controls
(actuator_ctrladr/ctrlnum, width defined by the actuator type) and a block
of consecutive force outputs (actuator_outadr/outnum, width defined by the
transmission type). Force outputs are the scalars of actuation space: one
force, length, velocity and moment row each. nout = dim(actuator_force) is
derived from transmission types; all current types have width 1, so all
three counts coincide for every existing model and behavior is bit-exact.

Array re-keying: ctrlrange/ctrllimited by nu; forcerange/forcelimited/gear/
acc0/length0/lengthrange and the moment row structure by nout; everything
else per actuator. The mjModel actuator block is re-sorted by size key.

Layout-breaking, not behavior-breaking: saved .mjb files are invalidated
(size list changed) and recompilation is required.

PiperOrigin-RevId: 948351772
Change-Id: Icbc196ffa083cb1eaa6f1a3710869c89d8f62540
This commit is contained in:
Yuval Tassa
2026-07-15 08:29:38 -07:00
committed by Copybara-Service
parent 06f12a9372
commit d507e92198
27 changed files with 750 additions and 538 deletions
+5
View File
@@ -31,6 +31,11 @@ General
error instead of the generic "could not decode content" message.
- Added support for resource writing via :ref:`mju_writeResource` and the ``write`` callback in :ref:`mjpResourceProvider`.
- Added support for :ref:`multiccd <coMultiCCD>` with arbitrarily large meshes.
- Refactored actuator infrastructure in preparation for MIMO (multi-input multi-output) actuator support. Each actuator
now has ``ctrlnum`` (number of controls) and ``outnum`` (number of force outputs). The total counts
``nu = sum(ctrlnum)`` and ``nout = sum(outnum)`` dimension ``mjData.ctrl`` and ``mjData.actuator_force``,
respectively, ``nactuator`` is the number of actuators. For existing actuators ``ctrnum = outnum = 1``, so
``nactuator == nu == nout`` and existing code is unaffected.
.. admonition:: Breaking API changes
:class: attention
+34 -27
View File
@@ -262,12 +262,21 @@ detailed summary of the :ref:`simulation pipeline <Pipeline>` is given at the en
Actuation model
~~~~~~~~~~~~~~~
MuJoCo provides a flexible actuator model. All actuators are single-input-single-output (SISO). The input to actuator
:math:`i` is a scalar control :math:`u_i` specified by the user. The output is a scalar force :math:`p_i` which is
mapped to joint coordinates by a vector of moment arms determined by the transmission. An actuator can also have
activation state :math:`w_i` with its own dynamics. The control inputs for all actuators are stored in ``mjData.ctrl``,
the force outputs are stored in ``mjData.actuator_force``, and the activation states (if any) are stored in
``mjData.act``.
MuJoCo provides a flexible actuator model. Most actuators are single-input-single-output (SISO): the input to
actuator :math:`i` is a scalar control :math:`u_i` specified by the user, and the output is a scalar force
:math:`p_i`, which is mapped to joint coordinates by a vector of moment arms determined by the transmission. An
actuator can also have activation state :math:`w_i` with its own dynamics. The control inputs for all actuators are
stored in ``mjData.ctrl``, the force outputs are stored in ``mjData.actuator_force``, and the activation states (if
any) are stored in ``mjData.act``.
More generally, an actuator can have several control inputs, several force outputs, or both. For example, a servo
which accepts both a position setpoint and a velocity setpoint has two inputs and one output. The number of inputs
is a property of the actuator type, and the number of outputs is a property of its transmission; neither is
specified directly by the user. Since the number of inputs and the number of outputs can vary per actuator, the model
has three separate counts: the number of actuators ``nactuator``, the total number of controls ``nu``, and the total
number of force outputs ``nout``. In a model where all actuators are SISO, all three are equal. Note that outputs are
counted in actuation space, before the mapping to joint coordinates: a tendon actuator with a single force output can
move many joints.
These three components of an actuator -- transmission, activation dynamics, and force generation -- determine how the
actuator works. The user can set them independently for maximum flexibility, or use :ref:`Actuator shortcuts
@@ -278,11 +287,13 @@ actuator works. The user can set them independently for maximum flexibility, or
Transmission
^^^^^^^^^^^^
Each actuator has a scalar length :math:`l_i(q)` defined by the type of transmission and its parameters. The gradient
:math:`\nabla l_i` is an :math:`\nv`-dimensional vector of moment arms. It determines the mapping from scalar
actuator force to joint force. The transmission properties are determined by the MuJoCo object to which the actuator
is attached; the possible attachment object types are :at:`joint`, :at:`tendon`, :at:`jointinparent`,
:at:`slider-crank`, :at:`site`, and :at:`body`.
The transmission attaches the actuator to the rest of the system. Each force output of the actuator has a scalar
length :math:`l_k(q)`, defined by the type of transmission and its parameters, for example the
:ref:`gear<actuator-general-gear>` vector. The gradient :math:`\nabla l_k` is an :math:`\nv`-dimensional vector of
moment arms; it determines the mapping from the scalar output force to joint forces. The number of force outputs is
likewise determined by the transmission; all the types listed below define a single output. The transmission
properties are determined by the MuJoCo object to which the actuator is attached; the possible attachment object
types are :at:`joint`, :at:`tendon`, :at:`jointinparent`, :at:`slider-crank`, :at:`site`, and :at:`body`.
:at:`joint` and :at:`tendon`
The :at:`joint` and :at:`tendon` transmission types act as expected and correspond to the actuator applying forces or
@@ -369,14 +380,10 @@ diverge for :math:`\texttt{t} < h`, while exactly-integrated filters are stable
Force generation
^^^^^^^^^^^^^^^^
Each actuator generates a scalar force :math:`p_i` which is some function
.. math::
p_i \left( u_i, w_i, l_i, \dot{l}_i \right)
Similarly to activation dynamics, the force generation mechanism is actuator-specific and cannot interact with the
other actuators in the model. Currently the force is affine in the activation state when present, and in the control
otherwise:
Each actuator generates a scalar force for each of its outputs, as a function of its controls, activation states,
and lengths and velocities. Similarly to activation dynamics, force generation is actuator-specific: the inputs and
outputs of a single actuator can interact, but different actuators cannot affect each other. For SISO actuators the
force is affine in the activation state when present, and in the control otherwise:
.. math::
p_i = (a w_i \; \text{or} \; a u_i) + b_0 + b_1 l_i + b_2 \dot{l}_i
@@ -384,17 +391,17 @@ otherwise:
Here :math:`a` is an actuator-specific gain parameter and :math:`b_0, b_1, b_2` are actuator-specific bias
parameters, stored in ``mjModel.actuator_gainprm`` and ``mjModel.actuator_biasprm`` respectively. Different settings
of the gain and bias parameters can be used to model direct force control as well as position and velocity servos -in
which case the control/activation has the meaning of reference position or velocity. One can also compute custom gain
and bias terms by installing the callbacks :ref:`mjcb_act_gain` and :ref:`mjcb_act_bias` and setting the gain and
bias type to "user". Note that affine force generation makes it possible to infer the controls/activations from the
applied force computed in inverse dynamics, using the pseudo-inverse of the matrix of moment arms. However some of
the actuators used in the real world are not affine (especially those that have embedded low-level controllers), so
we are considering extensions to the above model.
which case the control/activation has the meaning of reference position or velocity. Multi-input actuator types
define their own force laws. One can also compute custom gain and bias terms for SISO actuators by installing the
callbacks :ref:`mjcb_act_gain` and :ref:`mjcb_act_bias` and setting the gain and bias type to "user". Note that
affine force generation makes it possible to infer the controls/activations from the applied force computed in
inverse dynamics, using the pseudo-inverse of the matrix of moment arms.
Putting all this together, the net force in generalized coordinates contributed by all actuators is
Putting all this together, the net force in generalized coordinates contributed by all actuators is the sum over all
force outputs
.. math::
\sum_i \nabla l_i(q) \; p_i \left(u_i, w_i, l_i(q), \dot{l}_i(q, v) \right)
\sum_k \nabla l_k(q) \; p_k
This quantity is stored in ``mjData.qfrc_actuator``. It is added to the applied force vector :math:`\tau`, together
with any user-defined forces in joint or Cartesian coordinates (which are stored in ``mjData.qfrc_applied`` and
+43 -37
View File
@@ -215,9 +215,9 @@ typedef struct mjData_ {
mjtNum* wrap_xpos; // Cartesian 3D points in all paths (nwrap x 6)
// computed by mj_fwdPosition/mj_transmission
mjtNum* actuator_length; // actuator lengths (nu x 1)
int* moment_rownnz; // number of non-zeros in actuator_moment row (nu x 1)
int* moment_rowadr; // row start address in colind array (nu x 1)
mjtNum* actuator_length; // actuator lengths, one per force output (nout x 1)
int* moment_rownnz; // number of non-zeros in actuator_moment row (nout x 1)
int* moment_rowadr; // row start address in colind array (nout x 1)
int* moment_colind; // column indices in sparse Jacobian (nJmom x 1)
mjtNum* actuator_moment; // actuator moments (nJmom x 1)
@@ -244,7 +244,7 @@ typedef struct mjData_ {
// computed by mj_fwdVelocity
mjtNum* flexedge_velocity; // flex edge velocities (nflexedge x 1)
mjtNum* ten_velocity; // tendon velocities (ntendon x 1)
mjtNum* actuator_velocity; // actuator velocities (nu x 1)
mjtNum* actuator_velocity; // actuator velocities, one per force output (nout x 1)
// computed by mj_fwdVelocity/mj_comVel
mjtNum* cvel; // com-based velocity (rot:lin) (nbody x 6)
@@ -277,8 +277,8 @@ typedef struct mjData_ {
//-------------------- POSITION, VELOCITY, CONTROL/ACCELERATION dependent
// computed by mj_fwdActuation
mjtNum* actuator_force; // actuator force in actuation space (nu x 1)
mjtNum* qfrc_actuator; // actuator force (nv x 1)
mjtNum* actuator_force; // actuator force in actuation space (nout x 1)
mjtNum* qfrc_actuator; // actuator force in joint space (nv x 1)
// computed by mj_fwdAcceleration
mjtNum* qfrc_smooth; // net unconstrained force (nv x 1)
@@ -557,7 +557,9 @@ typedef struct mjModel_ {
// sizes needed at mjModel construction
mjtSize nq; // number of generalized coordinates = dim(qpos)
mjtSize nv; // number of degrees of freedom = dim(qvel)
mjtSize nu; // number of actuators/controls = dim(ctrl)
mjtSize nu; // number of scalar controls = dim(ctrl)
mjtSize nactuator; // number of actuators
mjtSize nout; // number of force outputs, derived from transmission type
mjtSize na; // number of activation states = dim(act)
mjtSize nbody; // number of bodies
mjtSize nbvh; // number of total bounding volumes in all bodies
@@ -1072,37 +1074,41 @@ typedef struct mjModel_ {
mjtNum* wrap_prm; // divisor, joint coef, or site id (nwrap x 1)
// actuators
int* actuator_trntype; // transmission type (mjtTrn) (nu x 1)
int* actuator_dyntype; // dynamics type (mjtDyn) (nu x 1)
int* actuator_gaintype; // gain type (mjtGain) (nu x 1)
int* actuator_biastype; // bias type (mjtBias) (nu x 1)
int* actuator_trnid; // transmission id: joint, tendon, site (nu x 2)
mjtNum* actuator_damping; // linear damping coefficient (nu x 1)
mjtNum* actuator_dampingpoly; // high-order damping coefficients (nu x mjNPOLY)
mjtNum* actuator_armature; // armature added to target (joint, tendon) (nu x 1)
int* actuator_actadr; // first activation address; -1: stateless (nu x 1)
int* actuator_actnum; // number of activation variables (nu x 1)
int* actuator_group; // group for visibility (nu x 1)
int* actuator_history; // history buffer: [nsample, interp] (nu x 2)
int* actuator_historyadr; // address in history buffer; -1: none (nu x 1)
mjtNum* actuator_delay; // delay time in seconds; 0: no delay (nu x 1)
int* actuator_trntype; // transmission type (mjtTrn) (nactuator x 1)
int* actuator_dyntype; // dynamics type (mjtDyn) (nactuator x 1)
int* actuator_gaintype; // gain type (mjtGain) (nactuator x 1)
int* actuator_biastype; // bias type (mjtBias) (nactuator x 1)
int* actuator_ctrladr; // address of first control (nactuator x 1)
int* actuator_ctrlnum; // number of controls (nactuator x 1)
int* actuator_outadr; // address of first force output (nactuator x 1)
int* actuator_outnum; // number of force outputs, from trntype (nactuator x 1)
int* actuator_actadr; // first activation address; -1: stateless (nactuator x 1)
int* actuator_actnum; // number of activation variables (nactuator x 1)
int* actuator_trnid; // transmission id: joint, tendon, site (nactuator x 2)
mjtNum* actuator_cranklength; // crank length for slider-crank (nactuator x 1)
mjtNum* actuator_dynprm; // dynamics parameters (nactuator x mjNDYN)
mjtNum* actuator_gainprm; // gain parameters (nactuator x mjNGAIN)
mjtNum* actuator_biasprm; // bias parameters (nactuator x mjNBIAS)
mjtBool* actuator_actlimited; // is activation limited (nactuator x 1)
mjtNum* actuator_actrange; // range of activations (nactuator x 2)
mjtBool* actuator_actearly; // step activation before force (nactuator x 1)
int* actuator_history; // history buffer: [nsample, interp] (nactuator x 2)
int* actuator_historyadr; // address in history buffer; -1: none (nactuator x 1)
mjtNum* actuator_delay; // delay time; 0: no delay (nactuator x 1)
mjtNum* actuator_damping; // linear damping coefficient (nactuator x 1)
mjtNum* actuator_dampingpoly; // high-order damping coefficients (nactuator x mjNPOLY)
mjtNum* actuator_armature; // armature added to target (joint, tendon) (nactuator x 1)
int* actuator_group; // group for visibility (nactuator x 1)
mjtNum* actuator_user; // user data (nactuator x nuser_actuator)
int* actuator_plugin; // plugin instance id; -1: not a plugin (nactuator x 1)
mjtBool* actuator_ctrllimited; // is control limited (nu x 1)
mjtBool* actuator_forcelimited;// is force limited (nu x 1)
mjtBool* actuator_actlimited; // is activation limited (nu x 1)
mjtNum* actuator_dynprm; // dynamics parameters (nu x mjNDYN)
mjtNum* actuator_gainprm; // gain parameters (nu x mjNGAIN)
mjtNum* actuator_biasprm; // bias parameters (nu x mjNBIAS)
mjtBool* actuator_actearly; // step activation before force (nu x 1)
mjtNum* actuator_ctrlrange; // range of controls (nu x 2)
mjtNum* actuator_forcerange; // range of forces (nu x 2)
mjtNum* actuator_actrange; // range of activations (nu x 2)
mjtNum* actuator_gear; // scale length and transmitted force (nu x 6)
mjtNum* actuator_cranklength; // crank length for slider-crank (nu x 1)
mjtNum* actuator_acc0; // acceleration from unit force in qpos0 (nu x 1)
mjtNum* actuator_length0; // actuator length in qpos0 (nu x 1)
mjtNum* actuator_lengthrange; // feasible actuator length range (nu x 2)
mjtNum* actuator_user; // user data (nu x nuser_actuator)
int* actuator_plugin; // plugin instance id; -1: not a plugin (nu x 1)
mjtNum* actuator_gear; // scale length and transmitted force (nout x 6)
mjtBool* actuator_forcelimited;// is force limited (nout x 1)
mjtNum* actuator_forcerange; // range of forces (nout x 2)
mjtNum* actuator_acc0; // acceleration from unit force in qpos0 (nout x 1)
mjtNum* actuator_length0; // actuator length in qpos0 (nout x 1)
mjtNum* actuator_lengthrange; // feasible actuator length range (nout x 2)
// sensors
int* sensor_type; // sensor type (mjtSensor) (nsensor x 1)
@@ -1174,7 +1180,7 @@ typedef struct mjModel_ {
int* name_excludeadr; // exclude name pointers (nexclude x 1)
int* name_eqadr; // equality constraint name pointers (neq x 1)
int* name_tendonadr; // tendon name pointers (ntendon x 1)
int* name_actuatoradr; // actuator name pointers (nu x 1)
int* name_actuatoradr; // actuator name pointers (nactuator x 1)
int* name_sensoradr; // sensor name pointers (nsensor x 1)
int* name_numericadr; // numeric name pointers (nnumeric x 1)
int* name_textadr; // text name pointers (ntext x 1)