Skip to content

Commit 5b66a14

Browse files
committed
Fix locomotion packaging and platform validation
1 parent 8dfe06e commit 5b66a14

22 files changed

Lines changed: 926 additions & 534 deletions

docs/env/dm_control_locomotion.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ unchanged. The four CMU egocentric cameras allow at most five color levels per
191191
channel and a total absolute error of 20 across the entire 64-by-64 frame;
192192
the rodent maze camera allows one level in one channel. Public renders allow
193193
one level in at most three channels for go-to-target and escape-bowl, or one
194-
channel for tracking. Other images and all native dynamics/reward replays
194+
channel for tracking and heterogeneous forage. Other images and all native dynamics/reward replays
195195
remain bitwise. These limits are checked per frame, not averaged over a rollout.
196196

197197
Oracle reward checks retain only small derived-math residuals: tracking's

envpool/mujoco/locomotion/cmu.cc

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18+
#include <array>
1819
#include <memory>
1920
#include <set>
2021
#include <sstream>
@@ -31,14 +32,20 @@ std::vector<double> Read(pugi::xml_node node, const char* attribute) {
3132
std::istringstream stream(node.attribute(attribute).value());
3233
std::vector<double> values;
3334
double value;
34-
while (stream >> value) values.push_back(value);
35+
while (stream >> value) {
36+
values.push_back(value);
37+
}
3538
return values;
3639
}
3740

3841
void Scale(pugi::xml_node node, const char* attribute, double factor) {
3942
auto values = Read(node, attribute);
40-
if (values.empty()) return;
41-
for (double& value : values) value *= factor;
43+
if (values.empty()) {
44+
return;
45+
}
46+
for (double& value : values) {
47+
value *= factor;
48+
}
4249
Set(node, attribute, Numbers(values.data(), values.size()));
4350
}
4451

@@ -65,22 +72,26 @@ void Rescale(pugi::xml_node model) {
6572
mjVFS vfs;
6673
mj_defaultVFS(&vfs);
6774
mj_addBufferVFS(&vfs, "cmu.xml", contents.data(), contents.size());
68-
char error[1024]{};
75+
std::array<char, 1024> error{};
6976
std::unique_ptr<mjModel, decltype(&mj_deleteModel)> physics(
70-
mj_loadXML("cmu.xml", &vfs, error, sizeof(error)), mj_deleteModel);
77+
mj_loadXML("cmu.xml", &vfs, error.data(), sizeof(error)), mj_deleteModel);
7178
mj_deleteVFS(&vfs);
72-
if (!physics) throw std::runtime_error(error);
79+
if (!physics) {
80+
throw std::runtime_error(error.data());
81+
}
7382
const int root = mj_name2id(physics.get(), mjOBJ_BODY, "root");
7483
const double mass_factor = 70 / physics->body_subtreemass[root];
75-
for (auto entry : Find(model, "body", "root").select_nodes(".//inertial"))
84+
for (auto entry : Find(model, "body", "root").select_nodes(".//inertial")) {
7685
Scale(entry.node(), "mass", mass_factor);
86+
}
7787
for (auto entry : Find(model, "body", "root").select_nodes(".//geom")) {
7888
auto geom = entry.node();
79-
if (geom.attribute("mass"))
89+
if (geom.attribute("mass") != nullptr) {
8090
Scale(geom, "mass", mass_factor);
81-
else
91+
} else {
8292
Set(geom, "density",
8393
geom.attribute("density").as_double(1000) * mass_factor);
94+
}
8495
}
8596
}
8697

@@ -124,8 +135,9 @@ void Scene::CmuVisuals(pugi::xml_node model, Walker walker, int player,
124135
auto visual = geom.parent().append_child("geom");
125136
Set(visual, "name", name + "_visual");
126137
for (const char* attr : {"type", "quat", "pos", "size"}) {
127-
if (geom.attribute(attr))
138+
if (geom.attribute(attr) != nullptr) {
128139
Set(visual, attr, geom.attribute(attr).value());
140+
}
129141
}
130142
Scale(visual, "size", name == hand ? 1.3 : 1.5);
131143
Scale(visual, "pos", 1.5);
@@ -153,33 +165,38 @@ void Scene::CmuVisuals(pugi::xml_node model, Walker walker, int player,
153165
for (const char* name :
154166
{"lhipjoint", "rhipjoint", "lfemur", "lowerback", "upperback",
155167
"rclavicle", "lclavicle", "thorax", "lhumerus", "root_geom",
156-
"lowerneck", "rhumerus", "rfemur"})
168+
"lowerneck", "rhumerus", "rfemur"}) {
157169
Set(Find(model, "geom", name), "rgba", {0, 0, 0, 0});
158-
const double neck = .066 - .0452401, arm = .20 - .138421,
159-
leg = .384 - .202473;
160-
const char* bodies[]{"lowerneck", "lhumerus", "rhumerus", "lfemur", "rfemur"};
161-
const char* names[]{"halfneck", "lelbow", "relbow", "lknee", "rknee"};
162-
const double sizes[5][2]{{.05, .02279225 - neck},
163-
{.035, .1245789 - arm},
164-
{.035, .1245789 - arm},
165-
{.055, .1822257 - leg},
166-
{.055, .1822257 - leg}};
167-
const double positions[5][3]{{-.00165071, .0452401 + neck, .00534359},
168-
{0, -.138421 - arm, 0},
169-
{0, -.138421 - arm, 0},
170-
{-5.0684e-8, -.202473 - leg, 0},
171-
{-5.0684e-8, -.202473 - leg, 0}};
172-
const double quats[5][4]{{.66437, .746906, .027253, 0},
173-
{.612372, -.612372, .353553, .353553},
174-
{.612372, -.612372, -.353553, -.353553},
175-
{.696364, -.696364, -.122788, -.122788},
176-
{.696364, -.696364, .122788, .122788}};
170+
}
171+
const double neck = .066 - .0452401;
172+
const double arm = .20 - .138421;
173+
const double leg = .384 - .202473;
174+
const std::array bodies{"lowerneck", "lhumerus", "rhumerus", "lfemur",
175+
"rfemur"};
176+
const std::array names{"halfneck", "lelbow", "relbow", "lknee", "rknee"};
177+
const std::array<std::array<double, 2>, 5> sizes{{{.05, .02279225 - neck},
178+
{.035, .1245789 - arm},
179+
{.035, .1245789 - arm},
180+
{.055, .1822257 - leg},
181+
{.055, .1822257 - leg}}};
182+
const std::array<std::array<double, 3>, 5> positions{
183+
{{-.00165071, .0452401 + neck, .00534359},
184+
{0, -.138421 - arm, 0},
185+
{0, -.138421 - arm, 0},
186+
{-5.0684e-8, -.202473 - leg, 0},
187+
{-5.0684e-8, -.202473 - leg, 0}}};
188+
const std::array<std::array<double, 4>, 5> quats{
189+
{{.66437, .746906, .027253, 0},
190+
{.612372, -.612372, .353553, .353553},
191+
{.612372, -.612372, -.353553, -.353553},
192+
{.696364, -.696364, -.122788, -.122788},
193+
{.696364, -.696364, .122788, .122788}}};
177194
for (int i = 0; i < 5; ++i) {
178195
auto geom = Find(model, "body", bodies[i]).append_child("geom");
179196
Set(geom, "name", names[i]);
180-
Set(geom, "size", Numbers(sizes[i], 2));
181-
Set(geom, "pos", Numbers(positions[i], 3));
182-
Set(geom, "quat", Numbers(quats[i], 4));
197+
Set(geom, "size", Numbers(sizes[i].data(), 2));
198+
Set(geom, "pos", Numbers(positions[i].data(), 3));
199+
Set(geom, "quat", Numbers(quats[i].data(), 4));
183200
Set(geom, "mass", 0);
184201
Set(geom, "contype", 0);
185202
Set(geom, "conaffinity", 0);

envpool/mujoco/locomotion/locomotion_env.h

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,14 @@ class LocomotionEnvSpec : public EnvSpec<LocomotionEnvFns> {
7676
Config config(values);
7777
const auto task = GetTaskConfig(config["task_name"_]);
7878
const int team = config["team_size"_];
79-
if (team < 1 || team > 11)
79+
if (team < 1 || team > 11) {
8080
throw std::invalid_argument("team_size must be between 1 and 11");
81+
}
8182
config["max_num_players"_] = task.task == Task::kSoccer ? 2 * team : 1;
8283
double& limit = config["time_limit"_];
83-
if (limit == -1) limit = task.time_limit;
84+
if (limit == -1) {
85+
limit = task.time_limit;
86+
}
8487
if (!std::isfinite(limit) || limit <= 0 ||
8588
config["max_episode_steps"_] <= 0) {
8689
throw std::invalid_argument(
@@ -97,16 +100,16 @@ class LocomotionEnv : public Env<LocomotionEnvSpec>, public RenderableEnv {
97100
LocomotionEnv(const Spec& spec, int env_id)
98101
: Base(spec, env_id), simulation_(MakeOptions(spec, seed_)) {}
99102

100-
bool IsDone() override { return simulation_.done(); }
103+
bool IsDone() override { return simulation_.Done(); }
101104

102105
void Reset() override {
103-
std::lock_guard<std::mutex> lock(mutex_);
106+
std::scoped_lock lock(mutex_);
104107
simulation_.Reset();
105108
WriteState();
106109
}
107110

108111
void Step(const Action& action) override {
109-
std::lock_guard<std::mutex> lock(mutex_);
112+
std::scoped_lock lock(mutex_);
110113
simulation_.Step(static_cast<const double*>(action["action"_].Data()));
111114
WriteState();
112115
}
@@ -117,9 +120,10 @@ class LocomotionEnv : public Env<LocomotionEnvSpec>, public RenderableEnv {
117120

118121
void Render(int width, int height, int camera,
119122
unsigned char* output) override {
120-
std::lock_guard<std::mutex> lock(mutex_);
121-
if (simulation_.model() == nullptr)
123+
std::scoped_lock lock(mutex_);
124+
if (simulation_.Model() == nullptr) {
122125
throw std::runtime_error("reset before rendering");
126+
}
123127
simulation_.Render(width, height, camera, output);
124128
}
125129

@@ -128,9 +132,10 @@ class LocomotionEnv : public Env<LocomotionEnvSpec>, public RenderableEnv {
128132
// changes the simulator or synchronizes either side of an oracle rollout.
129133
template <typename Function>
130134
auto Inspect(Function function) {
131-
std::lock_guard<std::mutex> lock(mutex_);
132-
if (simulation_.model() == nullptr)
135+
std::scoped_lock lock(mutex_);
136+
if (simulation_.Model() == nullptr) {
133137
throw std::runtime_error("reset before inspecting physics");
138+
}
134139
return function(simulation_);
135140
}
136141
#endif
@@ -154,7 +159,7 @@ class LocomotionEnv : public Env<LocomotionEnvSpec>, public RenderableEnv {
154159
}
155160

156161
void WriteState() {
157-
State state = Allocate(simulation_.players());
162+
State state = Allocate(simulation_.Players());
158163
state["obs:continuous"_] = 0;
159164
state["obs:discrete"_] = 0;
160165
state["obs:pixels"_] = 0;
@@ -164,13 +169,13 @@ class LocomotionEnv : public Env<LocomotionEnvSpec>, public RenderableEnv {
164169
static_cast<int64_t*>(state["obs:discrete"_].Data()));
165170
std::copy(simulation_.pixels.begin(), simulation_.pixels.end(),
166171
static_cast<uint8_t*>(state["obs:pixels"_].Data()));
167-
for (int i = 0; i < simulation_.players(); ++i) {
172+
for (int i = 0; i < simulation_.Players(); ++i) {
168173
state["reward"_][i] = simulation_.rewards[i];
169174
state["reward64"_][i] = simulation_.rewards[i];
170175
state["discount"_][i] = simulation_.discount;
171176
}
172-
state["terminated"_] = simulation_.terminated();
173-
state["trunc"_] = simulation_.truncated();
177+
state["terminated"_] = simulation_.Terminated();
178+
state["trunc"_] = simulation_.Truncated();
174179
}
175180

176181
Simulation simulation_;
@@ -196,8 +201,9 @@ class LocomotionEnvPool : public AsyncEnvPool<LocomotionEnv> {
196201
#ifdef ENVPOOL_TEST
197202
template <typename Function>
198203
auto Inspect(int env_id, Function function) {
199-
if (env_id < 0 || env_id >= static_cast<int>(envs_.size()))
204+
if (env_id < 0 || env_id >= static_cast<int>(envs_.size())) {
200205
throw std::out_of_range("invalid env_id");
206+
}
201207
return envs_[env_id]->Inspect(function);
202208
}
203209
#endif
@@ -225,13 +231,15 @@ class LocomotionEnvPool : public AsyncEnvPool<LocomotionEnv> {
225231
for (std::size_t i = 0; i < actions[0].size; ++i) {
226232
const int id = env_ids[i];
227233
if (id < 0 || id >= static_cast<int>(envs_.size()) ||
228-
!selected.insert(id).second)
234+
!selected.insert(id).second) {
229235
throw std::invalid_argument("invalid or duplicate env_id");
236+
}
230237
}
231238
for (std::size_t i = 0; i < actions[1].size; ++i) {
232239
const int id = player_ids[i];
233-
if (!selected.count(id) || ++counts[id] > players)
240+
if ((selected.count(id) == 0u) || ++counts[id] > players) {
234241
throw std::invalid_argument("expected one action for every player");
242+
}
235243
}
236244
}
237245
};

envpool/mujoco/locomotion/locomotion_envpool.cc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
#include <array>
1516
#include <cstring>
1617
#include <map>
1718
#include <string>
@@ -45,8 +46,8 @@ PYBIND11_MODULE(locomotion_envpool, m) {
4546
return self.Inspect(
4647
env_id, [include_model](const mujoco_locomotion::Simulation& sim) {
4748
py::dict result;
48-
const mjModel* model = sim.model();
49-
const mjData* data = sim.data();
49+
const mjModel* model = sim.Model();
50+
const mjData* data = sim.Data();
5051
for (const auto& [key, ptr, size] :
5152
std::vector<std::tuple<const char*, const double*, int>>{
5253
{"qpos", data->qpos, model->nq},
@@ -65,8 +66,8 @@ PYBIND11_MODULE(locomotion_envpool, m) {
6566
std::vector<char> bytes(mj_sizeModel(model));
6667
mj_saveModel(model, nullptr, bytes.data(), bytes.size());
6768
result["model"] = py::bytes(bytes.data(), bytes.size());
68-
result["maze"] = sim.scene().maze_entities;
69-
result["variations"] = sim.scene().maze_variations;
69+
result["maze"] = sim.GetScene().maze_entities;
70+
result["variations"] = sim.GetScene().maze_variations;
7071
}
7172
return result;
7273
});

envpool/mujoco/locomotion/locomotion_test.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ def assert_pixels(
5454
"rodent_escape_bowl",
5555
}:
5656
peak, total = 1, 3
57-
elif not egocentric and task == "cmu_humanoid_tracking":
57+
elif not egocentric and task in {
58+
"cmu_humanoid_tracking",
59+
"cmu_humanoid_heterogeneous_forage",
60+
}:
5861
peak = total = 1
5962
if total:
6063
np.testing.assert_equal(actual.shape, expected.shape)

0 commit comments

Comments
 (0)