// 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 "mujoco.h" #include #include #include // enable compilation with and without OpenMP support #if defined(_OPENMP) #include #else // omp timer replacement #include double omp_get_wtime(void) { static std::chrono::system_clock::time_point _start = std::chrono::system_clock::now(); std::chrono::duration elapsed = std::chrono::system_clock::now() - _start; return elapsed.count(); } // omp functions used below void omp_set_dynamic(int) {} void omp_set_num_threads(int) {} int omp_get_num_procs(void) {return 1;} #endif // gloval variables: internal const int MAXTHREAD = 64; // maximum number of threads allowed const int MAXEPOCH = 100; // maximum number of epochs int isforward = 0; // dynamics mode: forward or inverse mjtNum* deriv = 0; // dynamics derivatives (6*nv*nv): // dinv/dpos, dinv/dvel, dinv/dacc, dacc/dpos, dacc/dvel, dacc/dfrc // global variables: user-defined, with defaults int nthread = 0; // number of parallel threads (default set later) int niter = 30; // fixed number of solver iterations for finite-differencing int nwarmup = 3; // center point repetitions to improve warmstart int nepoch = 20; // number of timing epochs int nstep = 500; // number of simulation steps per epoch double eps = 1e-6; // finite-difference epsilon // worker function for parallel finite-difference computation of derivatives void worker(const mjModel* m, const mjData* dmain, mjData* d, int id) { int nv = m->nv; // allocate stack space for result at center mjMARKSTACK mjtNum* center = mj_stackAlloc(d, nv); mjtNum* warmstart = mj_stackAlloc(d, nv); // prepare static schedule: range of derivative columns to be computed by this thread int chunk = (m->nv + nthread-1) / nthread; int istart = id * chunk; int iend = mjMIN(istart + chunk, m->nv); // copy state and control from dmain to thread-specific d d->time = dmain->time; mju_copy(d->qpos, dmain->qpos, m->nq); mju_copy(d->qvel, dmain->qvel, m->nv); mju_copy(d->qacc, dmain->qacc, m->nv); mju_copy(d->qacc_warmstart, dmain->qacc_warmstart, m->nv); mju_copy(d->qfrc_applied, dmain->qfrc_applied, m->nv); mju_copy(d->xfrc_applied, dmain->xfrc_applied, 6*m->nbody); mju_copy(d->ctrl, dmain->ctrl, m->nu); // run full computation at center point (usually faster than copying dmain) if( isforward ) { mj_forward(m, d); // extra solver iterations to improve warmstart (qacc) at center point for( int rep=1; repqacc : d->qfrc_inverse); // save output for center point and warmstart (needed in forward only) mju_copy(center, output, nv); mju_copy(warmstart, d->qacc_warmstart, nv); // select target vector and original vector for force or acceleration derivative mjtNum* target = (isforward ? d->qfrc_applied : d->qacc); const mjtNum* original = (isforward ? dmain->qfrc_applied : dmain->qacc); // finite-difference over force or acceleration: skip = mjSTAGE_VEL for( int i=istart; iqacc_warmstart, warmstart, m->nv); mj_forwardSkip(m, d, mjSTAGE_VEL, 1); } else mj_inverseSkip(m, d, mjSTAGE_VEL, 1); // undo perturbation target[i] = original[i]; // compute column i of derivative 2 for( int j=0; jqvel[i] += eps; // evaluate dynamics, with center warmstart if( isforward ) { mju_copy(d->qacc_warmstart, warmstart, m->nv); mj_forwardSkip(m, d, mjSTAGE_POS, 1); } else mj_inverseSkip(m, d, mjSTAGE_POS, 1); // undo perturbation d->qvel[i] = dmain->qvel[i]; // compute column i of derivative 1 for( int j=0; jdof_jntid[i]; // get quaternion address and dof position within quaternion (-1: not in quaternion) int quatadr = -1, dofpos = 0; if( m->jnt_type[jid]==mjJNT_BALL ) { quatadr = m->jnt_qposadr[jid]; dofpos = i - m->jnt_dofadr[jid]; } else if( m->jnt_type[jid]==mjJNT_FREE && i>=m->jnt_dofadr[jid]+3 ) { quatadr = m->jnt_qposadr[jid] + 3; dofpos = i - m->jnt_dofadr[jid] - 3; } // apply quaternion or simple perturbation if( quatadr>=0 ) { mjtNum angvel[3] = {0,0,0}; angvel[dofpos] = eps; mju_quatIntegrate(d->qpos+quatadr, angvel, 1); } else d->qpos[m->jnt_qposadr[jid] + i - m->jnt_dofadr[jid]] += eps; // evaluate dynamics, with center warmstart if( isforward ) { mju_copy(d->qacc_warmstart, warmstart, m->nv); mj_forwardSkip(m, d, mjSTAGE_NONE, 1); } else mj_inverseSkip(m, d, mjSTAGE_NONE, 1); // undo perturbation mju_copy(d->qpos, dmain->qpos, m->nq); // compute column i of derivative 0 for( int j=0; jnv; // allocate space mjMARKSTACK mjtNum* mat = mj_stackAlloc(d, nv*nv); // get pointers to derivative matrices mjtNum* G0 = deriv; // dinv/dpos mjtNum* G1 = deriv + nv*nv; // dinv/dvel mjtNum* G2 = deriv + 2*nv*nv; // dinv/dacc mjtNum* F0 = deriv + 3*nv*nv; // dacc/dpos mjtNum* F1 = deriv + 4*nv*nv; // dacc/dvel mjtNum* F2 = deriv + 5*nv*nv; // dacc/dfrc // G2*F2 - I mju_mulMatMat(mat, G2, F2, nv, nv, nv); for( int i=0; i2 ) sscanf(argv[2], "%d", &nthread); if( argc>3 ) sscanf(argv[3], "%d", &niter); if( argc>4 ) sscanf(argv[4], "%d", &nwarmup); if( argc>5 ) sscanf(argv[5], "%d", &nepoch); if( argc>6 ) sscanf(argv[6], "%d", &nstep); if( argc>7 ) sscanf(argv[7], "%lf", &eps); // check number of threads if( nthread<1 || nthread>MAXTHREAD ) { printf("nthread must be between 1 and %d\n", MAXTHREAD); return 1; } // check number of epochs if( nepoch<1 || nepoch>MAXEPOCH ) { printf("nepoch must be between 1 and %d\n", MAXEPOCH); return 1; } // load model mjModel* m = 0; if( strlen(argv[1])>4 && !strcmp(argv[1]+strlen(argv[1])-4, ".mjb") ) m = mj_loadModel(argv[1], NULL); else m = mj_loadXML(argv[1], NULL, NULL, 0); if( !m ) { printf("Could not load modelfile '%s'\n", argv[1]); return 1; } // print arguments #if defined(_OPENMP) printf("\nnthread : %d (OpenMP)\n", nthread); #else printf("\nnthread : %d (serial)\n", nthread); #endif printf("niter : %d\n", niter); printf("nwarmup : %d\n", nwarmup); printf("nepoch : %d\n", nepoch); printf("nstep : %d\n", nstep); printf("eps : %g\n\n", eps); // make mjData: main, per-thread mjData* dmain = mj_makeData(m); mjData* d[MAXTHREAD]; for( int n=0; nnv*m->nv); // set up OpenMP (if not enabled, this does nothing) omp_set_dynamic(0); omp_set_num_threads(nthread); // save solver options int save_iterations = m->opt.iterations; mjtNum save_tolerance = m->opt.tolerance; // allocate statistics int nefc = 0; double cputm[MAXEPOCH][2]; mjtNum error[MAXEPOCH][8]; // run epochs, collect statistics for( int epoch=0; epochopt.iterations = save_iterations; m->opt.tolerance = save_tolerance; // advance main simulation for nstep for( int i=0; inefc; // set solver options for finite differences m->opt.iterations = niter; m->opt.tolerance = 0; // test forward and inverse for( isforward=0; isforward<2; isforward++ ) { // start timer double starttm = omp_get_wtime(); // run worker threads in parallel if OpenMP is enabled #pragma omp parallel for schedule(static) for( int n=0; nnv, nefc/nepoch); printf("inverse : %.2f ms\n", mcputm[0]/nepoch); printf("forward : %.2f ms\n\n", mcputm[1]/nepoch); printf("accuracy: log10(residual L1 relnorm)\n"); printf("------------------------------------\n"); for( int ie=0; ie<8; ie++ ) printf(" %s : %.2g\n", accuracy[ie], merror[ie]/nepoch); printf("\n"); // shut down mju_free(deriv); mj_deleteData(dmain); for( int n=0; n