// 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 #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) { std::sscanf(argv[2], "%d", &nthread); } if (argc>3) { std::sscanf(argv[3], "%d", &niter); } if (argc>4) { std::sscanf(argv[4], "%d", &nwarmup); } if (argc>5) { std::sscanf(argv[5], "%d", &nepoch); } if (argc>6) { std::sscanf(argv[6], "%d", &nstep); } if (argc>7) { std::sscanf(argv[7], "%lf", &eps); } // check number of threads if (nthread<1 || nthread>MAXTHREAD) { std::printf("nthread must be between 1 and %d\n", MAXTHREAD); return 1; } // check number of epochs if (nepoch<1 || nepoch>MAXEPOCH) { std::printf("nepoch must be between 1 and %d\n", MAXEPOCH); return 1; } // load model mjModel* m = 0; if (std::strlen(argv[1])>4 && !std::strcmp(argv[1]+std::strlen(argv[1])-4, ".mjb")) { m = mj_loadModel(argv[1], NULL); } else { m = mj_loadXML(argv[1], NULL, NULL, 0); } if (!m) { std::printf("Could not load modelfile '%s'\n", argv[1]); return 1; } // print arguments #if defined(_OPENMP) std::printf("\nnthread : %d (OpenMP)\n", nthread); #else std::printf("\nnthread : %d (serial)\n", nthread); #endif std::printf("niter : %d\n", niter); std::printf("nwarmup : %d\n", nwarmup); std::printf("nepoch : %d\n", nepoch); std::printf("nstep : %d\n", nstep); std::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); std::printf("inverse : %.2f ms\n", mcputm[0]/nepoch); std::printf("forward : %.2f ms\n\n", mcputm[1]/nepoch); std::printf("accuracy: log10(residual L1 relnorm)\n"); std::printf("------------------------------------\n"); for (int ie=0; ie<8; ie++) { std::printf(" %s : %.2g\n", accuracy[ie], merror[ie]/nepoch); } std::printf("\n"); // shut down mju_free(deriv); mj_deleteData(dmain); for (int n=0; n