#include "drake/solvers/unrevised_lemke_solver.h" #include #include #include #include "drake/common/test_utilities/eigen_matrix_compare.h" namespace drake { namespace solvers { const double epsilon = 5e-14; // Run the solver and test against the expected result. template void RunLCP(const Eigen::MatrixBase& M, const Eigen::VectorXd& q, const Eigen::VectorXd& expected_z_in) { UnrevisedLemkeSolver l; Eigen::VectorXd expected_z = expected_z_in; // NOTE: We don't necessarily expect the unregularized fast solver to succeed, // hence we don't test the result. Eigen::VectorXd lemke_z; int num_pivots; bool result = l.SolveLcpLemke(M, q, &lemke_z, &num_pivots); ASSERT_TRUE(result); EXPECT_TRUE(CompareMatrices(lemke_z, expected_z, epsilon, MatrixCompareType::absolute)); EXPECT_GT(num_pivots, 0); // We do not test any trivial LCPs. } // Checks that the solver detects a dimensional mismatch between the LCP matrix // and vector. GTEST_TEST(TestUnrevisedLemke, DimensionalMismatch) { UnrevisedLemkeSolver lcp; MatrixX M(3, 3); VectorX q(4); // Zero tolerance is arbitrary. const double zero_tol = 1e-15; // Verify that solver catches M not matching q. int num_pivots; VectorX z; EXPECT_THROW(lcp.SolveLcpLemke(M, q, &z, &num_pivots, zero_tol), std::logic_error); // Verify that solver catches non-square M. M.resize(3, 4); EXPECT_THROW(lcp.SolveLcpLemke(M, q, &z, &num_pivots, zero_tol), std::logic_error); } // Checks the robustness of the algorithm to a known test problem that results // in cycling. GTEST_TEST(TestUnrevisedLemke, TestCycling) { Eigen::Matrix M; // clang-format off M << 1, 2, 0, 0, 1, 2, 2, 0, 1; // clang-format on Eigen::Matrix q; q << -1, -1, -1; Eigen::VectorXd expected_z(3); expected_z << 1.0 / 3, 1.0 / 3, 1.0 / 3; RunLCP(M, q, expected_z); } // Tests a simple linear complementarity problem. GTEST_TEST(TestUnrevisedLemke, TestSimple) { // Create a 9x9 diagonal matrix from the vector [1 2 3 4 5 6 7 8 9]. MatrixX M = (Eigen::Matrix() << 1, 2, 3, 4, 5, 6, 7, 8, 9) .finished() .asDiagonal(); Eigen::Matrix q; q << -1, -1, -1, -1, -1, -1, -1, -1, -1; Eigen::VectorXd expected_z(9); expected_z << 1, 1.0 / 2, 1.0 / 3, 1.0 / 4, 1.0 / 5, 1.0 / 6, 1.0 / 7, 1.0 / 8, 1.0 / 9; RunLCP(M, q, expected_z); } // Tests that the artificial variable is always selected in a tie // (Example 4.4.16 in [Cottle 1992]). We know Lemke's algorithm can solve // this one since the matrix is symmetric and positive semi-definite. // Lemke implementations without the necessary special-case code can terminate // on an unblocked variable (i.e., fail to find the solution when one is known // to exist). // NOTE: This is a necessary but not sufficient test that the special-case code // is working. This test failed before the special-case code was added, but it's // possible that the test could succeed using other strategies for selecting // one of multiple valid blocking indices. For example, Miranda and Fackler's // Lemke solver uses a random blocking variable selection when multiple are // possible. GTEST_TEST(TestUnrevisedLemke, TestPSD) { MatrixX M(2, 2); // clang-format off M << 1, -1, -1, 1; // clang-format on Eigen::Vector2d q; q << 1, -1; Eigen::VectorXd expected_z(2); expected_z << 0, 1; RunLCP(M, q, expected_z); } GTEST_TEST(TestUnrevisedLemke, TestProblem1) { // Problem from example 10.2.1 in "Handbook of Test Problems in // Local and Global Optimization". Eigen::Matrix M; M.setIdentity(); for (int i = 0; i < M.rows() - 1; i++) { for (int j = i + 1; j < M.cols(); j++) { M(i, j) = 2; } } Eigen::Matrix q; q.fill(-1); Eigen::Matrix z; z.setZero(); z(15) = 1; RunLCP(M, q, z); } GTEST_TEST(TestUnrevisedLemke, TestProblem2) { // Problem from example 10.2.2 in "Handbook of Test Problems in // Local and Global Optimization". Eigen::Matrix M; M.fill(1); Eigen::Matrix q; q.fill(-1); // This problem also has valid solutions (0, 1) and (0.5, 0.5). Eigen::Matrix z; z << 1, 0; RunLCP(M, q, z); } GTEST_TEST(TestUnrevisedLemke, TestProblem3) { // Problem from example 10.2.3 in "Handbook of Test Problems in // Local and Global Optimization". Eigen::Matrix M; // clang-format off M << 0, -1, 2, 2, 0, -2, -1, 1, 0; // clang-format on Eigen::Matrix q; q << -3, 6, -1; Eigen::Matrix z; z << 0, 1, 3; RunLCP(M, q, z); } GTEST_TEST(TestUnrevisedLemke, TestProblem4) { // Problem from example 10.2.4 in "Handbook of Test Problems in // Local and Global Optimization". Eigen::Matrix M; // clang-format off M << 0, 0, 10, 20, 0, 0, 30, 15, 10, 20, 0, 0, 30, 15, 0, 0; // clang-format on Eigen::Matrix q; q.fill(-1); // This solution is the third in the book, which it explicitly // states cannot be found using the Lemke-Howson algorithm. Eigen::VectorXd z(4); z << 1. / 90., 2. / 45., 1. / 90., 2. / 45.; UnrevisedLemkeSolver l; int num_pivots; bool result = l.SolveLcpLemke(M, q, &z, &num_pivots); EXPECT_FALSE(result); } GTEST_TEST(TestUnrevisedLemke, TestProblem6) { // Problem from example 10.2.9 in "Handbook of Test Problems in // Local and Global Optimization". Eigen::Matrix M; // clang-format off M << 11, 0, 10, -1, 0, 11, 10, -1, 10, 10, 21, -1, 1, 1, 1, 0; // Note that the (3, 3) position is incorrectly // shown in the book with the value 1. // clang-format on // Pick a couple of arbitrary points in the [0, 23] range. for (double l = 1; l <= 23; l += 15) { Eigen::Matrix q; q << 50, 50, l, -6; Eigen::Matrix z; // clang-format off z << (l + 16.) / 13., (l + 16.) / 13., (2. * (23 - l)) / 13., (1286. - (9. * l)) / 13; // clang-format on RunLCP(M, q, z); } // Try again with a value > 23 and verify that Lemke is still successful. Eigen::Matrix q; q << 50, 50, 100, -6; Eigen::Matrix z; z << 3, 3, 0, 83; RunLCP(M, q, z); } GTEST_TEST(TestUnrevisedLemke, TestEmpty) { Eigen::MatrixXd empty_M(0, 0); Eigen::VectorXd empty_q(0); Eigen::VectorXd z; UnrevisedLemkeSolver l; int num_pivots; bool result = l.SolveLcpLemke(empty_M, empty_q, &z, &num_pivots); EXPECT_TRUE(result); EXPECT_EQ(z.size(), 0); } // Verifies that z is zero on LCP solver failure. GTEST_TEST(TestUnrevisedLemke, TestFailure) { Eigen::MatrixXd neg_M(1, 1); Eigen::VectorXd neg_q(1); // This LCP is unsolvable: -z - 1 cannot be greater than zero when z is // restricted to be non-negative. neg_M(0, 0) = -1; neg_q[0] = -1; Eigen::VectorXd z; UnrevisedLemkeSolver l; int num_pivots; bool result = l.SolveLcpLemke(neg_M, neg_q, &z, &num_pivots); LinearComplementarityConstraint constraint(neg_M, neg_q); EXPECT_FALSE(result); ASSERT_EQ(z.size(), neg_q.size()); EXPECT_EQ(z[0], 0.0); EXPECT_FALSE(constraint.CheckSatisfied(z)); } GTEST_TEST(TestUnrevisedLemke, TestSolutionQuality) { // Set the LCP and the solution. VectorX q(1), z(1); MatrixX M(1, 1); M(0, 0) = 1; q[0] = -1; z[0] = 1 - std::numeric_limits::epsilon(); // Check solution quality without a tolerance specified. UnrevisedLemkeSolver lcp; EXPECT_TRUE(lcp.IsSolution(M, q, z)); // Check solution quality with a tolerance specified. EXPECT_TRUE(lcp.IsSolution(M, q, z, 3e-16)); } GTEST_TEST(TestUnrevisedLemke, ZeroTolerance) { // Compute the zero tolerance for several matrices. // An scalar matrix- should be _around_ machine epsilon. const double eps = std::numeric_limits::epsilon(); MatrixX M(1, 1); M(0, 0) = 1; EXPECT_NEAR(UnrevisedLemkeSolver::ComputeZeroTolerance(M), eps, 10 * eps); // An scalar matrix * 1e10. Should be _around_ machine epsilon * 1e10. M(0, 0) = 1e10; EXPECT_NEAR(UnrevisedLemkeSolver::ComputeZeroTolerance(M), 1e10 * eps, 1e11 * eps); // A 100 x 100 identity matrix. Should be _around_ 100 * machine epsilon. M = MatrixX::Identity(10, 10); EXPECT_NEAR(UnrevisedLemkeSolver::ComputeZeroTolerance(M), 1e2 * eps, 1e3 * eps); } // Checks that warmstarting works as anticipated. GTEST_TEST(TestUnrevisedLemke, WarmStarting) { MatrixX M(3, 3); // clang-format off M << 1, 2, 0, 0, 1, 2, 2, 0, 1; // clang-format on Eigen::Matrix q; q << -1, -1, -1; // Solve the problem once. int num_pivots; Eigen::VectorXd expected_z(3); expected_z << 1.0 / 3, 1.0 / 3, 1.0 / 3; Eigen::VectorXd z; UnrevisedLemkeSolver lcp; bool result = lcp.SolveLcpLemke(M, q, &z, &num_pivots); ASSERT_TRUE(result); ASSERT_TRUE( CompareMatrices(z, expected_z, epsilon, MatrixCompareType::absolute)); // Verify that more than one pivot was required. EXPECT_GE(num_pivots, 1); // Solve the problem with a slightly different q and verify that exactly // one pivot was required. q *= 2; expected_z *= 2; result = lcp.SolveLcpLemke(M, q, &z, &num_pivots); ASSERT_TRUE(result); ASSERT_TRUE( CompareMatrices(z, expected_z, epsilon, MatrixCompareType::absolute)); EXPECT_EQ(num_pivots, 1); } // Checks that an LCP with a trivial solution is solvable without any pivots. GTEST_TEST(TestUnrevisedLemke, Trivial) { MatrixX M = MatrixX::Identity(3, 3); Eigen::Matrix q; q << 1, 1, 1; // Solve the problem. int num_pivots; Eigen::VectorXd expected_z(3); expected_z << 0, 0, 0; Eigen::VectorXd z; UnrevisedLemkeSolver lcp; bool result = lcp.SolveLcpLemke(M, q, &z, &num_pivots); ASSERT_TRUE(result); ASSERT_TRUE( CompareMatrices(z, expected_z, epsilon, MatrixCompareType::absolute)); ASSERT_EQ(num_pivots, 0); } // A class for testing various private functions in the Lemke solver. class UnrevisedLemkePrivateTests : public testing::Test { protected: void SetUp() { typedef UnrevisedLemkeSolver::LCPVariable LCPVariable; // clang-format off M_.resize(3, 3); M_ << 0, -1, 2, 2, 0, -2, -1, 1, 0; // clang-format on q_.resize(3, 1); q_ << -3, 6, -1; // Set the LCP variables. Start with all z variables independent and all w // variables dependent. const int n = 3; lcp_.indep_variables_.resize(n + 1); lcp_.dep_variables_.resize(n); for (int i = 0; i < n; ++i) { lcp_.dep_variables_[i] = LCPVariable(false, i); lcp_.indep_variables_[i] = LCPVariable(true, i); } // z needs one more variable (the artificial variable), whose index we // denote as n to keep it from corresponding to any actual vector index. lcp_.indep_variables_[n] = LCPVariable(true, n); } UnrevisedLemkeSolver lcp_; // The solver itself. MatrixX M_; // The LCP matrix used in pivoting tests. MatrixX q_; // The LCP vector used in pivoting tests. int kArtificial{3}; // Index of the artificial variable. }; // Tests proper operation of selecting a sub-matrix from a matrix that is // augmented with a covering vector. TEST_F(UnrevisedLemkePrivateTests, SelectSubMatrixWithCovering) { MatrixX result; // After augmentation, the matrix will be: // 1 0 0 1 // 0 1 0 1 // 0 0 1 1 MatrixX M = MatrixX::Identity(3, 3); // Select the upper-left 2x2 lcp_.SelectSubMatrixWithCovering(M, {0, 1}, {0, 1}, &result); ASSERT_EQ(result.rows(), 2); ASSERT_EQ(result.cols(), 2); MatrixX expected(result.rows(), result.cols()); // clang-format off expected << 1, 0, 0, 1; // clang-format on EXPECT_TRUE( CompareMatrices(result, expected, epsilon, MatrixCompareType::absolute)); // Select the lower-right 2x2. lcp_.SelectSubMatrixWithCovering(M, {1, 2}, {2, 3}, &result); ASSERT_EQ(result.rows(), 2); ASSERT_EQ(result.cols(), 2); // clang-format off expected << 0, 1, 1, 1; // clang-format on EXPECT_TRUE( CompareMatrices(result, expected, epsilon, MatrixCompareType::absolute)); // Select the right 3x3, with columns reversed. lcp_.SelectSubMatrixWithCovering(M, {0, 1, 2}, {3, 2, 1}, &result); ASSERT_EQ(result.rows(), 3); ASSERT_EQ(result.cols(), 3); expected = MatrixX(result.rows(), result.cols()); // clang-format off expected << 1, 0, 0, 1, 0, 1, 1, 1, 0; // clang-format on EXPECT_TRUE( CompareMatrices(result, expected, epsilon, MatrixCompareType::absolute)); // Select the right 3x3, with rows reversed. lcp_.SelectSubMatrixWithCovering(M, {2, 1, 0}, {1, 2, 3}, &result); ASSERT_EQ(result.rows(), 3); ASSERT_EQ(result.cols(), 3); expected = MatrixX(result.rows(), result.cols()); // clang-format off expected << 0, 1, 1, 1, 0, 1, 0, 0, 1; // clang-format on EXPECT_TRUE( CompareMatrices(result, expected, epsilon, MatrixCompareType::absolute)); // Select the entire matrix. lcp_.SelectSubMatrixWithCovering(M, {0, 1, 2}, {0, 1, 2, 3}, &result); expected = MatrixX(result.rows(), result.cols()); // clang-format off expected << 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1; // clang-format on EXPECT_TRUE( CompareMatrices(result, expected, epsilon, MatrixCompareType::absolute)); } // Tests proper operation of selecting a sub-column from a matrix that is // augmented with a covering vector. TEST_F(UnrevisedLemkePrivateTests, SelectSubColumnWithCovering) { // After augmentation, the matrix will be: // 1 0 0 1 // 0 1 0 1 // 0 0 1 1 MatrixX M = MatrixX::Identity(3, 3); VectorX result; // Get a single row, first column. VectorX expected(1); expected << 1; lcp_.SelectSubColumnWithCovering(M, {0}, 0 /* column */, &result); EXPECT_TRUE( CompareMatrices(result, expected, epsilon, MatrixCompareType::absolute)); // Get another single row from the first column. expected << 0; lcp_.SelectSubColumnWithCovering(M, {1}, 0 /* column */, &result); EXPECT_TRUE( CompareMatrices(result, expected, epsilon, MatrixCompareType::absolute)); // Get first and third rows in forward order, first column. lcp_.SelectSubColumnWithCovering(M, {0, 2}, 0 /* column */, &result); expected = VectorX(2); expected << 1, 0; EXPECT_TRUE( CompareMatrices(result, expected, epsilon, MatrixCompareType::absolute)); // Get first and third rows in reverse order, second column. lcp_.SelectSubColumnWithCovering(M, {2, 0}, 1 /* column */, &result); expected << 0, 0; EXPECT_TRUE( CompareMatrices(result, expected, epsilon, MatrixCompareType::absolute)); // Get all three rows in forward order, third column. lcp_.SelectSubColumnWithCovering(M, {0, 1, 2}, 2 /* column */, &result); expected = VectorX(3); expected << 0, 0, 1; EXPECT_TRUE( CompareMatrices(result, expected, epsilon, MatrixCompareType::absolute)); // Get all three rows in reverse order, third column. lcp_.SelectSubColumnWithCovering(M, {2, 1, 0}, 2 /* column */, &result); expected << 1, 0, 0; EXPECT_TRUE( CompareMatrices(result, expected, epsilon, MatrixCompareType::absolute)); // Get one row from the fourth column. lcp_.SelectSubColumnWithCovering(M, {0}, 3 /* column */, &result); EXPECT_EQ(result.lpNorm<1>(), 1); // Get two rows from the fourth column. lcp_.SelectSubColumnWithCovering(M, {0, 1}, 3 /* column */, &result); EXPECT_EQ(result.lpNorm<1>(), 2); // Get three rows from the fourth column. lcp_.SelectSubColumnWithCovering(M, {0, 1, 2}, 3 /* column */, &result); EXPECT_EQ(result.lpNorm<1>(), 3); } // Tests proper operation of selecting a sub-vector from a vector. TEST_F(UnrevisedLemkePrivateTests, SelectSubVector) { // Set the vector. VectorX v(3); v << 0, 1, 2; // One element (the middle one). VectorX result; lcp_.SelectSubVector(v, {1}, &result); EXPECT_EQ(result.size(), 1); EXPECT_EQ(result[0], 1); // Two elements (the ends). lcp_.SelectSubVector(v, {0, 2}, &result); EXPECT_EQ(result.size(), 2); EXPECT_EQ(result[0], 0); EXPECT_EQ(result[1], 2); // All three elements, not ordered sequentially. lcp_.SelectSubVector(v, {0, 2, 1}, &result); EXPECT_EQ(result.size(), 3); EXPECT_EQ(result[0], 0); EXPECT_EQ(result[1], 2); EXPECT_EQ(result[2], 1); } // Verifies proper operation of SetSubVector(). TEST_F(UnrevisedLemkePrivateTests, SetSubVector) { // Construct a zero vector that will be used repeatedly for reinitialization. VectorX zero(3); zero << 0, 0, 0; // Set a single element. VectorX result = zero; VectorX v_sub(1); v_sub << 1; lcp_.SetSubVector(v_sub, {0}, &result); EXPECT_EQ(result[0], 1.0); EXPECT_EQ(result.norm(), 1.0); // Verify no other elements were set. // Set another single element, this time at the end. result = zero; lcp_.SetSubVector(v_sub, {2}, &result); EXPECT_EQ(result[2], 1.0); EXPECT_EQ(result.norm(), 1.0); // Verify no other elements were set. // Set two elements, one at either end. v_sub = VectorX(2); v_sub << 2, 3; lcp_.SetSubVector(v_sub, {0, 2}, &result); EXPECT_EQ(result[0], 2); EXPECT_EQ(result[1], 0); EXPECT_EQ(result[2], 3); // Set an entire vector, in reverse order. v_sub = VectorX(3); v_sub << 1, 2, 3; lcp_.SetSubVector(v_sub, {2, 1, 0}, &result); EXPECT_EQ(result[0], 3); EXPECT_EQ(result[1], 2); EXPECT_EQ(result[2], 1); } // Checks whether ValidateIndices(), which checks that a vector of indices used // to select a sub-block of a matrix or vector is within range and unique. TEST_F(UnrevisedLemkePrivateTests, ValidateIndices) { // Verifies that a proper set of indices works. const int first_set_size = 3; EXPECT_TRUE(lcp_.ValidateIndices({0, 1, 2}, first_set_size)); // Verifies that indices need not be in sorted order. EXPECT_TRUE(lcp_.ValidateIndices({2, 1, 0}, first_set_size)); // Verifies that ValidateIndices() catches a repeated index. EXPECT_FALSE(lcp_.ValidateIndices({0, 1, 1}, first_set_size)); // Verifies that ValidateIndices() catches indices out of range. EXPECT_FALSE(lcp_.ValidateIndices({0, 1, 4}, first_set_size)); EXPECT_FALSE(lcp_.ValidateIndices({0, 1, -1}, first_set_size)); // ** Two-index set tests **. // Verifies that a proper set of indices works. const int second_set_size = 7; EXPECT_TRUE(lcp_.ValidateIndices({0, 1, 2}, {3, 4, 5, 6}, first_set_size, second_set_size)); // Verifies that indices need not be in sorted order. EXPECT_TRUE(lcp_.ValidateIndices({2, 1, 0}, {6, 5, 4, 3}, first_set_size, second_set_size)); // Verifies that ValidateIndices() catches a single repeated index. EXPECT_FALSE(lcp_.ValidateIndices({0, 1, 1}, {3, 4, 5, 6}, first_set_size, second_set_size)); // Verifies that ValidateIndices() catches indices out of range. EXPECT_FALSE(lcp_.ValidateIndices({0, 1, 4}, {3, 4, 5, 6}, first_set_size, second_set_size)); EXPECT_FALSE(lcp_.ValidateIndices({0, 1, -1}, {3, 4, 5, 6}, first_set_size, second_set_size)); } // Verifies proper operation of IsEachUnique(), which checks whether LCP // variables in a vector are unique. TEST_F(UnrevisedLemkePrivateTests, IsEachUnique) { // Create two variables with the same index, but one z and one w. These should // be reported as unique. EXPECT_TRUE( lcp_.IsEachUnique({UnrevisedLemkeSolver::LCPVariable(true, 0), UnrevisedLemkeSolver::LCPVariable(false, 0)})); // Create two variables with different indices, but both z. These should be // reported as unique. EXPECT_TRUE( lcp_.IsEachUnique({UnrevisedLemkeSolver::LCPVariable(true, 0), UnrevisedLemkeSolver::LCPVariable(true, 1)})); // Create two variables with different indices, but both w. These should be // reported as unique. EXPECT_TRUE( lcp_.IsEachUnique({UnrevisedLemkeSolver::LCPVariable(false, 0), UnrevisedLemkeSolver::LCPVariable(false, 1)})); // Create two identical variables. These should not be reported as unique. EXPECT_FALSE( lcp_.IsEachUnique({UnrevisedLemkeSolver::LCPVariable(false, 0), UnrevisedLemkeSolver::LCPVariable(false, 0)})); EXPECT_FALSE( lcp_.IsEachUnique({UnrevisedLemkeSolver::LCPVariable(true, 1), UnrevisedLemkeSolver::LCPVariable(true, 1)})); } // Tests that pivoting works as expected, using Example 4.7.7 from // [Cottle 1992], p. 273. TEST_F(UnrevisedLemkePrivateTests, LemkePivot) { typedef UnrevisedLemkeSolver::LCPVariable LCPVariable; // Use the computed zero tolerance. double zero_tol = lcp_.ComputeZeroTolerance(M_); // Use the blocking index that Cottle provides us with. const int blocking_index = 0; auto blocking = lcp_.dep_variables_[blocking_index]; int driving_index = blocking.index(); std::swap(lcp_.dep_variables_[blocking_index], lcp_.indep_variables_[kArtificial]); // Case 1: Driving variable is from 'z'. // Compute the pivot and verify the result. VectorX q_bar(3); VectorX M_bar_col(3); ASSERT_TRUE( lcp_.LemkePivot(M_, q_, driving_index, zero_tol, &M_bar_col, &q_bar)); VectorX M_bar_col_expected(3); VectorX q_bar_expected(3); M_bar_col_expected << 0, 2, -1; q_bar_expected << 3, 9, 2; EXPECT_TRUE(CompareMatrices(M_bar_col, M_bar_col_expected, epsilon, MatrixCompareType::absolute)); // Case 2: Driving variable is from 'w'. We use the second-to-last tableaux // from Example 4.4.7. lcp_.dep_variables_[0] = LCPVariable(true, 3); // artificial variable lcp_.dep_variables_[1] = LCPVariable(false, 0); lcp_.dep_variables_[2] = LCPVariable(true, 2); lcp_.indep_variables_[0] = LCPVariable(false, 1); lcp_.indep_variables_[1] = LCPVariable(false, 2); lcp_.indep_variables_[2] = LCPVariable(true, 2); lcp_.indep_variables_[3] = LCPVariable(true, 0); driving_index = 0; ASSERT_TRUE( lcp_.LemkePivot(M_, q_, driving_index, zero_tol, &M_bar_col, &q_bar)); M_bar_col_expected << 0, -1, -0.5; q_bar_expected << 1, 5, 3; EXPECT_TRUE(CompareMatrices(M_bar_col, M_bar_col_expected, epsilon, MatrixCompareType::absolute)); // Case 3: Pivoting in artificial variable (no M bar column passed in). // This is equivalent to the last tableaux of Example 4.4.7. lcp_.dep_variables_[0] = LCPVariable(true, 1); lcp_.dep_variables_[1] = LCPVariable(false, 0); lcp_.dep_variables_[2] = LCPVariable(true, 2); lcp_.indep_variables_[0] = LCPVariable(false, 1); lcp_.indep_variables_[1] = LCPVariable(false, 2); lcp_.indep_variables_[2] = LCPVariable(true, 3); // artificial variable lcp_.indep_variables_[3] = LCPVariable(true, 0); driving_index = 2; ASSERT_TRUE( lcp_.LemkePivot(M_, q_, driving_index, zero_tol, nullptr, &q_bar)); q_bar_expected << 0, 1, 3; } TEST_F(UnrevisedLemkePrivateTests, ConstructLemkeSolution) { typedef UnrevisedLemkeSolver::LCPVariable LCPVariable; // Set the variables as expected in the last tableaux of Example 4.4.7. lcp_.dep_variables_[0] = LCPVariable(true, 1); lcp_.dep_variables_[1] = LCPVariable(false, 0); lcp_.dep_variables_[2] = LCPVariable(true, 2); lcp_.indep_variables_[0] = LCPVariable(false, 1); lcp_.indep_variables_[1] = LCPVariable(false, 2); lcp_.indep_variables_[2] = LCPVariable(true, 3); // artificial variable lcp_.indep_variables_[3] = LCPVariable(true, 0); // Set the location of the artificial variable. int artificial_index_loc = 2; // Use the computed zero tolerance. double zero_tol = lcp_.ComputeZeroTolerance(M_); // Verify that the operation completes successfully. VectorX z; ASSERT_TRUE( lcp_.ConstructLemkeSolution(M_, q_, artificial_index_loc, zero_tol, &z)); // Verify that the solution is as expected. VectorX z_expected(3); z_expected << 0, 1, 3; EXPECT_TRUE( CompareMatrices(z, z_expected, epsilon, MatrixCompareType::absolute)); } // Verifies that DetermineIndexSets() works as expected. TEST_F(UnrevisedLemkePrivateTests, DetermineIndexSets) { typedef UnrevisedLemkeSolver::LCPVariable LCPVariable; // Set indep_variables_ and dep_variables_ as in Equation (1) of [1]. // Note: this equation must be kept up-to-date with equations in [1]. lcp_.dep_variables_[0] = LCPVariable(true, 3); // artificial variable. lcp_.dep_variables_[1] = LCPVariable(false, 1); lcp_.dep_variables_[2] = LCPVariable(true, 2); lcp_.indep_variables_[0] = LCPVariable(false, 0); lcp_.indep_variables_[1] = LCPVariable(false, 2); lcp_.indep_variables_[2] = LCPVariable(true, 1); lcp_.indep_variables_[3] = LCPVariable(true, 0); // Compute the index sets (uses indep_variables_ and dep_variables_). lcp_.DetermineIndexSets(); // Verify that the sets have indices we expect (from [1]). ASSERT_EQ(lcp_.index_sets_.alpha.size(), 2); EXPECT_EQ(lcp_.index_sets_.alpha[0], 0); EXPECT_EQ(lcp_.index_sets_.alpha[1], 2); ASSERT_EQ(lcp_.index_sets_.alpha_prime.size(), 2); EXPECT_EQ(lcp_.index_sets_.alpha_prime[0], 0); EXPECT_EQ(lcp_.index_sets_.alpha_prime[1], 1); ASSERT_EQ(lcp_.index_sets_.beta.size(), 2); EXPECT_EQ(lcp_.index_sets_.beta[0], 2); EXPECT_EQ(lcp_.index_sets_.beta[1], 3); ASSERT_EQ(lcp_.index_sets_.beta_prime.size(), 2); EXPECT_EQ(lcp_.index_sets_.beta_prime[0], 2); EXPECT_EQ(lcp_.index_sets_.beta_prime[1], 0); EXPECT_EQ(lcp_.index_sets_.alpha_bar.size(), 1); EXPECT_EQ(lcp_.index_sets_.alpha_bar[0], 1); EXPECT_EQ(lcp_.index_sets_.alpha_bar_prime.size(), 1); EXPECT_EQ(lcp_.index_sets_.alpha_bar_prime[0], 1); EXPECT_EQ(lcp_.index_sets_.beta_bar.size(), 2); EXPECT_EQ(lcp_.index_sets_.beta_bar[0], 0); EXPECT_EQ(lcp_.index_sets_.beta_bar[1], 1); EXPECT_EQ(lcp_.index_sets_.beta_bar_prime.size(), 2); EXPECT_EQ(lcp_.index_sets_.beta_bar_prime[0], 3); EXPECT_EQ(lcp_.index_sets_.beta_bar_prime[1], 2); } // Verifies that finding the index of the complement of an independent variable // works as expected. TEST_F(UnrevisedLemkePrivateTests, FindComplementIndex) { // From the setup of the LCP solver designated by SetUp(), all z variables // (including the artificial one are independent). The query variable will // be w1, meaning that we expect the second variable (i.e., z1) to be // the complement. typedef UnrevisedLemkeSolver::LCPVariable LCPVariable; LCPVariable query(false /* w */, 1); // We have to manually set the mapping from independent variables to their // indices, since the solver normally maintains this for us. for (int i = 0; i < static_cast(lcp_.indep_variables_.size()); ++i) lcp_.indep_variables_indices_[lcp_.indep_variables_[i]] = i; // Since the indices of the LCP variables from SetUp() // correspond to their array indices, verification is straightforward. EXPECT_EQ(lcp_.FindComplementIndex(query), 1); } TEST_F(UnrevisedLemkePrivateTests, FindBlockingIndex) { // Use the computed zero tolerance. double zero_tol = lcp_.ComputeZeroTolerance(M_); // Ratios are taken from '1' column (the q vector) in the first tableaux from // Example 4.3.3. Note that this is the exact procedure used to find the first // blocking variable, which means we can check our answer against Cottle's. VectorX col(3); col << -3, 6, 1; VectorX ratios = col; // Index should be the first one. int blocking_index = -1; ASSERT_TRUE(lcp_.FindBlockingIndex(zero_tol, col, ratios, &blocking_index)); EXPECT_EQ(blocking_index, 0); // Repeat the procedure using the second tableaux from Example 4.3.3. We // now compute the ratios manually using component-wise division of the column // marked '1' over the column marked 'z1'. col << 0, 2, -1; // NOTE: we replace 3.0 / 0 with infinity below to avoid divide by zero // warnings from the compiler. const double inf = std::numeric_limits::infinity(); ratios << inf, 9.0 / 2, 2.0 / -1.0; ASSERT_TRUE(lcp_.FindBlockingIndex(zero_tol, col, ratios, &blocking_index)); EXPECT_EQ(blocking_index, 2); // Blocking index must be the last entry. // Repeat the procedure, now using strictly positive column entries; no // blocking index should be possible. col << 0, 2, 1; ASSERT_FALSE(lcp_.FindBlockingIndex(zero_tol, col, ratios, &blocking_index)); EXPECT_EQ(blocking_index, -1); // Check that blocking index is invalid. } TEST_F(UnrevisedLemkePrivateTests, FindBlockingIndexCycling) { // Use the computed zero tolerance. double zero_tol = lcp_.ComputeZeroTolerance(M_); // We will have the column be the same as the ratios. This means that there // will be exactly two valid ratios, both identical. VectorX col(3); col << -3, -3, 1; VectorX ratios = col; // Index should be the first one. int blocking_index = -1; ASSERT_TRUE(lcp_.FindBlockingIndex(zero_tol, col, ratios, &blocking_index)); EXPECT_EQ(blocking_index, 0); // Repeat the procedure again. Index should be the next one. ASSERT_TRUE(lcp_.FindBlockingIndex(zero_tol, col, ratios, &blocking_index)); EXPECT_EQ(blocking_index, 1); // If we repeat one more time, there are no indices remaining. ASSERT_FALSE(lcp_.FindBlockingIndex(zero_tol, col, ratios, &blocking_index)); EXPECT_EQ(blocking_index, -1); // Check that blocking index is invalid. } } // namespace solvers } // namespace drake