Add Nesterov momentum with O'Donoghue-Candès restarts to PGS solver (~2x speedup)

Benchmark on `2humanoid100.xml` (nefc=1785, nv=654):

```
Convergence at fixed iteration count (mean relative error vs Newton):
  20 iters: 2.98e-03 vs 1.54e-02 (5.2x better)
  40 iters: 8.14e-05 vs 2.60e-03 (32x better)
  80 iters: 1.17e-07 vs 1.77e-04 (1500x better)

Pipeline throughput (tolerance=1e-8, islands disabled):
  Nesterov: 243 steps/s, 46 iters/step
  Baseline: 151 steps/s, 95 iters/step
  Solver speedup: 1.8x, overall step speedup: 1.6x

Pipeline throughput (tolerance=1e-8, islands enabled):
  Nesterov: 306 steps/s, 442 iters/step
  Baseline: 175 steps/s, 966 iters/step
  Solver speedup: 2.1x, overall step speedup: 1.7x
```

PiperOrigin-RevId: 936610759
Change-Id: I2978e8bd545971d9151005623967e5cf0ad125cc
This commit is contained in:
Yuval Tassa
2026-06-23 05:51:42 -07:00
committed by Copybara-Service
parent 5cef2472d5
commit c499f7f2b0
8 changed files with 798 additions and 15 deletions
+8
View File
@@ -2,6 +2,14 @@
Changelog
=========
Upcoming version (not yet released)
-----------------------------------
General
^^^^^^^
- Added Nesterov momentum extrapolation with adaptive gradient restart (O'Donoghue-Candès) to the PGS solver,
significantly improving convergence. Overall PGS now requires ~2x fewer iterations.
Version 3.10.0 (June 22, 2026)
------------------------------
+128 -8
View File
@@ -26,6 +26,7 @@
#include "engine/engine_core_smooth.h"
#include "engine/engine_core_util.h"
#include "engine/engine_memory.h"
#include "engine/engine_macro.h"
#include "engine/engine_util_blas.h"
#include "engine/engine_util_errmem.h"
#include "engine/engine_util_misc.h"
@@ -375,6 +376,27 @@ static int dualStateChange(const mjData* d, int* state, int* oldstate,
}
// project onto friction ellipsoid, write to force[i+1..i+dim-1]
// project tangential force onto friction ellipsoid: sum(f_t[j]^2/mu[j]^2) <= f_n^2
// if feasible is true, only scale down if outside the ellipsoid
// if feasible is false, always scale to the boundary
static void projectEllipsoid(mjtNum* friction, mjtNum normal, const mjtNum* mu,
int dim, int feasible) {
mjtNum s = 0;
for (int j=0; j < dim-1; j++) {
s += friction[j]*friction[j] / (mu[j]*mu[j]);
}
mjtNum normal2 = normal*normal;
if (!feasible || s > normal2) {
mjtNum scl = mju_sqrt(normal2 / mju_max(mjMINVAL, s));
for (int j=0; j < dim-1; j++) {
friction[j] *= scl;
}
}
}
// solve QCQP and project onto friction ellipsoid, write to force[i+1..i+dim-1]
static void solveQCQP(mjtNum* force, int i, int dim,
mjtNum* Ac, mjtNum* bc, const mjtNum* mu) {
@@ -392,14 +414,7 @@ static void solveQCQP(mjtNum* force, int i, int dim,
// on constraint: put v on ellipsoid, in case QCQP is approximate
if (flg_active) {
mjtNum s = 0;
for (int j=0; j < dim-1; j++) {
s += v[j]*v[j] / (mu[j]*mu[j]);
}
s = mju_sqrt(force[i]*force[i] / mju_max(mjMINVAL, s));
for (int j=0; j < dim-1; j++) {
v[j] *= s;
}
projectEllipsoid(v, force[i], mu, dim, /*feasible=*/0);
}
// assign
@@ -407,6 +422,31 @@ static void solveQCQP(mjtNum* force, int i, int dim,
}
// project contact force block onto friction cone (pyramidal or elliptic)
static void projectCone(mjtNum* force, const mjtNum* mu, int dim, int type) {
// elliptic cone: project onto friction ellipsoid
if (type == mjCNSTR_CONTACT_ELLIPTIC) {
// clamp normal force
if (force[0] < 0) {
mju_zero(force, dim);
} else {
projectEllipsoid(force+1, force[0], mu, dim, /*feasible=*/1);
}
}
// pyramidal or scalar: clamp to non-negative
else {
if (force[0] < 0) {
force[0] = 0;
}
}
}
// global variable to toggle Nesterov momentum (for benchmarks/tests)
mjTHREADLOCAL int mj_nesterov_momentum = 1;
//---------------------------- PGS solver ----------------------------------------------------------
// core PGS solver: iterates over constraints specified by efclist
@@ -423,6 +463,16 @@ static void solPGS(const mjModel* m, mjData* d, int island,
int* oldstate = mjSTACKALLOC(d, 2*nefc, int);
int* blockstart = oldstate + nefc;
// Nesterov momentum
mjtBool nesterov = (mj_nesterov_momentum != 0);
mjtNum* force_prev = NULL;
mjtNum* force_momentum = NULL;
if (nesterov) {
force_prev = mjSTACKALLOC(d, nefc, mjtNum);
force_momentum = mjSTACKALLOC(d, nefc, mjtNum);
mju_gather(force_prev, force, efclist, nefc);
}
int island_stat = mjMAX(0, island); // island index for diagnostic stats
mjtNum scale = 1 / (m->stat.meaninertia * mjMAX(1, m->nv));
@@ -452,7 +502,56 @@ static void solPGS(const mjModel* m, mjData* d, int island,
// main iteration
int iter = 0;
int nesterov_k = 0; // Nesterov counter (resets on adaptive restart)
while (iter < maxiter) {
// Nesterov momentum extrapolation
if (nesterov) {
mjtNum beta = 0;
if (iter > 0) {
beta = (mjtNum)(nesterov_k - 1) / (mjtNum)(nesterov_k + 2);
}
// update with momentum, save pre-extrapolation value
if (beta > 0) {
for (int c=0; c < nefc; c++) {
int i = efclist ? efclist[c] : c;
mjtNum f_save = force[i];
force[i] += beta*(force[i] - force_prev[c]);
force_prev[c] = f_save;
}
// friction loss: project onto bounds
for (int c=ne; c < ne+nf; c++) {
int i = efclist ? efclist[c] : c;
force[i] = mju_clip(force[i], -floss[i], floss[i]);
}
// contact force: project onto friction cone
for (int c=ne+nf; c < nefc; ) {
int i = efclist ? efclist[c] : c;
int dim = 1;
int type = d->efc_type[i];
const mjtNum* mu = NULL;
if (type == mjCNSTR_CONTACT_ELLIPTIC) {
dim = d->contact[d->efc_id[i]].dim;
mu = d->contact[d->efc_id[i]].friction;
}
projectCone(force+i, mu, dim, type);
c += dim;
}
}
// iter == 0 or beta <= 0 (nesterov_k <= 1): just save current force
else {
mju_gather(force_prev, force, efclist, nefc);
}
// save extrapolated point for gradient restart check
mju_gather(force_momentum, force, efclist, nefc);
}
// clear improvement
mjtNum improvement = 0;
@@ -591,6 +690,27 @@ static void solPGS(const mjModel* m, mjData* d, int island,
improvement *= scale;
saveStats(m, d, island_stat, iter, improvement, 0, 0, nactive, nchange, 0, 0);
// Nesterov gradient restart (O'Donoghue-Candès): reset when correction opposes extrapolation
if (nesterov) {
mjtBool restart = false;
if (iter > 0) {
mjtNum dot_corr_extr = 0;
for (int c=0; c < nefc; c++) {
int i = efclist ? efclist[c] : c;
mjtNum correction = force[i] - force_momentum[c];
mjtNum extrapolation = force_momentum[c] - force_prev[c];
dot_corr_extr += correction * extrapolation;
}
restart = (dot_corr_extr < 0);
}
if (restart) {
nesterov_k = 0;
} else {
nesterov_k++;
}
}
// increment iteration count
iter++;
+12 -2
View File
@@ -2129,8 +2129,13 @@ void mju_n2d(double* res, const mjtNum* vec, int n) {
}
// gather
// gather: res[i] = vec[ind[i]], or copy if ind is NULL
void mju_gather(mjtNum* restrict res, const mjtNum* restrict vec, const int* restrict ind, int n) {
if (!ind) {
mju_copy(res, vec, n);
return;
}
for (int i=0; i < n; i++) {
res[i] = vec[ind[i]];
}
@@ -2146,8 +2151,13 @@ void mju_gatherMasked(mjtNum* restrict res, const mjtNum* restrict vec,
}
// scatter
// scatter: res[ind[i]] = vec[i], or copy if ind is NULL
void mju_scatter(mjtNum* restrict res, const mjtNum* restrict vec, const int* restrict ind, int n) {
if (!ind) {
mju_copy(res, vec, n);
return;
}
for (int i=0; i < n; i++) {
res[ind[i]] = vec[i];
}
+2 -2
View File
@@ -281,13 +281,13 @@ MJAPI void mju_d2n(mjtNum* res, const double* vec, int n);
// convert from mjtNum to double
MJAPI void mju_n2d(double* res, const mjtNum* vec, int n);
// gather mjtNums
// gather mjtNums: res[i] = vec[ind[i]], or copy if ind is NULL
MJAPI void mju_gather(mjtNum* res, const mjtNum* vec, const int* ind, int n);
// gather mjtNums, set to 0 at negative indices
MJAPI void mju_gatherMasked(mjtNum* res, const mjtNum* vec, const int* ind, int n);
// scatter mjtNums
// scatter mjtNums: res[ind[i]] = vec[i], or copy if ind is NULL
MJAPI void mju_scatter(mjtNum* res, const mjtNum* vec, const int* ind, int n);
// gather integers
+274
View File
@@ -0,0 +1,274 @@
// Copyright 2026 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.
// PGS with/without Nesterov momentum: compare convergence and timing.
//
// Rolls out Newton ground truth on 2humanoid100.xml, then evaluates PGS with
// and without Nesterov momentum at various iteration budgets.
#include <chrono> // NOLINT
#include <cstdio>
#include <ratio> // NOLINT
#include <string>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <mujoco/mujoco.h>
#include "test/fixture.h"
extern "C" thread_local int mj_nesterov_momentum;
namespace mujoco {
namespace {
using ::testing::NotNull;
mjtNum gettm(void) {
using Clock = std::chrono::steady_clock;
using Microseconds = std::chrono::duration<mjtNum, std::micro>;
static const Clock::time_point tm_start = Clock::now();
return Microseconds(Clock::now() - tm_start).count();
}
using PgsConvergenceTest = MujocoTest;
// get total solver iterations across all islands
int get_total_solver_iters(const mjData* d) {
int nisland = mjMAX(1, mjMIN(d->nisland, mjNISLAND));
int total = 0;
for (int i = 0; i < nisland; i++) {
total += d->solver_niter[i];
}
return total;
}
// run solver benchmark at various iteration counts, print table
void run_benchmark(mjModel* model, mjData* data,
const std::vector<mjtNum>& all_qpos,
const std::vector<mjtNum>& all_qvel,
const std::vector<mjtNum>& all_warmstart,
const std::vector<mjtNum>& all_qacc,
int nq, int nv, int kStride, int kNumEval,
const int* kIterCounts, int kNumIter,
const char* label) {
std::printf("\n %s:\n", label);
std::printf(" %6s | %11s | %11s | %10s | %11s\n",
"Iters", "Mean Err", "Max Err", "Mean Iters", "Solver us");
std::printf(" %s\n",
"-------+-------------+-------------+------------+-----------");
for (int c = 0; c < kNumIter; c++) {
model->opt.iterations = kIterCounts[c];
for (int i = 0; i < mjNTIMER; i++) {
data->timer[i].duration = 0;
data->timer[i].number = 0;
}
int total_iters = 0;
mjtNum sum_rel_err = 0;
mjtNum max_rel_err = 0;
for (int e = 0; e < kNumEval; e++) {
int idx = e * kStride;
mju_copy(data->qpos, all_qpos.data() + idx*nq, nq);
mju_copy(data->qvel, all_qvel.data() + idx*nv, nv);
mju_copy(data->qacc_warmstart, all_warmstart.data() + idx*nv, nv);
mj_forward(model, data);
total_iters += get_total_solver_iters(data);
mjtNum newton_norm = mju_norm(all_qacc.data() + idx*nv, nv);
mjtNum err = 0;
for (int j = 0; j < nv; j++) {
mjtNum diff = data->qacc[j] - all_qacc[idx*nv + j];
err += diff * diff;
}
mjtNum rel_err = mju_sqrt(err) / mju_max(newton_norm, 1e-10);
sum_rel_err += rel_err;
if (rel_err > max_rel_err) {
max_rel_err = rel_err;
}
}
mjtNum solver_time = data->timer[mjTIMER_CONSTRAINT].duration;
mjtNum mean_iters = static_cast<mjtNum>(total_iters) / kNumEval;
std::printf(" %6d | %11.4e | %11.4e | %10.2f | %11.2f\n",
kIterCounts[c], sum_rel_err / kNumEval, max_rel_err,
mean_iters, solver_time);
}
std::printf(" %s\n",
"-------+-------------+-------------+------------+-----------");
}
// run pipeline mode: consecutive steps with tolerance, print summary
void run_pipeline(mjModel* model, mjData* data, int kNumSteps,
const char* label) {
std::printf("\n %s Pipeline mode (mj_step, tolerance = 1e-8):\n", label);
mj_resetData(model, data);
for (int i = 0; i < mjNTIMER; i++) {
data->timer[i].duration = 0;
data->timer[i].number = 0;
}
int pipe_total_iters = 0;
for (int i = 0; i < kNumSteps; i++) {
mj_step(model, data);
pipe_total_iters += get_total_solver_iters(data);
}
mjtNum pipe_constraint = data->timer[mjTIMER_CONSTRAINT].duration;
mjtNum pipe_step = data->timer[mjTIMER_STEP].duration;
int pipe_step_count = data->timer[mjTIMER_STEP].number;
std::printf(" Steps/s : %.0f\n",
pipe_step_count > 0 ? 1e6 * pipe_step_count / pipe_step : 0.0);
std::printf(" us/step (total) : %.1f\n",
pipe_step_count > 0 ? pipe_step / pipe_step_count : 0.0);
std::printf(" us/step (constr) : %.1f\n",
pipe_step_count > 0 ? pipe_constraint / pipe_step_count : 0.0);
std::printf(" Iters/step : %.2f\n",
pipe_step_count > 0
? static_cast<mjtNum>(pipe_total_iters) / pipe_step_count
: 0.0);
}
TEST_F(PgsConvergenceTest, PGSConvergence) {
static const char* const kPath =
"engine/testdata/forward/perf/2humanoid100_PGS.xml";
const std::string xml_path = GetTestDataFilePath(kPath);
char error[1024];
mjModel* model =
mj_loadXML(xml_path.c_str(), nullptr, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
// disable islands: monolithic solver makes statistics simpler
model->opt.disableflags |= mjDSBL_ISLAND;
int nq = model->nq;
int nv = model->nv;
// stronger sideways gravity
model->opt.gravity[0] = -2;
model->opt.gravity[1] = -2;
model->opt.gravity[2] = -10;
// configure Newton ground truth
model->opt.solver = mjSOL_NEWTON;
model->opt.jacobian = mjJAC_SPARSE;
model->opt.tolerance = 0;
model->opt.iterations = 10;
model->opt.disableflags &= ~mjDSBL_WARMSTART;
mjData* data = mj_makeData(model);
mjcb_time = gettm;
// roll out Newton for kNumSteps, save pre-step state and post-step qacc
constexpr int kNumSteps = 1000;
std::vector<mjtNum> all_qpos(kNumSteps * nq);
std::vector<mjtNum> all_qvel(kNumSteps * nv);
std::vector<mjtNum> all_warmstart(kNumSteps * nv);
std::vector<mjtNum> all_qacc(kNumSteps * nv);
std::printf("Rolling out ground truth Newton steps...\n");
for (int i = 0; i < kNumSteps; i++) {
mju_copy(all_qpos.data() + i*nq, data->qpos, nq);
mju_copy(all_qvel.data() + i*nv, data->qvel, nv);
mju_copy(all_warmstart.data() + i*nv, data->qacc_warmstart, nv);
mj_step(model, data);
mju_copy(all_qacc.data() + i*nv, data->qacc, nv);
}
// evaluation points
constexpr int kNumEval = 100;
constexpr int kStride = kNumSteps / kNumEval;
// iteration counts to test
constexpr int kIterCounts[] = {5, 10, 20, 40, 80, 160, 320};
constexpr int kNumIter = sizeof(kIterCounts) / sizeof(kIterCounts[0]);
std::printf("\nPGS vs Nesterov PGS: 2humanoid100_PGS.xml\n");
std::printf(" %d Newton steps, %d evaluation points\n",
kNumSteps, kNumEval);
std::printf(" nv = %d, nq = %d, nefc = %d\n", nv, nq, data->nefc);
// switch to PGS
model->opt.solver = mjSOL_PGS;
model->opt.tolerance = 0;
model->opt.disableflags &= ~mjDSBL_WARMSTART;
// 1. Row PGS (default)
mj_nesterov_momentum = 0;
run_benchmark(model, data, all_qpos, all_qvel, all_warmstart, all_qacc,
nq, nv, kStride, kNumEval, kIterCounts, kNumIter,
"Row PGS (Warmstart, tolerance = 0)");
// 2. Nesterov PGS (via REFSAFE hack)
mj_nesterov_momentum = 1;
run_benchmark(model, data, all_qpos, all_qvel, all_warmstart, all_qacc,
nq, nv, kStride, kNumEval, kIterCounts, kNumIter,
"Nesterov PGS (Warmstart, tolerance = 0)");
// 3. Pipeline: Row PGS
mj_nesterov_momentum = 0;
model->opt.tolerance = 1e-8;
model->opt.iterations = 100;
run_pipeline(model, data, kNumSteps, "Row PGS");
// 4. Pipeline: Nesterov PGS
mj_nesterov_momentum = 1;
model->opt.tolerance = 1e-8;
model->opt.iterations = 100;
run_pipeline(model, data, kNumSteps, "Nesterov PGS");
std::printf("\n");
// ========== PASS 2: ISLANDS ENABLED ==========
model->opt.disableflags &= ~mjDSBL_ISLAND;
// do one forward pass to get nisland
mj_resetData(model, data);
mj_forward(model, data);
std::printf("\n============================================\n");
std::printf("PASS 2: ISLANDS ENABLED\n");
std::printf("============================================\n");
// Pipeline: Row PGS with islands
mj_nesterov_momentum = 0;
model->opt.tolerance = 1e-8;
model->opt.iterations = 100;
run_pipeline(model, data, kNumSteps, "Row PGS (islands)");
// Pipeline: Nesterov PGS with islands
mj_nesterov_momentum = 1;
model->opt.tolerance = 1e-8;
model->opt.iterations = 100;
run_pipeline(model, data, kNumSteps, "Nesterov PGS (islands)");
// Reset to default
mj_nesterov_momentum = 1;
std::printf("\n");
mj_deleteData(data);
mj_deleteModel(model);
}
} // namespace
} // namespace mujoco
+3 -3
View File
@@ -241,15 +241,15 @@ TEST_F(SolverTest, SolversEquivalent) {
{
.newton = MjTol(1e-13, 1e-5),
.cg = MjTol(1e-13, 1e-5),
.pgs_pyramidal = MjTol(1e-12, 1e-5),
.pgs_elliptic = MjTol(1e-3, 1e-2),
.pgs_pyramidal = MjTol(1e-13, 1e-5),
.pgs_elliptic = MjTol(1e-3, 1e-3),
}},
{.path = kHumanoidPath,
.tolerances =
{
.newton = MjTol(1e-13, 1e-5),
.cg = MjTol(1e-12, 1e-5),
.pgs_pyramidal = MjTol(1e-5, 1e-5),
.pgs_pyramidal = MjTol(1e-12, 1e-5),
.pgs_elliptic = MjTol(1e-8, 1e-4),
}},
};
+119
View File
@@ -0,0 +1,119 @@
<mujoco model="2 Humanoids and 100 objects">
<option timestep="0.005" solver="PGS" gravity="-2 -2 -10">
<flag island="enable"/>
</option>
<size memory="100M"/>
<default>
<geom solimp=".9 .9 .01"/>
<default class="capsule">
<geom type="capsule" material="capsule" size="0.1 0.05"/>
</default>
<default class="ellipsoid">
<geom type="ellipsoid" material="ellipsoid" size="0.15 0.1 0.07"/>
</default>
<default class="box">
<geom type="box" material="box" size="0.15 0.1 0.05"/>
</default>
<default class="cylinder">
<geom type="cylinder" material="cylinder" size="0.1 0.05" condim="4" friction="1 .01 .01"/>
</default>
<default class="sphere">
<geom type="sphere" material="sphere" size="0.1"/>
</default>
<default class="border">
<geom type="capsule" size="0.4" rgba=".4 .4 .4 1"/>
</default>
<default class="borderpost">
<geom type="box" size="0.41 0.41 0.41" rgba=".55 .55 .55 1"/>
</default>
</default>
<asset>
<model file="humanoid.xml"/>
<texture type="skybox" builtin="gradient" width="512" height="512" rgb1=".4 .6 .8" rgb2="0 0 0"/>
<texture name="texgeom" type="cube" builtin="flat" mark="cross" width="128" height="128" rgb1="0.6 0.6 0.6" rgb2="0.6 0.6 0.6" markrgb="1 1 1"/>
<texture name="texplane" type="2d" builtin="checker" rgb1=".4 .4 .4" rgb2=".6 .6 .6" width="512" height="512"/>
<material name="MatPlane" reflectance="0.3" texture="texplane" texrepeat="1 1" texuniform="true" rgba=".7 .7 .7 1"/>
<material name="capsule" texture="texgeom" texuniform="true" rgba=".4 .9 .6 1"/>
<material name="ellipsoid" texture="texgeom" texuniform="true" rgba=".4 .6 .9 1"/>
<material name="box" texture="texgeom" texuniform="true" rgba=".4 .9 .9 1"/>
<material name="cylinder" texture="texgeom" texuniform="true" rgba=".8 .6 .8 1"/>
<material name="sphere" texture="texgeom" texuniform="true" rgba=".9 .1 .1 1"/>
</asset>
<visual>
<quality shadowsize="4096" offsamples="8"/>
<map znear="0.1" force="0.05"/>
</visual>
<statistic extent="4"/>
<worldbody>
<light directional="true" diffuse=".8 .8 .8" pos="0 0 10" dir="0 0 -10"/>
<geom name="floor" type="plane" size="3 3 .5" material="MatPlane"/>
<geom class="border" fromto="-3 3 0 3 3 0"/>
<geom class="border" fromto="-3 -3 0 3 -3 0"/>
<geom class="border" fromto="3 3 0 3 -3 0"/>
<geom class="border" fromto="-3 3 0 -3 -3 0"/>
<geom class="borderpost" pos="3 3 0"/>
<geom class="borderpost" pos="-3 3 0"/>
<geom class="borderpost" pos="3 -3 0"/>
<geom class="borderpost" pos="-3 -3 0"/>
<replicate count="4" euler="0 0 90">
<geom type="plane" size=".5 3 .05" zaxis="1 0 0" pos="-3 0 0.4"/>
</replicate>
<replicate count="20" offset="0 0 0.2" euler="0 0 20">
<body pos="-2 0 0.5" euler="30 40 0">
<freejoint/>
<geom class="capsule"/>
</body>
</replicate>
<attach model="Humanoid" body="torso" prefix="1_"/>
<frame euler="0 0 72">
<replicate count="20" offset="0 0 0.2" euler="0 0 20">
<body pos="-2 0 0.5" euler="20 40 60">
<freejoint/>
<geom class="ellipsoid"/>
</body>
</replicate>
</frame>
<frame euler="0 0 144">
<replicate count="20" offset="0 0 0.2" euler="0 0 20">
<body pos="-2 0 0.5" euler="30 70 110">
<freejoint/>
<geom class="box"/>
</body>
</replicate>
</frame>
<frame pos="1 1 0" euler="0 0 144">
<attach model="Humanoid" body="torso" prefix="2_"/>
</frame>
<frame euler="0 0 216">
<replicate count="20" offset="0 0 0.2" euler="0 0 20">
<body pos="-2 0 0.5" euler="60 30 0">
<freejoint/>
<geom class="cylinder"/>
</body>
</replicate>
</frame>
<frame euler="0 0 288">
<replicate count="20" offset="0 0 0.2" euler="0 0 20">
<body pos="-2 0 0.5" euler="60 30 0">
<freejoint/>
<geom class="sphere"/>
</body>
</replicate>
</frame>
</worldbody>
</mujoco>
+252
View File
@@ -0,0 +1,252 @@
<mujoco model="Humanoid">
<option timestep="0.005"/>
<visual>
<map force="0.1"/>
<rgba haze="0.15 0.25 0.35 1"/>
<global offwidth="2560" offheight="1440" elevation="-20" azimuth="120"/>
</visual>
<statistic center="0 0 0.7"/>
<asset>
<texture type="skybox" builtin="gradient" rgb1=".3 .5 .7" rgb2="0 0 0" width="32" height="512"/>
<texture name="body" type="cube" builtin="flat" mark="cross" width="128" height="128" rgb1="0.8 0.6 0.4" rgb2="0.8 0.6 0.4" markrgb="1 1 1"/>
<material name="body" texture="body" texuniform="true" rgba="0.8 0.6 .4 1"/>
<texture name="grid" type="2d" builtin="checker" width="512" height="512" rgb1=".1 .2 .3" rgb2=".2 .3 .4"/>
<material name="grid" texture="grid" texrepeat="1 1" texuniform="true" reflectance=".2"/>
</asset>
<default>
<motor ctrlrange="-1 1" ctrllimited="true"/>
<default class="body">
<!-- geoms -->
<geom type="capsule" condim="1" friction=".7" solimp=".9 .99 .003" solref=".015 1" material="body" group="1"/>
<default class="thigh">
<geom size=".06"/>
</default>
<default class="shin">
<geom fromto="0 0 0 0 0 -.3" size=".049"/>
</default>
<default class="foot">
<geom size=".027"/>
<default class="foot1">
<geom fromto="-.07 -.01 0 .14 -.03 0"/>
</default>
<default class="foot2">
<geom fromto="-.07 .01 0 .14 .03 0"/>
</default>
</default>
<default class="arm_upper">
<geom size=".04"/>
</default>
<default class="arm_lower">
<geom size=".031"/>
</default>
<default class="hand">
<geom type="sphere" size=".04"/>
</default>
<!-- joints -->
<joint type="hinge" damping=".2" stiffness="1" armature=".01" limited="true" solimplimit="0 .99 .01"/>
<default class="joint_big">
<joint damping="5" stiffness="10"/>
<default class="hip_x">
<joint range="-30 10"/>
</default>
<default class="hip_z">
<joint range="-60 35"/>
</default>
<default class="hip_y">
<joint axis="0 1 0" range="-150 20"/>
</default>
<default class="joint_big_stiff">
<joint stiffness="20"/>
</default>
</default>
<default class="knee">
<joint pos="0 0 .02" axis="0 -1 0" range="-160 2"/>
</default>
<default class="ankle">
<joint range="-50 50"/>
<default class="ankle_y">
<joint pos="0 0 .08" axis="0 1 0" stiffness="6"/>
</default>
<default class="ankle_x">
<joint pos="0 0 .04" stiffness="3"/>
</default>
</default>
<default class="shoulder">
<joint range="-85 60"/>
</default>
<default class="elbow">
<joint range="-100 50" stiffness="0"/>
</default>
</default>
</default>
<worldbody>
<geom name="floor" size="0 0 .05" type="plane" material="grid" condim="3"/>
<light name="spotlight" mode="targetbodycom" target="torso" diffuse=".8 .8 .8" specular="0.3 0.3 0.3" pos="0 -6 4" cutoff="30"/>
<light name="top" pos="0 0 2" mode="trackcom"/>
<body name="torso" pos="0 0 1.282" childclass="body">
<camera name="back" pos="-3 0 1" xyaxes="0 -1 0 1 0 2" mode="trackcom"/>
<camera name="side" pos="0 -3 1" xyaxes="1 0 0 0 1 2" mode="trackcom"/>
<freejoint name="root"/>
<geom name="torso" fromto="0 -.07 0 0 .07 0" size=".07"/>
<geom name="waist_upper" fromto="-.01 -.06 -.12 -.01 .06 -.12" size=".06"/>
<body name="head" pos="0 0 .19">
<geom name="head" type="sphere" size=".09"/>
<camera name="egocentric" pos=".09 0 0" xyaxes="0 -1 0 .1 0 1" fovy="80"/>
</body>
<body name="waist_lower" pos="-.01 0 -.26">
<geom name="waist_lower" fromto="0 -.06 0 0 .06 0" size=".06"/>
<joint name="abdomen_z" pos="0 0 .065" axis="0 0 1" range="-45 45" class="joint_big_stiff"/>
<joint name="abdomen_y" pos="0 0 .065" axis="0 1 0" range="-75 30" class="joint_big"/>
<body name="pelvis" pos="0 0 -.165">
<joint name="abdomen_x" pos="0 0 .1" axis="1 0 0" range="-35 35" class="joint_big"/>
<geom name="butt" fromto="-.02 -.07 0 -.02 .07 0" size=".09"/>
<body name="thigh_right" pos="0 -.1 -.04">
<joint name="hip_x_right" axis="1 0 0" class="hip_x"/>
<joint name="hip_z_right" axis="0 0 1" class="hip_z"/>
<joint name="hip_y_right" class="hip_y"/>
<geom name="thigh_right" fromto="0 0 0 0 .01 -.34" class="thigh"/>
<body name="shin_right" pos="0 .01 -.4">
<joint name="knee_right" class="knee"/>
<geom name="shin_right" class="shin"/>
<body name="foot_right" pos="0 0 -.39">
<joint name="ankle_y_right" class="ankle_y"/>
<joint name="ankle_x_right" class="ankle_x" axis="1 0 .5"/>
<geom name="foot1_right" class="foot1"/>
<geom name="foot2_right" class="foot2"/>
</body>
</body>
</body>
<body name="thigh_left" pos="0 .1 -.04">
<joint name="hip_x_left" axis="-1 0 0" class="hip_x"/>
<joint name="hip_z_left" axis="0 0 -1" class="hip_z"/>
<joint name="hip_y_left" class="hip_y"/>
<geom name="thigh_left" fromto="0 0 0 0 -.01 -.34" class="thigh"/>
<body name="shin_left" pos="0 -.01 -.4">
<joint name="knee_left" class="knee"/>
<geom name="shin_left" fromto="0 0 0 0 0 -.3" class="shin"/>
<body name="foot_left" pos="0 0 -.39">
<joint name="ankle_y_left" class="ankle_y"/>
<joint name="ankle_x_left" class="ankle_x" axis="-1 0 -.5"/>
<geom name="foot1_left" class="foot1"/>
<geom name="foot2_left" class="foot2"/>
</body>
</body>
</body>
</body>
</body>
<body name="upper_arm_right" pos="0 -.17 .06">
<joint name="shoulder1_right" axis="2 1 1" class="shoulder"/>
<joint name="shoulder2_right" axis="0 -1 1" class="shoulder"/>
<geom name="upper_arm_right" fromto="0 0 0 .16 -.16 -.16" class="arm_upper"/>
<body name="lower_arm_right" pos=".18 -.18 -.18">
<joint name="elbow_right" axis="0 -1 1" class="elbow"/>
<geom name="lower_arm_right" fromto=".01 .01 .01 .17 .17 .17" class="arm_lower"/>
<body name="hand_right" pos=".18 .18 .18">
<geom name="hand_right" zaxis="1 1 1" class="hand"/>
</body>
</body>
</body>
<body name="upper_arm_left" pos="0 .17 .06">
<joint name="shoulder1_left" axis="-2 1 -1" class="shoulder"/>
<joint name="shoulder2_left" axis="0 -1 -1" class="shoulder"/>
<geom name="upper_arm_left" fromto="0 0 0 .16 .16 -.16" class="arm_upper"/>
<body name="lower_arm_left" pos=".18 .18 -.18">
<joint name="elbow_left" axis="0 -1 -1" class="elbow"/>
<geom name="lower_arm_left" fromto=".01 -.01 .01 .17 -.17 .17" class="arm_lower"/>
<body name="hand_left" pos=".18 -.18 .18">
<geom name="hand_left" zaxis="1 -1 1" class="hand"/>
</body>
</body>
</body>
</body>
</worldbody>
<contact>
<exclude body1="waist_lower" body2="thigh_right"/>
<exclude body1="waist_lower" body2="thigh_left"/>
</contact>
<tendon>
<fixed name="hamstring_right" limited="true" range="-0.3 2">
<joint joint="hip_y_right" coef=".5"/>
<joint joint="knee_right" coef="-.5"/>
</fixed>
<fixed name="hamstring_left" limited="true" range="-0.3 2">
<joint joint="hip_y_left" coef=".5"/>
<joint joint="knee_left" coef="-.5"/>
</fixed>
</tendon>
<actuator>
<motor name="abdomen_z" gear="40" joint="abdomen_z"/>
<motor name="abdomen_y" gear="40" joint="abdomen_y"/>
<motor name="abdomen_x" gear="40" joint="abdomen_x"/>
<motor name="hip_x_right" gear="40" joint="hip_x_right"/>
<motor name="hip_z_right" gear="40" joint="hip_z_right"/>
<motor name="hip_y_right" gear="120" joint="hip_y_right"/>
<motor name="knee_right" gear="80" joint="knee_right"/>
<motor name="ankle_y_right" gear="20" joint="ankle_y_right"/>
<motor name="ankle_x_right" gear="20" joint="ankle_x_right"/>
<motor name="hip_x_left" gear="40" joint="hip_x_left"/>
<motor name="hip_z_left" gear="40" joint="hip_z_left"/>
<motor name="hip_y_left" gear="120" joint="hip_y_left"/>
<motor name="knee_left" gear="80" joint="knee_left"/>
<motor name="ankle_y_left" gear="20" joint="ankle_y_left"/>
<motor name="ankle_x_left" gear="20" joint="ankle_x_left"/>
<motor name="shoulder1_right" gear="20" joint="shoulder1_right"/>
<motor name="shoulder2_right" gear="20" joint="shoulder2_right"/>
<motor name="elbow_right" gear="40" joint="elbow_right"/>
<motor name="shoulder1_left" gear="20" joint="shoulder1_left"/>
<motor name="shoulder2_left" gear="20" joint="shoulder2_left"/>
<motor name="elbow_left" gear="40" joint="elbow_left"/>
</actuator>
<keyframe>
<!--
The values below are split into rows for readibility:
torso position
torso orientation
spinal
right leg
left leg
arms
-->
<key name="squat"
qpos="0 0 0.596
0.988015 0 0.154359 0
0 0.4 0
-0.25 -0.5 -2.5 -2.65 -0.8 0.56
-0.25 -0.5 -2.5 -2.65 -0.8 0.56
0 0 0 0 0 0"/>
<key name="stand_on_left_leg"
qpos="0 0 1.21948
0.971588 -0.179973 0.135318 -0.0729076
-0.0516 -0.202 0.23
-0.24 -0.007 -0.34 -1.76 -0.466 -0.0415
-0.08 -0.01 -0.37 -0.685 -0.35 -0.09
0.109 -0.067 -0.7 -0.05 0.12 0.16"/>
<key name="prone"
qpos="0.4 0 0.0757706
0.7325 0 0.680767 0
0 0.0729 0
0.0077 0.0019 -0.026 -0.351 -0.27 0
0.0077 0.0019 -0.026 -0.351 -0.27 0
0.56 -0.62 -1.752
0.56 -0.62 -1.752"/>
<key name="supine"
qpos="-0.4 0 0.08122
0.722788 0 -0.69107 0
0 -0.25 0
0.0182 0.0142 0.3 0.042 -0.44 -0.02
0.0182 0.0142 0.3 0.042 -0.44 -0.02
0.186 -0.73 -1.73
0.186 -0.73 -1.73"/>
</keyframe>
</mujoco>