forked from pz4kybsvg/Conception
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
893 lines
30 KiB
893 lines
30 KiB
#include "drake/solvers/unrevised_lemke_solver.h"
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#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 <typename Derived>
|
|
void RunLCP(const Eigen::MatrixBase<Derived>& M, const Eigen::VectorXd& q,
|
|
const Eigen::VectorXd& expected_z_in) {
|
|
UnrevisedLemkeSolver<double> 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<double> lcp;
|
|
MatrixX<double> M(3, 3);
|
|
VectorX<double> q(4);
|
|
|
|
// Zero tolerance is arbitrary.
|
|
const double zero_tol = 1e-15;
|
|
|
|
// Verify that solver catches M not matching q.
|
|
int num_pivots;
|
|
VectorX<double> 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<double, 3, 3> M;
|
|
|
|
// clang-format off
|
|
M <<
|
|
1, 2, 0,
|
|
0, 1, 2,
|
|
2, 0, 1;
|
|
// clang-format on
|
|
|
|
Eigen::Matrix<double, 3, 1> 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<double> M =
|
|
(Eigen::Matrix<double, 9, 1>() << 1, 2, 3, 4, 5, 6, 7, 8, 9)
|
|
.finished()
|
|
.asDiagonal();
|
|
|
|
Eigen::Matrix<double, 9, 1> 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<double> 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<double, 16, 16> 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<double, 1, 16> q;
|
|
q.fill(-1);
|
|
|
|
Eigen::Matrix<double, 1, 16> 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<double, 2, 2> M;
|
|
M.fill(1);
|
|
|
|
Eigen::Matrix<double, 1, 2> q;
|
|
q.fill(-1);
|
|
|
|
// This problem also has valid solutions (0, 1) and (0.5, 0.5).
|
|
Eigen::Matrix<double, 1, 2> 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<double, 3, 3> M;
|
|
|
|
// clang-format off
|
|
M <<
|
|
0, -1, 2,
|
|
2, 0, -2,
|
|
-1, 1, 0;
|
|
// clang-format on
|
|
|
|
Eigen::Matrix<double, 1, 3> q;
|
|
q << -3, 6, -1;
|
|
|
|
Eigen::Matrix<double, 1, 3> 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<double, 4, 4> 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<double, 1, 4> 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<double> 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<double, 4, 4> 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<double, 1, 4> q;
|
|
q << 50, 50, l, -6;
|
|
|
|
Eigen::Matrix<double, 1, 4> 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<double, 1, 4> q;
|
|
q << 50, 50, 100, -6;
|
|
|
|
Eigen::Matrix<double, 1, 4> 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<double> 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<double> 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<double> q(1), z(1);
|
|
MatrixX<double> M(1, 1);
|
|
M(0, 0) = 1;
|
|
q[0] = -1;
|
|
z[0] = 1 - std::numeric_limits<double>::epsilon();
|
|
|
|
// Check solution quality without a tolerance specified.
|
|
UnrevisedLemkeSolver<double> 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<double>::epsilon();
|
|
MatrixX<double> M(1, 1);
|
|
M(0, 0) = 1;
|
|
EXPECT_NEAR(UnrevisedLemkeSolver<double>::ComputeZeroTolerance(M), eps,
|
|
10 * eps);
|
|
|
|
// An scalar matrix * 1e10. Should be _around_ machine epsilon * 1e10.
|
|
M(0, 0) = 1e10;
|
|
EXPECT_NEAR(UnrevisedLemkeSolver<double>::ComputeZeroTolerance(M), 1e10 * eps,
|
|
1e11 * eps);
|
|
|
|
// A 100 x 100 identity matrix. Should be _around_ 100 * machine epsilon.
|
|
M = MatrixX<double>::Identity(10, 10);
|
|
EXPECT_NEAR(UnrevisedLemkeSolver<double>::ComputeZeroTolerance(M), 1e2 * eps,
|
|
1e3 * eps);
|
|
}
|
|
|
|
// Checks that warmstarting works as anticipated.
|
|
GTEST_TEST(TestUnrevisedLemke, WarmStarting) {
|
|
MatrixX<double> M(3, 3);
|
|
// clang-format off
|
|
M <<
|
|
1, 2, 0,
|
|
0, 1, 2,
|
|
2, 0, 1;
|
|
// clang-format on
|
|
|
|
Eigen::Matrix<double, 3, 1> 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<double> 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<double> M = MatrixX<double>::Identity(3, 3);
|
|
Eigen::Matrix<double, 3, 1> 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<double> 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<double>::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<double> lcp_; // The solver itself.
|
|
MatrixX<double> M_; // The LCP matrix used in pivoting tests.
|
|
MatrixX<double> 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<double> result;
|
|
|
|
// After augmentation, the matrix will be:
|
|
// 1 0 0 1
|
|
// 0 1 0 1
|
|
// 0 0 1 1
|
|
MatrixX<double> M = MatrixX<double>::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<double> 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<double>(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<double>(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<double>(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<double> M = MatrixX<double>::Identity(3, 3);
|
|
|
|
VectorX<double> result;
|
|
|
|
// Get a single row, first column.
|
|
VectorX<double> 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<double>(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<double>(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<double> v(3);
|
|
v << 0, 1, 2;
|
|
|
|
// One element (the middle one).
|
|
VectorX<double> 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<double> zero(3);
|
|
zero << 0, 0, 0;
|
|
|
|
// Set a single element.
|
|
VectorX<double> result = zero;
|
|
VectorX<double> 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<double>(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<double>(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<double>::LCPVariable(true, 0),
|
|
UnrevisedLemkeSolver<double>::LCPVariable(false, 0)}));
|
|
|
|
// Create two variables with different indices, but both z. These should be
|
|
// reported as unique.
|
|
EXPECT_TRUE(
|
|
lcp_.IsEachUnique({UnrevisedLemkeSolver<double>::LCPVariable(true, 0),
|
|
UnrevisedLemkeSolver<double>::LCPVariable(true, 1)}));
|
|
|
|
// Create two variables with different indices, but both w. These should be
|
|
// reported as unique.
|
|
EXPECT_TRUE(
|
|
lcp_.IsEachUnique({UnrevisedLemkeSolver<double>::LCPVariable(false, 0),
|
|
UnrevisedLemkeSolver<double>::LCPVariable(false, 1)}));
|
|
|
|
// Create two identical variables. These should not be reported as unique.
|
|
EXPECT_FALSE(
|
|
lcp_.IsEachUnique({UnrevisedLemkeSolver<double>::LCPVariable(false, 0),
|
|
UnrevisedLemkeSolver<double>::LCPVariable(false, 0)}));
|
|
EXPECT_FALSE(
|
|
lcp_.IsEachUnique({UnrevisedLemkeSolver<double>::LCPVariable(true, 1),
|
|
UnrevisedLemkeSolver<double>::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<double>::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<double> q_bar(3);
|
|
VectorX<double> M_bar_col(3);
|
|
ASSERT_TRUE(
|
|
lcp_.LemkePivot(M_, q_, driving_index, zero_tol, &M_bar_col, &q_bar));
|
|
VectorX<double> M_bar_col_expected(3);
|
|
VectorX<double> 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<double>::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<double> z;
|
|
ASSERT_TRUE(
|
|
lcp_.ConstructLemkeSolution(M_, q_, artificial_index_loc, zero_tol, &z));
|
|
|
|
// Verify that the solution is as expected.
|
|
VectorX<double> 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<double>::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<double>::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<int>(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<double> col(3);
|
|
col << -3, 6, 1;
|
|
VectorX<double> 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<double>::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<double> col(3);
|
|
col << -3, -3, 1;
|
|
VectorX<double> 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
|