// Copyright 2021 DeepMind Technologies Limited // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "engine/engine_setconst.h" #include #include #include #include #include "engine/engine_core_constraint.h" #include "engine/engine_core_smooth.h" #include "engine/engine_forward.h" #include "engine/engine_io.h" #include "engine/engine_support.h" #include "engine/engine_util_blas.h" #include "engine/engine_util_errmem.h" #include "engine/engine_util_misc.h" #include "engine/engine_util_spatial.h" // set quantities that depend on qpos0 static void set0(mjModel* m, mjData* d) { int id, id1, id2, dnum, nv = m->nv; mjtNum A[36] = {0}, pos[3], quat[4]; mjMARKSTACK; mjtNum* jac = mj_stackAlloc(d, 6*nv); mjtNum* tmp = mj_stackAlloc(d, 6*nv); int* cammode = 0; int* lightmode = 0; // save camera and light mode, set to fixed if (m->ncam) { cammode = (int*) mj_stackAlloc(d, m->ncam); for (int i=0; i < m->ncam; i++) { cammode[i] = m->cam_mode[i]; m->cam_mode[i] = mjCAMLIGHT_FIXED; } } if (m->nlight) { lightmode = (int*) mj_stackAlloc(d, m->nlight); for (int i=0; i < m->nlight; i++) { lightmode[i] = m->light_mode[i]; m->light_mode[i] = mjCAMLIGHT_FIXED; } } // run computations in qpos0 mju_copy(d->qpos, m->qpos0, m->nq); mj_kinematics(m, d); mj_comPos(m, d); mj_camlight(m, d); mj_crbSkip(m, d, 0); // save dof_M0 for (int i=0; i < nv; i++) { m->dof_M0[i] = d->qM[m->dof_Madr[i]]; } // run remaining computations (factorM needs dof_M0) mj_factorM(m, d); mj_tendon(m, d); mj_transmission(m, d); // restore camera and light mode for (int i=0; i < m->ncam; i++) { m->cam_mode[i] = cammode[i]; } for (int i=0; i < m->nlight; i++) { m->light_mode[i] = lightmode[i]; } // set tendon_length0, actuator_length0 mju_copy(m->tendon_length0, d->ten_length, m->ntendon); mju_copy(m->actuator_length0, d->actuator_length, m->nu); // compute body_invweight0 m->body_invweight0[0] = m->body_invweight0[1] = 0.0; for (int i=1; i < m->nbody; i++) { if (nv) { // inverse spatial inertia: A = J*inv(M)*J' mj_jacBodyCom(m, d, jac, jac+3*nv, i); mj_solveM(m, d, tmp, jac, 6); mju_mulMatMatT(A, jac, tmp, 6, nv, 6); } // average diagonal and assign m->body_invweight0[2*i] = (A[0] + A[7] + A[14])/3; m->body_invweight0[2*i+1] = (A[21] + A[28] + A[35])/3; } // compute dof_invweight0 for (int i=0; i < m->njnt; i++) { id = m->jnt_dofadr[i]; // get number of components if (m->jnt_type[i] == mjJNT_FREE) { dnum = 6; } else if (m->jnt_type[i] == mjJNT_BALL) { dnum = 3; } else { dnum = 1; } // inverse joint inertia: A = J*inv(M)*J' if (nv) { mju_zero(jac, dnum*nv); for (int j=0; j < dnum; j++) { jac[j*(nv+1) + id] = 1; } mj_solveM(m, d, tmp, jac, dnum); mju_mulMatMatT(A, jac, tmp, dnum, nv, dnum); } // average diagonal and assign if (dnum == 6) { m->dof_invweight0[id] = m->dof_invweight0[id+1] = m->dof_invweight0[id+2] = (A[0] + A[7] + A[14])/3; m->dof_invweight0[id+3] = m->dof_invweight0[id+4] = m->dof_invweight0[id+5] = (A[21] + A[28] + A[35])/3; } else if (dnum == 3) m->dof_invweight0[id] = m->dof_invweight0[id+1] = m->dof_invweight0[id+2] = (A[0] + A[4] + A[8])/3; else { m->dof_invweight0[id] = A[0]; } } // compute tendon_invweight0 if (nv) { for (int i=0; i < m->ntendon; i++) { // make dense vector into tmp if (mj_isSparse(m)) { mju_zero(tmp, nv); int end = d->ten_J_rowadr[i] + d->ten_J_rownnz[i]; for (int j=d->ten_J_rowadr[i]; j < end; j++) { tmp[d->ten_J_colind[j]] = d->ten_J[j]; } } else { mju_copy(tmp, d->ten_J+i*nv, nv); } // solve into tmp+nv mj_solveM(m, d, tmp+nv, tmp, 1); m->tendon_invweight0[i] = mju_dot(tmp, tmp+nv, nv); } // compute actuator_acc0 for (int i=0; i < m->nu; i++) { mj_solveM(m, d, tmp, d->actuator_moment+i*nv, 1); m->actuator_acc0[i] = mju_norm(tmp, nv); } } else { for (int i=0; i < m->ntendon; i++) { m->tendon_invweight0[i] = 0; } for (int i=0; i < m->nu; i++) { m->actuator_acc0[i] = 0; } } // compute missing eq_data for body constraints for (int i=0; i < m->neq; i++) { // get ids id1 = m->eq_obj1id[i]; id2 = m->eq_obj2id[i]; // connect constraint if (m->eq_type[i] == mjEQ_CONNECT) { // pos = anchor position in global frame mj_local2Global(d, pos, 0, m->eq_data+mjNEQDATA*i, 0, id1, 0); // data[3-5] = anchor position in body2 local frame mju_subFrom3(pos, d->xpos+3*id2); mju_rotVecMatT(m->eq_data+mjNEQDATA*i+3, pos, d->xmat+9*id2); } // weld constraint else if (m->eq_type[i] == mjEQ_WELD) { // skip if user has set any quaternion data if (m->eq_data[mjNEQDATA*i+6] || m->eq_data[mjNEQDATA*i+7] || m->eq_data[mjNEQDATA*i+8] || m->eq_data[mjNEQDATA*i+9]) { // normalize quaternion just in case mju_normalize4(m->eq_data+mjNEQDATA*i+6); continue; } // anchor position is in body2 local frame mj_local2Global(d, pos, 0, m->eq_data+mjNEQDATA*i, 0, id2, 0); // data[3-5] = anchor position in body1 local frame mju_subFrom3(pos, d->xpos+3*id1); mju_rotVecMatT(m->eq_data+mjNEQDATA*i+3, pos, d->xmat+9*id1); // data[6-9] = neg(xquat1)*xquat2 = "xquat2-xquat1" in body1 local frame mju_negQuat(quat, d->xquat+4*id1); mju_mulQuat(m->eq_data+mjNEQDATA*i+6, quat, d->xquat+4*id2); } } // camera compos0, pos0, mat0 for (int i=0; i < m->ncam; i++) { // get body ids id = m->cam_bodyid[i]; // camera body id1 = m->cam_targetbodyid[i]; // target body // compute positional offsets mju_sub3(m->cam_pos0+3*i, d->cam_xpos+3*i, d->xpos+3*id); mju_sub3(m->cam_poscom0+3*i, d->cam_xpos+3*i, d->subtree_com+ (id1 >= 0 ? 3*id1 : 3*id)); // copy mat mju_copy(m->cam_mat0+9*i, d->cam_xmat+9*i, 9); } // light compos0, pos0, dir0 for (int i=0; i < m->nlight; i++) { // get body ids id = m->light_bodyid[i]; // light body id1 = m->light_targetbodyid[i]; // target body // compute positional offsets mju_sub3(m->light_pos0+3*i, d->light_xpos+3*i, d->xpos+3*id); mju_sub3(m->light_poscom0+3*i, d->light_xpos+3*i, d->subtree_com+ (id1 >= 0 ? 3*id1 : 3*id)); // copy dir mju_copy3(m->light_dir0+3*i, d->light_xdir+3*i); } mjFREESTACK; } // accumulate bounding box static void updateBox(mjtNum* xmin, mjtNum* xmax, mjtNum* pos, mjtNum radius) { for (int i=0; i < 3; i++) { xmin[i] = mjMIN(xmin[i], pos[i] - radius); xmax[i] = mjMAX(xmax[i], pos[i] + radius); } } // compute stat; assume computations already executed in qpos0 static void setStat(mjModel* m, mjData* d) { mjtNum xmin[3] = {1E+10, 1E+10, 1E+10}; mjtNum xmax[3] = {-1E+10, -1E+10, -1E+10}; mjtNum rbound; mjMARKSTACK; mjtNum* body = mj_stackAlloc(d, m->nbody); // compute bounding box of bodies, joint centers, geoms and sites for (int i=1; i < m->nbody; i++) { updateBox(xmin, xmax, d->xpos+3*i, 0); updateBox(xmin, xmax, d->xipos+3*i, 0); } for (int i=0; i < m->njnt; i++) { updateBox(xmin, xmax, d->xanchor+3*i, 0); } for (int i=0; i < m->nsite; i++) { updateBox(xmin, xmax, d->site_xpos+3*i, 0); } for (int i=0; i < m->ngeom; i++) { // set rbound: regular geom rbound, or 0.1 of plane or hfield max size rbound = 0; if (m->geom_rbound[i] > 0) { rbound = m->geom_rbound[i]; } else if (m->geom_type[i] == mjGEOM_PLANE) { // finite in at least one direction if (m->geom_size[3*i] || m->geom_size[3*i+1]) { rbound = mjMAX(m->geom_size[3*i], m->geom_size[3*i+1]) * 0.1; } // infinite in both directions else { rbound = 1; } } else if (m->geom_type[i] == mjGEOM_HFIELD) { int j = m->geom_dataid[i]; rbound = mjMAX(m->hfield_size[4*j], mjMAX(m->hfield_size[4*j+1], mjMAX(m->hfield_size[4*j+2], m->hfield_size[4*j+3]))) * 0.1; } updateBox(xmin, xmax, d->geom_xpos+3*i, rbound); } // compute center mju_add3(m->stat.center, xmin, xmax); mju_scl3(m->stat.center, m->stat.center, 0.5); // compute bounding box size if (xmax[0] > xmin[0]) m->stat.extent = mju_max(1E-5, mju_max(xmax[0]-xmin[0], mju_max(xmax[1]-xmin[1], xmax[2]-xmin[2]))); // set body size to max com-joint distance mju_zero(body, m->nbody); for (int i=0; i < m->njnt; i++) { // handle this body int id = m->jnt_bodyid[i]; body[id] = mju_max(body[id], mju_dist3(d->xipos+3*id, d->xanchor+3*i)); // handle parent body id = m->body_parentid[id]; body[id] = mju_max(body[id], mju_dist3(d->xipos+3*id, d->xanchor+3*i)); } body[0] = 0; // set body size to max of old value, and geom rbound + com-geom dist for (int i=1; i < m->nbody; i++) { for (int id=m->body_geomadr[i]; id < m->body_geomadr[i]+m->body_geomnum[i]; id++) { if (m->geom_rbound[id] > 0) { body[i] = mju_max(body[i], m->geom_rbound[id] + mju_dist3(d->xipos+3*i, d->geom_xpos+3*id)); } } } // compute meansize, make sure all sizes are above min if (m->nbody > 1) { m->stat.meansize = 0; for (int i=1; i < m->nbody; i++) { body[i] = mju_max(body[i], 1E-5); m->stat.meansize += body[i]/(m->nbody-1); } } // fix extent if too small compared to meanbody m->stat.extent = mju_max(m->stat.extent, 2 * m->stat.meansize); // compute meanmass if (m->nbody > 1) { m->stat.meanmass = 0; for (int i=1; i < m->nbody; i++) { m->stat.meanmass += m->body_mass[i]; } m->stat.meanmass /= (m->nbody-1); } // compute meaninertia if (m->nv) { m->stat.meaninertia = 0; for (int i=0; i < m->nv; i++) { m->stat.meaninertia += d->qM[m->dof_Madr[i]]; } m->stat.meaninertia /= m->nv; } mjFREESTACK; } // set quantities that depend on qpos_spring static void setSpring(mjModel* m, mjData* d) { // run computations in qpos_spring mju_copy(d->qpos, m->qpos_spring, m->nq); mj_kinematics(m, d); mj_comPos(m, d); mj_tendon(m, d); mj_transmission(m, d); // copy if model spring length is -1 for (int i=0; i < m->ntendon; i++) { if (m->tendon_lengthspring[2*i] == -1 && m->tendon_lengthspring[2*i+1] == -1) { // explicit springlength unused, set equal to ten_length m->tendon_lengthspring[2*i] = m->tendon_lengthspring[2*i+1] = d->ten_length[i]; } } } // entry point: set all constant fields of mjModel, except for lengthrange void mj_setConst(mjModel* m, mjData* d) { // compute subtreemass for (int i=0; i < m->nbody; i++) { m->body_subtreemass[i] = m->body_mass[i]; } for (int i=m->nbody-1; i > 0; i--) { m->body_subtreemass[m->body_parentid[i]] += m->body_subtreemass[i]; } // call functions set0(m, d); setStat(m, d); setSpring(m, d); } //----------------------------- actuator length range computation ---------------------------------- // evaluate actuator length, advance special dynamics static mjtNum evalAct(const mjModel* m, mjData* d, int index, int side, const mjLROpt* opt) { int nv = m->nv; // reduce velocity mju_scl(d->qvel, d->qvel, mju_exp(-m->opt.timestep/mjMAX(0.01, opt->timeconst)), nv); // step1: compute inertia and actuator moments mj_step1(m, d); // set force to generate desired acceleration mj_solveM(m, d, d->qfrc_applied, d->actuator_moment+index*nv, 1); mjtNum nrm = mju_norm(d->qfrc_applied, nv); mju_scl(d->qfrc_applied, d->actuator_moment+index*nv, (2*side-1)*opt->accel/mjMAX(mjMINVAL, nrm), nv); // impose maxforce nrm = mju_norm(d->qfrc_applied, nv); if (opt->maxforce > 0 && nrm > opt->maxforce) { mju_scl(d->qfrc_applied, d->qfrc_applied, opt->maxforce/mjMAX(mjMINVAL, nrm), nv); } // step2: apply force mj_step2(m, d); // return actuator length return d->actuator_length[index]; } // Set length range for specified actuator, return 1 if ok, 0 if error. int mj_setLengthRange(mjModel* m, mjData* d, int index, const mjLROpt* opt, char* error, int error_sz) { // check index if (index < 0 || index >= m->nu) { mjERROR("invalid actuator index"); } // skip depending on mode and type int ismuscle = (m->actuator_gaintype[index] == mjGAIN_MUSCLE || m->actuator_biastype[index] == mjBIAS_MUSCLE); int isuser = (m->actuator_gaintype[index] == mjGAIN_USER || m->actuator_biastype[index] == mjBIAS_USER); if ((opt->mode == mjLRMODE_NONE) || (opt->mode == mjLRMODE_MUSCLE && !ismuscle) || (opt->mode == mjLRMODE_MUSCLEUSER && !ismuscle && !isuser)) { return 1; } // use existing length range if available if (opt->useexisting && (m->actuator_lengthrange[2*index] < m->actuator_lengthrange[2*index+1])) { return 1; } // get transmission id int threadid = m->actuator_trnid[index]; // use joint and tendon limits if available if (opt->uselimit) { // joint or jointinparent if (m->actuator_trntype[index] == mjTRN_JOINT || m->actuator_trntype[index] == mjTRN_JOINTINPARENT) { // make sure joint is limited if (m->jnt_limited[threadid]) { // copy range m->actuator_lengthrange[2*index] = m->jnt_range[2*threadid]; m->actuator_lengthrange[2*index+1] = m->jnt_range[2*threadid+1]; // skip optimization return 1; } } // tendon if (m->actuator_trntype[index] == mjTRN_TENDON) { // make sure tendon is limited if (m->tendon_limited[threadid]) { // copy range m->actuator_lengthrange[2*index] = m->tendon_range[2*threadid]; m->actuator_lengthrange[2*index+1] = m->tendon_range[2*threadid+1]; // skip optimization return 1; } } } // optimize in both directions mjtNum lmin[2] = {0, 0}, lmax[2] = {0, 0}; int side; for (side=0; side < 2; side++) { // init at qpos0 mj_resetData(m, d); // simulate int updated = 0; while (d->time < opt->inttotal) { // advance and get length mjtNum len = evalAct(m, d, index, side, opt); // reset: cannot proceed if (d->time == 0) { snprintf(error, error_sz, "Unstable lengthrange simulation in actuator %d", index); return 0; } // update limits if (d->time > opt->inttotal-opt->interval) { if (len < lmin[side] || !updated) { lmin[side] = len; } if (len > lmax[side] || !updated) { lmax[side] = len; } updated = 1; } } // assign m->actuator_lengthrange[2*index+side] = (side == 0 ? lmin[side] : lmax[side]); } // check range mjtNum dif = m->actuator_lengthrange[2*index+1] - m->actuator_lengthrange[2*index]; if (dif <= 0) { snprintf(error, error_sz, "Invalid lengthrange (%g, %g) in actuator %d", m->actuator_lengthrange[2*index], m->actuator_lengthrange[2*index+1], index); return 0; } // check convergence, side 0 if (lmax[0]-lmin[0] > opt->tolrange*dif) { snprintf(error, error_sz, "Lengthrange computation did not converge in actuator %d:\n" " eval (%g, %g)\n range (%g, %g)", index, lmin[0], lmax[0], m->actuator_lengthrange[2*index], m->actuator_lengthrange[2*index+1]); return 0; } // check convergence, side 1 if (lmax[1]-lmin[1] > opt->tolrange*dif) { snprintf(error, error_sz, "Lengthrange computation did not converge in actuator %d:\n" " eval (%g, %g)\n range (%g, %g)", index, lmin[1], lmax[1], m->actuator_lengthrange[2*index], m->actuator_lengthrange[2*index+1]); return 0; } return 1; }