Enable float32 testing for most MuJoCo engine and user tests.

PiperOrigin-RevId: 886697701
Change-Id: I4a96fae03ea18494c3fcef8eb17b3b6f0863e9b7
This commit is contained in:
Yuval Tassa
2026-03-20 04:04:34 -07:00
committed by Copybara-Service
parent cab4b73f41
commit 9fa3f6f77e
28 changed files with 536 additions and 423 deletions
+7 -2
View File
@@ -29,7 +29,6 @@
namespace mujoco {
namespace {
using ::testing::DoubleNear;
using ::testing::IsNull;
using ::testing::NotNull;
using ::testing::HasSubstr;
@@ -266,6 +265,9 @@ TEST_F(UserFlexTest, FlexNotCollide) {
}
TEST_F(UserFlexTest, BoundingBoxCoordinates) {
#ifdef mjUSESINGLE
GTEST_SKIP() << "Float32 rounding in bounding box centering gives ~3e-8 error";
#endif
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
@@ -428,6 +430,9 @@ TEST_F(UserFlexTest, TrilinearInterpolation) {
}
TEST_F(UserFlexTest, StiffnessMatrix) {
#ifdef mjUSESINGLE
GTEST_SKIP() << "Stiffness matrix kernel check fails in float32 precision";
#endif
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
@@ -452,7 +457,7 @@ TEST_F(UserFlexTest, StiffnessMatrix) {
ones[i] = 1;
}
mju_mulMatVec(res, m->flex_stiffness, ones, 3*m->nflexnode, 3*m->nflexnode);
EXPECT_THAT(res, Pointwise(DoubleNear(1e-8), zeros));
EXPECT_THAT(res, Pointwise(MjNear(1e-8, 1e-4), zeros));
mj_deleteModel(m);
}
+37 -37
View File
@@ -979,14 +979,14 @@ TEST_F(MjCMeshTest, MeshPosQuat) {
mju_mulPose(recovered_pos, recovered_quat,
&model->geom_pos[0], &model->geom_quat[0],
inverse_mesh_pos, inverse_mesh_quat);
EXPECT_NEAR(recovered_pos[0], 0, 1e-12);
EXPECT_NEAR(recovered_pos[1], 0, 1e-12);
EXPECT_NEAR(recovered_pos[2], 0, 1e-12);
EXPECT_NEAR(recovered_pos[0], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[1], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[2], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[0], 1, 1e-12);
EXPECT_NEAR(recovered_quat[1], 0, 1e-12);
EXPECT_NEAR(recovered_quat[2], 0, 1e-12);
EXPECT_NEAR(recovered_quat[3], 0, 1e-12);
EXPECT_NEAR(recovered_quat[0], 1, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[1], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[2], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[3], 0, MjTol(1e-12, 1e-6));
// same test on the other geom
mju_negPose(inverse_mesh_pos, inverse_mesh_quat,
@@ -994,14 +994,14 @@ TEST_F(MjCMeshTest, MeshPosQuat) {
mju_mulPose(recovered_pos, recovered_quat,
&model->geom_pos[3], &model->geom_quat[4],
inverse_mesh_pos, inverse_mesh_quat);
EXPECT_NEAR(recovered_pos[0], 1, 1e-12);
EXPECT_NEAR(recovered_pos[1], 2, 1e-12);
EXPECT_NEAR(recovered_pos[2], 3, 1e-12);
EXPECT_NEAR(recovered_pos[0], 1, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[1], 2, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[2], 3, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[0], 0.5, 1e-12);
EXPECT_NEAR(recovered_quat[1], 0.5, 1e-12);
EXPECT_NEAR(recovered_quat[2], 0.5, 1e-12);
EXPECT_NEAR(recovered_quat[3], 0.5, 1e-12);
EXPECT_NEAR(recovered_quat[0], 0.5, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[1], 0.5, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[2], 0.5, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[3], 0.5, MjTol(1e-12, 1e-6));
mj_deleteModel(model);
}
@@ -1038,14 +1038,14 @@ TEST_F(MjCMeshTest, MeshPosQuatShellInertia) {
mju_mulPose(recovered_pos, recovered_quat,
&model->geom_pos[0], &model->geom_quat[0],
inverse_mesh_pos, inverse_mesh_quat);
EXPECT_NEAR(recovered_pos[0], 0, 1e-12);
EXPECT_NEAR(recovered_pos[1], 0, 1e-12);
EXPECT_NEAR(recovered_pos[2], 0, 1e-12);
EXPECT_NEAR(recovered_pos[0], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[1], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[2], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[0], 1, 1e-12);
EXPECT_NEAR(recovered_quat[1], 0, 1e-12);
EXPECT_NEAR(recovered_quat[2], 0, 1e-12);
EXPECT_NEAR(recovered_quat[3], 0, 1e-12);
EXPECT_NEAR(recovered_quat[0], 1, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[1], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[2], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[3], 0, MjTol(1e-12, 1e-6));
// same test on the other geom
mju_negPose(inverse_mesh_pos, inverse_mesh_quat,
@@ -1053,14 +1053,14 @@ TEST_F(MjCMeshTest, MeshPosQuatShellInertia) {
mju_mulPose(recovered_pos, recovered_quat,
&model->geom_pos[3], &model->geom_quat[4],
inverse_mesh_pos, inverse_mesh_quat);
EXPECT_NEAR(recovered_pos[0], 1, 1e-12);
EXPECT_NEAR(recovered_pos[1], 2, 1e-12);
EXPECT_NEAR(recovered_pos[2], 3, 1e-12);
EXPECT_NEAR(recovered_pos[0], 1, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[1], 2, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[2], 3, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[0], 0.5, 1e-12);
EXPECT_NEAR(recovered_quat[1], 0.5, 1e-12);
EXPECT_NEAR(recovered_quat[2], 0.5, 1e-12);
EXPECT_NEAR(recovered_quat[3], 0.5, 1e-12);
EXPECT_NEAR(recovered_quat[0], 0.5, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[1], 0.5, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[2], 0.5, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[3], 0.5, MjTol(1e-12, 1e-6));
mj_deleteModel(model);
}
@@ -1443,7 +1443,7 @@ TEST_F(MjCMeshTest, OctreeHangingNodeInterpolation) {
mjModel* model = mj_compile(spec, 0);
ASSERT_THAT(model, NotNull()) << error.data();
EXPECT_GT(model->mesh_octnum[0], 0);
double kEps = 1e-6;
const mjtNum kEps = MjTol(1e-6, 1e-4);
const int octree_adr = model->mesh_octadr[0];
const int noct = model->mesh_octnum[0];
@@ -1604,14 +1604,14 @@ TEST_F(MjCMeshTest, OctreeNotComputedForNonSDF) {
mj_deleteModel(model);
}
double CubeSDF(double p[3], double b[3]) {
double q[3] = {std::abs(p[0]) - b[0],
std::abs(p[1]) - b[1],
std::abs(p[2]) - b[2]};
return std::sqrt(std::pow(std::max(q[0], 0.0), 2) +
std::pow(std::max(q[1], 0.0), 2) +
std::pow(std::max(q[2], 0.0), 2)) +
std::min(std::max(q[0], std::max(q[1], q[2])), 0.0);
mjtNum CubeSDF(mjtNum p[3], mjtNum b[3]) {
mjtNum q[3] = {mju_abs(p[0]) - b[0],
mju_abs(p[1]) - b[1],
mju_abs(p[2]) - b[2]};
return mju_sqrt(std::pow(std::max(q[0], (mjtNum)0), 2) +
std::pow(std::max(q[1], (mjtNum)0), 2) +
std::pow(std::max(q[2], (mjtNum)0), 2)) +
std::min(std::max(q[0], std::max(q[1], q[2])), (mjtNum)0);
}
TEST_F(MjCMeshTest, OctreeCube) {
+5 -7
View File
@@ -31,7 +31,6 @@
namespace mujoco {
namespace {
using ::testing::DoubleNear;
using ::testing::ElementsAre;
using ::testing::HasSubstr;
using ::testing::IsNull;
@@ -168,8 +167,8 @@ TEST_F(UserModelTest, SameFrame) {
// expect them to be equal
constexpr double eps = 1e-6;
EXPECT_THAT(geom_xpos, Pointwise(DoubleNear(eps), geom_xpos2));
EXPECT_THAT(geom_xmat, Pointwise(DoubleNear(eps), geom_xmat2));
EXPECT_THAT(geom_xpos, Pointwise(MjNear(eps, eps), geom_xpos2));
EXPECT_THAT(geom_xmat, Pointwise(MjNear(eps, eps), geom_xmat2));
mj_deleteData(data);
mj_deleteModel(model);
@@ -606,7 +605,7 @@ TEST_F(FuseStaticTest, FuseStaticEquivalent) {
mj_step(m_fuse, d_fuse);
mj_step(m_no_fuse, d_no_fuse);
EXPECT_THAT(d_fuse->qvel[0], DoubleNear(d_no_fuse->qvel[0], 2e-17))
EXPECT_NEAR(d_fuse->qvel[0], d_no_fuse->qvel[0], MjTol(2e-17, 1e-8))
<< "Velocity should be the same after 1 step";
EXPECT_NE(d_fuse->qvel[0], 0);
@@ -887,9 +886,8 @@ TEST_F(LengthRangeTest, LengthRangeThreading) {
EXPECT_THAT(model1, NotNull()) << error;
// model is such that the lengthrange for first actuator is [1, sqrt(5)]
EXPECT_THAT(model1->actuator_lengthrange[0], DoubleNear(1.0, 1e-3));
EXPECT_THAT(model1->actuator_lengthrange[1],
DoubleNear(std::sqrt(5.0), 1e-3));
EXPECT_NEAR(model1->actuator_lengthrange[0], 1.0, 1e-3);
EXPECT_NEAR(model1->actuator_lengthrange[1], std::sqrt(5.0), 1e-3);
// recompile without threads
ASSERT_EQ(spec->compiler.usethread, 1);
+46 -32
View File
@@ -31,10 +31,9 @@
namespace mujoco {
namespace {
constexpr double kInertiaTol = 1e-6;
constexpr double kInertiaTol = MjTol(1e-6, 1e-6);
using std::string;
using ::testing::DoubleNear;
using ::testing::ElementsAre;
using ::testing::HasSubstr;
using ::testing::IsNull;
@@ -385,10 +384,10 @@ TEST_F(KeyframeTest, CheckValues) {
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, nullptr, 0);
ASSERT_THAT(model, NotNull());
EXPECT_EQ(model->nkey, 7);
EXPECT_EQ(model->key_time[0 * 1], 0.1);
EXPECT_EQ(model->key_qpos[1 * model->nq], 0.2);
EXPECT_EQ(model->key_qvel[2 * model->nv], 0.3);
EXPECT_EQ(model->key_act[3 * model->na], 0.4);
EXPECT_MJTNUM_EQ(model->key_time[0 * 1], 0.1);
EXPECT_MJTNUM_EQ(model->key_qpos[1 * model->nq], 0.2);
EXPECT_MJTNUM_EQ(model->key_qvel[2 * model->nv], 0.3);
EXPECT_MJTNUM_EQ(model->key_act[3 * model->na], 0.4);
EXPECT_THAT(AsVector(model->key_ctrl + 4 * model->nu, model->nu),
ElementsAre(0.5, 0.6));
EXPECT_THAT(AsVector(model->key_mpos + 3 * model->nmocap * 5, 3),
@@ -405,20 +404,20 @@ TEST_F(KeyframeTest, ResetDataKeyframe) {
mjData* data = mj_makeData(model);
mj_resetDataKeyframe(model, data, 0);
EXPECT_EQ(data->time, 0.1);
EXPECT_MJTNUM_EQ(data->time, 0.1);
mj_resetDataKeyframe(model, data, 1);
EXPECT_EQ(data->qpos[0], 0.2);
EXPECT_MJTNUM_EQ(data->qpos[0], 0.2);
mj_resetDataKeyframe(model, data, 2);
EXPECT_EQ(data->qvel[0], 0.3);
EXPECT_MJTNUM_EQ(data->qvel[0], 0.3);
mj_resetDataKeyframe(model, data, 3);
EXPECT_EQ(data->act[0], 0.4);
EXPECT_MJTNUM_EQ(data->act[0], 0.4);
mj_resetDataKeyframe(model, data, 4);
EXPECT_EQ(data->ctrl[0], 0.5);
EXPECT_EQ(data->ctrl[1], 0.6);
EXPECT_MJTNUM_EQ(data->ctrl[0], 0.5);
EXPECT_MJTNUM_EQ(data->ctrl[1], 0.6);
mj_resetDataKeyframe(model, data, 5);
EXPECT_THAT(AsVector(data->mocap_pos, 3), ElementsAre(.1, .2, .3));
@@ -750,6 +749,9 @@ TEST_F(MjCGeomTest, CapsuleInertiaX) {
}
TEST_F(MjCGeomTest, ShellInertiaSphere) {
if constexpr (sizeof(mjtNum) == sizeof(float)) {
GTEST_SKIP() << "ShellInertia tests use radii differences of ~1e-8, which vanish in float32 precision";
}
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
@@ -793,7 +795,7 @@ TEST_F(MjCGeomTest, ShellInertiaSphere) {
mjtNum mass3 = m->body_mass[3];
mjtNum mass4 = m->body_mass[4];
EXPECT_FLOAT_EQ(mass4 - mass3, m->body_mass[2]);
EXPECT_MJTNUM_EQ(mass4 - mass3, m->body_mass[2]);
// compute approximate shell inertia by subtracting inertias of massive bodies
// with small radius difference
@@ -809,6 +811,9 @@ TEST_F(MjCGeomTest, ShellInertiaSphere) {
}
TEST_F(MjCGeomTest, ShellInertiaCapsule) {
if constexpr (sizeof(mjtNum) == sizeof(float)) {
GTEST_SKIP() << "ShellInertia tests use radii differences of ~1e-8, which vanish in float32 precision";
}
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
@@ -890,6 +895,9 @@ TEST_F(MjCGeomTest, ShellInertiaCapsule) {
}
TEST_F(MjCGeomTest, ShellInertiaCylinder) {
if constexpr (sizeof(mjtNum) == sizeof(float)) {
GTEST_SKIP() << "ShellInertia tests use radii differences of ~1e-8, which vanish in float32 precision";
}
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
@@ -965,6 +973,9 @@ TEST_F(MjCGeomTest, ShellInertiaCylinder) {
}
TEST_F(MjCGeomTest, ShellInertiaEllipsoid) {
if constexpr (sizeof(mjtNum) == sizeof(float)) {
GTEST_SKIP() << "ShellInertia tests use radii differences of ~1e-8, which vanish in float32 precision";
}
// test special case of ellipsoid with dimensions: a = b = c
// TODO(taylorhowell): add test for ellipsoid with dimensions: a != b != c
static constexpr char xml[] = R"(
@@ -1028,6 +1039,9 @@ TEST_F(MjCGeomTest, ShellInertiaEllipsoid) {
}
TEST_F(MjCGeomTest, ShellInertiaBox) {
if constexpr (sizeof(mjtNum) == sizeof(float)) {
GTEST_SKIP() << "ShellInertia tests use radii differences of ~1e-8, which vanish in float32 precision";
}
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
@@ -1247,16 +1261,16 @@ TEST_F(MjCJointTest, AlignFree) {
mj_forward(m_u, d_u);
// expect x-frames (sensors) to match to very high precision
double eps = 1e-10;
double eps = MjTol(1e-10, 1e-6);
EXPECT_THAT(
AsVector(d->sensordata, m->nsensordata),
Pointwise(DoubleNear(eps), AsVector(d_u->sensordata, m->nsensordata)));
Pointwise(MjNear(eps, eps), AsVector(d_u->sensordata, m->nsensordata)));
// no frame sensors for lights, test separately
EXPECT_THAT(AsVector(d->light_xpos, 3),
Pointwise(DoubleNear(eps), AsVector(d_u->light_xpos, 3)));
Pointwise(MjNear(eps, eps), AsVector(d_u->light_xpos, 3)));
EXPECT_THAT(AsVector(d->light_xdir, 3),
Pointwise(DoubleNear(eps), AsVector(d_u->light_xdir, 3)));
Pointwise(MjNear(eps, eps), AsVector(d_u->light_xdir, 3)));
// reduce timestep to 0.1ms and use RK4, simulate for 1 second
m->opt.timestep = m_u->opt.timestep = 1e-4;
@@ -1275,14 +1289,14 @@ TEST_F(MjCJointTest, AlignFree) {
mj_freeStack(d);
// expect x-frames to match to reasonable precision
eps = 1e-5;
eps = MjTol(1e-5, 1e-2);
EXPECT_THAT(
AsVector(d->sensordata, m->nsensordata),
Pointwise(DoubleNear(eps), AsVector(d_u->sensordata, m->nsensordata)));
Pointwise(MjNear(eps, eps), AsVector(d_u->sensordata, m->nsensordata)));
EXPECT_THAT(AsVector(d->light_xpos, 3),
Pointwise(DoubleNear(eps), AsVector(d_u->light_xpos, 3)));
Pointwise(MjNear(eps, eps), AsVector(d_u->light_xpos, 3)));
EXPECT_THAT(AsVector(d->light_xdir, 3),
Pointwise(DoubleNear(eps), AsVector(d_u->light_xdir, 3)));
Pointwise(MjNear(eps, eps), AsVector(d_u->light_xdir, 3)));
mj_deleteData(d_u);
mj_deleteData(d);
@@ -1618,8 +1632,8 @@ TEST_F(InheritrangeTest, WorksForDegrees) {
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
EXPECT_DOUBLE_EQ(model->actuator_ctrlrange[0], mjPI / 2);
EXPECT_DOUBLE_EQ(model->actuator_ctrlrange[1], mjPI);
EXPECT_MJTNUM_EQ(model->actuator_ctrlrange[0], mjPI / 2);
EXPECT_MJTNUM_EQ(model->actuator_ctrlrange[1], mjPI);
mj_deleteModel(model);
}
@@ -2191,8 +2205,8 @@ TEST_F(SpringrangeTest, DefaultsPropagate) {
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
EXPECT_EQ(model->tendon_lengthspring[0], .2);
EXPECT_EQ(model->tendon_lengthspring[1], .5);
EXPECT_MJTNUM_EQ(model->tendon_lengthspring[0], .2);
EXPECT_MJTNUM_EQ(model->tendon_lengthspring[1], .5);
mj_deleteModel(model);
}
@@ -2262,7 +2276,7 @@ TEST_F(UserObjectsTest, Frame) {
</worldbody>
</mujoco>
)";
constexpr mjtNum eps = 1e-14;
const mjtNum eps = MjTol(1e-14, 1e-5);
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, testing::NotNull()) << error.data();
@@ -2331,7 +2345,7 @@ TEST_F(UserObjectsTest, FrameTransformsLight) {
EXPECT_THAT(m, NotNull()) << error.data();
EXPECT_EQ(m->nlight, 1);
constexpr mjtNum eps = 1e-14;
const mjtNum eps = MjTol(1e-14, 1e-5);
EXPECT_NEAR(m->light_pos[0], -mju_sqrt(.5), eps);
EXPECT_NEAR(m->light_pos[1], 0, eps);
EXPECT_NEAR(m->light_pos[2], 1 + mju_sqrt(.5), eps);
@@ -2628,7 +2642,7 @@ TEST_F(UserObjectsTest, Inertial) {
const mjtNum euler[3] = {3, 4, 5};
mju_euler2Quat(quat, euler, "xyz");
EXPECT_THAT(AsVector(m->body_iquat + 4, 4),
Pointwise(DoubleNear(1e-8), AsVector(quat, 4)));
Pointwise(MjNear(1e-8, 1e-6), AsVector(quat, 4)));
EXPECT_EQ(m->body_mass[2], 2);
EXPECT_THAT(AsVector(m->body_ipos + 6, 3), ElementsAre(1, 2, 3));
@@ -2730,7 +2744,7 @@ TEST_F(OctreeSDFTest, SphereSDF) {
for (double x = -2.0; x <= 2.0; x += 0.5) {
for (double y = -2.0; y <= 2.0; y += 0.5) {
for (double z = -2.0; z <= 2.0; z += 0.5) {
mjtNum p[3] = {x, y, z};
mjtNum p[3] = {(mjtNum)x, (mjtNum)y, (mjtNum)z};
double sdf_dist = mjc_distance(model, data, &sdf, p);
double gt_dist = analyticSdf(p);
@@ -2799,9 +2813,9 @@ TEST_F(OctreeSDFTest, TorusSDF) {
double sum_sq_error = 0.0;
// Test grid of points
for (double x = -2.0; x <= 2.0; x += 0.5) {
for (double y = -2.0; y <= 2.0; y += 0.5) {
for (double z = -2.0; z <= 2.0; z += 0.5) {
for (mjtNum x = -2.0; x <= 2.0; x += 0.5) {
for (mjtNum y = -2.0; y <= 2.0; y += 0.5) {
for (mjtNum z = -2.0; z <= 2.0; z += 0.5) {
mjtNum p[3] = {x, y, z};
double sdf_dist = mjc_distance(model, data, &sdf, p);
double gt_dist = analyticSdf(p);