Add the pid actuator: setpoint inputs, integral action, slew rate limiting.

<pid kp kv|dampratio [ki imax] [slewmax]> is a PID controller with real position and velocity setpoint inputs on a single force output, plus an optional feedforward input. With a zero velocity setpoint it reproduces <position> bit-exactly; the input signature is any subset of [pos, vel, ff], selected with input="..." and recorded as mjtCtrlInput bits in
actuator_ctrlspec; absent setpoint inputs are fixed at zero, so the control vector contains no inert entries.

kp and kv are single-sourced in the affine bias parameters (biasprm[1,2]) with no gainprm mirror: every consumer of the position-servo shape
(dampratio conversion, inheritrange, qDeriv) reads one location, which is what makes the bit-exact <position> parity possible. Controller state uses dyntype 'pid' with slot-gated activations in the order [slew, integral], following the dcmotor slot idiom: slewmax (dynprm[1]) rate limits the effective position setpoint through an activation holding it;
ki (gainprm[0]) integrates the position error -- wrapped on rotational transmissions -- with anti-windup clamping of the integrand at imax (dynprm[0]). Both features require the pos input. Servo input unpacking is shared with the dcmotor controller (unpackServoInputs); per-input ranges are exposed as posrange/velrange/ffrange.

This subsumes the functionality of the mujoco.pid plugin with proper activation state: correct under all integrators, visible to keyframes, act sensors and reset. Migration: kp/ki/kd map to kp/ki/kv, plugin imax is in force units (divide by ki), slewmax carries over; the single ctrl becomes input="pos".

PiperOrigin-RevId: 957588898
Change-Id: Id2786836ca6e76f58e5b5cc8323fc23be0a53784
This commit is contained in:
Yuval Tassa
2026-08-01 04:28:10 -07:00
committed by Copybara-Service
parent 7bc1aa9b05
commit 279df98cd0
33 changed files with 1504 additions and 62 deletions
+10
View File
@@ -357,6 +357,16 @@ Orientation input charts of so3 actuators. These values are used in ``m->actuato
.. mujoco-include:: mjtCtrlChart .. mujoco-include:: mjtCtrlChart
.. _mjtCtrlInput:
mjtCtrlInput
~~~~~~~~~~~~
Input bitflags of servo-family (pd, dcmotor) actuators. These values are used in ``m->actuator_ctrlspec``.
.. mujoco-include:: mjtCtrlInput
.. _mjtObj: .. _mjtObj:
mjtObj mjtObj
+9
View File
@@ -5315,6 +5315,15 @@ Set actuator to velocity servo; return error if any.
Set actuator to orientation servo. Set actuator to orientation servo.
.. _mjs_setToPID:
`mjs_setToPID <#mjs_setToPID>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mjs_setToPID
Set actuator to PID controller.
.. _mjs_setToDamper: .. _mjs_setToDamper:
`mjs_setToDamper <#mjs_setToDamper>`__ `mjs_setToDamper <#mjs_setToDamper>`__
+265 -19
View File
@@ -5664,25 +5664,27 @@ specify them independently.
.. _actuator-general-dyntype: .. _actuator-general-dyntype:
:at:`dyntype`: :at-val:`[none, integrator, filter, filterexact, muscle, user], "none"` :at:`dyntype`: :at-val:`[none, integrator, filter, filterexact, pid, muscle, user], "none"`
Activation dynamics type for the actuator. The available dynamics types were already described in the :ref:`Actuation Activation dynamics type for the actuator. The available dynamics types were already described in the :ref:`Actuation
model <geActuation>` section. Repeating that description in somewhat different notation (corresponding to the mjModel model <geActuation>` section. Repeating that description in somewhat different notation (corresponding to the mjModel
and mjData fields involved) we have: and mjData fields involved) we have:
=========== ====================================== ============= ======================================
Keyword Description Keyword Description
=========== ====================================== ============= ======================================
none No internal state none No internal state
integrator act_dot = ctrl integrator act_dot = ctrl
filter act_dot = (ctrl - act) / dynprm[0] filter act_dot = (ctrl - act) / dynprm[0]
filterexact Like filter but with exact integration filterexact Like filter but with exact integration
muscle act_dot = mju_muscleDynamics(...) pid act_dot = position error; see :ref:`pid<actuator-pid>`
user act_dot = mjcb_act_dyn(...) dcmotor DC motor electrical dynamics, see :ref:`dcmotor<actuator-dcmotor>`
=========== ====================================== muscle act_dot = mju_muscleDynamics(...)
user act_dot = mjcb_act_dyn(...)
============= ======================================
.. _actuator-general-gaintype: .. _actuator-general-gaintype:
:at:`gaintype`: :at-val:`[fixed, affine, muscle, so3, user], "fixed"` :at:`gaintype`: :at-val:`[fixed, affine, muscle, pid, so3, user], "fixed"`
The gain and bias together determine the output of the force generation mechanism, which is currently assumed to be The gain and bias together determine the output of the force generation mechanism, which is currently assumed to be
affine. As already explained in :ref:`Actuation model <geActuation>`, the general formula is: affine. As already explained in :ref:`Actuation model <geActuation>`, the general formula is:
scalar_force = gain_term \* (act or ctrl) + bias_term. scalar_force = gain_term \* (act or ctrl) + bias_term.
@@ -5695,6 +5697,7 @@ specify them independently.
fixed gain_term = gainprm[0] fixed gain_term = gainprm[0]
affine gain_term = gain_prm[0] + gain_prm[1]*length + gain_prm[2]*velocity affine gain_term = gain_prm[0] + gain_prm[1]*length + gain_prm[2]*velocity
muscle gain_term = mju_muscleGain(...) muscle gain_term = mju_muscleGain(...)
pid PID controller with setpoint inputs, see :ref:`pid<actuator-pid>`
so3 geodesic orientation servo, computed jointly over 3 force outputs, see :ref:`orientation<actuator-orientation>` so3 geodesic orientation servo, computed jointly over 3 force outputs, see :ref:`orientation<actuator-orientation>`
user gain_term = mjcb_act_gain(...) user gain_term = mjcb_act_gain(...)
======= =============================== ======= ===============================
@@ -5739,12 +5742,23 @@ specify them independently.
so the user can enter as many parameters as needed. These defaults are not compatible with muscle actuators; see so the user can enter as many parameters as needed. These defaults are not compatible with muscle actuators; see
:ref:`muscle <actuator-muscle>` below. :ref:`muscle <actuator-muscle>` below.
.. _actuator-general-velrange:
:at:`velrange`: :at-val:`real(2), "0 0"`
Range of the velocity-setpoint input of a :ref:`pid<actuator-pid>` actuator.
.. _actuator-general-ffrange:
:at:`ffrange`: :at-val:`real(2), "0 0"`
Range of the feedforward input of a :ref:`pid<actuator-pid>` actuator.
.. _actuator-general-input: .. _actuator-general-input:
:at:`input`: :at-val:`string, optional` :at:`input`: :at-val:`string, optional`
Input signature of the actuator: which controls make up its control block, recorded in Input signature of the actuator: which controls make up its control block, recorded in
``mjModel.actuator_ctrlspec``. Available for gaintype "so3", where it selects the orientation chart: "expmap" ``mjModel.actuator_ctrlspec``. For gaintype "so3" it selects the orientation chart: "expmap" (3 controls, the
(3 controls, the default) or "quat" (4 controls); see :ref:`orientation/input<actuator-orientation-input>`. default) or "quat" (4 controls); see :ref:`orientation/input<actuator-orientation-input>`. For gaintype "pid" it is
a token list selecting the input subset; see :ref:`pid/input<actuator-pid-input>`.
.. _actuator-general-actearly: .. _actuator-general-actearly:
@@ -5954,6 +5968,146 @@ This element has one custom attribute in addition to the common attributes:
:ref:`position<actuator-position>` attribute and in the :ref:`default class<default-position-inheritrange>`, :ref:`position<actuator-position>` attribute and in the :ref:`default class<default-position-inheritrange>`,
saved XMLs always convert it to explicit :at:`ctrlrange` at the actuator. saved XMLs always convert it to explicit :at:`ctrlrange` at the actuator.
.. _actuator-pid:
:el-prefix:`actuator/` |-| **pid** |*|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element creates a PID controller with position and velocity setpoint inputs on a single force output, with optional
integral action and feedforward. With the default input signature ``[pos, vel]`` the force is
:math:`k_p (u_{pos} - l) + k_v (u_{vel} - v)` where :math:`l, v` are the actuator length and velocity; with a zero
velocity setpoint this is identical to :ref:`position<actuator-position>`. The input signature is any subset of
``[pos, vel, ff]``, selected by :ref:`input<actuator-pid-input>`: an absent setpoint input is fixed at zero, and the
``ff`` input adds a feedforward force. Integral action is enabled by :ref:`ki<actuator-pid-ki>`: the position error is integrated in
:ref:`act<siPhysicsState>` and contributes :math:`k_i \cdot act` to the force, with anti-windup clamping by
:ref:`imax<actuator-pid-imax>`.
:ref:`slewmax<actuator-pid-slewmax>` limits the rate of change of the effective position setpoint. Each of these
features, when enabled, adds one activation state, in the order [slew, integral]. The underlying
:ref:`general<actuator-general>` attributes are set as follows:
========= ===================== ========= =========
Attribute Setting Attribute Setting
========= ===================== ========= =========
dyntype none or pid dynprm imax 0 0
gaintype pid gainprm ki 0 0
biastype affine biasprm 0 -kp -kv
========= ===================== ========= =========
This element has custom attributes in addition to the common attributes:
.. _actuator-pid-name:
.. _actuator-pid-class:
.. _actuator-pid-group:
.. _actuator-pid-nsample:
.. _actuator-pid-interp:
.. _actuator-pid-delay:
.. _actuator-pid-ctrllimited:
.. _actuator-pid-forcelimited:
.. _actuator-pid-ctrlrange:
.. _actuator-pid-forcerange:
.. _actuator-pid-lengthrange:
.. _actuator-pid-gear:
.. _actuator-pid-damping:
.. _actuator-pid-armature:
.. _actuator-pid-cranklength:
.. _actuator-pid-user:
.. _actuator-pid-joint:
.. _actuator-pid-jointinparent:
.. _actuator-pid-tendon:
.. _actuator-pid-slidersite:
.. _actuator-pid-cranksite:
.. _actuator-pid-site:
.. _actuator-pid-refsite:
.. _actuator-pid-kp:
:at:`kp`: :at-val:`real, "1"`
Position feedback gain.
.. _actuator-pid-kv:
:at:`kv`: :at-val:`real, "0"`
Velocity feedback gain: applied to the velocity error when the ``vel`` input is present, and as pure damping
otherwise. When using this attribute, it is recommended to use the implicitfast or implicit
:ref:`integrators<geIntegration>`.
.. _actuator-pid-dampratio:
:at:`dampratio`: :at-val:`real, "0"`
Damping applied by the actuator, using damping ratio units, as for
:ref:`position/dampratio<actuator-position-dampratio>`. This attribute is exclusive with :at:`kv`.
.. _actuator-pid-ki:
:at:`ki`: :at-val:`real, "0"`
Integral gain. A nonzero value enables integral action: the position error is integrated in
:ref:`act<siPhysicsState>` (:ref:`dyntype<actuator-general-dyntype>` "pid") and contributes :math:`k_i \cdot act`
to the force.
Requires the ``pos`` input.
.. _actuator-pid-imax:
:at:`imax`: :at-val:`real, "0"`
Anti-windup limit on the integral state: accumulation stops beyond ±\ :at:`imax`. The default value 0 means
"unclamped".
.. _actuator-pid-slewmax:
:at:`slewmax`: :at-val:`real, "0"`
Maximum rate of change of the effective position setpoint. When positive, the commanded setpoint is rate-limited
through an activation state holding the effective setpoint, as for the
:ref:`dcmotor controller<actuator-dcmotor-controller>`. The default value 0 means "unlimited".
.. _actuator-pid-input:
:at:`input`: :at-val:`string, "pos vel"`
Input signature: a space-separated subset of the tokens "pos", "vel" and "ff", packed in this canonical order.
Absent setpoint inputs are fixed at zero, so the control vector contains no inert entries.
.. _actuator-pid-posrange:
:at:`posrange`: :at-val:`real(2), "0 0"`
Range of the position-setpoint input; an alias of :ref:`ctrlrange<actuator-general-ctrlrange>` (the first
input).
.. _actuator-pid-velrange:
:at:`velrange`: :at-val:`real(2), "0 0"`
Range of the velocity-setpoint input.
.. _actuator-pid-ffrange:
:at:`ffrange`: :at-val:`real(2), "0 0"`
Range of the feedforward input.
.. _actuator-pid-inheritrange:
:at:`inheritrange`: :at-val:`real, "0"`
Identical to :ref:`position/inheritrange<actuator-position-inheritrange>`, setting :at:`posrange` from the
transmission target's :at:`range`.
.. _actuator-orientation: .. _actuator-orientation:
:el-prefix:`actuator/` |-| **orientation** |*| :el-prefix:`actuator/` |-| **orientation** |*|
@@ -5976,7 +6130,7 @@ preserving its direction; the lower bound must be 0.
:ref:`Actuator sensors<sensor-actuatorpos>` report one value per force output. The integrator variant, which :ref:`Actuator sensors<sensor-actuatorpos>` report one value per force output. The integrator variant, which
stores the orientation setpoint in :ref:`act<siPhysicsState>`, is available via :ref:`general<actuator-general>` with stores the orientation setpoint in :ref:`act<siPhysicsState>`, is available via :ref:`general<actuator-general>` with
:ref:`dyntype<actuator-general-dyntype>` "integrator" and is expmap-only. The video on the right shows this `example :ref:`dyntype<actuator-general-dyntype>` "integrator" and is expmap-only. The video on the right shows this `example
model <https://github.com/google-deepmind/mujoco/blob/main/test/engine/testdata/sensor/actuation/orientation.xml>`__. model <https://github.com/google-deepmind/mujoco/blob/main/test/engine/testdata/actuation/orientation.xml>`__.
The underlying :el:`general` attributes are set as follows: The underlying :el:`general` attributes are set as follows:
========= ======= ========= ========= ========= ======= ========= =========
@@ -6056,9 +6210,8 @@ This element has custom attributes in addition to the common attributes:
:el-prefix:`actuator/` |-| **velocity** |*| :el-prefix:`actuator/` |-| **velocity** |*|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element creates a velocity servo. Note that in order to create a PD controller, one has to define two actuators: a This element creates a velocity servo. Note that a PD controller with both position and velocity setpoint inputs is
position servo and a velocity servo. This is because MuJoCo actuators are SISO while a PD controller takes two control provided by the :ref:`pid<actuator-pid>` actuator.
inputs (reference position and reference velocity).
When using this actuator, it is recommended to use the implicitfast or implicit :ref:`integrators<geIntegration>`. When using this actuator, it is recommended to use the implicitfast or implicit :ref:`integrators<geIntegration>`.
The underlying :el:`general` attributes are set as follows: The underlying :el:`general` attributes are set as follows:
@@ -6802,7 +6955,7 @@ Associate this actuator with an :ref:`engine plugin<exPlugin>`. Either :at:`plug
.. _actuator-plugin-dyntype: .. _actuator-plugin-dyntype:
:at:`dyntype`: :at-val:`[none, integrator, filter, filterexact, muscle, user], "none"` :at:`dyntype`: :at-val:`[none, integrator, filter, filterexact, pid, muscle, user], "none"`
Activation dynamics type for the actuator. The available dynamics types were already described in the :ref:`Actuation Activation dynamics type for the actuator. The available dynamics types were already described in the :ref:`Actuation
model <geActuation>` section. If :ref:`dyntype<actuator-general-dyntype>` is not "none", an activation variable will model <geActuation>` section. If :ref:`dyntype<actuator-general-dyntype>` is not "none", an activation variable will
be added to the actuator. This variable will be added after any activation state computed by the plugin (see be added to the actuator. This variable will be added after any activation state computed by the plugin (see
@@ -10020,6 +10173,10 @@ if omitted.
.. _default-general-biasprm: .. _default-general-biasprm:
.. _default-general-velrange:
.. _default-general-ffrange:
.. _default-general-input: .. _default-general-input:
.. _default-general-actearly: .. _default-general-actearly:
@@ -10199,6 +10356,95 @@ All :ref:`intvelocity <actuator-intvelocity>` attributes are available here exce
site, refsite, tendon, slidersite, cranksite. site, refsite, tendon, slidersite, cranksite.
.. _default-pid:
.. _default-pid-ctrllimited:
.. _default-pid-forcelimited:
.. _default-pid-ctrlrange:
.. _default-pid-posrange:
.. _default-pid-velrange:
.. _default-pid-ffrange:
.. _default-pid-forcerange:
.. _default-pid-inheritrange:
.. _default-pid-gear:
.. _default-pid-damping:
.. _default-pid-armature:
.. _default-pid-cranklength:
.. _default-pid-user:
.. _default-pid-group:
.. _default-pid-nsample:
.. _default-pid-interp:
.. _default-pid-delay:
.. _default-pid-kp:
.. _default-pid-kv:
.. _default-pid-dampratio:
.. _default-pid-ki:
.. _default-pid-imax:
.. _default-pid-slewmax:
.. _default-pid-input:
:el-prefix:`default/` |-| **pid** |?|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
All :ref:`pid <actuator-pid>` attributes are available here except: name, class, joint, jointinparent,
site, refsite, tendon, slidersite, cranksite.
.. _default-orientation:
.. _default-orientation-forcelimited:
.. _default-orientation-ctrlrange:
.. _default-orientation-forcerange:
.. _default-orientation-user:
.. _default-orientation-group:
.. _default-orientation-nsample:
.. _default-orientation-interp:
.. _default-orientation-delay:
.. _default-orientation-kp:
.. _default-orientation-kv:
.. _default-orientation-dampratio:
.. _default-orientation-input:
:el-prefix:`default/` |-| **orientation** |?|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
All :ref:`orientation <actuator-orientation>` attributes are available here except: name, class, joint, site, refsite.
.. _default-damper: .. _default-damper:
.. _default-damper-forcelimited: .. _default-damper-forcelimited:
+240
View File
@@ -2378,6 +2378,12 @@
.. grid-item:: .. grid-item::
:ref:`input<actuator-general-input>` :ref:`input<actuator-general-input>`
.. grid-item::
:ref:`velrange<actuator-general-velrange>`
.. grid-item::
:ref:`ffrange<actuator-general-ffrange>`
.. grid-item:: .. grid-item::
:ref:`dyntype<actuator-general-dyntype>` :ref:`dyntype<actuator-general-dyntype>`
@@ -2793,6 +2799,114 @@
:ref:`input<actuator-orientation-input>` :ref:`input<actuator-orientation-input>`
.. dropdown:: :ref:`pid<actuator-pid>` |*|
.. grid:: 2 3 4 4
:gutter: 0
.. grid-item::
:ref:`name<actuator-pid-name>`
.. grid-item::
:ref:`class<actuator-pid-class>`
.. grid-item::
:ref:`group<actuator-pid-group>`
.. grid-item::
:ref:`nsample<actuator-pid-nsample>`
.. grid-item::
:ref:`interp<actuator-pid-interp>`
.. grid-item::
:ref:`delay<actuator-pid-delay>`
.. grid-item::
:ref:`ctrllimited<actuator-pid-ctrllimited>`
.. grid-item::
:ref:`forcelimited<actuator-pid-forcelimited>`
.. grid-item::
:ref:`ctrlrange<actuator-pid-ctrlrange>`
.. grid-item::
:ref:`posrange<actuator-pid-posrange>`
.. grid-item::
:ref:`velrange<actuator-pid-velrange>`
.. grid-item::
:ref:`ffrange<actuator-pid-ffrange>`
.. grid-item::
:ref:`forcerange<actuator-pid-forcerange>`
.. grid-item::
:ref:`inheritrange<actuator-pid-inheritrange>`
.. grid-item::
:ref:`lengthrange<actuator-pid-lengthrange>`
.. grid-item::
:ref:`gear<actuator-pid-gear>`
.. grid-item::
:ref:`damping<actuator-pid-damping>`
.. grid-item::
:ref:`armature<actuator-pid-armature>`
.. grid-item::
:ref:`cranklength<actuator-pid-cranklength>`
.. grid-item::
:ref:`user<actuator-pid-user>`
.. grid-item::
:ref:`joint<actuator-pid-joint>`
.. grid-item::
:ref:`jointinparent<actuator-pid-jointinparent>`
.. grid-item::
:ref:`tendon<actuator-pid-tendon>`
.. grid-item::
:ref:`slidersite<actuator-pid-slidersite>`
.. grid-item::
:ref:`cranksite<actuator-pid-cranksite>`
.. grid-item::
:ref:`site<actuator-pid-site>`
.. grid-item::
:ref:`refsite<actuator-pid-refsite>`
.. grid-item::
:ref:`kp<actuator-pid-kp>`
.. grid-item::
:ref:`kv<actuator-pid-kv>`
.. grid-item::
:ref:`dampratio<actuator-pid-dampratio>`
.. grid-item::
:ref:`ki<actuator-pid-ki>`
.. grid-item::
:ref:`imax<actuator-pid-imax>`
.. grid-item::
:ref:`slewmax<actuator-pid-slewmax>`
.. grid-item::
:ref:`input<actuator-pid-input>`
.. dropdown:: :ref:`damper<actuator-damper>` |*| .. dropdown:: :ref:`damper<actuator-damper>` |*|
.. grid:: 2 3 4 4 .. grid:: 2 3 4 4
@@ -5927,6 +6041,12 @@
.. grid-item:: .. grid-item::
:ref:`input<default-general-input>` :ref:`input<default-general-input>`
.. grid-item::
:ref:`velrange<default-general-velrange>`
.. grid-item::
:ref:`ffrange<default-general-ffrange>`
.. grid-item:: .. grid-item::
:ref:`dyntype<default-general-dyntype>` :ref:`dyntype<default-general-dyntype>`
@@ -6165,6 +6285,126 @@
:ref:`dampratio<default-intvelocity-dampratio>` :ref:`dampratio<default-intvelocity-dampratio>`
.. dropdown:: :ref:`orientation<default-orientation>` :octicon:`dot`
.. grid:: 2 3 4 4
:gutter: 0
.. grid-item::
:ref:`forcelimited<default-orientation-forcelimited>`
.. grid-item::
:ref:`ctrlrange<default-orientation-ctrlrange>`
.. grid-item::
:ref:`forcerange<default-orientation-forcerange>`
.. grid-item::
:ref:`user<default-orientation-user>`
.. grid-item::
:ref:`group<default-orientation-group>`
.. grid-item::
:ref:`nsample<default-orientation-nsample>`
.. grid-item::
:ref:`interp<default-orientation-interp>`
.. grid-item::
:ref:`delay<default-orientation-delay>`
.. grid-item::
:ref:`kp<default-orientation-kp>`
.. grid-item::
:ref:`kv<default-orientation-kv>`
.. grid-item::
:ref:`dampratio<default-orientation-dampratio>`
.. grid-item::
:ref:`input<default-orientation-input>`
.. dropdown:: :ref:`pid<default-pid>` :octicon:`dot`
.. grid:: 2 3 4 4
:gutter: 0
.. grid-item::
:ref:`ctrllimited<default-pid-ctrllimited>`
.. grid-item::
:ref:`forcelimited<default-pid-forcelimited>`
.. grid-item::
:ref:`ctrlrange<default-pid-ctrlrange>`
.. grid-item::
:ref:`posrange<default-pid-posrange>`
.. grid-item::
:ref:`velrange<default-pid-velrange>`
.. grid-item::
:ref:`ffrange<default-pid-ffrange>`
.. grid-item::
:ref:`forcerange<default-pid-forcerange>`
.. grid-item::
:ref:`inheritrange<default-pid-inheritrange>`
.. grid-item::
:ref:`gear<default-pid-gear>`
.. grid-item::
:ref:`damping<default-pid-damping>`
.. grid-item::
:ref:`armature<default-pid-armature>`
.. grid-item::
:ref:`cranklength<default-pid-cranklength>`
.. grid-item::
:ref:`user<default-pid-user>`
.. grid-item::
:ref:`group<default-pid-group>`
.. grid-item::
:ref:`nsample<default-pid-nsample>`
.. grid-item::
:ref:`interp<default-pid-interp>`
.. grid-item::
:ref:`delay<default-pid-delay>`
.. grid-item::
:ref:`kp<default-pid-kp>`
.. grid-item::
:ref:`kv<default-pid-kv>`
.. grid-item::
:ref:`dampratio<default-pid-dampratio>`
.. grid-item::
:ref:`ki<default-pid-ki>`
.. grid-item::
:ref:`imax<default-pid-imax>`
.. grid-item::
:ref:`slewmax<default-pid-slewmax>`
.. grid-item::
:ref:`input<default-pid-input>`
.. dropdown:: :ref:`damper<default-damper>` :octicon:`dot` .. dropdown:: :ref:`damper<default-damper>` :octicon:`dot`
.. grid:: 2 3 4 4 .. grid:: 2 3 4 4
+17
View File
@@ -5,6 +5,23 @@ Changelog
Upcoming version (not yet released) Upcoming version (not yet released)
----------------------------------- -----------------------------------
Actuation
^^^^^^^^^
- Added the :ref:`pid<actuator-pid>` actuator: a PID controller with real position and velocity setpoint inputs,
optional integral action (:ref:`ki<actuator-pid-ki>`, integrating the position error with
:ref:`imax<actuator-pid-imax>` anti-windup), setpoint rate limiting (:ref:`slewmax<actuator-pid-slewmax>`), and an
optional feedforward input. This subsumes the functionality of the ``mujoco.pid`` plugin with proper activation
state: correct under all integrators and visible to keyframes and sensors. With a zero velocity setpoint it is
identical to :ref:`position<actuator-position>`. The input signature is any subset of ``[pos, vel, ff]``, selected
by :ref:`input<actuator-pid-input>`; absent setpoint inputs are fixed at zero, so the control vector contains no
inert entries.
.. admonition:: Breaking ABI changes
:class: caution
- :ref:`mjsActuator` gained ``velrange`` and ``ffrange`` fields, changing its size and layout. The :ref:`mjtGain`
and :ref:`mjtDyn` enums gained ``pid`` members, shifting the values of ``mjGAIN_USER`` and ``mjDYN_USER``.
Engine Engine
^^^^^^ ^^^^^^
+12
View File
@@ -2244,6 +2244,8 @@ typedef struct mjsActuator_ { // actuator specification
double dynprm[mjNDYN]; // dynamics parameters double dynprm[mjNDYN]; // dynamics parameters
int actdim; // number of activation variables int actdim; // number of activation variables
int ctrlspec; // input signature, scoped by gaintype; 0: type default int ctrlspec; // input signature, scoped by gaintype; 0: type default
double velrange[2]; // range of the velocity-setpoint input (pid)
double ffrange[2]; // range of the feedforward input (pid)
mjtBool actearly; // apply next activations to qfrc mjtBool actearly; // apply next activations to qfrc
// transmission // transmission
@@ -2513,6 +2515,7 @@ typedef enum mjtDyn { // type of actuator dynamics
mjDYN_FILTEREXACT, // linear filter: da/dt = (u-a) / tau, with exact integration mjDYN_FILTEREXACT, // linear filter: da/dt = (u-a) / tau, with exact integration
mjDYN_MUSCLE, // piecewise linear filter with two time constants mjDYN_MUSCLE, // piecewise linear filter with two time constants
mjDYN_DCMOTOR, // DC motor electrical dynamics mjDYN_DCMOTOR, // DC motor electrical dynamics
mjDYN_PID, // PID controller states: slew, integral
mjDYN_USER // user-defined dynamics type mjDYN_USER // user-defined dynamics type
} mjtDyn; } mjtDyn;
typedef enum mjtGain { // type of actuator gain typedef enum mjtGain { // type of actuator gain
@@ -2521,6 +2524,7 @@ typedef enum mjtGain { // type of actuator gain
mjGAIN_MUSCLE, // muscle FLV curve computed by mju_muscleGain() mjGAIN_MUSCLE, // muscle FLV curve computed by mju_muscleGain()
mjGAIN_DCMOTOR, // DC motor gain: K or K/R mjGAIN_DCMOTOR, // DC motor gain: K or K/R
mjGAIN_SO3, // geodesic servo on an SO3 transmission: force = kp * log(error) mjGAIN_SO3, // geodesic servo on an SO3 transmission: force = kp * log(error)
mjGAIN_PID, // PID controller: position and velocity setpoint inputs
mjGAIN_USER // user-defined gain type mjGAIN_USER // user-defined gain type
} mjtGain; } mjtGain;
typedef enum mjtBias { // type of actuator bias typedef enum mjtBias { // type of actuator bias
@@ -2535,6 +2539,11 @@ typedef enum mjtCtrlChart { // so3 input signature (actuator_ctrlspec): or
mjCHART_EXPMAP = 1, // exponential-map orientation target: 3 controls mjCHART_EXPMAP = 1, // exponential-map orientation target: 3 controls
mjCHART_QUAT = 2 // quaternion orientation target: 4 controls mjCHART_QUAT = 2 // quaternion orientation target: 4 controls
} mjtCtrlChart; } mjtCtrlChart;
typedef enum mjtCtrlInput { // servo input signature (actuator_ctrlspec): present-input bits
mjINPUT_POS = 1, // position setpoint input
mjINPUT_VEL = 2, // velocity setpoint input
mjINPUT_FF = 4 // feedforward input
} mjtCtrlInput;
typedef enum mjtObj { // type of MujoCo object typedef enum mjtObj { // type of MujoCo object
mjOBJ_UNKNOWN = 0, // unknown object type mjOBJ_UNKNOWN = 0, // unknown object type
mjOBJ_BODY, // body mjOBJ_BODY, // body
@@ -3989,6 +3998,9 @@ const char* mjs_setToIntVelocity(mjsActuator* actuator, double kp, double kv[1],
const char* mjs_setToVelocity(mjsActuator* actuator, double kv); const char* mjs_setToVelocity(mjsActuator* actuator, double kv);
const char* mjs_setToOrientation(mjsActuator* actuator, double kp, double kv[1], const char* mjs_setToOrientation(mjsActuator* actuator, double kp, double kv[1],
double dampratio[1], int ctrlspec); double dampratio[1], int ctrlspec);
const char* mjs_setToPID(mjsActuator* actuator, double kp, double kv[1], double dampratio[1],
double ki[1], double imax[1], double slewmax[1], double inheritrange,
int ctrlspec);
const char* mjs_setToDamper(mjsActuator* actuator, double kv); const char* mjs_setToDamper(mjsActuator* actuator, double kv);
const char* mjs_setToCylinder(mjsActuator* actuator, double timeconst, const char* mjs_setToCylinder(mjsActuator* actuator, double timeconst,
double bias, double area, double diameter); double bias, double area, double diameter);
+2
View File
@@ -725,6 +725,8 @@ typedef struct mjsActuator_ { // actuator specification
double dynprm[mjNDYN]; // dynamics parameters double dynprm[mjNDYN]; // dynamics parameters
int actdim; // number of activation variables int actdim; // number of activation variables
int ctrlspec; // input signature, scoped by gaintype; 0: type default int ctrlspec; // input signature, scoped by gaintype; 0: type default
double velrange[2]; // range of the velocity-setpoint input (pid)
double ffrange[2]; // range of the feedforward input (pid)
mjtBool actearly; // apply next activations to qfrc mjtBool actearly; // apply next activations to qfrc
// transmission // transmission
+2
View File
@@ -497,6 +497,8 @@
XVEC( double, dynprm, mjNDYN ) \ XVEC( double, dynprm, mjNDYN ) \
X ( int, actdim, 1 ) \ X ( int, actdim, 1 ) \
X ( int, ctrlspec, 1 ) \ X ( int, ctrlspec, 1 ) \
XVEC( double, velrange, 2 ) \
XVEC( double, ffrange, 2 ) \
X ( mjtBool, actearly, 1 ) \ X ( mjtBool, actearly, 1 ) \
X ( mjtTrn, trntype, 1 ) \ X ( mjtTrn, trntype, 1 ) \
XVEC( double, gear, 6 ) \ XVEC( double, gear, 6 ) \
+9
View File
@@ -247,6 +247,7 @@ typedef enum mjtDyn { // type of actuator dynamics
mjDYN_FILTEREXACT, // linear filter: da/dt = (u-a) / tau, with exact integration mjDYN_FILTEREXACT, // linear filter: da/dt = (u-a) / tau, with exact integration
mjDYN_MUSCLE, // piecewise linear filter with two time constants mjDYN_MUSCLE, // piecewise linear filter with two time constants
mjDYN_DCMOTOR, // DC motor electrical dynamics mjDYN_DCMOTOR, // DC motor electrical dynamics
mjDYN_PID, // PID controller states: slew, integral
mjDYN_USER // user-defined dynamics type mjDYN_USER // user-defined dynamics type
} mjtDyn; } mjtDyn;
@@ -257,6 +258,7 @@ typedef enum mjtGain { // type of actuator gain
mjGAIN_MUSCLE, // muscle FLV curve computed by mju_muscleGain() mjGAIN_MUSCLE, // muscle FLV curve computed by mju_muscleGain()
mjGAIN_DCMOTOR, // DC motor gain: K or K/R mjGAIN_DCMOTOR, // DC motor gain: K or K/R
mjGAIN_SO3, // geodesic servo on an SO3 transmission: force = kp * log(error) mjGAIN_SO3, // geodesic servo on an SO3 transmission: force = kp * log(error)
mjGAIN_PID, // PID controller: position and velocity setpoint inputs
mjGAIN_USER // user-defined gain type mjGAIN_USER // user-defined gain type
} mjtGain; } mjtGain;
@@ -277,6 +279,13 @@ typedef enum mjtCtrlChart { // so3 input signature (actuator_ctrlspec): or
} mjtCtrlChart; } mjtCtrlChart;
typedef enum mjtCtrlInput { // servo input signature (actuator_ctrlspec): present-input bits
mjINPUT_POS = 1, // position setpoint input
mjINPUT_VEL = 2, // velocity setpoint input
mjINPUT_FF = 4 // feedforward input
} mjtCtrlInput;
typedef enum mjtObj { // type of MujoCo object typedef enum mjtObj { // type of MujoCo object
mjOBJ_UNKNOWN = 0, // unknown object type mjOBJ_UNKNOWN = 0, // unknown object type
mjOBJ_BODY, // body mjOBJ_BODY, // body
+5
View File
@@ -1760,6 +1760,11 @@ MJAPI const char* mjs_setToVelocity(mjsActuator* actuator, double kv);
MJAPI const char* mjs_setToOrientation(mjsActuator* actuator, double kp, double kv[1], MJAPI const char* mjs_setToOrientation(mjsActuator* actuator, double kp, double kv[1],
double dampratio[1], int ctrlspec); double dampratio[1], int ctrlspec);
// Set actuator to PID controller.
MJAPI const char* mjs_setToPID(mjsActuator* actuator, double kp, double kv[1], double dampratio[1],
double ki[1], double imax[1], double slewmax[1], double inheritrange,
int ctrlspec);
// Set actuator to activate damper; return error if any. // Set actuator to activate damper; return error if any.
MJAPI const char* mjs_setToDamper(mjsActuator* actuator, double kv); MJAPI const char* mjs_setToDamper(mjsActuator* actuator, double kv);
+14 -2
View File
@@ -266,7 +266,8 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjDYN_FILTEREXACT', 3), ('mjDYN_FILTEREXACT', 3),
('mjDYN_MUSCLE', 4), ('mjDYN_MUSCLE', 4),
('mjDYN_DCMOTOR', 5), ('mjDYN_DCMOTOR', 5),
('mjDYN_USER', 6), ('mjDYN_PID', 6),
('mjDYN_USER', 7),
]), ]),
)), )),
('mjtGain', ('mjtGain',
@@ -279,7 +280,8 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjGAIN_MUSCLE', 2), ('mjGAIN_MUSCLE', 2),
('mjGAIN_DCMOTOR', 3), ('mjGAIN_DCMOTOR', 3),
('mjGAIN_SO3', 4), ('mjGAIN_SO3', 4),
('mjGAIN_USER', 5), ('mjGAIN_PID', 5),
('mjGAIN_USER', 6),
]), ]),
)), )),
('mjtBias', ('mjtBias',
@@ -304,6 +306,16 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjCHART_QUAT', 2), ('mjCHART_QUAT', 2),
]), ]),
)), )),
('mjtCtrlInput',
EnumDecl(
name='mjtCtrlInput',
declname='enum mjtCtrlInput',
values=dict([
('mjINPUT_POS', 1),
('mjINPUT_VEL', 2),
('mjINPUT_FF', 4),
]),
)),
('mjtObj', ('mjtObj',
EnumDecl( EnumDecl(
name='mjtObj', name='mjtObj',
+63
View File
@@ -11035,6 +11035,69 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
), ),
doc='Set actuator to orientation servo.', doc='Set actuator to orientation servo.',
)), )),
('mjs_setToPID',
FunctionDecl(
name='mjs_setToPID',
return_type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
parameters=(
FunctionParameterDecl(
name='actuator',
type=PointerType(
inner_type=ValueType(name='mjsActuator'),
),
),
FunctionParameterDecl(
name='kp',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='kv',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(1,),
),
),
FunctionParameterDecl(
name='dampratio',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(1,),
),
),
FunctionParameterDecl(
name='ki',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(1,),
),
),
FunctionParameterDecl(
name='imax',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(1,),
),
),
FunctionParameterDecl(
name='slewmax',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(1,),
),
),
FunctionParameterDecl(
name='inheritrange',
type=ValueType(name='double'),
),
FunctionParameterDecl(
name='ctrlspec',
type=ValueType(name='int'),
),
),
doc='Set actuator to PID controller.',
)),
('mjs_setToDamper', ('mjs_setToDamper',
FunctionDecl( FunctionDecl(
name='mjs_setToDamper', name='mjs_setToDamper',
+16
View File
@@ -9647,6 +9647,22 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='int'), type=ValueType(name='int'),
doc='input signature, scoped by gaintype; 0: type default', doc='input signature, scoped by gaintype; 0: type default',
), ),
StructFieldDecl(
name='velrange',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(2,),
),
doc='range of the velocity-setpoint input (pid)',
),
StructFieldDecl(
name='ffrange',
type=ArrayType(
inner_type=ValueType(name='double'),
extents=(2,),
),
doc='range of the feedforward input (pid)',
),
StructFieldDecl( StructFieldDecl(
name='actearly', name='actearly',
type=ValueType(name='mjtBool'), type=ValueType(name='mjtBool'),
+17
View File
@@ -1567,6 +1567,23 @@ PYBIND11_MODULE(_specs, m, pybind11::mod_gil_not_used()) {
}, },
py::arg("kp"), py::arg("kv") = -1, py::arg("dampratio") = -1, py::arg("kp"), py::arg("kv") = -1, py::arg("dampratio") = -1,
py::arg("ctrlspec") = 0); py::arg("ctrlspec") = 0);
mjsActuator.def(
"set_to_pid",
[](raw::MjsActuator* self, double kp, double kv, double dampratio,
double ki, double imax, double slewmax, double inheritrange,
int ctrlspec) {
std::string err = mjs_setToPID(
self, kp, kv == -1 ? nullptr : &kv,
dampratio == -1 ? nullptr : &dampratio, ki == -1 ? nullptr : &ki,
imax == -1 ? nullptr : &imax, slewmax == -1 ? nullptr : &slewmax,
inheritrange, ctrlspec);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("kp"), py::arg("kv") = -1, py::arg("dampratio") = -1,
py::arg("ki") = -1, py::arg("imax") = -1, py::arg("slewmax") = -1,
py::arg("inheritrange") = 0, py::arg("ctrlspec") = 0);
mjsActuator.def( mjsActuator.def(
"set_to_damper", "set_to_damper",
[](raw::MjsActuator* self, double kv) { [](raw::MjsActuator* self, double kv) {
+9
View File
@@ -1596,6 +1596,15 @@ class SpecsTest(absltest.TestCase):
self.assertEqual(actuator.biastype, mujoco.mjtBias.mjBIAS_SO3) self.assertEqual(actuator.biastype, mujoco.mjtBias.mjBIAS_SO3)
self.assertEqual(actuator.dyntype, mujoco.mjtDyn.mjDYN_NONE) self.assertEqual(actuator.dyntype, mujoco.mjtDyn.mjDYN_NONE)
actuator.set_to_pid(kp=2.0, kv=3.0, ki=0.5, imax=1.5, slewmax=4.0)
self.assertEqual(actuator.biasprm[1], -2)
self.assertEqual(actuator.biasprm[2], -3)
self.assertEqual(actuator.gainprm[0], 0.5)
self.assertEqual(actuator.dynprm[0], 1.5)
self.assertEqual(actuator.dynprm[1], 4.0)
self.assertEqual(actuator.gaintype, mujoco.mjtGain.mjGAIN_PID)
self.assertEqual(actuator.dyntype, mujoco.mjtDyn.mjDYN_PID)
actuator.set_to_velocity(kv=5.0) actuator.set_to_velocity(kv=5.0)
self.assertEqual(actuator.gainprm[0], 5) self.assertEqual(actuator.gainprm[0], 5)
self.assertEqual(actuator.biasprm[2], -5) self.assertEqual(actuator.biasprm[2], -5)
+103 -5
View File
@@ -225,6 +225,16 @@ void mj_fwdVelocity(const mjModel* m, mjData* d) {
} }
// unpack servo-family inputs from control block in canonical order [pos, vel, ff]
// absent input: setpoint 0
static void unpackServoInputs(const mjtNum* u, int spec, mjtNum out[3]) {
int adr = 0;
out[0] = (spec & mjINPUT_POS) ? u[adr++] : 0;
out[1] = (spec & mjINPUT_VEL) ? u[adr++] : 0;
out[2] = (spec & mjINPUT_FF) ? u[adr] : 0;
}
// helper for DC motor: computes control voltage from PID state // helper for DC motor: computes control voltage from PID state
static mjtNum dcmotorVoltage(mjtNum ctrl, mjtNum length, mjtNum velocity, static mjtNum dcmotorVoltage(mjtNum ctrl, mjtNum length, mjtNum velocity,
mjtNum x_I, const mjtNum* gainprm) { mjtNum x_I, const mjtNum* gainprm) {
@@ -285,10 +295,15 @@ static void expmap2Quat(mjtNum quat[4], const mjtNum v[3]) {
static mjtNum wrapPeriod(const mjModel* m, int i) { static mjtNum wrapPeriod(const mjModel* m, int i) {
// servo shape: fixed gain, affine bias, matching kp, setpoint input // servo shape: fixed gain, affine bias, matching kp, setpoint input
mjtDyn dyntype = m->actuator_dyntype[i]; mjtDyn dyntype = m->actuator_dyntype[i];
if (m->actuator_gaintype[i] != mjGAIN_FIXED || int servo = m->actuator_gaintype[i] == mjGAIN_FIXED &&
m->actuator_biastype[i] != mjBIAS_AFFINE || m->actuator_biastype[i] == mjBIAS_AFFINE &&
m->actuator_gainprm[mjNGAIN*i] != -m->actuator_biasprm[mjNBIAS*i+1] || m->actuator_gainprm[mjNGAIN*i] == -m->actuator_biasprm[mjNBIAS*i+1] &&
(dyntype != mjDYN_NONE && dyntype != mjDYN_INTEGRATOR)) { (dyntype == mjDYN_NONE || dyntype == mjDYN_INTEGRATOR);
// PID shape: kp and kv are single-sourced in the affine bias
int pid = m->actuator_gaintype[i] == mjGAIN_PID;
if (!servo && !pid) {
return 0; return 0;
} }
@@ -318,6 +333,20 @@ static mjtNum wrapSetpoint(mjtNum u, mjtNum length, mjtNum period) {
} }
// slew-rate-limit setpoint u given previous effective setpoint u_prev, write act_dot
// period > 0: wrap u to the representative nearest u_prev before limiting
static mjtNum slewLimit(mjtNum u, mjtNum u_prev, mjtNum slew_s, mjtNum dt,
mjtNum period, mjtNum* act_dot) {
if (period > 0) {
u = wrapSetpoint(u, u_prev, period);
}
mjtNum slew = slew_s * dt;
mjtNum u_eff = mju_clip(u, u_prev - slew, u_prev + slew);
*act_dot = (u_eff - u_prev) / dt;
return u_eff;
}
// (qpos, qvel, ctrl, act) => (qfrc_actuator, actuator_force, act_dot) // (qpos, qvel, ctrl, act) => (qfrc_actuator, actuator_force, act_dot)
void mj_fwdActuation(const mjModel* m, mjData* d) { void mj_fwdActuation(const mjModel* m, mjData* d) {
TM_START; TM_START;
@@ -419,6 +448,42 @@ void mj_fwdActuation(const mjModel* m, mjData* d) {
d->act_dot[act_last] = mju_muscleDynamics(ctrl[uadr], d->act[act_last], dynprm); d->act_dot[act_last] = mju_muscleDynamics(ctrl[uadr], d->act[act_last], dynprm);
break; break;
case mjDYN_PID: { // PID controller states, slot order: slew, integral
int adr = act_first;
mjtNum period = wrapPeriod(m, i);
// slew rate limiting of the position setpoint
mjtNum slew_s = dynprm[1];
if (slew_s > 0) {
ctrl[uadr] = slewLimit(ctrl[uadr], d->act[adr], slew_s, m->opt.timestep,
period, d->act_dot + adr);
adr++;
}
// integral of the position error
if (m->actuator_gainprm[mjNGAIN*i] > 0) {
mjtNum err = ctrl[uadr] - d->actuator_length[oadr];
// rotational transmission: error on the circle
if (period > 0) {
err -= period*mju_round(err/period);
}
// anti-windup: stop accumulating beyond imax
mjtNum imax = dynprm[0];
if (imax > 0) {
mjtNum z = d->act[adr];
if (z >= imax) {
err = mju_min(err, 0);
} else if (z <= -imax) {
err = mju_max(err, 0);
}
}
d->act_dot[adr] = err;
}
break;
}
case mjDYN_DCMOTOR: { // DC motor: up to 5 optional states case mjDYN_DCMOTOR: { // DC motor: up to 5 optional states
const mjtNum* gainprm = m->actuator_gainprm + mjNGAIN*i; const mjtNum* gainprm = m->actuator_gainprm + mjNGAIN*i;
@@ -630,6 +695,10 @@ void mj_fwdActuation(const mjModel* m, mjData* d) {
gain = gainprm[0]; gain = gainprm[0];
break; break;
case mjGAIN_PID: // PID servo: input side handled below, state side in bias
gain = 0;
break;
case mjGAIN_AFFINE: // affine: prm = [const, kp, kv] case mjGAIN_AFFINE: // affine: prm = [const, kp, kv]
gain = gainprm[0] + gainprm[1]*d->actuator_length[oadr] + gain = gainprm[0] + gainprm[1]*d->actuator_length[oadr] +
gainprm[2]*d->actuator_velocity[oadr]; gainprm[2]*d->actuator_velocity[oadr];
@@ -693,7 +762,36 @@ void mj_fwdActuation(const mjModel* m, mjData* d) {
// DC motor without current state: use ctrl even if other activations exist // DC motor without current state: use ctrl even if other activations exist
int dcmotor_no_current = (gaintype == mjGAIN_DCMOTOR && dynprm[0] <= 0); int dcmotor_no_current = (gaintype == mjGAIN_DCMOTOR && dynprm[0] <= 0);
if (actnum == 0 || dcmotor_no_current) {
// PID servo: force = kp*(qref - l) + kv*(vref - l_dot) [+ ff] [+ ki*z]
// input-side terms computed here; state-side terms added by the affine bias below
if (gaintype == mjGAIN_PID) {
const mjtNum* prm = m->actuator_biasprm + mjNBIAS*i;
// unpack present inputs in canonical order [pos, vel, ff]; absent input: setpoint 0
mjtNum u3[3];
unpackServoInputs(ctrl + uadr, m->actuator_ctrlspec[i], u3);
mjtNum qref = u3[0], vref = u3[1], ff = u3[2];
// position setpoint: representative nearest the length on rotational transmissions
mjtNum period = wrapPeriod(m, i);
if (period > 0) {
qref = wrapSetpoint(qref, d->actuator_length[oadr], period);
}
// kp and kv are single-sourced in the affine bias parameters
force[oadr] = -prm[1]*qref - prm[2]*vref + ff;
// integral state (last slot): force += ki * z
if (actnum && gainprm[0] > 0) {
int act_adr = m->actuator_actadr[i] + actnum - 1;
mjtNum z = m->actuator_actearly[i]
? mj_nextActivation(m, d, i, act_adr, d->act_dot[act_adr])
: d->act[act_adr];
force[oadr] += gainprm[0]*z;
}
}
else if (actnum == 0 || dcmotor_no_current) {
mjtNum input = ctrl[uadr]; mjtNum input = ctrl[uadr];
// rotational setpoint: use representative nearest the length (local, no state change) // rotational setpoint: use representative nearest the length (local, no state change)
+15
View File
@@ -297,5 +297,20 @@ const char* mj_actuatorInputName(const mjModel* m, int id, int input) {
return m->actuator_ctrlspec[id] == mjCHART_QUAT ? quat[input] : expmap[input]; return m->actuator_ctrlspec[id] == mjCHART_QUAT ? quat[input] : expmap[input];
} }
// servo family: input names are the present members of [pos, vel, ff]
if (m->actuator_gaintype[id] == mjGAIN_PID) {
static const char* servo[3] = {"pos", "vel", "ff"};
static const int bits[3] = {mjINPUT_POS, mjINPUT_VEL, mjINPUT_FF};
int spec = m->actuator_ctrlspec[id];
for (int k=0; k < 3; k++) {
if (spec & bits[k]) {
if (input == 0) {
return servo[k];
}
input--;
}
}
}
return NULL; return NULL;
} }
+5 -3
View File
@@ -1138,8 +1138,9 @@ static void set0(mjModel* m, mjData* d) {
mjtNum* biasprm = m->actuator_biasprm + i*mjNBIAS; mjtNum* biasprm = m->actuator_biasprm + i*mjNBIAS;
mjtNum* gainprm = m->actuator_gainprm + i*mjNGAIN; mjtNum* gainprm = m->actuator_gainprm + i*mjNGAIN;
// not a position-like actuator: skip // not a position-like actuator: skip (PID single-sources kp in biasprm[1])
if (gainprm[0] != -biasprm[1]) { int is_pid = m->actuator_gaintype[i] == mjGAIN_PID;
if (!is_pid && gainprm[0] != -biasprm[1]) {
continue; continue;
} }
@@ -1165,7 +1166,8 @@ static void set0(mjModel* m, mjData* d) {
} }
// damping = dampratio * 2 * sqrt(kp * mass) // damping = dampratio * 2 * sqrt(kp * mass)
mjtNum damping = biasprm[2] * 2 * mju_sqrt(gainprm[0] * mass); mjtNum kp = is_pid ? -biasprm[1] : gainprm[0];
mjtNum damping = biasprm[2] * 2 * mju_sqrt(kp * mass);
// set biasprm[2] to negative damping // set biasprm[2] to negative damping
biasprm[2] = -damping; biasprm[2] = -damping;
+42
View File
@@ -1348,6 +1348,48 @@ const char* mjs_setToOrientation(mjsActuator* actuator, double kp, double kv[1],
} }
// Set to PID actuator.
const char* mjs_setToPID(mjsActuator* actuator, double kp, double kv[1], double dampratio[1],
double ki[1], double imax[1], double slewmax[1], double inheritrange,
int ctrlspec) {
if (kv && dampratio) {
return "kv and dampratio cannot both be defined";
}
actuator->biasprm[1] = -kp;
if (kv) {
if (*kv < 0) return "kv cannot be negative";
actuator->biasprm[2] = -(*kv);
}
if (dampratio) {
if (*dampratio < 0) return "dampratio cannot be negative";
actuator->biasprm[2] = *dampratio;
}
// controller states: ki in gainprm[0], imax in dynprm[0], slewmax in dynprm[1]
double ki_value = ki ? *ki : 0;
double slew_value = slewmax ? *slewmax : 0;
if (slew_value < 0) return "slewmax cannot be negative";
actuator->gainprm[0] = ki_value;
actuator->dynprm[1] = slew_value;
actuator->dyntype = (ki_value || slew_value) ? mjDYN_PID : mjDYN_NONE;
if (ki_value && imax) {
actuator->dynprm[0] = *imax;
}
actuator->inheritrange = inheritrange;
if (inheritrange > 0) {
if (actuator->ctrlrange[0] || actuator->ctrlrange[1]) {
return "posrange and inheritrange cannot both be defined";
}
}
actuator->ctrlspec = ctrlspec;
actuator->gaintype = mjGAIN_PID;
actuator->biastype = mjBIAS_AFFINE;
return "";
}
// Set to velocity actuator. // Set to velocity actuator.
const char* mjs_setToVelocity(mjsActuator* actuator, double kv) { const char* mjs_setToVelocity(mjsActuator* actuator, double kv) {
+5
View File
@@ -189,6 +189,11 @@ MJAPI const char* mjs_setToVelocity(mjsActuator* actuator, double kv);
MJAPI const char* mjs_setToOrientation(mjsActuator* actuator, double kp, double kv[1], MJAPI const char* mjs_setToOrientation(mjsActuator* actuator, double kp, double kv[1],
double dampratio[1], int ctrlspec); double dampratio[1], int ctrlspec);
// Set to PID actuator.
MJAPI const char* mjs_setToPID(mjsActuator* actuator, double kp, double kv[1], double dampratio[1],
double ki[1], double imax[1], double slewmax[1], double inheritrange,
int ctrlspec);
// Set actuator to damper, return error on failure. // Set actuator to damper, return error on failure.
MJAPI const char* mjs_setToDamper(mjsActuator* actuator, double kv); MJAPI const char* mjs_setToDamper(mjsActuator* actuator, double kv);
+2
View File
@@ -342,6 +342,8 @@ void mjs_defaultActuator(mjsActuator* actuator) {
actuator->dynprm[0] = 1; actuator->dynprm[0] = 1;
actuator->actdim = -1; actuator->actdim = -1;
actuator->ctrlspec = 0; actuator->ctrlspec = 0;
actuator->velrange[0] = actuator->velrange[1] = 0;
actuator->ffrange[0] = actuator->ffrange[1] = 0;
// transmission // transmission
actuator->trntype = mjTRN_UNDEFINED; actuator->trntype = mjTRN_UNDEFINED;
+4 -4
View File
@@ -4066,10 +4066,10 @@ void mjCModel::CopyObjects(mjModel* m) {
mjuu_copyvec(m->actuator_user+nuser_actuator*i, pac->get_userdata().data(), nuser_actuator); mjuu_copyvec(m->actuator_user+nuser_actuator*i, pac->get_userdata().data(), nuser_actuator);
// per-input arrays, at the actuator's ctrl block // per-input arrays, at the actuator's ctrl block
for (int j = m->actuator_ctrladr[i]; for (int k = 0; k < m->actuator_ctrlnum[i]; k++) {
j < m->actuator_ctrladr[i] + m->actuator_ctrlnum[i]; j++) { int j = m->actuator_ctrladr[i] + k;
m->actuator_ctrllimited[j] = (mjtBool)pac->is_ctrllimited(); m->actuator_ctrllimited[j] = (mjtBool)pac->ctrllimiteds_[k];
mjuu_copyvec(m->actuator_ctrlrange + 2 * j, pac->ctrlrange, 2); mjuu_copyvec(m->actuator_ctrlrange + 2 * j, pac->ctrlranges_[k], 2);
} }
// per-output arrays, at the actuator's output block // per-output arrays, at the actuator's output block
+87 -7
View File
@@ -7214,12 +7214,69 @@ void mjCActuator::Compile(void) {
so3_ = true; so3_ = true;
} }
// input signature selection is so3-only // PID servo: validate and resolve input block
if (ctrlspec && gaintype != mjGAIN_SO3) { if (gaintype == mjGAIN_PID) {
throw mjCError(this, "input is only available for so3 actuators, actuator '%s' (id = %d)", if (biastype != mjBIAS_AFFINE) {
throw mjCError(this, "pid requires biastype 'affine' in actuator '%s' (id = %d)",
name.c_str(), id);
}
if (dyntype != mjDYN_NONE && dyntype != mjDYN_PID) {
throw mjCError(this, "pid requires dyntype 'none' or 'pid' in actuator '%s' "
"(id = %d)", name.c_str(), id);
}
if (dyntype == mjDYN_NONE && gainprm[0]) {
throw mjCError(this, "ki (gainprm[0]) requires dyntype 'pid' in actuator '%s' "
"(id = %d)", name.c_str(), id);
}
if (trntype == mjTRN_BODY) {
throw mjCError(this, "pid cannot use body transmission, actuator '%s' (id = %d)",
name.c_str(), id);
}
// controller states, slot order [slew, integral]: gated on slewmax (dynprm[1]) and ki
if (dyntype == mjDYN_PID) {
if (dynprm[0] < 0) {
throw mjCError(this, "imax (dynprm[0]) must be non-negative in actuator '%s' (id = %d)",
name.c_str(), id);
}
if (dynprm[1] < 0) {
throw mjCError(this, "slewmax (dynprm[1]) must be non-negative in actuator '%s' (id = %d)",
name.c_str(), id);
}
int nslot = (dynprm[1] > 0) + (gainprm[0] > 0);
if (actdim > 0 && actdim != nslot) {
throw mjCError(this, "pid controller states require matching actdim in actuator '%s' "
"(id = %d)", name.c_str(), id);
}
actdim = nslot;
}
// input block: any subset of [pos, vel, ff], default [pos, vel]
ctrlspec_ = ctrlspec ? ctrlspec : (mjINPUT_POS | mjINPUT_VEL);
if (ctrlspec_ & ~(mjINPUT_POS | mjINPUT_VEL | mjINPUT_FF)) {
throw mjCError(this, "pid inputs are a subset of [pos, vel, ff] in actuator '%s' (id = %d)",
name.c_str(), id);
}
if (dyntype == mjDYN_PID && !(ctrlspec_ & mjINPUT_POS)) {
throw mjCError(this, "pid controller states require the pos input in actuator '%s' (id = %d)",
name.c_str(), id);
}
ctrlnum_ = !!(ctrlspec_ & mjINPUT_POS) + !!(ctrlspec_ & mjINPUT_VEL) +
!!(ctrlspec_ & mjINPUT_FF);
}
// pid dynamics are pid-only
if (dyntype == mjDYN_PID && gaintype != mjGAIN_PID) {
throw mjCError(this, "dyntype 'pid' requires gaintype 'pid', actuator '%s' (id = %d)",
name.c_str(), id); name.c_str(), id);
} }
// input signature selection is so3- or pid-only
if (ctrlspec && gaintype != mjGAIN_SO3 && gaintype != mjGAIN_PID) {
throw mjCError(this, "input is only available for so3 and pid actuators, actuator '%s' "
"(id = %d)", name.c_str(), id);
}
// check damping/armature only valid for joint and tendon transmission // check damping/armature only valid for joint and tendon transmission
bool has_damping = false; bool has_damping = false;
for (int i = 0; i < mjNPOLY+1; i++) { for (int i = 0; i < mjNPOLY+1; i++) {
@@ -7242,12 +7299,13 @@ void mjCActuator::Compile(void) {
} }
// handle inheritrange // handle inheritrange
if (gaintype == mjGAIN_FIXED && biastype == mjBIAS_AFFINE && if (((gaintype == mjGAIN_FIXED && gainprm[0] == -biasprm[1]) || gaintype == mjGAIN_PID) &&
gainprm[0] == -biasprm[1] && inheritrange > 0) { biastype == mjBIAS_AFFINE && inheritrange > 0) {
// semantic of actuator is the same as transmission, inheritrange is applicable // semantic of actuator is the same as transmission, inheritrange is applicable
double* range; double* range;
if (dyntype == mjDYN_NONE || dyntype == mjDYN_FILTEREXACT) { if (dyntype == mjDYN_NONE || dyntype == mjDYN_FILTEREXACT ||
// position actuator dyntype == mjDYN_PID) {
// position or pd actuator: range applies to the position input
range = ctrlrange; range = ctrlrange;
} else if (dyntype == mjDYN_INTEGRATOR) { } else if (dyntype == mjDYN_INTEGRATOR) {
// intvelocity actuator // intvelocity actuator
@@ -7397,6 +7455,28 @@ void mjCActuator::Compile(void) {
if (nsample > 16777216) { if (nsample > 16777216) {
throw mjCError(this, "at most 2^24 samples in history buffer, got %d", nullptr, nsample); throw mjCError(this, "at most 2^24 samples in history buffer, got %d", nullptr, nsample);
} }
// resolve per-input control ranges: broadcast ctrlrange, pd overrides vel and ff
for (int j=0; j < ctrlnum_ && j < 4; j++) {
ctrllimiteds_[j] = (mjtByte)is_ctrllimited();
ctrlranges_[j][0] = ctrlrange[0];
ctrlranges_[j][1] = ctrlrange[1];
}
if (gaintype == mjGAIN_PID) {
// present inputs pack in canonical order [pos, vel, ff]; pos keeps the ctrlrange broadcast
int j = ctrlspec_ & mjINPUT_POS ? 1 : 0;
if (ctrlspec_ & mjINPUT_VEL) {
ctrllimiteds_[j] = velrange[0] < velrange[1];
ctrlranges_[j][0] = velrange[0];
ctrlranges_[j][1] = velrange[1];
j++;
}
if (ctrlspec_ & mjINPUT_FF) {
ctrllimiteds_[j] = ffrange[0] < ffrange[1];
ctrlranges_[j][0] = ffrange[0];
ctrlranges_[j][1] = ffrange[1];
}
}
} }
+2
View File
@@ -1816,6 +1816,8 @@ class mjCActuator_ : public mjCBase {
int outadr_; // address of first force output int outadr_; // address of first force output
int outnum_; // number of force outputs, from trntype int outnum_; // number of force outputs, from trntype
bool so3_; // compiles to an SO3 transmission bool so3_; // compiles to an SO3 transmission
double ctrlranges_[4][2]; // resolved per-input control ranges
mjtByte ctrllimiteds_[4]; // resolved per-input limited flags
std::map<std::string, std::vector<mjtNum>> act_; // act at the previous step std::map<std::string, std::vector<mjtNum>> act_; // act at the previous step
std::map<std::string, mjtNum> ctrl_; // ctrl at the previous step std::map<std::string, mjtNum> ctrl_; // ctrl at the previous step
+4 -2
View File
@@ -41,7 +41,8 @@ extern const int colorspace_sz;
extern const int builtin_sz; extern const int builtin_sz;
extern const int mark_sz; extern const int mark_sz;
extern const int dyn_sz; extern const int dyn_sz;
extern const int input_sz; extern const int inputchart_sz;
extern const int inputbit_sz;
extern const int gain_sz; extern const int gain_sz;
extern const int bias_sz; extern const int bias_sz;
extern const int interp_sz; extern const int interp_sz;
@@ -76,7 +77,8 @@ extern const mjMap texrole_map[];
extern const mjMap builtin_map[]; extern const mjMap builtin_map[];
extern const mjMap mark_map[]; extern const mjMap mark_map[];
extern const mjMap dyn_map[]; extern const mjMap dyn_map[];
extern const mjMap input_map[]; extern const mjMap inputchart_map[];
extern const mjMap inputbit_map[];
extern const mjMap gain_map[]; extern const mjMap gain_map[];
extern const mjMap bias_map[]; extern const mjMap bias_map[];
extern const mjMap interp_map[]; extern const mjMap interp_map[];
+102 -13
View File
@@ -258,7 +258,7 @@ std::vector<const char*> MJCF[nMJCF] = {
"margin", "stiffness", "damping", "rgba", "user"}, "margin", "stiffness", "damping", "rgba", "user"},
{"general", "?", "ctrllimited", "forcelimited", "actlimited", "ctrlrange", "forcerange", {"general", "?", "ctrllimited", "forcelimited", "actlimited", "ctrlrange", "forcerange",
"actrange", "gear", "damping", "armature", "cranklength", "user", "group", "nsample", "actrange", "gear", "damping", "armature", "cranklength", "user", "group", "nsample",
"interp", "delay", "actdim", "input", "dyntype", "gaintype", "biastype", "dynprm", "gainprm", "interp", "delay", "actdim", "input", "velrange", "ffrange", "dyntype", "gaintype", "biastype", "dynprm", "gainprm",
"biasprm", "actearly"}, "biasprm", "actearly"},
{"motor", "?", "ctrllimited", "forcelimited", "ctrlrange", "forcerange", {"motor", "?", "ctrllimited", "forcelimited", "ctrlrange", "forcerange",
"gear", "damping", "armature", "cranklength", "user", "group", "nsample", "interp", "delay"}, "gear", "damping", "armature", "cranklength", "user", "group", "nsample", "interp", "delay"},
@@ -270,6 +270,12 @@ std::vector<const char*> MJCF[nMJCF] = {
{"intvelocity", "?", "ctrllimited", "forcelimited", "actlimited", "ctrlrange", "forcerange", {"intvelocity", "?", "ctrllimited", "forcelimited", "actlimited", "ctrlrange", "forcerange",
"actrange", "inheritrange", "gear", "damping", "armature", "cranklength", "user", "group", "actrange", "inheritrange", "gear", "damping", "armature", "cranklength", "user", "group",
"nsample", "interp", "delay", "kp", "kv", "dampratio"}, "nsample", "interp", "delay", "kp", "kv", "dampratio"},
{"orientation", "?", "forcelimited", "ctrlrange", "forcerange", "user", "group",
"nsample", "interp", "delay", "kp", "kv", "dampratio", "input"},
{"pid", "?", "ctrllimited", "forcelimited", "ctrlrange", "posrange", "velrange", "ffrange",
"forcerange", "inheritrange", "gear", "damping", "armature", "cranklength", "user",
"group", "nsample", "interp", "delay", "kp", "kv", "dampratio", "ki", "imax", "slewmax",
"input"},
{"damper", "?", "forcelimited", "ctrlrange", "forcerange", {"damper", "?", "forcelimited", "ctrlrange", "forcerange",
"gear", "damping", "armature", "cranklength", "user", "group", "nsample", "interp", "delay", "kv"}, "gear", "damping", "armature", "cranklength", "user", "group", "nsample", "interp", "delay", "kv"},
{"cylinder", "?", "ctrllimited", "forcelimited", "ctrlrange", "forcerange", {"cylinder", "?", "ctrllimited", "forcelimited", "ctrlrange", "forcerange",
@@ -476,7 +482,7 @@ std::vector<const char*> MJCF[nMJCF] = {
"ctrllimited", "forcelimited", "actlimited", "ctrlrange", "forcerange", "actrange", "ctrllimited", "forcelimited", "actlimited", "ctrlrange", "forcerange", "actrange",
"lengthrange", "gear", "damping", "armature", "cranklength", "user", "lengthrange", "gear", "damping", "armature", "cranklength", "user",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site", "refsite", "joint", "jointinparent", "tendon", "slidersite", "cranksite", "site", "refsite",
"body", "actdim", "input", "dyntype", "gaintype", "biastype", "dynprm", "gainprm", "biasprm", "body", "actdim", "input", "velrange", "ffrange", "dyntype", "gaintype", "biastype", "dynprm", "gainprm", "biasprm",
"actearly"}, "actearly"},
{"motor", "*", "name", "class", "group", "nsample", "interp", "delay", {"motor", "*", "name", "class", "group", "nsample", "interp", "delay",
"ctrllimited", "forcelimited", "ctrlrange", "forcerange", "ctrllimited", "forcelimited", "ctrlrange", "forcerange",
@@ -502,6 +508,12 @@ std::vector<const char*> MJCF[nMJCF] = {
"forcelimited", "ctrlrange", "forcerange", "user", "forcelimited", "ctrlrange", "forcerange", "user",
"joint", "site", "refsite", "joint", "site", "refsite",
"kp", "kv", "dampratio", "input"}, "kp", "kv", "dampratio", "input"},
{"pid", "*", "name", "class", "group", "nsample", "interp", "delay",
"ctrllimited", "forcelimited", "ctrlrange", "posrange", "velrange", "ffrange",
"forcerange", "inheritrange", "lengthrange", "gear", "damping", "armature",
"cranklength", "user",
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site", "refsite",
"kp", "kv", "dampratio", "ki", "imax", "slewmax", "input"},
{"damper", "*", "name", "class", "group", "nsample", "interp", "delay", {"damper", "*", "name", "class", "group", "nsample", "interp", "delay",
"forcelimited", "ctrlrange", "forcerange", "forcelimited", "ctrlrange", "forcerange",
"lengthrange", "gear", "damping", "armature", "cranklength", "user", "lengthrange", "gear", "damping", "armature", "cranklength", "user",
@@ -822,7 +834,7 @@ const mjMap mark_map[mark_sz] = {
// dyn type // dyn type
const int dyn_sz = 7; const int dyn_sz = 8;
const mjMap dyn_map[dyn_sz] = { const mjMap dyn_map[dyn_sz] = {
{"none", mjDYN_NONE}, {"none", mjDYN_NONE},
{"integrator", mjDYN_INTEGRATOR}, {"integrator", mjDYN_INTEGRATOR},
@@ -830,6 +842,7 @@ const mjMap dyn_map[dyn_sz] = {
{"filterexact", mjDYN_FILTEREXACT}, {"filterexact", mjDYN_FILTEREXACT},
{"muscle", mjDYN_MUSCLE}, {"muscle", mjDYN_MUSCLE},
{"dcmotor", mjDYN_DCMOTOR}, {"dcmotor", mjDYN_DCMOTOR},
{"pid", mjDYN_PID},
{"user", mjDYN_USER} {"user", mjDYN_USER}
}; };
@@ -844,25 +857,61 @@ const mjMap dcmotorinput_map[dcmotorinput_sz] = {
// gain type // gain type
const int gain_sz = 6; const int gain_sz = 7;
const mjMap gain_map[gain_sz] = { const mjMap gain_map[gain_sz] = {
{"fixed", mjGAIN_FIXED}, {"fixed", mjGAIN_FIXED},
{"affine", mjGAIN_AFFINE}, {"affine", mjGAIN_AFFINE},
{"muscle", mjGAIN_MUSCLE}, {"muscle", mjGAIN_MUSCLE},
{"dcmotor", mjGAIN_DCMOTOR}, {"dcmotor", mjGAIN_DCMOTOR},
{"so3", mjGAIN_SO3}, {"so3", mjGAIN_SO3},
{"pid", mjGAIN_PID},
{"user", mjGAIN_USER} {"user", mjGAIN_USER}
}; };
// so3 input chart // so3 input chart
const int input_sz = 2; const int inputchart_sz = 2;
const mjMap input_map[input_sz] = { const mjMap inputchart_map[inputchart_sz] = {
{"expmap", mjCHART_EXPMAP}, {"expmap", mjCHART_EXPMAP},
{"quat", mjCHART_QUAT} {"quat", mjCHART_QUAT}
}; };
// servo-family input tokens
const int inputbit_sz = 3;
const mjMap inputbit_map[inputbit_sz] = {
{"pos", mjINPUT_POS},
{"vel", mjINPUT_VEL},
{"ff", mjINPUT_FF}
};
// read the "input" attribute: so3 chart keyword, or servo input token list
static bool ReadInputSpec(tinyxml2::XMLElement* elem, int* ctrlspec) {
std::string text;
if (!mjXUtil::ReadAttrTxt(elem, "input", text)) {
return false;
}
// so3 chart keyword
int chart = mjXUtil::FindKey(inputchart_map, inputchart_sz, text);
if (chart >= 0) {
*ctrlspec = chart;
return true;
}
// servo input tokens
int bits[inputbit_sz];
int nbit = mjXUtil::MapValues(elem, "input", bits, inputbit_map, inputbit_sz);
int spec = 0;
for (int k=0; k < nbit; k++) {
spec |= bits[k];
}
*ctrlspec = spec;
return true;
}
// bias type // bias type
const int bias_sz = 6; const int bias_sz = 6;
const mjMap bias_map[bias_sz] = { const mjMap bias_map[bias_sz] = {
@@ -2528,9 +2577,9 @@ void mjXReader::OneActuator(XMLElement* elem, mjsActuator* actuator) {
ReadAttr(elem, "gainprm", mjNGAIN, actuator->gainprm, text, false, false); ReadAttr(elem, "gainprm", mjNGAIN, actuator->gainprm, text, false, false);
ReadAttr(elem, "biasprm", mjNBIAS, actuator->biasprm, text, false, false); ReadAttr(elem, "biasprm", mjNBIAS, actuator->biasprm, text, false, false);
ReadAttrInt(elem, "actdim", &actuator->actdim); ReadAttrInt(elem, "actdim", &actuator->actdim);
if (MapValue(elem, "input", &n, input_map, input_sz)) { ReadInputSpec(elem, &actuator->ctrlspec);
actuator->ctrlspec = n; ReadAttr(elem, "velrange", 2, actuator->velrange, text);
} ReadAttr(elem, "ffrange", 2, actuator->ffrange, text);
} }
// direct drive motor // direct drive motor
@@ -2593,14 +2642,53 @@ void mjXReader::OneActuator(XMLElement* elem, mjsActuator* actuator) {
} }
// input chart: expmap (default) or quat // input chart: expmap (default) or quat
int n; ReadInputSpec(elem, &actuator->ctrlspec);
if (MapValue(elem, "input", &n, input_map, input_sz)) {
actuator->ctrlspec = n;
}
err = mjs_setToOrientation(actuator, kp, kv, dampratio, actuator->ctrlspec); err = mjs_setToOrientation(actuator, kp, kv, dampratio, actuator->ctrlspec);
} }
// PID servo: inputs are position and velocity setpoints
else if (type == "pid") {
// kp: default inherited via -biasprm[1]
double kp = -actuator->biasprm[1];
ReadAttr(elem, "kp", 1, &kp, text);
double kv_data;
double *kv = &kv_data;
if (!ReadAttr(elem, "kv", 1, kv, text)) {
kv = nullptr;
}
double dampratio_data;
double *dampratio = &dampratio_data;
if (!ReadAttr(elem, "dampratio", 1, dampratio, text)) {
dampratio = nullptr;
}
// controller parameters: ki (gainprm[0]), imax (dynprm[0]), slewmax (dynprm[1]); inherited
double ki = actuator->gainprm[0] * (actuator->dyntype == mjDYN_PID);
ReadAttr(elem, "ki", 1, &ki, text);
double imax = actuator->dynprm[0] * (actuator->dyntype == mjDYN_PID);
ReadAttr(elem, "imax", 1, &imax, text);
double slewmax = actuator->dynprm[1] * (actuator->dyntype == mjDYN_PID);
ReadAttr(elem, "slewmax", 1, &slewmax, text);
// input subset selection
ReadInputSpec(elem, &actuator->ctrlspec);
// per-input ranges; posrange is an alias of ctrlrange (input 0)
ReadAttr(elem, "posrange", 2, actuator->ctrlrange, text);
ReadAttr(elem, "velrange", 2, actuator->velrange, text);
ReadAttr(elem, "ffrange", 2, actuator->ffrange, text);
// handle inheritrange
double inheritrange = actuator->inheritrange;
ReadAttr(elem, "inheritrange", 1, &inheritrange, text);
err = mjs_setToPID(actuator, kp, kv, dampratio, &ki, &imax, &slewmax, inheritrange,
actuator->ctrlspec);
}
// velocity servo // velocity servo
else if (type == "velocity") { else if (type == "velocity") {
double kv = actuator->gainprm[0]; double kv = actuator->gainprm[0];
@@ -3168,6 +3256,7 @@ void mjXReader::Default(XMLElement* section, const mjsDefault* def, const mjVFS*
name == "damper" || name == "damper" ||
name == "intvelocity" || name == "intvelocity" ||
name == "orientation" || name == "orientation" ||
name == "pid" ||
name == "cylinder" || name == "cylinder" ||
name == "muscle" || name == "muscle" ||
name == "adhesion" || name == "adhesion" ||
+1 -1
View File
@@ -102,7 +102,7 @@ class mjXReader : public mjXBase {
}; };
// MJCF schema // MJCF schema
#define nMJCF 249 #define nMJCF 252
extern std::vector<const char*> MJCF[nMJCF]; extern std::vector<const char*> MJCF[nMJCF];
#endif // MUJOCO_SRC_XML_XML_NATIVE_READER_H_ #endif // MUJOCO_SRC_XML_XML_NATIVE_READER_H_
+14 -1
View File
@@ -899,7 +899,20 @@ void mjXWriter::OneActuator(XMLElement* elem, const mjCActuator* actuator, mjCDe
// non-plugins: write actuator parameters // non-plugins: write actuator parameters
else { else {
WriteAttrKey(elem, "gaintype", gain_map, gain_sz, actuator->gaintype, def->Actuator().gaintype); WriteAttrKey(elem, "gaintype", gain_map, gain_sz, actuator->gaintype, def->Actuator().gaintype);
WriteAttrKey(elem, "input", input_map, input_sz, actuator->ctrlspec, def->Actuator().ctrlspec); if (actuator->gaintype == mjGAIN_SO3) {
WriteAttrKey(elem, "input", inputchart_map, inputchart_sz, actuator->ctrlspec,
def->Actuator().ctrlspec);
} else if (actuator->ctrlspec != def->Actuator().ctrlspec) {
std::string tokens;
for (int k=0; k < inputbit_sz; k++) {
if (actuator->ctrlspec & inputbit_map[k].value) {
tokens += std::string(tokens.empty() ? "" : " ") + inputbit_map[k].key;
}
}
WriteAttrTxt(elem, "input", tokens);
}
WriteAttr(elem, "velrange", 2, actuator->velrange, def->Actuator().velrange);
WriteAttr(elem, "ffrange", 2, actuator->ffrange, def->Actuator().ffrange);
WriteAttrKey(elem, "biastype", bias_map, bias_sz, actuator->biastype, def->Actuator().biastype); WriteAttrKey(elem, "biastype", bias_map, bias_sz, actuator->biastype, def->Actuator().biastype);
WriteAttr(elem, "gainprm", mjNGAIN, actuator->gainprm, def->Actuator().gainprm, true); WriteAttr(elem, "gainprm", mjNGAIN, actuator->gainprm, def->Actuator().gainprm, true);
WriteAttr(elem, "biasprm", mjNBIAS, actuator->biasprm, def->Actuator().biasprm, true); WriteAttr(elem, "biasprm", mjNBIAS, actuator->biasprm, def->Actuator().biasprm, true);
+358 -1
View File
@@ -17,6 +17,7 @@
#include "src/engine/engine_core_smooth.h" #include "src/engine/engine_core_smooth.h"
#include <algorithm> #include <algorithm>
#include <cstdio>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@@ -597,7 +598,7 @@ TEST_F(CoreSmoothTest, RefsiteConservesMomentum) {
ASSERT_THAT(model, NotNull()); ASSERT_THAT(model, NotNull());
mjData* data = mj_makeData(model); mjData* data = mj_makeData(model);
// this test asserts tight momentum conservation: solve exactly, no early termination // assert tight momentum conservation: solve exactly, no early termination
model->opt.tolerance = 0; model->opt.tolerance = 0;
data->ctrl[0] = 1; data->ctrl[0] = 1;
@@ -1378,6 +1379,362 @@ TEST_F(CoreSmoothTest, SO3QuatSetpointRequiresStateless) {
EXPECT_THAT(error, HasSubstr("dyntype")); EXPECT_THAT(error, HasSubstr("dyntype"));
} }
// PID servo with vref = 0 reproduces the position servo exactly.
TEST_F(CoreSmoothTest, PidMatchesPositionServo) {
static constexpr char xml[] = R"(
<mujoco>
<option integrator="implicitfast">
<flag contact="disable"/>
</option>
<worldbody>
<!-- identical overlapping bodies (contacts disabled): world-frame
arithmetic is bit-identical, so the trajectories must be too -->
<body pos="0 0 .2">
<joint name="h_position" axis="0 1 0"/>
<geom type="capsule" size=".02" fromto="0 0 0 .2 0 0"/>
</body>
<body pos="0 0 .2">
<joint name="h_pd" axis="0 1 0"/>
<geom type="capsule" size=".02" fromto="0 0 0 .2 0 0"/>
</body>
</worldbody>
<actuator>
<position name="servo" joint="h_position" kp="10" dampratio="1"/>
<pid name="pid" joint="h_pd" kp="10" dampratio="1"/>
</actuator>
</mujoco>
)";
char error[1024];
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
// layout: 2 actuators, 1+2 controls, 1+1 outputs
EXPECT_EQ(model->nactuator, 2);
EXPECT_EQ(model->nu, 3);
EXPECT_EQ(model->nout, 2);
int pid = mj_name2id(model.get(), mjOBJ_ACTUATOR, "pid");
int uadr = model->actuator_ctrladr[pid];
EXPECT_EQ(model->actuator_ctrlnum[pid], 2);
mjData* data = mj_makeData(model.get());
int j_servo = mj_name2id(model.get(), mjOBJ_JOINT, "h_position");
int j_pd = mj_name2id(model.get(), mjOBJ_JOINT, "h_pd");
// ramp the position target on both, v* = 0 on the pid
while (data->time < 3) {
mjtNum target = 0.8 * data->time;
data->ctrl[0] = target;
data->ctrl[uadr] = target;
data->ctrl[uadr+1] = 0;
mj_step(model.get(), data);
ASSERT_EQ(data->warning[mjWARN_BADQACC].number, 0) << "diverged";
ASSERT_EQ(data->qpos[model->jnt_qposadr[j_servo]],
data->qpos[model->jnt_qposadr[j_pd]])
<< "trajectories diverge at time " << data->time;
}
mj_deleteData(data);
}
// Constant velocity setpoint produces steady motion at the commanded rate.
TEST_F(CoreSmoothTest, PidVelocitySetpoint) {
static constexpr char xml[] = R"(
<mujoco>
<option integrator="implicitfast">
<flag contact="disable" gravity="disable"/>
</option>
<worldbody>
<body>
<joint name="slide" type="slide" axis="1 0 0"/>
<geom type="box" size=".05 .05 .05"/>
</body>
</worldbody>
<actuator>
<pid name="pid" joint="slide" kp="0" kv="10"/>
</actuator>
</mujoco>
)";
char error[1024];
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
mjData* data = mj_makeData(model.get());
data->ctrl[1] = 0.7; // v*
while (data->time < 3) {
mj_step(model.get(), data);
ASSERT_EQ(data->warning[mjWARN_BADQACC].number, 0) << "diverged";
}
EXPECT_NEAR(data->qvel[0], 0.7, MjTol(1e-6, 2e-6));
mj_deleteData(data);
}
// The feedforward input adds directly to the actuator force.
TEST_F(CoreSmoothTest, PidFeedforward) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body>
<joint name="slide" type="slide" axis="1 0 0"/>
<geom type="box" size=".05 .05 .05"/>
</body>
</worldbody>
<actuator>
<pid name="pid" joint="slide" kp="1" kv="1" input="pos vel ff"/>
</actuator>
</mujoco>
)";
char error[1024];
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
EXPECT_EQ(model->nu, 3);
EXPECT_EQ(model->nout, 1);
mjData* data = mj_makeData(model.get());
data->ctrl[2] = 1.25; // ff, with zero position/velocity error
mj_forward(model.get(), data);
EXPECT_EQ(data->actuator_force[0], 1.25);
mj_deleteData(data);
}
// Input subsets: present inputs pack in canonical order, absent setpoints are
// zero, making single-input PIDs match the corresponding SISO servos.
TEST_F(CoreSmoothTest, PidInputSubsets) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body>
<joint name="j1" type="slide" axis="1 0 0"/>
<geom type="box" size=".05 .05 .05"/>
</body>
<body>
<joint name="j2" type="slide" axis="1 0 0"/>
<geom type="box" size=".05 .05 .05"/>
</body>
</worldbody>
<actuator>
<pid joint="j1" kp="7" input="pos"/>
<position joint="j1" kp="7"/>
<pid joint="j2" kv="3" input="vel"/>
<velocity joint="j2" kv="3"/>
<pid joint="j2" kp="5" kv="2" input="pos ff"/>
</actuator>
</mujoco>
)";
char error[1024];
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
mjModel* m = model.get();
// layout: subsets shrink the control blocks
EXPECT_EQ(m->nactuator, 5);
EXPECT_EQ(m->nout, 5);
EXPECT_EQ(m->nu, 6);
int expected_ctrlnum[5] = {1, 1, 1, 1, 2};
for (int i=0; i < 5; i++) {
EXPECT_EQ(m->actuator_ctrlnum[i], expected_ctrlnum[i]) << "actuator " << i;
}
mjData* data = mj_makeData(m);
data->qpos[0] = 0.2;
data->qpos[1] = -0.3;
data->qvel[0] = 0.5;
data->qvel[1] = -0.4;
data->ctrl[0] = data->ctrl[1] = 0.6; // pos-only pid and position servo
data->ctrl[2] = data->ctrl[3] = -0.8; // vel-only pid and velocity servo
data->ctrl[4] = 0.3; // pos of the [pos, ff] pid
data->ctrl[5] = 0.9; // ff of the [pos, ff] pid
mj_forward(m, data);
// single-input PIDs match the SISO servos
EXPECT_DOUBLE_EQ(data->actuator_force[0], data->actuator_force[1]);
EXPECT_DOUBLE_EQ(data->actuator_force[2], data->actuator_force[3]);
// [pos, ff]: kp*(qref - l) - kv*ldot + ff (absent velocity setpoint is zero)
mjtNum expected = 5*(0.3 - data->qpos[1]) - 2*data->qvel[1] + 0.9;
EXPECT_MJTNUM_EQ(data->actuator_force[4], expected);
// input names skip absent inputs
EXPECT_STREQ(mj_actuatorInputName(m, 4, 0), "pos");
EXPECT_STREQ(mj_actuatorInputName(m, 4, 1), "ff");
mj_deleteData(data);
}
// Integral action eliminates the steady-state error of a weak P servo under
// gravity; the integral state is clamped by imax.
TEST_F(CoreSmoothTest, PidIntegralAction) {
static constexpr char xml_fmt[] = R"(
<mujoco>
<option integrator="implicitfast">
<flag contact="disable"/>
</option>
<worldbody>
<body pos="0 0 .5">
<joint name="hinge" axis="0 1 0"/>
<geom type="capsule" size=".02" fromto="0 0 0 .2 0 0"/>
</body>
</worldbody>
<actuator>
<pid name="pid" joint="hinge" kp="1" dampratio="1" ki="%s" imax="2"/>
</actuator>
</mujoco>
)";
char error[1024];
char xml[2048];
// P-only: gravity induces a steady-state error at target q* = 0
snprintf(xml, sizeof(xml), xml_fmt, "0");
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
EXPECT_EQ(model->na, 0);
mjData* data = mj_makeData(model.get());
while (data->time < 10) {
mj_step(model.get(), data);
ASSERT_EQ(data->warning[mjWARN_BADQACC].number, 0) << "diverged";
}
mjtNum p_error = mju_abs(data->qpos[0]);
EXPECT_GT(p_error, 0.05);
mj_deleteData(data);
// with integral action: error is eliminated, integral state is bounded
snprintf(xml, sizeof(xml), xml_fmt, "2");
model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
EXPECT_EQ(model->na, 1);
data = mj_makeData(model.get());
while (data->time < 20) {
mj_step(model.get(), data);
ASSERT_LE(mju_abs(data->act[0]), 2 + 1e-10)
<< "integral state exceeds imax";
}
EXPECT_LT(mju_abs(data->qpos[0]), 1e-3);
mj_deleteData(data);
}
// slewmax limits the effective setpoint rate through an activation state.
TEST_F(CoreSmoothTest, PidSlewRateLimit) {
static constexpr char xml[] = R"(
<mujoco>
<option timestep="0.001"/>
<worldbody>
<body>
<joint name="slide" type="slide" axis="1 0 0"/>
<geom type="box" size=".1 .1 .1" mass="1"/>
</body>
</worldbody>
<actuator>
<pid name="servo" joint="slide" kp="200" kv="30" slewmax="0.5"/>
</actuator>
</mujoco>
)";
char error[1024];
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
EXPECT_EQ(model->actuator_actnum[0], 1); // slew state
mjData* data = mj_makeData(model.get());
// step the setpoint to 1: the effective setpoint (act) ramps at slewmax
data->ctrl[0] = 1.0;
mjtNum t_ramp = 1.0 / 0.5; // setpoint distance / slewmax
while (data->time < t_ramp - 0.1) {
mj_step(model.get(), data);
EXPECT_LE(data->act[0], 0.5 * data->time + 1e-9) << "slew exceeded";
}
while (data->time < 3 * t_ramp) {
mj_step(model.get(), data);
}
// the effective setpoint reached the command, and the joint tracked it
EXPECT_NEAR(data->act[0], 1.0, 1e-6);
EXPECT_NEAR(data->qpos[0], 1.0, 0.01);
mj_deleteData(data);
}
// PID on a rotational transmission: the position setpoint wraps, winding
// targets are tracked smoothly through pi.
TEST_F(CoreSmoothTest, PidTracksWindingTarget) {
static constexpr char xml[] = R"(
<mujoco>
<option integrator="implicitfast">
<flag contact="disable" gravity="disable"/>
</option>
<worldbody>
<body>
<joint name="ball" type="ball"/>
<geom type="box" size=".05 .07 .03"/>
</body>
</worldbody>
<actuator>
<pid name="rz" joint="ball" gear="0 0 1" kp="1" dampratio="1"/>
</actuator>
</mujoco>
)";
char error[1024];
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
mjData* data = mj_makeData(model.get());
const mjtNum rate = 0.5;
while (data->time < 2*mjPI / rate) {
data->ctrl[0] = rate * data->time;
data->ctrl[1] = rate; // matched velocity setpoint
mj_step(model.get(), data);
mjtNum err = data->ctrl[0] - data->actuator_length[0];
err -= 2*mjPI * mju_round(err / (2*mjPI));
ASSERT_LT(mju_abs(err), 0.5) << "tracking lost at time " << data->time;
}
mj_deleteData(data);
}
// PID parameters compose through defaults classes; the model round-trips.
TEST_F(CoreSmoothTest, PidDefaultsAndRoundtrip) {
static constexpr char xml[] = R"(
<mujoco>
<default>
<default class="arm">
<pid kp="7" ki="3" imax="1.5" velrange="-2 2" input="pos vel ff"/>
</default>
</default>
<worldbody>
<body>
<joint name="slide" type="slide" axis="1 0 0"/>
<geom type="box" size=".05 .05 .05"/>
</body>
</worldbody>
<actuator>
<pid name="pid" joint="slide" class="arm"/>
</actuator>
</mujoco>
)";
char error[1024];
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
// inherited: kp, ki, imax, input, velrange
EXPECT_EQ(model->nu, 3);
EXPECT_EQ(model->na, 1);
EXPECT_EQ(model->actuator_gainprm[0], 3); // ki
EXPECT_EQ(model->actuator_biasprm[1], -7); // -kp
EXPECT_EQ(model->actuator_dynprm[0], 1.5); // imax
EXPECT_EQ(model->actuator_dyntype[0], mjDYN_PID);
EXPECT_EQ(model->actuator_ctrlrange[2], -2); // velrange lo, input 1
EXPECT_EQ(model->actuator_ctrlrange[3], 2);
EXPECT_TRUE(model->actuator_ctrllimited[1]);
// round-trip preserves everything
std::string saved = SaveAndReadXml(model.get());
MjModelPtr model2 = LoadModelFromString(saved.c_str(), error, sizeof(error));
ASSERT_THAT(model2.get(), NotNull()) << error;
EXPECT_EQ(model2->nu, 3);
EXPECT_EQ(model2->na, 1);
EXPECT_EQ(model2->actuator_gainprm[0], 3);
EXPECT_EQ(model2->actuator_ctrlrange[2], -2);
}
static const char* const kInertiaPath = "engine/testdata/inertia.xml"; static const char* const kInertiaPath = "engine/testdata/inertia.xml";
TEST_F(CoreSmoothTest, FactorI) { TEST_F(CoreSmoothTest, FactorI) {
+36 -1
View File
@@ -18,7 +18,7 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <filesystem> #include <filesystem> // NOLINT
#include <functional> #include <functional>
#include <map> #include <map>
#include <memory> #include <memory>
@@ -513,6 +513,41 @@ TEST_F(MujocoTest, SetToOrientation) {
mj_deleteSpec(spec); mj_deleteSpec(spec);
} }
TEST_F(MujocoTest, SetToPID) {
mjSpec* spec = mj_makeSpec();
mjsActuator* actuator = mjs_addActuator(spec, 0);
// stateless PID with kv, default input signature
double kv = 3.0;
const char* err = mjs_setToPID(actuator, 5.0, &kv, nullptr, nullptr, nullptr,
nullptr, 0, 0);
EXPECT_STREQ(err, "");
EXPECT_EQ(actuator->gaintype, mjGAIN_PID);
EXPECT_EQ(actuator->biastype, mjBIAS_AFFINE);
EXPECT_EQ(actuator->dyntype, mjDYN_NONE);
EXPECT_EQ(actuator->biasprm[1], -5.0);
EXPECT_EQ(actuator->biasprm[2], -3.0);
EXPECT_EQ(actuator->gainprm[0], 0.0);
// integral action with anti-windup, pos-only signature
double ki = 0.5, imax = 2.0, dampratio = 1.0;
err = mjs_setToPID(actuator, 5.0, nullptr, &dampratio, &ki, &imax, nullptr, 0,
mjINPUT_POS);
EXPECT_STREQ(err, "");
EXPECT_EQ(actuator->dyntype, mjDYN_PID);
EXPECT_EQ(actuator->gainprm[0], 0.5);
EXPECT_EQ(actuator->dynprm[0], 2.0);
EXPECT_EQ(actuator->biasprm[2], 1.0);
EXPECT_EQ(actuator->ctrlspec, mjINPUT_POS);
// kv and dampratio are mutually exclusive
err = mjs_setToPID(actuator, 5.0, &kv, &dampratio, nullptr, nullptr, nullptr,
0, 0);
EXPECT_STREQ(err, "kv and dampratio cannot both be defined");
mj_deleteSpec(spec);
}
static constexpr char xml_plugin_1[] = R"( static constexpr char xml_plugin_1[] = R"(
<mujoco model="MuJoCo Model"> <mujoco model="MuJoCo Model">
<worldbody> <worldbody>
+9 -2
View File
@@ -276,7 +276,8 @@ public enum mjtDyn : int{
mjDYN_FILTEREXACT = 3, mjDYN_FILTEREXACT = 3,
mjDYN_MUSCLE = 4, mjDYN_MUSCLE = 4,
mjDYN_DCMOTOR = 5, mjDYN_DCMOTOR = 5,
mjDYN_USER = 6, mjDYN_PID = 6,
mjDYN_USER = 7,
} }
public enum mjtGain : int{ public enum mjtGain : int{
mjGAIN_FIXED = 0, mjGAIN_FIXED = 0,
@@ -284,7 +285,8 @@ public enum mjtGain : int{
mjGAIN_MUSCLE = 2, mjGAIN_MUSCLE = 2,
mjGAIN_DCMOTOR = 3, mjGAIN_DCMOTOR = 3,
mjGAIN_SO3 = 4, mjGAIN_SO3 = 4,
mjGAIN_USER = 5, mjGAIN_PID = 5,
mjGAIN_USER = 6,
} }
public enum mjtBias : int{ public enum mjtBias : int{
mjBIAS_NONE = 0, mjBIAS_NONE = 0,
@@ -298,6 +300,11 @@ public enum mjtCtrlChart : int{
mjCHART_EXPMAP = 1, mjCHART_EXPMAP = 1,
mjCHART_QUAT = 2, mjCHART_QUAT = 2,
} }
public enum mjtCtrlInput : int{
mjINPUT_POS = 1,
mjINPUT_VEL = 2,
mjINPUT_FF = 4,
}
public enum mjtObj : int{ public enum mjtObj : int{
mjOBJ_UNKNOWN = 0, mjOBJ_UNKNOWN = 0,
mjOBJ_BODY = 1, mjOBJ_BODY = 1,
+19 -1
View File
@@ -2923,6 +2923,15 @@ std::string mjs_setToOrientation_wrapper(MjsActuator& actuator, double kp, const
return std::string(mjs_setToOrientation(actuator.get(), kp, kv_.data(), dampratio_.data(), ctrlspec)); return std::string(mjs_setToOrientation(actuator.get(), kp, kv_.data(), dampratio_.data(), ctrlspec));
} }
std::string mjs_setToPID_wrapper(MjsActuator& actuator, double kp, const val& kv, const val& dampratio, const val& ki, const val& imax, const val& slewmax, double inheritrange, int ctrlspec) {
UNPACK_VALUE(double, kv);
UNPACK_VALUE(double, dampratio);
UNPACK_VALUE(double, ki);
UNPACK_VALUE(double, imax);
UNPACK_VALUE(double, slewmax);
return std::string(mjs_setToPID(actuator.get(), kp, kv_.data(), dampratio_.data(), ki_.data(), imax_.data(), slewmax_.data(), inheritrange, ctrlspec));
}
std::string mjs_setToPosition_wrapper(MjsActuator& actuator, double kp, const val& kv, const val& dampratio, const val& timeconst, double inheritrange) { std::string mjs_setToPosition_wrapper(MjsActuator& actuator, double kp, const val& kv, const val& dampratio, const val& timeconst, double inheritrange) {
UNPACK_VALUE(double, kv); UNPACK_VALUE(double, kv);
UNPACK_VALUE(double, dampratio); UNPACK_VALUE(double, dampratio);
@@ -3981,6 +3990,10 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
enum_<mjtCtrlChart>("mjtCtrlChart") enum_<mjtCtrlChart>("mjtCtrlChart")
.value("mjCHART_EXPMAP", mjCHART_EXPMAP) .value("mjCHART_EXPMAP", mjCHART_EXPMAP)
.value("mjCHART_QUAT", mjCHART_QUAT); .value("mjCHART_QUAT", mjCHART_QUAT);
enum_<mjtCtrlInput>("mjtCtrlInput")
.value("mjINPUT_POS", mjINPUT_POS)
.value("mjINPUT_VEL", mjINPUT_VEL)
.value("mjINPUT_FF", mjINPUT_FF);
enum_<mjtDataType>("mjtDataType") enum_<mjtDataType>("mjtDataType")
.value("mjDATATYPE_REAL", mjDATATYPE_REAL) .value("mjDATATYPE_REAL", mjDATATYPE_REAL)
.value("mjDATATYPE_POSITIVE", mjDATATYPE_POSITIVE) .value("mjDATATYPE_POSITIVE", mjDATATYPE_POSITIVE)
@@ -4018,6 +4031,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
.value("mjDYN_FILTEREXACT", mjDYN_FILTEREXACT) .value("mjDYN_FILTEREXACT", mjDYN_FILTEREXACT)
.value("mjDYN_MUSCLE", mjDYN_MUSCLE) .value("mjDYN_MUSCLE", mjDYN_MUSCLE)
.value("mjDYN_DCMOTOR", mjDYN_DCMOTOR) .value("mjDYN_DCMOTOR", mjDYN_DCMOTOR)
.value("mjDYN_PID", mjDYN_PID)
.value("mjDYN_USER", mjDYN_USER); .value("mjDYN_USER", mjDYN_USER);
enum_<mjtEnableBit>("mjtEnableBit") enum_<mjtEnableBit>("mjtEnableBit")
.value("mjENBL_OVERRIDE", mjENBL_OVERRIDE) .value("mjENBL_OVERRIDE", mjENBL_OVERRIDE)
@@ -4082,6 +4096,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
.value("mjGAIN_MUSCLE", mjGAIN_MUSCLE) .value("mjGAIN_MUSCLE", mjGAIN_MUSCLE)
.value("mjGAIN_DCMOTOR", mjGAIN_DCMOTOR) .value("mjGAIN_DCMOTOR", mjGAIN_DCMOTOR)
.value("mjGAIN_SO3", mjGAIN_SO3) .value("mjGAIN_SO3", mjGAIN_SO3)
.value("mjGAIN_PID", mjGAIN_PID)
.value("mjGAIN_USER", mjGAIN_USER); .value("mjGAIN_USER", mjGAIN_USER);
enum_<mjtGeom>("mjtGeom") enum_<mjtGeom>("mjtGeom")
.value("mjGEOM_PLANE", mjGEOM_PLANE) .value("mjGEOM_PLANE", mjGEOM_PLANE)
@@ -5612,6 +5627,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
.property("dynprm", &MjsActuator::dynprm) .property("dynprm", &MjsActuator::dynprm)
.property("dyntype", &MjsActuator::dyntype, &MjsActuator::set_dyntype, reference()) .property("dyntype", &MjsActuator::dyntype, &MjsActuator::set_dyntype, reference())
.property("element", &MjsActuator::element, reference()) .property("element", &MjsActuator::element, reference())
.property("ffrange", &MjsActuator::ffrange)
.property("forcelimited", &MjsActuator::forcelimited, &MjsActuator::set_forcelimited, reference()) .property("forcelimited", &MjsActuator::forcelimited, &MjsActuator::set_forcelimited, reference())
.property("forcerange", &MjsActuator::forcerange) .property("forcerange", &MjsActuator::forcerange)
.property("gainprm", &MjsActuator::gainprm) .property("gainprm", &MjsActuator::gainprm)
@@ -5628,7 +5644,8 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
.property("slidersite", &MjsActuator::slidersite, &MjsActuator::set_slidersite, reference()) .property("slidersite", &MjsActuator::slidersite, &MjsActuator::set_slidersite, reference())
.property("target", &MjsActuator::target, &MjsActuator::set_target, reference()) .property("target", &MjsActuator::target, &MjsActuator::set_target, reference())
.property("trntype", &MjsActuator::trntype, &MjsActuator::set_trntype, reference()) .property("trntype", &MjsActuator::trntype, &MjsActuator::set_trntype, reference())
.property("userdata", &MjsActuator::userdata, reference()); .property("userdata", &MjsActuator::userdata, reference())
.property("velrange", &MjsActuator::velrange);
emscripten::class_<MjsAuthored>("MjsAuthored") emscripten::class_<MjsAuthored>("MjsAuthored")
.property("disableactuator", &MjsAuthored::disableactuator, &MjsAuthored::set_disableactuator, reference()) .property("disableactuator", &MjsAuthored::disableactuator, &MjsAuthored::set_disableactuator, reference())
.property("disableflags", &MjsAuthored::disableflags, &MjsAuthored::set_disableflags, reference()) .property("disableflags", &MjsAuthored::disableflags, &MjsAuthored::set_disableflags, reference())
@@ -6510,6 +6527,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
function("mjs_setToMotor", &mjs_setToMotor_wrapper); function("mjs_setToMotor", &mjs_setToMotor_wrapper);
function("mjs_setToMuscle", &mjs_setToMuscle_wrapper); function("mjs_setToMuscle", &mjs_setToMuscle_wrapper);
function("mjs_setToOrientation", &mjs_setToOrientation_wrapper); function("mjs_setToOrientation", &mjs_setToOrientation_wrapper);
function("mjs_setToPID", &mjs_setToPID_wrapper);
function("mjs_setToPosition", &mjs_setToPosition_wrapper); function("mjs_setToPosition", &mjs_setToPosition_wrapper);
function("mjs_setToVelocity", &mjs_setToVelocity_wrapper); function("mjs_setToVelocity", &mjs_setToVelocity_wrapper);
function("mjs_wrapGeom", &mjs_wrapGeom_wrapper); function("mjs_wrapGeom", &mjs_wrapGeom_wrapper);
+6
View File
@@ -6025,6 +6025,12 @@ struct MjsActuator {
void set_ctrlspec(int value) { void set_ctrlspec(int value) {
ptr_->ctrlspec = value; ptr_->ctrlspec = value;
} }
emscripten::val velrange() const {
return emscripten::val(emscripten::typed_memory_view(2, ptr_->velrange));
}
emscripten::val ffrange() const {
return emscripten::val(emscripten::typed_memory_view(2, ptr_->ffrange));
}
mjtBool actearly() const { mjtBool actearly() const {
return ptr_->actearly; return ptr_->actearly;
} }