Skip to content

Commit 56a9397

Browse files
yuvaltassacopybara-github
authored andcommitted
Interpret position and intvelocity setpoints on 3D rotational transmissions (ball joints, site+refsite) on the circle. The force uses the setpoint representative nearest the current length for smooth tracking beyond pi.
- Wrapping in force path is local; act is re-anchored at integration time. - Remove hardcoded `actrange` for intvelocity actuators. PiperOrigin-RevId: 949566477 Change-Id: I349fdf17eedfbb2174d698cc1a6a91d52810b4a3
1 parent 9331c1c commit 56a9397

9 files changed

Lines changed: 301 additions & 39 deletions

File tree

‎doc/XMLreference.rst‎

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5531,16 +5531,23 @@ specify them independently.
55315531

55325532
:at:`joint`: :at-val:`string, optional`
55335533
This and the next four attributes determine the type of actuator transmission. All of them are optional, and exactly
5534-
one of them must be specified. If this attribute is specified, the actuator acts on the given joint. For **hinge**
5535-
and **slide** joints, the actuator length equals the joint position/angle times the first element of :at:`gear`. For
5536-
**ball** joints, the first three elements of gear define a 3d rotation axis in the child frame around which the
5534+
one of them must be specified. If this attribute is specified, the actuator acts on the given joint.
5535+
5536+
For **hinge** and **slide** joints, the actuator length equals the joint position/angle times the first element of
5537+
:at:`gear`.
5538+
5539+
For **ball** joints, the first three elements of gear define a 3d rotation axis in the child frame around which the
55375540
actuator produces torque. The actuator length is defined as the dot-product between this gear axis and the angle-axis
55385541
representation of the joint quaternion, and is in units of radian if :at:`gear` is normalized (generally scaled by
5539-
by the norm of :at:`gear`). Note that after total rotation of more than :math:`\pi`, the length will wrap to :math:`-
5540-
\pi`, and vice-versa. Therefore :el:`position` servos for ball joints should generally use tighter limits which
5541-
prevent this wrapping. For **free** joints, gear defines a 3d translation axis in the world frame followed by a 3d
5542-
rotation axis in the child frame. The actuator generates force and torque relative to the specified axes. The
5543-
actuator length for free joints is defined as zero (so it should not be used with position servos).
5542+
the norm of :at:`gear`). Note that the length is defined on a circle: after total rotation of more than :math:`\pi`
5543+
it wraps to :math:`-\pi`, and vice-versa. :ref:`position<actuator-position>` and
5544+
:ref:`intvelocity<actuator-intvelocity>` servos on such transmissions interpret their setpoint on the circle, driving
5545+
towards the nearest representative of the target, so targets can be wound continuously through any number of turns
5546+
and no control limits are required to prevent wrapping.
5547+
5548+
For **free** joints, gear defines a 3d translation axis in the world frame followed by a 3d rotation axis in the
5549+
child frame. The actuator generates force and torque relative to the specified axes. The actuator length for free
5550+
joints is defined as zero (so cannot be used with position servos).
55445551

55455552
.. _actuator-general-jointinparent:
55465553

@@ -5570,13 +5577,14 @@ specify them independently.
55705577
When using a :at:`site` transmission, measure the translation and rotation w.r.t the frame of the :at:`refsite`. In
55715578
this case the actuator *does* have length and :el:`position` actuators can be used to directly control an end
55725579
effector, see `refsite.xml
5573-
<https://github.com/google-deepmind/mujoco/tree/main/test/engine/testdata/actuation/refsite.xml>`__ example
5574-
model. As above, the length is the dot product of the :at:`gear` vector and the frame difference. So ``gear="0 1 0 0
5575-
0 0"`` means "Y-offset of :at:`site` in the :at:`refsite` frame", while ``gear="0 0 0 0 0 1"`` means rotation "Z-
5576-
rotation of :at:`site` in the :at:`refsite` frame". It is recommended to use a normalized :at:`gear` vector with
5577-
nonzeros in only the first 3 *or* the last 3 elements of :at:`gear`, so the actuator length will be in either length
5578-
units or radians, respectively. As with ball joints (see :at:`joint` above), for rotations which exceed a total angle
5579-
of :math:`\pi` will wrap around, so tighter limits are recommended.
5580+
<https://github.com/google-deepmind/mujoco/tree/main/test/engine/testdata/actuation/refsite.xml>`__ example model. As
5581+
above, the length is the dot product of the :at:`gear` vector and the frame difference. So ``gear="0 1 0 0 0 0"``
5582+
means "Y-offset of :at:`site` in the :at:`refsite` frame", while ``gear="0 0 0 0 0 1"`` means rotation "Z- rotation
5583+
of :at:`site` in the :at:`refsite` frame". It is recommended to use a normalized :at:`gear` vector with nonzeros in
5584+
only the first 3 *or* the last 3 elements of :at:`gear`, so the actuator length will be in either length units or
5585+
radians, respectively. As with ball joints (see :ref:`general/joint<actuator-general-joint>` above), rotational
5586+
lengths are defined on a circle and servo setpoints are interpreted on it; control limits are not required to prevent
5587+
wrapping.
55805588

55815589
.. _actuator-general-body:
55825590

@@ -5794,6 +5802,7 @@ gaintype fixed gainprm kp 0 0
57945802
biastype affine biasprm 0 -kp -kv
57955803
========= =================== ========= =============
57965804

5805+
On purely rotational transmissions, setpoints are interpreted on the circle; see :ref:`gear<actuator-general-gear>`.
57975806

57985807
This element has one custom attribute in addition to the common attributes:
57995808

@@ -5989,14 +5998,18 @@ This element creates an integrated-velocity servo. For more information, see the
59895998
:ref:`Activation clamping <CActRange>` section of the Modeling chapter. The underlying
59905999
:el:`general` attributes are set as follows:
59916000

5992-
========== =========== ========= =========
5993-
Attribute Setting Attribute Setting
5994-
========== =========== ========= =========
5995-
dyntype integrator dynprm 1 0 0
5996-
gaintype fixed gainprm kp 0 0
5997-
biastype affine biasprm 0 -kp -kv
5998-
actlimited true
5999-
========== =========== ========= =========
6001+
========= =========== ========= =========
6002+
Attribute Setting Attribute Setting
6003+
========= =========== ========= =========
6004+
dyntype integrator dynprm 1 0 0
6005+
gaintype fixed gainprm kp 0 0
6006+
biastype affine biasprm 0 -kp -kv
6007+
========= =========== ========= =========
6008+
6009+
Activation clamping is controlled by :at:`actlimited` and :at:`actrange`, like any stateful actuator. On purely
6010+
rotational transmissions, setpoints are interpreted on the circle, as for :ref:`position<actuator-position>`; the
6011+
integrated setpoint is re-anchored to a bounded representative at each timestep, so clamping is not required for
6012+
winding targets.
60006013

60016014
This element has one custom attribute in addition to the common attributes:
60026015

@@ -6016,6 +6029,8 @@ This element has one custom attribute in addition to the common attributes:
60166029

60176030
.. _actuator-intvelocity-forcelimited:
60186031

6032+
.. _actuator-intvelocity-actlimited:
6033+
60196034
.. _actuator-intvelocity-ctrlrange:
60206035

60216036
.. _actuator-intvelocity-forcerange:
@@ -6049,9 +6064,10 @@ This element has one custom attribute in addition to the common attributes:
60496064
.. _actuator-intvelocity-armature:
60506065

60516066
.. |actuator/intvelocity attrib list| replace::
6052-
:at:`name`, :at:`class`, :at:`group`, :at:`delay`, :at:`ctrllimited`, :at:`forcelimited`, :at:`ctrlrange`,
6053-
:at:`forcerange`, :at:`actrange`, :at:`lengthrange`, :at:`gear`, :at:`cranklength`, :at:`joint`, :at:`jointinparent`,
6054-
:at:`tendon`, :at:`cranksite`, :at:`slidersite`, :at:`site`, :at:`refsite`, :at:`user`, :at:`damping`, :at:`armature`
6067+
:at:`name`, :at:`class`, :at:`group`, :at:`delay`, :at:`ctrllimited`, :at:`forcelimited`, :at:`actlimited`,
6068+
:at:`ctrlrange`, :at:`forcerange`, :at:`actrange`, :at:`lengthrange`, :at:`gear`, :at:`cranklength`, :at:`joint`,
6069+
:at:`jointinparent`, :at:`tendon`, :at:`cranksite`, :at:`slidersite`, :at:`site`, :at:`refsite`, :at:`user`,
6070+
:at:`damping`, :at:`armature`
60556071

60566072
|actuator/intvelocity attrib list|
60576073
Same as in actuator/ :ref:`general <actuator-general>`.
@@ -9990,6 +10006,8 @@ refsite, tendon, slidersite, cranksite.
999010006

999110007
.. _default-intvelocity-forcelimited:
999210008

10009+
.. _default-intvelocity-actlimited:
10010+
999310011
.. _default-intvelocity-ctrlrange:
999410012

999510013
.. _default-intvelocity-forcerange:

‎doc/XMLschema.rst‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2660,6 +2660,9 @@
26602660
.. grid-item::
26612661
:ref:`forcelimited<actuator-intvelocity-forcelimited>`
26622662

2663+
.. grid-item::
2664+
:ref:`actlimited<actuator-intvelocity-actlimited>`
2665+
26632666
.. grid-item::
26642667
:ref:`ctrlrange<actuator-intvelocity-ctrlrange>`
26652668

@@ -6032,6 +6035,9 @@
60326035
.. grid-item::
60336036
:ref:`forcelimited<default-intvelocity-forcelimited>`
60346037

6038+
.. grid-item::
6039+
:ref:`actlimited<default-intvelocity-actlimited>`
6040+
60356041
.. grid-item::
60366042
:ref:`ctrlrange<default-intvelocity-ctrlrange>`
60376043

‎doc/changelog.rst‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ General
5252
error instead of the generic "could not decode content" message.
5353
- Added support for resource writing via :ref:`mju_writeResource` and the ``write`` callback in :ref:`mjpResourceProvider`.
5454
- Added support for :ref:`multiccd <coMultiCCD>` with arbitrarily large meshes.
55+
- Setpoints of :ref:`position<actuator-position>` and :ref:`intvelocity<actuator-intvelocity>` servos acting on 3D
56+
rotational transmissions (ball joints, or site transmissions with a :ref:`refsite<actuator-general-refsite>` and
57+
purely rotational gear) are now interpreted on the circle: the force uses the setpoint representative nearest the
58+
current angle, so targets winding beyond half a turn are tracked continuously instead of slipping by full turns.
59+
Behavior is identical whenever the error does not exceed π. Relatedly, ``intvelocity`` actuators now expose
60+
:ref:`actlimited<actuator-intvelocity-actlimited>`, which was previously hardcoded to "true": as for
61+
:ref:`general<actuator-general>` actuators it defaults to "auto", so activation clamping is enabled by specifying
62+
``actrange``. Unclamped integrated setpoints are well-behaved on rotational transmissions, where they wrap.
5563
- Refactored actuator infrastructure in preparation for MIMO (multi-input multi-output) actuator support. Each actuator
5664
now has ``ctrlnum`` (number of controls) and ``outnum`` (number of force outputs). The total counts
5765
``nu = sum(ctrlnum)`` and ``nout = sum(outnum)`` dimension ``mjData.ctrl`` and ``mjData.actuator_force``,

‎doc/modeling.rst‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -828,8 +828,9 @@ is the most common implementation of actuators with velocity semantics, rather t
828828
often quite unstable (both in real life and in simulation).
829829

830830
In the case of integrated-velocity actuators, it is often desirable to *clamp* the activation state, since otherwise the
831-
position target would keep integrating beyond the joint limits, leading to loss of controllabillity. To see the effect
832-
of activation clamping, load the example model below:
831+
position target would keep integrating beyond the joint limits, leading to loss of controllabillity. (On purely
832+
rotational transmissions, the setpoint wraps on the circle and stays bounded without clamping; see
833+
:ref:`gear<actuator-general-gear>`.) To see the effect of activation clamping, load the example model below:
833834

834835
.. collapse:: Example model with activation limits
835836

@@ -853,8 +854,7 @@ of activation clamping, load the example model below:
853854
</worldbody>
854855
855856
<actuator>
856-
<general name="unclamped" joint="joint1" gainprm="1" biastype="affine"
857-
biasprm="0 -1" dyntype="integrator"/>
857+
<intvelocity name="unclamped" joint="joint1"/>
858858
<intvelocity name="clamped" joint="joint2" actrange="-1.57 1.57"/>
859859
</actuator>
860860
</mujoco>

‎src/engine/engine_forward.c‎

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,43 @@ static void clampVec(mjtNum* vec, const mjtNum* range, const mjtBool* limited, i
268268
}
269269

270270

271+
// period of the rotational transmission for wrap-eligible servo actuators, 0 otherwise
272+
static mjtNum wrapPeriod(const mjModel* m, int i) {
273+
// servo shape: fixed gain, affine bias, matching kp, setpoint input
274+
mjtDyn dyntype = m->actuator_dyntype[i];
275+
if (m->actuator_gaintype[i] != mjGAIN_FIXED ||
276+
m->actuator_biastype[i] != mjBIAS_AFFINE ||
277+
m->actuator_gainprm[mjNGAIN*i] != -m->actuator_biasprm[mjNBIAS*i+1] ||
278+
(dyntype != mjDYN_NONE && dyntype != mjDYN_INTEGRATOR)) {
279+
return 0;
280+
}
281+
282+
const mjtNum* gear = m->actuator_gear+6*m->actuator_outadr[i];
283+
mjtTrn trntype = m->actuator_trntype[i];
284+
285+
// site transmission with refsite and purely rotational gear
286+
if (trntype == mjTRN_SITE && m->actuator_trnid[2*i+1] >= 0 &&
287+
!gear[0] && !gear[1] && !gear[2]) {
288+
return 2*mjPI * mju_norm3(gear+3);
289+
}
290+
291+
// joint transmission on a ball joint
292+
if ((trntype == mjTRN_JOINT || trntype == mjTRN_JOINTINPARENT) &&
293+
m->jnt_type[m->actuator_trnid[2*i]] == mjJNT_BALL) {
294+
return 2*mjPI * mju_norm3(gear);
295+
}
296+
297+
return 0;
298+
}
299+
300+
301+
// representative of setpoint u nearest to length, given period
302+
static mjtNum wrapSetpoint(mjtNum u, mjtNum length, mjtNum period) {
303+
mjtNum err = u - length;
304+
return u - period * mju_round(err / period);
305+
}
306+
307+
271308
// (qpos, qvel, ctrl, act) => (qfrc_actuator, actuator_force, act_dot)
272309
void mj_fwdActuation(const mjModel* m, mjData* d) {
273310
TM_START;
@@ -590,7 +627,14 @@ void mj_fwdActuation(const mjModel* m, mjData* d) {
590627
// DC motor without current state: use ctrl even if other activations exist
591628
int dcmotor_no_current = (gaintype == mjGAIN_DCMOTOR && dynprm[0] <= 0);
592629
if (actnum == 0 || dcmotor_no_current) {
593-
force[oadr] = gain * ctrl[uadr];
630+
mjtNum input = ctrl[uadr];
631+
632+
// rotational setpoint: use representative nearest the length (local, no state change)
633+
mjtNum period = wrapPeriod(m, i);
634+
if (period > 0) {
635+
input = wrapSetpoint(input, d->actuator_length[oadr], period);
636+
}
637+
force[oadr] = gain * input;
594638
} else {
595639
// use last activation variable associated with actuator i
596640
int act_adr = m->actuator_actadr[i] + actnum - 1;
@@ -601,6 +645,12 @@ void mj_fwdActuation(const mjModel* m, mjData* d) {
601645
} else {
602646
act = d->act[act_adr];
603647
}
648+
649+
// rotational setpoint: use representative nearest the length (local, no state change)
650+
mjtNum period = wrapPeriod(m, i);
651+
if (period > 0) {
652+
act = wrapSetpoint(act, d->actuator_length[oadr], period);
653+
}
604654
force[oadr] = gain * act;
605655
}
606656

@@ -1087,6 +1137,21 @@ static void mj_advance(const mjModel* m, mjData* d,
10871137
d->act[j] = mj_nextActivation(m, d, i, j, mj_actuatorDisabled(m, i) ? 0 : act_dot[j]);
10881138
}
10891139
}
1140+
1141+
// rotational setpoints stored in act: replace with an equivalent bounded representative,
1142+
// like the actrange clamp above, this is a projection applied at integration time
1143+
for (int i=0; i < nactuator; i++) {
1144+
if (m->actuator_dyntype[i] != mjDYN_INTEGRATOR) {
1145+
continue;
1146+
}
1147+
1148+
// per-axis servo: wrap act to the representative nearest the length
1149+
mjtNum period = wrapPeriod(m, i);
1150+
if (period > 0) {
1151+
int adr = m->actuator_actadr[i] + m->actuator_actnum[i] - 1;
1152+
d->act[adr] = wrapSetpoint(d->act[adr], d->actuator_length[m->actuator_outadr[i]], period);
1153+
}
1154+
}
10901155
}
10911156

10921157
// put islands to sleep according to velocity tolerance

‎src/user/user_api.cc‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1313,7 +1313,6 @@ const char* mjs_setToIntVelocity(mjsActuator* actuator, double kp, double kv[1],
13131313
double dampratio[1], double timeconst[1], double inheritrange) {
13141314
mjs_setToPosition(actuator, kp, kv, dampratio, timeconst, inheritrange);
13151315
actuator->dyntype = mjDYN_INTEGRATOR;
1316-
actuator->actlimited = mjLIMITED_TRUE;
13171316

13181317
if (inheritrange > 0) {
13191318
if (actuator->actrange[0] || actuator->actrange[1]) {

‎src/xml/xml_native_reader.cc‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,8 @@ std::vector<const char*> MJCF[nMJCF] = {
266266
"delay", "kp", "kv", "dampratio", "timeconst"},
267267
{"velocity", "?", "ctrllimited", "forcelimited", "ctrlrange", "forcerange", "gear",
268268
"damping", "armature", "cranklength", "user", "group", "nsample", "interp", "delay", "kv"},
269-
{"intvelocity", "?", "ctrllimited", "forcelimited", "ctrlrange", "forcerange", "actrange",
270-
"inheritrange", "gear", "damping", "armature", "cranklength", "user", "group",
269+
{"intvelocity", "?", "ctrllimited", "forcelimited", "actlimited", "ctrlrange", "forcerange",
270+
"actrange", "inheritrange", "gear", "damping", "armature", "cranklength", "user", "group",
271271
"nsample", "interp", "delay", "kp", "kv", "dampratio"},
272272
{"damper", "?", "forcelimited", "ctrlrange", "forcerange",
273273
"gear", "damping", "armature", "cranklength", "user", "group", "nsample", "interp", "delay", "kv"},
@@ -491,7 +491,7 @@ std::vector<const char*> MJCF[nMJCF] = {
491491
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site", "refsite",
492492
"kv"},
493493
{"intvelocity", "*", "name", "class", "group", "nsample", "interp", "delay",
494-
"ctrllimited", "forcelimited",
494+
"ctrllimited", "forcelimited", "actlimited",
495495
"ctrlrange", "forcerange", "actrange", "inheritrange", "lengthrange",
496496
"gear", "damping", "armature", "cranklength", "user",
497497
"joint", "jointinparent", "tendon", "slidersite", "cranksite", "site", "refsite",

0 commit comments

Comments
 (0)
⌂ 换网址