Skip to content

Commit d05251a

Browse files
yuvaltassacopybara-github
authored andcommitted
Add tendon armature
PiperOrigin-RevId: 743939992 Change-Id: I587214f5d6fabbc0cc273c33d82decbe9ad8f919
1 parent e1f5ceb commit d05251a

22 files changed

+735
-44
lines changed

doc/XMLreference.rst

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4706,12 +4706,30 @@ length X, as in the clip on the right of `this example model
47064706
joint damping which is integrated implicitly by the Euler method, tendon damping is not integrated implicitly, thus
47074707
joint damping should be used if possible.
47084708

4709-
.. TODO(tassa): Update here once the feature is implemented.
4709+
.. image:: images/XMLreference/tendon_armature.gif
4710+
:width: 30%
4711+
:align: right
4712+
:class: only-light
4713+
:target: https://github.com/google-deepmind/mujoco/blob/main/test/engine/testdata/core_smooth/ten_armature_1_compare.xml
4714+
.. image:: images/XMLreference/tendon_armature_dark.gif
4715+
:width: 30%
4716+
:align: right
4717+
:class: only-dark
4718+
:target: https://github.com/google-deepmind/mujoco/blob/main/test/engine/testdata/core_smooth/ten_armature_1_compare.xml
47104719

47114720
.. _tendon-spatial-armature:
47124721

47134722
:at:`armature`: :at-val:`real, "0"`
4714-
Inertia associated with tendon. This feature is not yet implemented.
4723+
Inertia associated with changes in tendon length. Setting this attribute to a positive value :math:`m` adds a kinetic
4724+
energy term :math:`\frac{1}{2}mv^2`, where :math:`v` is the tendon velocity. Tendon inertia is most valuable
4725+
when modeling the :ref:`armature<body-joint-armature>` inertia in a linear actuator which contains a spinning element
4726+
or the inertial motion of a fluid in a linear hydraulic actuator. In the illustration, we compare (*left*) a 3-dof
4727+
system with a "tendon" implemented with a rotational joint and a slider joint with
4728+
:ref:`armature<body-joint-armature>`, attached to the world with a :ref:`connect<equality-connect>` constraint and
4729+
(*right*) an equivalent 1-dof model with an armature-bearing tendon. Like joint :ref:`armature<body-joint-armature>`,
4730+
this added inertia is only associated with changes in tendon length, and would not affect the dynamics of a moving
4731+
fixed-length tendon. Because the tendon Jacobian :math:`J` is position-dependent, tendon armature leads to an
4732+
additional bias-force term :math:`c = m J \dot{J}^T \dot{q}`.
47154733

47164734
.. _tendon-spatial-user:
47174735

doc/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Upcoming version (not yet released)
1515

1616
General
1717
^^^^^^^
18+
- Added :ref:`tendon armature<tendon-spatial-armature>`: inertia associated with changes in tendon length.
1819
- Added the :ref:`compiler/saveinertial<compiler-saveinertial>` flag, writing explicit inertial clauses for all
1920
bodies when saving to XML.
2021
- Added :ref:`orientation<body-composite-quat>` attribute to :ref:`composite<body-composite>`. Moreover, allow the
123 KB
Loading
133 KB
Loading

src/engine/engine_core_smooth.c

Lines changed: 115 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -865,15 +865,7 @@ void mj_tendon(const mjModel* m, mjData* d) {
865865
void mj_tendonDot(const mjModel* m, mjData* d, int id, mjtNum* Jdot) {
866866
int nv = m->nv;
867867

868-
// allocate stack arrays
869-
mjtNum *jac1, *jac2, *jacdif, *tmp;
870-
mj_markStack(d);
871-
jac1 = mjSTACKALLOC(d, 3*nv, mjtNum);
872-
jac2 = mjSTACKALLOC(d, 3*nv, mjtNum);
873-
jacdif = mjSTACKALLOC(d, 3*nv, mjtNum);
874-
tmp = mjSTACKALLOC(d, nv, mjtNum);
875-
876-
// return if tendon id is invalid
868+
// tendon id is invalid: return
877869
if (id < 0 || id >= m->ntendon) {
878870
return;
879871
}
@@ -887,6 +879,13 @@ void mj_tendonDot(const mjModel* m, mjData* d, int id, mjtNum* Jdot) {
887879
return;
888880
}
889881

882+
// allocate stack arrays
883+
mj_markStack(d);
884+
mjtNum* jac1 = mjSTACKALLOC(d, 3*nv, mjtNum);
885+
mjtNum* jac2 = mjSTACKALLOC(d, 3*nv, mjtNum);
886+
mjtNum* jacdif = mjSTACKALLOC(d, 3*nv, mjtNum);
887+
mjtNum* tmp = mjSTACKALLOC(d, nv, mjtNum);
888+
890889
// process spatial tendon
891890
mjtNum divisor = 1;
892891
int wraptype, j = 0;
@@ -1470,6 +1469,63 @@ void mj_transmission(const mjModel* m, mjData* d) {
14701469

14711470
//-------------------------- inertia ---------------------------------------------------------------
14721471

1472+
// add tendon armature to qM
1473+
void mj_tendonArmature(const mjModel* m, mjData* d) {
1474+
TM_START;
1475+
int nv = m->nv, ntendon = m->ntendon, issparse = mj_isSparse(m);
1476+
1477+
for (int k=0; k < ntendon; k++) {
1478+
mjtNum armature = m->tendon_armature[k];
1479+
1480+
if (!armature) {
1481+
continue;
1482+
}
1483+
1484+
// dense
1485+
if (!issparse) {
1486+
mjtNum* ten_J = d->ten_J + nv*k;
1487+
for (int i=0; i < m->nv; i++) {
1488+
int Madr = m->dof_Madr[i];
1489+
for (int j = i; j >= 0; j = m->dof_parentid[j]) {
1490+
d->qM[Madr++] += armature * ten_J[j] * ten_J[i];
1491+
}
1492+
}
1493+
}
1494+
1495+
// sparse
1496+
else {
1497+
// get sparse info for tendon k
1498+
int rowadr = d->ten_J_rowadr[k];
1499+
int rownnz = d->ten_J_rownnz[k];
1500+
const int* colind = d->ten_J_colind + rowadr;
1501+
mjtNum* ten_J = d->ten_J + rowadr;
1502+
1503+
// iterate forward on nonzero rows i
1504+
for (int adr_i=0; adr_i < rownnz; adr_i++) {
1505+
int i = colind[adr_i];
1506+
int Madr = m->dof_Madr[i];
1507+
int adr_j = rownnz - 1;
1508+
1509+
// iterate backward on ancestors of i, find matching column j
1510+
for (int j = i; j >= 0; j = m->dof_parentid[j]) {
1511+
// reduce adr_j until column index is no bigger than j
1512+
while (colind[adr_j] > j && adr_j >= 0) {
1513+
adr_j--;
1514+
}
1515+
1516+
// found match, update qM
1517+
if (colind[adr_j] == j) {
1518+
d->qM[Madr++] += armature * ten_J[adr_j] * ten_J[adr_i];
1519+
}
1520+
}
1521+
}
1522+
}
1523+
}
1524+
TM_END(mjTIMER_POS_INERTIA);
1525+
}
1526+
1527+
1528+
14731529
// composite rigid body inertia algorithm
14741530
void mj_crb(const mjModel* m, mjData* d) {
14751531
TM_START;
@@ -2321,3 +2377,53 @@ void mj_rnePostConstraint(const mjModel* m, mjData* d) {
23212377
mju_addTo(d->cfrc_int+6*m->body_parentid[j], d->cfrc_int+6*j, 6);
23222378
}
23232379
}
2380+
2381+
2382+
2383+
// add bias force due to tendon armature
2384+
void mj_tendonBias(const mjModel* m, mjData* d, mjtNum* qfrc) {
2385+
int ntendon = m->ntendon, nv = m->nv, issparse = mj_isSparse(m);
2386+
mjtNum* ten_Jdot = NULL;
2387+
mj_markStack(d);
2388+
2389+
// add bias term due to tendon armature
2390+
for (int i=0; i < ntendon; i++) {
2391+
mjtNum armature = m->tendon_armature[i];
2392+
2393+
// no armature: skip
2394+
if (!armature) {
2395+
continue;
2396+
}
2397+
2398+
// allocate if required
2399+
if (!ten_Jdot) {
2400+
ten_Jdot = mjSTACKALLOC(d, nv, mjtNum);
2401+
}
2402+
2403+
// get dense d/dt(tendon Jacobian) for tendon i
2404+
mj_tendonDot(m, d, i, ten_Jdot);
2405+
2406+
// add bias term: qfrc += ten_J * armature * dot(ten_Jdot, qvel)
2407+
mjtNum coef = armature * mju_dot(ten_Jdot, d->qvel, nv);
2408+
2409+
if (coef) {
2410+
// dense
2411+
if (!issparse) {
2412+
mju_addToScl(qfrc, d->ten_J + nv*i, coef, nv);
2413+
}
2414+
2415+
// sparse
2416+
else {
2417+
int nnz = d->ten_J_rownnz[i];
2418+
int adr = d->ten_J_rowadr[i];
2419+
const int* colind = d->ten_J_colind + adr;
2420+
const mjtNum* ten_J = d->ten_J + adr;
2421+
for (int j=0; j < nnz; j++) {
2422+
qfrc[colind[j]] += coef * ten_J[j];
2423+
}
2424+
}
2425+
}
2426+
}
2427+
2428+
mj_freeStack(d);
2429+
}

src/engine/engine_core_smooth.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ MJAPI void mj_transmission(const mjModel* m, mjData* d);
5151
// composite rigid body inertia algorithm
5252
MJAPI void mj_crb(const mjModel* m, mjData* d);
5353

54+
// add tendon armature to qM
55+
MJAPI void mj_tendonArmature(const mjModel* m, mjData* d);
56+
5457
// sparse L'*D*L factorizaton of inertia-like matrix M, assumed spd (legacy implementation)
5558
MJAPI void mj_factorI_legacy(const mjModel* m, mjData* d, const mjtNum* M,
5659
mjtNum* qLD, mjtNum* qLDiagInv);
@@ -99,6 +102,12 @@ MJAPI void mj_rne(const mjModel* m, mjData* d, int flg_acc, mjtNum* result);
99102
// RNE with complete data: compute cacc, cfrc_ext, cfrc_int
100103
MJAPI void mj_rnePostConstraint(const mjModel* m, mjData* d);
101104

105+
106+
//-------------------------- tendon bias -----------------------------------------------------------
107+
108+
// add bias force due to tendon armature
109+
MJAPI void mj_tendonBias(const mjModel* m, mjData* d, mjtNum* qfrc);
110+
102111
#ifdef __cplusplus
103112
}
104113
#endif

src/engine/engine_forward.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,9 @@ typedef struct mjFwdPositionArgs_ mjFwdPositionArgs;
114114
// wrapper for mj_crb and mj_factorM
115115
void* mj_inertialThreaded(void* args) {
116116
mjFwdPositionArgs* forward_args = (mjFwdPositionArgs*) args;
117-
mj_crb(forward_args->m, forward_args->d); // timed internally (POS_INERTIA)
118-
mj_factorM(forward_args->m, forward_args->d); // timed internally (POS_INERTIA)
117+
mj_crb(forward_args->m, forward_args->d); // timed internally (POS_INERTIA)
118+
mj_tendonArmature(forward_args->m, forward_args->d); // timed internally (POS_INERTIA)
119+
mj_factorM(forward_args->m, forward_args->d); // timed internally (POS_INERTIA)
119120
return NULL;
120121
}
121122

@@ -142,9 +143,10 @@ void mj_fwdPosition(const mjModel* m, mjData* d) {
142143

143144
// no threadpool: inertia and collision on main thread
144145
if (!d->threadpool) {
145-
mj_crb(m, d); // timed internally (POS_INERTIA)
146-
mj_factorM(m, d); // timed internally (POS_INERTIA)
147-
mj_collision(m, d); // timed internally (POS_COLLISION)
146+
mj_crb(m, d); // timed internally (POS_INERTIA)
147+
mj_tendonArmature(m, d); // timed internally (POS_INERTIA)
148+
mj_factorM(m, d); // timed internally (POS_INERTIA)
149+
mj_collision(m, d); // timed internally (POS_COLLISION)
148150
}
149151

150152
// have threadpool: inertia and collision on separate threads
@@ -222,6 +224,9 @@ void mj_fwdVelocity(const mjModel* m, mjData* d) {
222224
// compute qfrc_bias with abbreviated RNE (without acceleration)
223225
mj_rne(m, d, 0, d->qfrc_bias);
224226

227+
// add bias force due to tendon armature
228+
mj_tendonBias(m, d, d->qfrc_bias);
229+
225230
TM_END(mjTIMER_VELOCITY);
226231
}
227232

src/engine/engine_inverse.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ void mj_invPosition(const mjModel* m, mjData* d) {
4545
mj_tendon(m, d);
4646
TM_END(mjTIMER_POS_KINEMATICS);
4747

48-
mj_crb(m, d); // timed internally (POS_INERTIA)
49-
mj_factorM(m, d); // timed internally (POS_INERTIA)
48+
mj_crb(m, d); // timed internally (POS_INERTIA)
49+
mj_tendonArmature(m, d); // timed internally (POS_INERTIA)
50+
mj_factorM(m, d); // timed internally (POS_INERTIA)
5051

5152
mj_collision(m, d); // timed internally (POS_COLLISION)
5253

src/engine/engine_setconst.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,11 @@ static void set0(mjModel* m, mjData* d) {
102102
memset(m->flex_rigid, 0, m->nflex);
103103

104104
// run remaining computations
105+
mj_tendon(m, d);
105106
mj_crb(m, d);
107+
mj_tendonArmature(m, d);
106108
mj_factorM(m, d);
107109
mj_flex(m, d);
108-
mj_tendon(m, d);
109110
mj_transmission(m, d);
110111

111112
// restore flex rigidity

src/user/user_model.cc

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2769,31 +2769,6 @@ void mjCModel::CopyTree(mjModel* m) {
27692769
}
27702770
}
27712771
m->nB = nB;
2772-
2773-
// set dof_simplenum
2774-
int count = 0;
2775-
for (int i=nv-1; i >= 0; i--) {
2776-
if (m->body_simple[m->dof_bodyid[i]]) {
2777-
count++; // increment counter
2778-
} else {
2779-
count = 0; // reset
2780-
}
2781-
m->dof_simplenum[i] = count;
2782-
}
2783-
2784-
// compute nC
2785-
int nOD = 0; // number of off-diagonal (non-simple) parent dofs
2786-
for (int i=0; i < nv; i++) {
2787-
// count ancestor (off-diagonal) dofs
2788-
if (!m->dof_simplenum[i]) {
2789-
int j = i;
2790-
while (j >= 0) {
2791-
if (j != i) nOD++;
2792-
j = m->dof_parentid[j];
2793-
}
2794-
}
2795-
}
2796-
m->nC = nC = nOD + nv;
27972772
}
27982773

27992774
// copy plugin data
@@ -3564,6 +3539,54 @@ void mjCModel::CopyObjects(mjModel* m) {
35643539

35653540

35663541

3542+
// finalize simple bodies/dofs including tendon information
3543+
void mjCModel::FinalizeSimple(mjModel* m) {
3544+
// demote bodies affected by inertia-bearing tendon to non-simple
3545+
for (int i=0; i < ntendon; i++) {
3546+
if (m->tendon_armature[i] == 0) {
3547+
continue;
3548+
}
3549+
int adr = m->tendon_adr[i];
3550+
int num = m->tendon_num[i];
3551+
for (int j=adr; j < adr+num; j++) {
3552+
int objid = m->wrap_objid[j];
3553+
if (m->wrap_type[j] == mjWRAP_SITE) {
3554+
m->body_simple[m->site_bodyid[objid]] = 0;
3555+
}
3556+
if (m->wrap_type[j] == mjWRAP_CYLINDER || m->wrap_type[j] == mjWRAP_SPHERE) {
3557+
m->body_simple[m->geom_bodyid[objid]] = 0;
3558+
}
3559+
}
3560+
}
3561+
3562+
// set dof_simplenum
3563+
int count = 0;
3564+
for (int i=nv-1; i >= 0; i--) {
3565+
if (m->body_simple[m->dof_bodyid[i]]) {
3566+
count++; // increment counter
3567+
} else {
3568+
count = 0; // reset
3569+
}
3570+
m->dof_simplenum[i] = count;
3571+
}
3572+
3573+
// compute nC
3574+
int nOD = 0; // number of off-diagonal (non-simple) parent dofs
3575+
for (int i=0; i < nv; i++) {
3576+
// count ancestor (off-diagonal) dofs
3577+
if (!m->dof_simplenum[i]) {
3578+
int j = i;
3579+
while (j >= 0) {
3580+
if (j != i) nOD++;
3581+
j = m->dof_parentid[j];
3582+
}
3583+
}
3584+
}
3585+
m->nC = nC = nOD + nv;
3586+
}
3587+
3588+
3589+
35673590
// save the current state
35683591
template <class T>
35693592
void mjCModel::SaveState(const std::string& state_name, const T* qpos, const T* qvel, const T* act,
@@ -4509,6 +4532,9 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
45094532
// copy objects outsite kinematic tree (including keyframes)
45104533
CopyObjects(m);
45114534

4535+
// finalize simple bodies/dofs including tendon information
4536+
FinalizeSimple(m);
4537+
45124538
// compute non-zeros in actuator_moment
45134539
m->nJmom = nJmom = CountNJmom(m);
45144540

0 commit comments

Comments
 (0)