Allow more than 1 activation state per actuator, but only for dyntype user.
Fixes #305. PiperOrigin-RevId: 486597273 Change-Id: I3eb806f60949e68ebe6cbe35a6e66218a167ed45
This commit is contained in:
committed by
Copybara-Service
parent
0514010928
commit
36b30e4500
@@ -429,5 +429,68 @@ TEST_F(ForwardTest, gravcomp) {
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// user defined 2nd-order activation dynamics: frequency-controlled oscillator
|
||||
// note that scalar mjcb_act_dyn callbacks are expected to return act_dot, but
|
||||
// since we have a vector output we write into act_dot directly
|
||||
mjtNum oscillator(const mjModel* m, const mjData *d, int id) {
|
||||
// check that actnum == 2
|
||||
if (m->actuator_actnum[id] != 2) {
|
||||
mju_error("callback expected actnum == 2");
|
||||
}
|
||||
|
||||
// get pointers to activations (inputs) and their derivatives (outputs)
|
||||
mjtNum* act = d->act + m->actuator_actadr[id];
|
||||
mjtNum* act_dot = d->act_dot + m->actuator_actadr[id];
|
||||
|
||||
// harmonic oscillator with controlled frequency
|
||||
mjtNum frequency = 2*mjPI*d->ctrl[id];
|
||||
act_dot[0] = -act[1] * frequency;
|
||||
act_dot[1] = act[0] * frequency;
|
||||
|
||||
return 0; // ignored by caller
|
||||
}
|
||||
|
||||
TEST_F(ForwardTest, MjcbActDynSecondOrderExpectsActnum) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<option timestep="1e-4"/>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom size="1"/>
|
||||
<joint name="hinge"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general joint="hinge" dyntype="user" actdim="2"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
// install global dynamics callback
|
||||
mjcb_act_dyn = oscillator;
|
||||
|
||||
// for two arbitrary frequencies, compare actuator force as output by the
|
||||
// user-defined oscillator and analytical sine function
|
||||
for (mjtNum frequency : {1.5, 0.7}) {
|
||||
mj_resetData(model, data);
|
||||
data->ctrl[0] = frequency; // set desired oscillation frequency
|
||||
data->act[0] = 1; // initialise activation
|
||||
|
||||
// simulate and compare to sine function
|
||||
while (data->time < 1) {
|
||||
mjtNum expected_force = mju_sin(2*mjPI*data->time*frequency);
|
||||
mj_step(model, data);
|
||||
EXPECT_NEAR(data->actuator_force[0], expected_force, .01);
|
||||
}
|
||||
}
|
||||
|
||||
// uninstall global dynamics callback
|
||||
mjcb_act_dyn = nullptr;
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
|
||||
@@ -536,6 +536,74 @@ TEST_F(ActRangeTest, ActRangeDefaultsPropagate) {
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// ---------------------------- test actdim ------------------------------------
|
||||
|
||||
using ActDimTest = MujocoTest;
|
||||
|
||||
TEST_F(ActDimTest, BiggerThanOneOnlyForUser) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom size="1"/>
|
||||
<joint name="hinge"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general joint="hinge" actdim="2"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(),
|
||||
HasSubstr("actdim > 1 is only allowed for dyntype 'user'"));
|
||||
}
|
||||
|
||||
TEST_F(ActDimTest, NonzeroNotAllowedInStateless) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom size="1"/>
|
||||
<joint name="hinge"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general joint="hinge" actdim="1"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("invalid actdim 1 in stateless"));
|
||||
}
|
||||
|
||||
TEST_F(ActDimTest, ZeroNotAllowedInStateful) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom size="1"/>
|
||||
<joint name="hinge"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general joint="hinge" dyntype="filter" actdim="0"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("invalid actdim 0 in stateful"));
|
||||
}
|
||||
|
||||
// ------------- test nuser_xxx fields -----------------------------------------
|
||||
|
||||
using UserDataTest = MujocoTest;
|
||||
|
||||
@@ -361,21 +361,6 @@ TEST_F(XMLReaderTest, AllowsSpaces) {
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(XMLReaderTest, gravcomp) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body gravcomp="1.01">
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
EXPECT_EQ(model->body_gravcomp[1], 1.01);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(XMLReaderTest, InvalidDoubleOrientation) {
|
||||
std::string prefix = "<mujoco><worldbody><";
|
||||
std::string suffix = "/></worldbody></mujoco>";
|
||||
@@ -924,6 +909,31 @@ TEST_F(ActuatorParseTest, AdhesionInheritsFromGeneral) {
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(ActuatorParseTest, ActdimDefaultsPropagate) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<default>
|
||||
<general actdim="2"/>
|
||||
</default>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom size="1"/>
|
||||
<joint name="hinge"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general joint="hinge" dyntype="user"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << error.data();
|
||||
|
||||
// expect that actdim was inherited from the general default
|
||||
EXPECT_EQ(model->actuator_actnum[0], 2);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// ------------- test general parsing ------------------------------------------
|
||||
|
||||
|
||||
@@ -770,6 +770,51 @@ TEST_F(XMLWriterTest, SpringlengthTwoValues) {
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(XMLWriterTest, Actdim) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom size="1"/>
|
||||
<joint name="hinge"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general joint="hinge" dyntype="user" actdim="2"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
std::string saved_xml = SaveAndReadXml(model);
|
||||
EXPECT_THAT(saved_xml, HasSubstr("actdim=\"2\""));
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(XMLWriterTest, ActdimDefaults) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<default>
|
||||
<general actdim="2"/>
|
||||
</default>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom size="1"/>
|
||||
<joint name="hinge"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general joint="hinge" dyntype="user"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
std::string saved_xml = SaveAndReadXml(model);
|
||||
EXPECT_THAT(saved_xml, HasSubstr("actdim=\"2\""));
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// check that no precision is lost when saving XMLs with FullFloatPrecision
|
||||
TEST_F(XMLWriterTest, SetPrecision) {
|
||||
static constexpr char xml[] = R"(
|
||||
|
||||
Reference in New Issue
Block a user