Add tendon armature

PiperOrigin-RevId: 743939992
Change-Id: I587214f5d6fabbc0cc273c33d82decbe9ad8f919
This commit is contained in:
Yuval Tassa
2025-04-04 07:42:40 -07:00
committed by Copybara-Service
parent e1f5ceb65a
commit d05251af2a
22 changed files with 735 additions and 44 deletions
+20 -2
View File
@@ -4706,12 +4706,30 @@ length X, as in the clip on the right of `this example model
joint damping which is integrated implicitly by the Euler method, tendon damping is not integrated implicitly, thus joint damping which is integrated implicitly by the Euler method, tendon damping is not integrated implicitly, thus
joint damping should be used if possible. joint damping should be used if possible.
.. TODO(tassa): Update here once the feature is implemented. .. image:: images/XMLreference/tendon_armature.gif
:width: 30%
:align: right
:class: only-light
:target: https://github.com/google-deepmind/mujoco/blob/main/test/engine/testdata/core_smooth/ten_armature_1_compare.xml
.. image:: images/XMLreference/tendon_armature_dark.gif
:width: 30%
:align: right
:class: only-dark
:target: https://github.com/google-deepmind/mujoco/blob/main/test/engine/testdata/core_smooth/ten_armature_1_compare.xml
.. _tendon-spatial-armature: .. _tendon-spatial-armature:
:at:`armature`: :at-val:`real, "0"` :at:`armature`: :at-val:`real, "0"`
Inertia associated with tendon. This feature is not yet implemented. Inertia associated with changes in tendon length. Setting this attribute to a positive value :math:`m` adds a kinetic
energy term :math:`\frac{1}{2}mv^2`, where :math:`v` is the tendon velocity. Tendon inertia is most valuable
when modeling the :ref:`armature<body-joint-armature>` inertia in a linear actuator which contains a spinning element
or the inertial motion of a fluid in a linear hydraulic actuator. In the illustration, we compare (*left*) a 3-dof
system with a "tendon" implemented with a rotational joint and a slider joint with
:ref:`armature<body-joint-armature>`, attached to the world with a :ref:`connect<equality-connect>` constraint and
(*right*) an equivalent 1-dof model with an armature-bearing tendon. Like joint :ref:`armature<body-joint-armature>`,
this added inertia is only associated with changes in tendon length, and would not affect the dynamics of a moving
fixed-length tendon. Because the tendon Jacobian :math:`J` is position-dependent, tendon armature leads to an
additional bias-force term :math:`c = m J \dot{J}^T \dot{q}`.
.. _tendon-spatial-user: .. _tendon-spatial-user:
+1
View File
@@ -15,6 +15,7 @@ Upcoming version (not yet released)
General General
^^^^^^^ ^^^^^^^
- Added :ref:`tendon armature<tendon-spatial-armature>`: inertia associated with changes in tendon length.
- Added the :ref:`compiler/saveinertial<compiler-saveinertial>` flag, writing explicit inertial clauses for all - Added the :ref:`compiler/saveinertial<compiler-saveinertial>` flag, writing explicit inertial clauses for all
bodies when saving to XML. bodies when saving to XML.
- Added :ref:`orientation<body-composite-quat>` attribute to :ref:`composite<body-composite>`. Moreover, allow the - Added :ref:`orientation<body-composite-quat>` attribute to :ref:`composite<body-composite>`. Moreover, allow the
Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

+115 -9
View File
@@ -865,15 +865,7 @@ void mj_tendon(const mjModel* m, mjData* d) {
void mj_tendonDot(const mjModel* m, mjData* d, int id, mjtNum* Jdot) { void mj_tendonDot(const mjModel* m, mjData* d, int id, mjtNum* Jdot) {
int nv = m->nv; int nv = m->nv;
// allocate stack arrays // tendon id is invalid: return
mjtNum *jac1, *jac2, *jacdif, *tmp;
mj_markStack(d);
jac1 = mjSTACKALLOC(d, 3*nv, mjtNum);
jac2 = mjSTACKALLOC(d, 3*nv, mjtNum);
jacdif = mjSTACKALLOC(d, 3*nv, mjtNum);
tmp = mjSTACKALLOC(d, nv, mjtNum);
// return if tendon id is invalid
if (id < 0 || id >= m->ntendon) { if (id < 0 || id >= m->ntendon) {
return; return;
} }
@@ -887,6 +879,13 @@ void mj_tendonDot(const mjModel* m, mjData* d, int id, mjtNum* Jdot) {
return; return;
} }
// allocate stack arrays
mj_markStack(d);
mjtNum* jac1 = mjSTACKALLOC(d, 3*nv, mjtNum);
mjtNum* jac2 = mjSTACKALLOC(d, 3*nv, mjtNum);
mjtNum* jacdif = mjSTACKALLOC(d, 3*nv, mjtNum);
mjtNum* tmp = mjSTACKALLOC(d, nv, mjtNum);
// process spatial tendon // process spatial tendon
mjtNum divisor = 1; mjtNum divisor = 1;
int wraptype, j = 0; int wraptype, j = 0;
@@ -1470,6 +1469,63 @@ void mj_transmission(const mjModel* m, mjData* d) {
//-------------------------- inertia --------------------------------------------------------------- //-------------------------- inertia ---------------------------------------------------------------
// add tendon armature to qM
void mj_tendonArmature(const mjModel* m, mjData* d) {
TM_START;
int nv = m->nv, ntendon = m->ntendon, issparse = mj_isSparse(m);
for (int k=0; k < ntendon; k++) {
mjtNum armature = m->tendon_armature[k];
if (!armature) {
continue;
}
// dense
if (!issparse) {
mjtNum* ten_J = d->ten_J + nv*k;
for (int i=0; i < m->nv; i++) {
int Madr = m->dof_Madr[i];
for (int j = i; j >= 0; j = m->dof_parentid[j]) {
d->qM[Madr++] += armature * ten_J[j] * ten_J[i];
}
}
}
// sparse
else {
// get sparse info for tendon k
int rowadr = d->ten_J_rowadr[k];
int rownnz = d->ten_J_rownnz[k];
const int* colind = d->ten_J_colind + rowadr;
mjtNum* ten_J = d->ten_J + rowadr;
// iterate forward on nonzero rows i
for (int adr_i=0; adr_i < rownnz; adr_i++) {
int i = colind[adr_i];
int Madr = m->dof_Madr[i];
int adr_j = rownnz - 1;
// iterate backward on ancestors of i, find matching column j
for (int j = i; j >= 0; j = m->dof_parentid[j]) {
// reduce adr_j until column index is no bigger than j
while (colind[adr_j] > j && adr_j >= 0) {
adr_j--;
}
// found match, update qM
if (colind[adr_j] == j) {
d->qM[Madr++] += armature * ten_J[adr_j] * ten_J[adr_i];
}
}
}
}
}
TM_END(mjTIMER_POS_INERTIA);
}
// composite rigid body inertia algorithm // composite rigid body inertia algorithm
void mj_crb(const mjModel* m, mjData* d) { void mj_crb(const mjModel* m, mjData* d) {
TM_START; TM_START;
@@ -2321,3 +2377,53 @@ void mj_rnePostConstraint(const mjModel* m, mjData* d) {
mju_addTo(d->cfrc_int+6*m->body_parentid[j], d->cfrc_int+6*j, 6); mju_addTo(d->cfrc_int+6*m->body_parentid[j], d->cfrc_int+6*j, 6);
} }
} }
// add bias force due to tendon armature
void mj_tendonBias(const mjModel* m, mjData* d, mjtNum* qfrc) {
int ntendon = m->ntendon, nv = m->nv, issparse = mj_isSparse(m);
mjtNum* ten_Jdot = NULL;
mj_markStack(d);
// add bias term due to tendon armature
for (int i=0; i < ntendon; i++) {
mjtNum armature = m->tendon_armature[i];
// no armature: skip
if (!armature) {
continue;
}
// allocate if required
if (!ten_Jdot) {
ten_Jdot = mjSTACKALLOC(d, nv, mjtNum);
}
// get dense d/dt(tendon Jacobian) for tendon i
mj_tendonDot(m, d, i, ten_Jdot);
// add bias term: qfrc += ten_J * armature * dot(ten_Jdot, qvel)
mjtNum coef = armature * mju_dot(ten_Jdot, d->qvel, nv);
if (coef) {
// dense
if (!issparse) {
mju_addToScl(qfrc, d->ten_J + nv*i, coef, nv);
}
// sparse
else {
int nnz = d->ten_J_rownnz[i];
int adr = d->ten_J_rowadr[i];
const int* colind = d->ten_J_colind + adr;
const mjtNum* ten_J = d->ten_J + adr;
for (int j=0; j < nnz; j++) {
qfrc[colind[j]] += coef * ten_J[j];
}
}
}
}
mj_freeStack(d);
}
+9
View File
@@ -51,6 +51,9 @@ MJAPI void mj_transmission(const mjModel* m, mjData* d);
// composite rigid body inertia algorithm // composite rigid body inertia algorithm
MJAPI void mj_crb(const mjModel* m, mjData* d); MJAPI void mj_crb(const mjModel* m, mjData* d);
// add tendon armature to qM
MJAPI void mj_tendonArmature(const mjModel* m, mjData* d);
// sparse L'*D*L factorizaton of inertia-like matrix M, assumed spd (legacy implementation) // sparse L'*D*L factorizaton of inertia-like matrix M, assumed spd (legacy implementation)
MJAPI void mj_factorI_legacy(const mjModel* m, mjData* d, const mjtNum* M, MJAPI void mj_factorI_legacy(const mjModel* m, mjData* d, const mjtNum* M,
mjtNum* qLD, mjtNum* qLDiagInv); mjtNum* qLD, mjtNum* qLDiagInv);
@@ -99,6 +102,12 @@ MJAPI void mj_rne(const mjModel* m, mjData* d, int flg_acc, mjtNum* result);
// RNE with complete data: compute cacc, cfrc_ext, cfrc_int // RNE with complete data: compute cacc, cfrc_ext, cfrc_int
MJAPI void mj_rnePostConstraint(const mjModel* m, mjData* d); MJAPI void mj_rnePostConstraint(const mjModel* m, mjData* d);
//-------------------------- tendon bias -----------------------------------------------------------
// add bias force due to tendon armature
MJAPI void mj_tendonBias(const mjModel* m, mjData* d, mjtNum* qfrc);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
+10 -5
View File
@@ -114,8 +114,9 @@ typedef struct mjFwdPositionArgs_ mjFwdPositionArgs;
// wrapper for mj_crb and mj_factorM // wrapper for mj_crb and mj_factorM
void* mj_inertialThreaded(void* args) { void* mj_inertialThreaded(void* args) {
mjFwdPositionArgs* forward_args = (mjFwdPositionArgs*) args; mjFwdPositionArgs* forward_args = (mjFwdPositionArgs*) args;
mj_crb(forward_args->m, forward_args->d); // timed internally (POS_INERTIA) mj_crb(forward_args->m, forward_args->d); // timed internally (POS_INERTIA)
mj_factorM(forward_args->m, forward_args->d); // timed internally (POS_INERTIA) mj_tendonArmature(forward_args->m, forward_args->d); // timed internally (POS_INERTIA)
mj_factorM(forward_args->m, forward_args->d); // timed internally (POS_INERTIA)
return NULL; return NULL;
} }
@@ -142,9 +143,10 @@ void mj_fwdPosition(const mjModel* m, mjData* d) {
// no threadpool: inertia and collision on main thread // no threadpool: inertia and collision on main thread
if (!d->threadpool) { if (!d->threadpool) {
mj_crb(m, d); // timed internally (POS_INERTIA) mj_crb(m, d); // timed internally (POS_INERTIA)
mj_factorM(m, d); // timed internally (POS_INERTIA) mj_tendonArmature(m, d); // timed internally (POS_INERTIA)
mj_collision(m, d); // timed internally (POS_COLLISION) mj_factorM(m, d); // timed internally (POS_INERTIA)
mj_collision(m, d); // timed internally (POS_COLLISION)
} }
// have threadpool: inertia and collision on separate threads // have threadpool: inertia and collision on separate threads
@@ -222,6 +224,9 @@ void mj_fwdVelocity(const mjModel* m, mjData* d) {
// compute qfrc_bias with abbreviated RNE (without acceleration) // compute qfrc_bias with abbreviated RNE (without acceleration)
mj_rne(m, d, 0, d->qfrc_bias); mj_rne(m, d, 0, d->qfrc_bias);
// add bias force due to tendon armature
mj_tendonBias(m, d, d->qfrc_bias);
TM_END(mjTIMER_VELOCITY); TM_END(mjTIMER_VELOCITY);
} }
+3 -2
View File
@@ -45,8 +45,9 @@ void mj_invPosition(const mjModel* m, mjData* d) {
mj_tendon(m, d); mj_tendon(m, d);
TM_END(mjTIMER_POS_KINEMATICS); TM_END(mjTIMER_POS_KINEMATICS);
mj_crb(m, d); // timed internally (POS_INERTIA) mj_crb(m, d); // timed internally (POS_INERTIA)
mj_factorM(m, d); // timed internally (POS_INERTIA) mj_tendonArmature(m, d); // timed internally (POS_INERTIA)
mj_factorM(m, d); // timed internally (POS_INERTIA)
mj_collision(m, d); // timed internally (POS_COLLISION) mj_collision(m, d); // timed internally (POS_COLLISION)
+2 -1
View File
@@ -102,10 +102,11 @@ static void set0(mjModel* m, mjData* d) {
memset(m->flex_rigid, 0, m->nflex); memset(m->flex_rigid, 0, m->nflex);
// run remaining computations // run remaining computations
mj_tendon(m, d);
mj_crb(m, d); mj_crb(m, d);
mj_tendonArmature(m, d);
mj_factorM(m, d); mj_factorM(m, d);
mj_flex(m, d); mj_flex(m, d);
mj_tendon(m, d);
mj_transmission(m, d); mj_transmission(m, d);
// restore flex rigidity // restore flex rigidity
+51 -25
View File
@@ -2769,31 +2769,6 @@ void mjCModel::CopyTree(mjModel* m) {
} }
} }
m->nB = nB; m->nB = nB;
// set dof_simplenum
int count = 0;
for (int i=nv-1; i >= 0; i--) {
if (m->body_simple[m->dof_bodyid[i]]) {
count++; // increment counter
} else {
count = 0; // reset
}
m->dof_simplenum[i] = count;
}
// compute nC
int nOD = 0; // number of off-diagonal (non-simple) parent dofs
for (int i=0; i < nv; i++) {
// count ancestor (off-diagonal) dofs
if (!m->dof_simplenum[i]) {
int j = i;
while (j >= 0) {
if (j != i) nOD++;
j = m->dof_parentid[j];
}
}
}
m->nC = nC = nOD + nv;
} }
// copy plugin data // copy plugin data
@@ -3564,6 +3539,54 @@ void mjCModel::CopyObjects(mjModel* m) {
// finalize simple bodies/dofs including tendon information
void mjCModel::FinalizeSimple(mjModel* m) {
// demote bodies affected by inertia-bearing tendon to non-simple
for (int i=0; i < ntendon; i++) {
if (m->tendon_armature[i] == 0) {
continue;
}
int adr = m->tendon_adr[i];
int num = m->tendon_num[i];
for (int j=adr; j < adr+num; j++) {
int objid = m->wrap_objid[j];
if (m->wrap_type[j] == mjWRAP_SITE) {
m->body_simple[m->site_bodyid[objid]] = 0;
}
if (m->wrap_type[j] == mjWRAP_CYLINDER || m->wrap_type[j] == mjWRAP_SPHERE) {
m->body_simple[m->geom_bodyid[objid]] = 0;
}
}
}
// set dof_simplenum
int count = 0;
for (int i=nv-1; i >= 0; i--) {
if (m->body_simple[m->dof_bodyid[i]]) {
count++; // increment counter
} else {
count = 0; // reset
}
m->dof_simplenum[i] = count;
}
// compute nC
int nOD = 0; // number of off-diagonal (non-simple) parent dofs
for (int i=0; i < nv; i++) {
// count ancestor (off-diagonal) dofs
if (!m->dof_simplenum[i]) {
int j = i;
while (j >= 0) {
if (j != i) nOD++;
j = m->dof_parentid[j];
}
}
}
m->nC = nC = nOD + nv;
}
// save the current state // save the current state
template <class T> template <class T>
void mjCModel::SaveState(const std::string& state_name, const T* qpos, const T* qvel, const T* act, 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) {
// copy objects outsite kinematic tree (including keyframes) // copy objects outsite kinematic tree (including keyframes)
CopyObjects(m); CopyObjects(m);
// finalize simple bodies/dofs including tendon information
FinalizeSimple(m);
// compute non-zeros in actuator_moment // compute non-zeros in actuator_moment
m->nJmom = nJmom = CountNJmom(m); m->nJmom = nJmom = CountNJmom(m);
+1
View File
@@ -349,6 +349,7 @@ class mjCModel : public mjCModel_, private mjSpec {
void CopyPaths(mjModel*); // copy paths, compute path addresses void CopyPaths(mjModel*); // copy paths, compute path addresses
void CopyObjects(mjModel*); // copy objects outside kinematic tree void CopyObjects(mjModel*); // copy objects outside kinematic tree
void CopyTree(mjModel*); // copy objects inside kinematic tree void CopyTree(mjModel*); // copy objects inside kinematic tree
void FinalizeSimple(mjModel* m); // finalize simple bodies/dofs including tendon information
void CopyPlugins(mjModel*); // copy plugin data void CopyPlugins(mjModel*); // copy plugin data
int CountNJmom(const mjModel* m); // compute number of non-zeros in actuator_moment matrix int CountNJmom(const mjModel* m); // compute number of non-zeros in actuator_moment matrix
+178
View File
@@ -17,6 +17,7 @@
#include "src/engine/engine_core_smooth.h" #include "src/engine/engine_core_smooth.h"
#include "src/engine/engine_util_sparse.h" #include "src/engine/engine_util_sparse.h"
#include <algorithm>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@@ -213,6 +214,183 @@ TEST_F(CoreSmoothTest, TendonJdot) {
} }
} }
static const char* const kTen_offtree =
"engine/testdata/core_smooth/ten_armature_offtree.xml";
TEST_F(CoreSmoothTest, TendonArmature) {
const std::string xml_path = GetTestDataFilePath(kTen_offtree);
char error[1024];
mjModel* m = mj_loadXML(xml_path.c_str(), nullptr, error, sizeof(error));
ASSERT_THAT(m, NotNull()) << "Failed to load model: " << error;
int nv = m->nv;
mjData* d = mj_makeData(m);
for (mjtJacobian sparsity : {mjJAC_DENSE, mjJAC_SPARSE}) {
m->opt.jacobian = sparsity;
mj_forward(m, d);
// get full M, includes both CRB and tendon inertia
vector<mjtNum> M(nv*nv);
mj_fullM(m, M.data(), d->qM);
// put only CRB inertia in M2
mj_crb(m, d);
vector<mjtNum> M2(nv*nv);
mj_fullM(m, M2.data(), d->qM);
vector<mjtNum> ten_J(nv); // tendon Jacobian
vector<mjtNum> ten_M(nv*nv); // tendon inertia
// add tendon inertias to M2 using outer product
for (int j=0; j < m->ntendon; j++) {
// get tendon Jacobian
if (mj_isSparse(m)) {
int rowadr = d->ten_J_rowadr[j];
int* rownnz = d->ten_J_rownnz + j;
int zero = 0;
mju_sparse2dense(ten_J.data(), d->ten_J + rowadr, 1, nv,
rownnz, &zero, d->ten_J_colind + rowadr);
} else {
mju_copy(ten_J.data(), d->ten_J + j*nv, nv);
}
// get tendon inertia only, using outer product
mju_mulMatMat(ten_M.data(), ten_J.data(), ten_J.data(), nv, 1, nv);
mju_scl(ten_M.data(), ten_M.data(), m->tendon_armature[j], nv * nv);
// manually add values, at nonzeros only
for (int i=0; i < nv*nv; i++) {
if (M[i]) M2[i] += ten_M[i];
}
}
// expect matrices to match
EXPECT_THAT(M2, Pointwise(DoubleNear(1e-9), M));
}
mj_deleteData(d);
mj_deleteModel(m);
}
static const char* const kTen_i0 =
"engine/testdata/core_smooth/ten_armature_0.xml";
static const char* const kTen_i1 =
"engine/testdata/core_smooth/ten_armature_1.xml";
static const char* const kTen_i2 =
"engine/testdata/core_smooth/ten_armature_2.xml";
static const char* const kTen_i3 =
"engine/testdata/core_smooth/ten_armature_3.xml";
static const char* const kTen_i4 =
"engine/testdata/core_smooth/ten_armature_4.xml";
TEST_F(CoreSmoothTest, TendonArmatureConservesEnergy) {
for (const char* local_path : {kTen_i0, kTen_i1, kTen_i2, kTen_i3, kTen_i4}) {
const std::string xml_path = GetTestDataFilePath(local_path);
char error[1024];
mjModel* m = mj_loadXML(xml_path.c_str(), nullptr, error, sizeof(error));
ASSERT_THAT(m, NotNull()) << "Failed to load model: " << error;
mjData* d = mj_makeData(m);
for (mjtJacobian sparsity : {mjJAC_DENSE, mjJAC_SPARSE}) {
m->opt.jacobian = sparsity;
mj_resetDataKeyframe(m, d, 0);
mj_forward(m, d);
double energy_0 = d->energy[0] + d->energy[1];
double eps = std::max(energy_0, 1.0) * 1e-5;
while (d->time < 1) {
mj_step(m, d);
double energy_t = d->energy[0] + d->energy[1];
EXPECT_THAT(energy_t, DoubleNear(energy_0, eps));
}
}
mj_deleteData(d);
mj_deleteModel(m);
}
}
TEST_F(CoreSmoothTest, TendonArmatureConservesMomentum) {
const std::string xml_path = GetTestDataFilePath(kTen_i4);
char error[1024];
mjModel* m = mj_loadXML(xml_path.c_str(), nullptr, error, sizeof(error));
ASSERT_THAT(m, NotNull()) << "Failed to load model: " << error;
mjData* d = mj_makeData(m);
for (mjtJacobian sparsity : {mjJAC_DENSE, mjJAC_SPARSE}) {
m->opt.jacobian = sparsity;
mj_resetData(m, d);
mj_forward(m, d);
// this model contains subtreelinvel and subtreeangmom sensors
vector<mjtNum> sdata_0 = AsVector(d->sensordata, m->nsensordata);
EXPECT_THAT(sdata_0, Each(Eq(0)));
double eps = 1e-5;
while (d->time < 1) {
mj_step(m, d);
vector<mjtNum> sdata_t = AsVector(d->sensordata, m->nsensordata);
EXPECT_THAT(sdata_t, Pointwise(DoubleNear(eps), sdata_0));
}
// momentum is conserved nontrivially (velocities are non-zero)
EXPECT_GT(d->energy[1], 0);
}
mj_deleteData(d);
mj_deleteModel(m);
}
static const char* const kTen_i0_equiv =
"engine/testdata/core_smooth/ten_armature_0_equiv.xml";
static const char* const kTen_i1_equiv =
"engine/testdata/core_smooth/ten_armature_1_equiv.xml";
TEST_F(CoreSmoothTest, TendonInertiaEquivalent) {
for (const char* lpath : {kTen_i0, kTen_i1}) {
// load tendon model
const std::string path = GetTestDataFilePath(lpath);
char error[1024];
mjModel* m = mj_loadXML(path.c_str(), nullptr, error, sizeof(error));
ASSERT_THAT(m, NotNull()) << "Failed to load model: " << error;
int gid = mj_name2id(m, mjOBJ_GEOM, "query");
mjData* d = mj_makeData(m);
if (m->nkey) mj_resetDataKeyframe(m, d, 0);
// load equivalent model
const char* lpath_e = lpath == kTen_i0 ? kTen_i0_equiv : kTen_i1_equiv;
const std::string path_e = GetTestDataFilePath(lpath_e);
mjModel* m_e = mj_loadXML(path_e.c_str(), nullptr, error, sizeof(error));
ASSERT_THAT(m, NotNull()) << "Failed to load model: " << error;
int gid_e = mj_name2id(m_e, mjOBJ_GEOM, "query");
mjData* d_e = mj_makeData(m_e);
if (m_e->nkey) mj_resetDataKeyframe(m_e, d_e, 0);
// the equality constraint in kTen_i1_equiv reduces precision
double eps = lpath == kTen_i0 ? 1e-6 : 1e-3;
while (d->time < 1) {
mj_step(m, d);
vector<mjtNum> xpos = AsVector(d->geom_xpos + 3*gid, 3);
mj_step(m_e, d_e);
vector<mjtNum> xpos_e = AsVector(d_e->geom_xpos + 3*gid_e, 3);
EXPECT_THAT(xpos, Pointwise(DoubleNear(eps), xpos_e));
}
mj_deleteData(d);
mj_deleteModel(m);
mj_deleteData(d_e);
mj_deleteModel(m_e);
}
}
// --------------------------- connect constraint ------------------------------ // --------------------------- connect constraint ------------------------------
// test that bodies hanging on connects lead to expected force sensor readings // test that bodies hanging on connects lead to expected force sensor readings
+28
View File
@@ -0,0 +1,28 @@
<mujoco>
<option integrator="RK4">
<flag energy="enable"/>
</option>
<default>
<geom rgba="0.8 0.6 .4 1"/>
<site rgba="0.4 0.8 .6 .5"/>
</default>
<worldbody>
<site name="decoration" type="cylinder" zaxis="0 -1 0" size="1 .02" pos="0 .1 0"/>
<site name="world"/>
<body pos="1 0 0">
<joint name="x" type="slide" axis="1 0 0"/>
<joint name="z" type="slide" axis="0 0 1"/>
<geom name="query" type="sphere" size=".1" mass="1"/>
<site name="body"/>
</body>
</worldbody>
<tendon>
<spatial width=".01" rgba=".2 .2 1 1" springlength=".5" stiffness="100" armature="5">
<site site="world"/>
<site site="body"/>
</spatial>
</tendon>
</mujoco>
@@ -0,0 +1,17 @@
<mujoco>
<asset>
<model name="tendon_model" file="ten_armature_0.xml"/>
<model name="equivalent_model" file="ten_armature_0_equiv.xml"/>
</asset>
<option integrator="RK4">
<flag energy="enable"/>
</option>
<worldbody>
<attach model="tendon_model" prefix="t_"/>
<frame pos="2.2 0 0">
<attach model="equivalent_model" prefix="e_"/>
</frame>
</worldbody>
</mujoco>
@@ -0,0 +1,20 @@
<mujoco>
<option integrator="RK4">
<flag energy="enable"/>
</option>
<default>
<geom rgba="0.8 0.6 .4 1"/>
<site rgba="0.4 0.8 .6 .5"/>
</default>
<worldbody>
<site name="decoration" type="cylinder" zaxis="0 -1 0" size="1 .02" pos="0 .1 0"/>
<body>
<inertial pos="1 0 0" mass="1" diaginertia="1e-8 1e-8 1e-8"/>
<joint name="hinge" axis="0 -1 0"/>
<joint name="slide" type="slide" axis="1 0 0" springref="-.5" stiffness="100" armature="5"/>
<geom name="query" type="sphere" size=".1" pos="1 0 0"/>
</body>
</worldbody>
</mujoco>
+28
View File
@@ -0,0 +1,28 @@
<mujoco>
<option integrator="RK4" timestep="0.0001">
<flag contact="disable" gravity="disable" energy="enable"/>
</option>
<default>
<geom rgba="0.8 0.6 .4 1"/>
<site rgba="0.4 0.8 .6 .5"/>
</default>
<worldbody>
<site name="tendon" type="box" size=".03 .03 .03" pos=".5 0 -.5"/>
<body name="link1" pos="0 0 0">
<joint name="link1" axis="0 -1 0" stiffness="100" springref="90"/>
<geom type="capsule" size=".02" fromto="0 0 0 0 0 -1"/>
<geom name="query" type="sphere" size=".08" pos="0 0 -1"/>
<site name="link1" type="box" size=".03 .03 .03" pos="0 0 -.5"/>
<site name="acc1" pos="0 0 -1"/>
</body>
</worldbody>
<tendon>
<spatial width=".01" rgba=".2 .2 1 1" armature="9">
<site site="link1"/>
<site site="tendon"/>
</spatial>
</tendon>
</mujoco>
@@ -0,0 +1,26 @@
<mujoco>
<visual>
<global elevation="-20"/>
<headlight ambient=".3 .3 .3" diffuse=".8 .8 .8"/>
</visual>
<asset>
<model name="tendon_model" file="ten_armature_1.xml"/>
<model name="equivalent_model" file="ten_armature_1_equiv.xml"/>
</asset>
<visual>
<global elevation="0"/>
</visual>
<option integrator="RK4" timestep="0.0001">
<flag contact="disable" gravity="disable" energy="enable"/>
</option>
<worldbody>
<attach model="tendon_model" prefix="t_"/>
<frame pos="-1.2 0 0">
<attach model="equivalent_model" prefix="e_"/>
</frame>
</worldbody>
</mujoco>
@@ -0,0 +1,34 @@
<mujoco>
<option integrator="RK4" timestep="0.0001">
<flag contact="disable" gravity="disable" energy="enable"/>
</option>
<default>
<geom rgba="0.8 0.6 .4 1"/>
<site rgba="0.4 0.8 .6 .5"/>
</default>
<worldbody>
<site name="connect" type="box" size=".03 .03 .03" pos=".5 0 -.5"/>
<body name="link2" pos="0 0 0">
<joint name="link2" axis="0 -1 0" stiffness="100" springref="90"/>
<geom type="capsule" size=".02" fromto="0 0 0 0 0 -1"/>
<geom name="query" type="sphere" size=".08" pos="0 0 -1"/>
<body name="link3" pos="0 0 -.5" euler="0 90 0">
<joint name="link3" axis="0 -1 0"/>
<geom name="link3" type="box" size=".02 .02 .02" mass="1e-3"/>
<geom type="capsule" size=".015" fromto="0 0 0 0 0 .2" mass="0"/>
<body name="link4">
<joint name="link4" type="slide" axis="0 0 1" armature="9"/>
<geom type="capsule" size=".015" fromto="0 0 .3 0 0 .5" mass="0"/>
<geom type="sphere" size=".001" pos="0 0 .5" mass=".1"/>
<site name="link4" type="box" size=".03 .03 .03" pos="0 0 .5"/>
</body>
</body>
</body>
</worldbody>
<equality>
<connect site1="connect" site2="link4" solimp=".99 .99 .001" solref="0.0001 1"/>
</equality>
</mujoco>
+29
View File
@@ -0,0 +1,29 @@
<mujoco>
<option integrator="RK4" timestep="1e-4">
<flag contact="disable" gravity="disable" energy="enable"/>
</option>
<worldbody>
<site name="tendon" type="box" size=".03 .03 .03" pos="-.5 0 0"/>
<body name="link1" pos="-1 0 0">
<joint name="link1" axis="0 -1 0" damping="0" stiffness="50"/>
<geom name="link1" type="capsule" size=".02" fromto="0 0 0 0 0 -1"/>
<body name="link2" pos="0 0 -1">
<joint name="link2" axis="0 -1 0" damping="0" stiffness="1" springref="-180"/>
<geom name="link2" type="capsule" size=".03" fromto="0 0 0 .6 0 0"/>
<site name="link2" type="box" size=".03 .03 .03" pos=".5 0 0"/>
</body>
</body>
</worldbody>
<tendon>
<spatial width=".01" rgba=".2 .2 1 1" armature="9">
<site site="link2"/>
<site site="tendon"/>
</spatial>
</tendon>
<keyframe>
<key qpos="-1 0" qvel="1 1"/>
</keyframe>
</mujoco>
+39
View File
@@ -0,0 +1,39 @@
<mujoco>
<option integrator="RK4">
<flag contact="disable" gravity="disable" energy="enable"/>
</option>
<worldbody>
<body name="distractor">
<freejoint/>
<geom size=".1"/>
</body>
<body name="thigh" pos="-1 0 0">
<site name="0" type="box" size=".03 .03 .03" pos=".1 0 -.3"/>
<joint axis="1 0 0" stiffness="50"/>
<joint axis="0 1 0" stiffness="50"/>
<joint axis="0 0 1" stiffness="50"/>
<geom type="capsule" size=".04" fromto="0 0 0 0 0 -1"/>
<geom type="capsule" size=".02" fromto="0 0 -1 .05 -.08 -1.1"/>
<site name="1" type="box" size=".03 .03 .03" pos=".05 -.08 -1.1"/>
<body name="calf" pos="0 0 -1">
<joint axis="0 -1 0" damping="0" stiffness="20" springref="90"/>
<geom type="capsule" size=".03" fromto="0 0 0 -.6 0 0"/>
<body name="foot" pos="-.6 0 0">
<joint type="ball" stiffness="20" armature=".2"/>
<site name="2" type="box" size=".03 .03 .03" pos=".03 .1 .15"/>
<geom name="foot" type="box" size=".03 .1 .15" pos="0 0 -.05"/>
</body>
</body>
</body>
</worldbody>
<tendon>
<spatial name="ten" width=".01" rgba=".2 .2 1 1" armature="9">
<site site="0"/>
<site site="1"/>
<site site="2"/>
</spatial>
</tendon>
</mujoco>
+47
View File
@@ -0,0 +1,47 @@
<mujoco>
<option integrator="RK4">
<flag contact="disable" gravity="disable" energy="enable"/>
</option>
<default>
<geom rgba="0.8 0.6 .4 1"/>
<site rgba="0.4 0.8 .6 .5"/>
</default>
<worldbody>
<body name="distractor">
<freejoint/>
<geom size=".1"/>
</body>
<body name="thigh" pos="-1 0 0">
<site name="0" type="box" size=".03 .03 .03" pos=".1 0 -.3"/>
<freejoint/>
<geom type="capsule" size=".04" fromto="0 0 0 0 0 -1"/>
<geom type="capsule" size=".02" fromto="0 0 -1 .05 -.08 -1.1"/>
<site name="1" type="box" size=".03 .03 .03" pos=".05 -.08 -1.1"/>
<body name="calf" pos="0 0 -1">
<joint axis="0 -1 0" damping="0" stiffness="20" springref="90"/>
<geom type="capsule" size=".03" fromto="0 0 0 -.6 0 0"/>
<body name="foot" pos="-.6 0 0">
<joint type="ball" stiffness="20" armature=".2"/>
<site name="2" type="box" size=".03 .03 .03" pos=".03 .1 .15"/>
<geom name="foot" type="box" size=".03 .1 .15" pos="0 0 -.05"/>
</body>
</body>
</body>
</worldbody>
<tendon>
<spatial name="ten" width=".01" rgba=".2 .2 1 1" armature="100" stiffness="10">
<site site="0"/>
<site site="1"/>
<site site="2"/>
</spatial>
</tendon>
<sensor>
<subtreelinvel body="thigh"/>
<subtreeangmom body="thigh"/>
</sensor>
</mujoco>
@@ -0,0 +1,77 @@
<mujoco>
<!-- this is an example of a "bad" model where the tendon spans multiple branches of the tree -->
<option integrator="RK4">
<flag contact="disable" gravity="disable" energy="enable"/>
</option>
<default>
<geom rgba="0.8 0.6 .4 1"/>
<site rgba="0.4 0.8 .6 .5"/>
</default>
<worldbody>
<body name="distractor1">
<freejoint/>
<geom size=".1"/>
<site name="a"/>
</body>
<body>
<site name="0" type="box" size=".03 .03 .03" pos=".1 0 -.3"/>
<joint type="hinge" axis="0 -1 0"/>
<geom type="capsule" size=".04" fromto="0 0 0 0 0 -1"/>
<site name="1" type="box" size=".03 .03 .03" pos=".05 -.08 -1.1"/>
<body pos="0 0 -1">
<joint axis="0 -1 0" damping="0" stiffness="20" springref="90"/>
<geom type="capsule" size=".03" fromto="0 0 0 -.6 0 0"/>
<body pos="-.6 0 0">
<joint type="ball" stiffness="20" armature=".6"/>
<site name="2" type="box" size=".03 .03 .03" pos=".03 .1 .15"/>
<geom type="box" size=".03 .1 .15" pos="0 0 -.05"/>
</body>
</body>
</body>
<body name="distractor2" pos="0 0 1">
<freejoint/>
<geom size=".1"/>
<site name="b"/>
</body>
<body pos="1 0 0">
<site name="3" type="box" size=".03 .03 .03" pos=".1 0 -.3"/>
<joint type="hinge" axis="0 -1 0"/>
<geom type="capsule" size=".04" fromto="0 0 0 0 0 -1"/>
<site name="4" type="box" size=".03 .03 .03" pos=".05 -.08 -1.1"/>
<body pos="0 0 -1">
<joint axis="0 -1 0" damping="0" stiffness="20" springref="90"/>
<geom type="capsule" size=".03" fromto="0 0 0 -.6 0 0"/>
<body pos="-.6 0 0">
<joint type="ball" stiffness="20" armature=".2"/>
<site name="5" type="box" size=".03 .03 .03" pos=".03 .1 .15"/>
<geom type="box" size=".03 .1 .15" pos="0 0 -.05"/>
</body>
</body>
</body>
</worldbody>
<tendon>
<spatial armature="4">
<site site="b"/>
<site site="a"/>
</spatial>
<spatial width=".01" rgba=".2 .2 1 1" armature="10" stiffness="10">
<site site="2"/>
<site site="1"/>
<site site="0"/>
<site site="3"/>
<site site="4"/>
<site site="5"/>
</spatial>
<spatial width=".01" rgba=".2 .2 1 1" armature="10" stiffness="10">
<site site="0"/>
<site site="2"/>
<site site="1"/>
</spatial>
</tendon>
</mujoco>