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
+31 -24
View File
@@ -34,9 +34,9 @@
namespace mujoco {
namespace {
using ::testing::DoubleEq;
using ::testing::Pointwise;
using ::testing::DoubleNear;
using ::testing::ElementsAre;
using ::std::string;
using ::std::vector;
@@ -92,7 +92,8 @@ mjtNum objective(const mjtNum* x, const mjtNum* H, const mjtNum* g, int n) {
// utility: test if res is the minimum of a given box-QP problem
bool isQPminimum(const mjtNum* res, const mjtNum* H, const mjtNum* g, int n,
const mjtNum* lower, const mjtNum* upper) {
static const mjtNum eps = 1e-4; // epsilon used for nudging
constexpr mjtNum eps = MjTol(1e-4, 5e-2); // epsilon used for nudging
constexpr mjtNum threshold = MjTol(0, -2e-3); // comparison threshold
bool is_minimum = true;
mjtNum* res_nudge = (mjtNum*) mju_malloc(sizeof(mjtNum)*n);
@@ -109,7 +110,7 @@ bool isQPminimum(const mjtNum* res, const mjtNum* H, const mjtNum* g, int n,
res_nudge[i] = mju_max(lower[i], res_nudge[i]);
}
value_nudge = objective(res_nudge, H, g, n);
if (value_nudge - value < 0) {
if (value_nudge - value < threshold) {
is_minimum = false;
break;
}
@@ -120,7 +121,7 @@ bool isQPminimum(const mjtNum* res, const mjtNum* H, const mjtNum* g, int n,
res_nudge[i] = mju_min(upper[i], res_nudge[i]);
}
value_nudge = objective(res_nudge, H, g, n);
if (value_nudge - value < 0) {
if (value_nudge - value < threshold) {
is_minimum = false;
break;
}
@@ -185,14 +186,14 @@ TEST_F(BoxQPTest, UnboundedQP) {
// no bounds, expect Newton point
EXPECT_EQ(nfree, 2);
EXPECT_THAT(res[0], DoubleEq(-g[0]/H[0]));
EXPECT_THAT(res[1], DoubleEq(-g[1]/H[3]));
EXPECT_MJTNUM_EQ(res[0], -g[0]/H[0]);
EXPECT_MJTNUM_EQ(res[1], -g[1]/H[3]);
// check that solution is actual minimum
EXPECT_TRUE(isQPminimum(res, H, g, n, /*lower=*/nullptr, /*upper=*/nullptr));
// perturb solution, expected it no longer be the minimum
res[0] += 0.001;
res[0] += MjTol(0.001, 0.1);
EXPECT_FALSE(isQPminimum(res, H, g, n, /*lower=*/nullptr, /*upper=*/nullptr));
// negative-definite Hessian, no solution
@@ -223,8 +224,8 @@ TEST_F(BoxQPTest, AsymmetricUpperIgnored) {
EXPECT_EQ(nfree, 1);
EXPECT_THAT(res[0], DoubleEq(-g[0]/H[0]));
EXPECT_THAT(res[1], DoubleEq(lower[1]));
EXPECT_MJTNUM_EQ(res[0], -g[0]/H[0]);
EXPECT_MJTNUM_EQ(res[1], lower[1]);
}
// test mju_boxQP on a single random bounded QP
@@ -244,9 +245,9 @@ TEST_F(BoxQPTest, BoundedQP) {
// use default options
int maxiter = 100; // maximum number of iterations
mjtNum mingrad = 1E-16; // minimum squared norm of (unclamped) gradient
mjtNum mingrad = MjTol(1E-16, 1E-5); // minimum squared norm of (unclamped) gradient
mjtNum backtrack = 0.5; // backtrack factor for decreasing stepsize
mjtNum minstep = 1E-22; // minimum stepsize for linesearch
mjtNum minstep = MjTol(1E-22, 1E-10); // minimum stepsize for linesearch
mjtNum armijo = 0.1; // Armijo parameter
// logging
@@ -285,6 +286,12 @@ TEST_F(BoxQPTest, BoundedQP) {
// test mju_boxQP on a set of random bounded QPs
TEST_F(BoxQPTest, BoundedQPvariations) {
if constexpr (sizeof(mjtNum) == sizeof(float)) {
GTEST_SKIP()
<< "BoxQP test permutations contain ill-conditioned matrices which "
"natively fail to find descent directions under float32 precision";
}
int nmax = 100;
// allocate maximum size on heap
@@ -317,9 +324,9 @@ TEST_F(BoxQPTest, BoundedQPvariations) {
// default algorithm options
int maxiter = 100;
mjtNum mingrad = 1E-16;
mjtNum mingrad = MjTol(1E-16, 1E-5);
mjtNum backtrack = 0.5;
mjtNum minstep = 1E-22;
mjtNum minstep = MjTol(1E-22, 1E-10);
mjtNum armijo = 0.1;
// solve box-QP with logging
@@ -500,9 +507,9 @@ TEST_F(BandMatrixTest, Multiplication) {
/*nVec=*/1, /*flg_sym=*/1);
// expect numerical equality
mjtNum eps = 1e-12;
mjtNum eps = MjTol(1e-12, 5e-5);
EXPECT_THAT(AsVector(res, nTotal),
Pointwise(DoubleNear(eps), AsVector(res1, nTotal)));
Pointwise(MjNear(eps, eps), AsVector(res1, nTotal)));
mju_free(res1);
mju_free(res);
@@ -565,9 +572,9 @@ TEST_F(BandMatrixTest, Factorization) {
}
// expect numerical equality
mjtNum eps = 1e-12;
mjtNum eps = MjTol(1e-12, 5e-5);
EXPECT_THAT(AsVector(H, nH),
Pointwise(DoubleNear(eps), AsVector(H1, nH)));
Pointwise(MjNear(eps, eps), AsVector(H1, nH)));
// multiply dense
mju_mulMatVec(res, H, vec, nTotal, nTotal);
@@ -578,7 +585,7 @@ TEST_F(BandMatrixTest, Factorization) {
// expect numerical equality
EXPECT_THAT(AsVector(res, nTotal),
Pointwise(DoubleNear(eps), AsVector(res1, nTotal)));
Pointwise(MjNear(eps, eps), AsVector(res1, nTotal)));
mju_free(res1);
mju_free(res);
@@ -638,9 +645,9 @@ TEST_F(BandMatrixTest, Solve) {
mju_cholSolveBand(res1, B, vec, nTotal, nBand, nDense);
// expect numerical equality
mjtNum eps = 1e-12;
mjtNum eps = MjTol(1e-12, 5e-5);
EXPECT_THAT(AsVector(res, nTotal),
Pointwise(DoubleNear(eps), AsVector(res1, nTotal)));
Pointwise(MjNear(eps, eps), AsVector(res1, nTotal)));
mju_free(res1);
mju_free(res);
@@ -808,7 +815,7 @@ TEST_F(EngineUtilSolveTest, MjuCholUpdate) {
n, n);
// compare
mjtNum eps = 1e-8;
mjtNum eps = MjTol(1e-8, 5e-4);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
EXPECT_NEAR(H_reconstructed[i * n + j], H_expected[i * n + j], eps)
@@ -922,7 +929,7 @@ TEST_F(EngineUtilSolveTest, MjuCholUpdateSparse) {
L_sparse_dense.data(), n, n, n);
// compare
mjtNum eps = 1e-8;
mjtNum eps = MjTol(1e-8, 5e-4);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
EXPECT_NEAR(H_sparse_reconstructed[i * n + j], H_expected[i * n + j],
@@ -1025,7 +1032,7 @@ TEST_F(EngineUtilSolveTest, CholFactorSymbolicNumeric) {
EXPECT_EQ(rank_new, n);
// compare L values
mjtNum eps = 1e-10;
mjtNum eps = MjTol(1e-10, 5e-5);
for (int i = 0; i < nnz; i++) {
EXPECT_NEAR(L_new[i], L_ref[i], eps) << "mismatch at index " << i;
}