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.
628 lines
23 KiB
628 lines
23 KiB
// David Eberly, Geometric Tools, Redmond WA 98052
|
|
// Copyright (c) 1998-2021
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// https://www.boost.org/LICENSE_1_0.txt
|
|
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
|
|
// Version: 4.0.2019.10.04
|
|
|
|
#pragma once
|
|
|
|
#include <Mathematics/Logger.h>
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <vector>
|
|
|
|
// A class for solving the Linear Complementarity Problem (LCP)
|
|
// w = q + M * z, w^T * z = 0, w >= 0, z >= 0. The vectors q, w, and z are
|
|
// n-tuples and the matrix M is n-by-n. The inputs to Solve(...) are q and M.
|
|
// The outputs are w and z, which are valid when the returned bool is true but
|
|
// are invalid when the returned bool is false.
|
|
//
|
|
// The comments at the end of this file explain what the preprocessor symbol
|
|
// means regarding the LCP solver implementation. If the algorithm fails to
|
|
// converge within the specified maximum number of iterations, consider
|
|
// increasing the number and calling Solve(...) again.
|
|
|
|
// Expose the following preprocessor symbol if you want the code to throw an
|
|
// exception the algorithm fails to converge. You can choose to trap the
|
|
// exception and handle it as you please. If you do not expose the
|
|
// preprocessor symbol, you can pass a Result object and check whether the
|
|
// algorithm failed to converge. Again, you can handle this as you please.
|
|
//
|
|
//#define GTE_THROW_ON_LCPSOLVER_ERRORS
|
|
|
|
namespace gte
|
|
{
|
|
// Support templates for number of dimensions known at compile time or
|
|
// known only at run time.
|
|
template <typename Real, int... Dimensions>
|
|
class LCPSolver {};
|
|
|
|
|
|
template <typename Real>
|
|
class LCPSolverShared
|
|
{
|
|
protected:
|
|
// Abstract base class construction. A virtual destructor is not
|
|
// provided because there are no required side effects when destroying
|
|
// objects from the derived classes. The member mMaxIterations is set
|
|
// by this call to the default value n*n.
|
|
LCPSolverShared(int n)
|
|
:
|
|
mNumIterations(0),
|
|
mVarBasic(nullptr),
|
|
mVarNonbasic(nullptr),
|
|
mNumCols(0),
|
|
mAugmented(nullptr),
|
|
mQMin(nullptr),
|
|
mMinRatio(nullptr),
|
|
mRatio(nullptr),
|
|
mPoly(nullptr),
|
|
mZero((Real)0),
|
|
mOne((Real)1)
|
|
{
|
|
if (n > 0)
|
|
{
|
|
mDimension = n;
|
|
mMaxIterations = n * n;
|
|
}
|
|
else
|
|
{
|
|
mDimension = 0;
|
|
mMaxIterations = 0;
|
|
}
|
|
}
|
|
|
|
// Use this constructor when you need a specific representation of
|
|
// zero and of one to be used when manipulating the polynomials of the
|
|
// base class. In particular, this is needed to select the correct
|
|
// zero and correct one for QFNumber objects.
|
|
LCPSolverShared(int n, Real const& zero, Real const& one)
|
|
:
|
|
mNumIterations(0),
|
|
mVarBasic(nullptr),
|
|
mVarNonbasic(nullptr),
|
|
mNumCols(0),
|
|
mAugmented(nullptr),
|
|
mQMin(nullptr),
|
|
mMinRatio(nullptr),
|
|
mRatio(nullptr),
|
|
mPoly(nullptr),
|
|
mZero(zero),
|
|
mOne(one)
|
|
{
|
|
if (n > 0)
|
|
{
|
|
mDimension = n;
|
|
mMaxIterations = n * n;
|
|
}
|
|
else
|
|
{
|
|
mDimension = 0;
|
|
mMaxIterations = 0;
|
|
}
|
|
}
|
|
|
|
public:
|
|
// Theoretically, when there is a solution the algorithm must converge
|
|
// in a finite number of iterations. The number of iterations depends
|
|
// on the problem at hand, but we need to guard against an infinite
|
|
// loop by limiting the number. The implementation uses a maximum
|
|
// number of n*n (chosen arbitrarily). You can set the number
|
|
// yourself, perhaps when a call to Solve fails--increase the number
|
|
// of iterations and call and solve again.
|
|
inline void SetMaxIterations(int maxIterations)
|
|
{
|
|
mMaxIterations = (maxIterations > 0 ? maxIterations : mDimension * mDimension);
|
|
}
|
|
|
|
inline int GetMaxIterations() const
|
|
{
|
|
return mMaxIterations;
|
|
}
|
|
|
|
// Access the actual number of iterations used in a call to Solve.
|
|
inline int GetNumIterations() const
|
|
{
|
|
return mNumIterations;
|
|
}
|
|
|
|
enum Result
|
|
{
|
|
HAS_TRIVIAL_SOLUTION,
|
|
HAS_NONTRIVIAL_SOLUTION,
|
|
NO_SOLUTION,
|
|
FAILED_TO_CONVERGE,
|
|
INVALID_INPUT
|
|
};
|
|
|
|
protected:
|
|
// Bookkeeping of variables during the iterations of the solver. The
|
|
// name is either 'w' or 'z' and is used for human-readable debugging
|
|
// help. The 'index' is that for the original variables w[index] or
|
|
// z[index]. The 'complementary' index is the location of the
|
|
// complementary variable in mVarBasic[] or in mVarNonbasic[]. The
|
|
// 'tuple' is a pointer to &w[0] or &z[0], the choice based on name of
|
|
// 'w' or 'z', and is used to fill in the solution values (the
|
|
// variables are permuted during the pivoting algorithm).
|
|
struct Variable
|
|
{
|
|
char name;
|
|
int index;
|
|
int complementary;
|
|
Real* tuple;
|
|
};
|
|
|
|
// The augmented problem is w = q + M*z + z[n]*U = 0, where U is an
|
|
// n-tuple of 1-values. We manipulate the augmented matrix
|
|
// [M | U | p(t)] where p(t) is a column vector of polynomials of at
|
|
// most degree n. If p[r](t) is the polynomial for row r, then
|
|
// p[r](0) = q[r]. These are perturbations of q[r] designed so that
|
|
// the algorithm avoids degeneracies (a q-term becomes zero during the
|
|
// iterations). The basic variables are w[0] through w[n-1] and the
|
|
// nonbasic variables are z[0] through z[n]. The returned z consists
|
|
// only of z[0] through z[n-1].
|
|
|
|
// The derived classes ensure that the pointers point to the correct
|
|
// of elements for each array. The matrix M must be stored in
|
|
// row-major order.
|
|
bool Solve(Real const* q, Real const* M, Real* w, Real* z, Result* result)
|
|
{
|
|
// Perturb the q[r] constants to be polynomials of degree r+1
|
|
// represented as an array of n+1 coefficients. The coefficient
|
|
// with index r+1 is 1 and the coefficients with indices larger
|
|
// than r+1 are 0.
|
|
for (int r = 0; r < mDimension; ++r)
|
|
{
|
|
mPoly[r] = &Augmented(r, mDimension + 1);
|
|
MakeZero(mPoly[r]);
|
|
mPoly[r][0] = q[r];
|
|
mPoly[r][r + 1] = mOne;
|
|
}
|
|
|
|
// Determine whether there is the trivial solution w = z = 0.
|
|
Copy(mPoly[0], mQMin);
|
|
int basic = 0;
|
|
for (int r = 1; r < mDimension; ++r)
|
|
{
|
|
if (LessThan(mPoly[r], mQMin))
|
|
{
|
|
Copy(mPoly[r], mQMin);
|
|
basic = r;
|
|
}
|
|
}
|
|
|
|
if (!LessThanZero(mQMin))
|
|
{
|
|
for (int r = 0; r < mDimension; ++r)
|
|
{
|
|
w[r] = q[r];
|
|
z[r] = mZero;
|
|
}
|
|
|
|
if (result)
|
|
{
|
|
*result = HAS_TRIVIAL_SOLUTION;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Initialize the remainder of the augmented matrix with M and U.
|
|
for (int r = 0; r < mDimension; ++r)
|
|
{
|
|
for (int c = 0; c < mDimension; ++c)
|
|
{
|
|
Augmented(r, c) = M[c + mDimension * r];
|
|
}
|
|
Augmented(r, mDimension) = mOne;
|
|
}
|
|
|
|
// Keep track of when the variables enter and exit the dictionary,
|
|
// including where complementary variables are relocated.
|
|
for (int i = 0; i <= mDimension; ++i)
|
|
{
|
|
mVarBasic[i].name = 'w';
|
|
mVarBasic[i].index = i;
|
|
mVarBasic[i].complementary = i;
|
|
mVarBasic[i].tuple = w;
|
|
mVarNonbasic[i].name = 'z';
|
|
mVarNonbasic[i].index = i;
|
|
mVarNonbasic[i].complementary = i;
|
|
mVarNonbasic[i].tuple = z;
|
|
}
|
|
|
|
// The augmented variable z[n] is the initial driving variable for
|
|
// pivoting. The equation 'basic' is the one to solve for z[n]
|
|
// and pivoting with w[basic]. The last column of M remains all
|
|
// 1-values for this initial step, so no algebraic computations
|
|
// occur for M[r][n].
|
|
int driving = mDimension;
|
|
for (int r = 0; r < mDimension; ++r)
|
|
{
|
|
if (r != basic)
|
|
{
|
|
for (int c = 0; c < mNumCols; ++c)
|
|
{
|
|
if (c != mDimension)
|
|
{
|
|
Augmented(r, c) -= Augmented(basic, c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int c = 0; c < mNumCols; ++c)
|
|
{
|
|
if (c != mDimension)
|
|
{
|
|
Augmented(basic, c) = -Augmented(basic, c);
|
|
}
|
|
}
|
|
|
|
mNumIterations = 0;
|
|
for (int i = 0; i < mMaxIterations; ++i, ++mNumIterations)
|
|
{
|
|
// The basic variable of equation 'basic' exited the
|
|
// dictionary, so/ its complementary (nonbasic) variable must
|
|
// become the next driving variable in order for it to enter
|
|
// the dictionary.
|
|
int nextDriving = mVarBasic[basic].complementary;
|
|
mVarNonbasic[nextDriving].complementary = driving;
|
|
std::swap(mVarBasic[basic], mVarNonbasic[driving]);
|
|
if (mVarNonbasic[driving].index == mDimension)
|
|
{
|
|
// The algorithm has converged.
|
|
for (int r = 0; r < mDimension; ++r)
|
|
{
|
|
mVarBasic[r].tuple[mVarBasic[r].index] = mPoly[r][0];
|
|
}
|
|
for (int c = 0; c <= mDimension; ++c)
|
|
{
|
|
int index = mVarNonbasic[c].index;
|
|
if (index < mDimension)
|
|
{
|
|
mVarNonbasic[c].tuple[index] = mZero;
|
|
}
|
|
}
|
|
if (result)
|
|
{
|
|
*result = HAS_NONTRIVIAL_SOLUTION;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Determine the 'basic' equation for which the ratio
|
|
// -q[r]/M(r,driving) is minimized among all equations r with
|
|
// M(r,driving) < 0.
|
|
driving = nextDriving;
|
|
basic = -1;
|
|
for (int r = 0; r < mDimension; ++r)
|
|
{
|
|
if (Augmented(r, driving) < mZero)
|
|
{
|
|
Real factor = -mOne / Augmented(r, driving);
|
|
Multiply(mPoly[r], factor, mRatio);
|
|
if (basic == -1 || LessThan(mRatio, mMinRatio))
|
|
{
|
|
Copy(mRatio, mMinRatio);
|
|
basic = r;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (basic == -1)
|
|
{
|
|
// The coefficients of z[driving] in all the equations are
|
|
// nonnegative, so the z[driving] variable cannot leave
|
|
// the dictionary. There is no solution to the LCP.
|
|
for (int r = 0; r < mDimension; ++r)
|
|
{
|
|
w[r] = mZero;
|
|
z[r] = mZero;
|
|
}
|
|
|
|
if (result)
|
|
{
|
|
*result = NO_SOLUTION;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Solve the basic equation so that z[driving] enters the
|
|
// dictionary and w[basic] exits the dictionary.
|
|
Real invDenom = mOne / Augmented(basic, driving);
|
|
for (int r = 0; r < mDimension; ++r)
|
|
{
|
|
if (r != basic && Augmented(r, driving) != mZero)
|
|
{
|
|
Real multiplier = Augmented(r, driving) * invDenom;
|
|
for (int c = 0; c < mNumCols; ++c)
|
|
{
|
|
if (c != driving)
|
|
{
|
|
Augmented(r, c) -= Augmented(basic, c) * multiplier;
|
|
}
|
|
else
|
|
{
|
|
Augmented(r, driving) = multiplier;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int c = 0; c < mNumCols; ++c)
|
|
{
|
|
if (c != driving)
|
|
{
|
|
Augmented(basic, c) = -Augmented(basic, c) * invDenom;
|
|
}
|
|
else
|
|
{
|
|
Augmented(basic, driving) = invDenom;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Numerical round-off errors can cause the Lemke algorithm not to
|
|
// converge. In particular, the code above has a test
|
|
// if (mAugmented[r][driving] < (Real)0) { ... }
|
|
// to determine the 'basic' equation with which to pivot. It is
|
|
// possible that theoretically mAugmented[r][driving]is zero but
|
|
// rounding errors cause it to be slightly negative. If
|
|
// theoretically all mAugmented[r][driving] >= 0, there is no
|
|
// solution to the LCP. With the rounding errors, if the
|
|
// algorithm fails to converge within the specified number of
|
|
// iterations, NO_SOLUTION is returned, which is hopefully the
|
|
// correct result. It is also possible that the rounding errors
|
|
// lead to a NO_SOLUTION (returned from inside the loop) when in
|
|
// fact there is a solution. When the LCP solver is used by
|
|
// intersection testing algorithms, the hope is that
|
|
// misclassifications occur only when the two objects are nearly
|
|
// in tangential contact.
|
|
//
|
|
// To determine whether the rounding errors are the problem, you
|
|
// can execute the query using exact arithmetic with the following
|
|
// type used for 'Real' (replacing 'float' or 'double') of
|
|
// BSRational<UIntegerAP32> Rational.
|
|
//
|
|
// That said, if the algorithm fails to converge and you believe
|
|
// that the rounding errors are not causing this, please file a
|
|
// bug report and provide the input data to the solver.
|
|
|
|
#if defined(GTE_THROW_ON_LCPSOLVER_ERRORS)
|
|
LogError("LCPSolverShared::Solve failed to converge.");
|
|
#endif
|
|
if (result)
|
|
{
|
|
*result = FAILED_TO_CONVERGE;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Access mAugmented as a 2-dimensional array.
|
|
inline Real const& Augmented(int row, int col) const
|
|
{
|
|
return mAugmented[col + mNumCols * row];
|
|
}
|
|
|
|
inline Real& Augmented(int row, int col)
|
|
{
|
|
return mAugmented[col + mNumCols * row];
|
|
}
|
|
|
|
// Support for polynomials with n+1 coefficients and degree no larger
|
|
// than n.
|
|
void MakeZero(Real* poly)
|
|
{
|
|
for (int i = 0; i <= mDimension; ++i)
|
|
{
|
|
poly[i] = mZero;
|
|
}
|
|
}
|
|
|
|
void Copy(Real const* poly0, Real* poly1)
|
|
{
|
|
for (int i = 0; i <= mDimension; ++i)
|
|
{
|
|
poly1[i] = poly0[i];
|
|
}
|
|
}
|
|
|
|
bool LessThan(Real const* poly0, Real const* poly1)
|
|
{
|
|
for (int i = 0; i <= mDimension; ++i)
|
|
{
|
|
if (poly0[i] < poly1[i])
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (poly0[i] > poly1[i])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LessThanZero(Real const* poly)
|
|
{
|
|
for (int i = 0; i <= mDimension; ++i)
|
|
{
|
|
if (poly[i] < mZero)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (poly[i] > mZero)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Multiply(Real const* poly, Real scalar, Real* product)
|
|
{
|
|
for (int i = 0; i <= mDimension; ++i)
|
|
{
|
|
product[i] = poly[i] * scalar;
|
|
}
|
|
}
|
|
|
|
int mDimension;
|
|
int mMaxIterations;
|
|
int mNumIterations;
|
|
|
|
// These pointers are set by the derived-class constructors to arrays
|
|
// that have the correct number of elements. The arrays mVarBasic,
|
|
// mVarNonbasic, mQMin, mMinRatio, and mRatio each have n+1 elements.
|
|
// The mAugmented array has n rows and 2*(n+1) columns stored in
|
|
// row-major order in a 1-dimensional array. The array of pointers
|
|
// mPoly has n elements.
|
|
Variable* mVarBasic;
|
|
Variable* mVarNonbasic;
|
|
int mNumCols;
|
|
Real* mAugmented;
|
|
Real* mQMin;
|
|
Real* mMinRatio;
|
|
Real* mRatio;
|
|
Real** mPoly;
|
|
Real mZero, mOne;
|
|
};
|
|
|
|
|
|
template <typename Real, int n>
|
|
class LCPSolver<Real, n> : public LCPSolverShared<Real>
|
|
{
|
|
public:
|
|
// Construction. The member mMaxIterations is set by this call to the
|
|
// default value n*n.
|
|
LCPSolver()
|
|
:
|
|
LCPSolverShared<Real>(n)
|
|
{
|
|
this->mVarBasic = mArrayVarBasic.data();
|
|
this->mVarNonbasic = mArrayVarNonbasic.data();
|
|
this->mNumCols = 2 * (n + 1);
|
|
this->mAugmented = mArrayAugmented.data();
|
|
this->mQMin = mArrayQMin.data();
|
|
this->mMinRatio = mArrayMinRatio.data();
|
|
this->mRatio = mArrayRatio.data();
|
|
this->mPoly = mArrayPoly.data();
|
|
}
|
|
|
|
// Use this constructor when you need a specific representation of
|
|
// zero and of one to be used when manipulating the polynomials of the
|
|
// base class. In particular, this is needed to select the correct
|
|
// zero and correct one for QFNumber objects.
|
|
LCPSolver(Real const& zero, Real const& one)
|
|
:
|
|
LCPSolverShared<Real>(n, zero, one)
|
|
{
|
|
this->mVarBasic = mArrayVarBasic.data();
|
|
this->mVarNonbasic = mArrayVarNonbasic.data();
|
|
this->mNumCols = 2 * (n + 1);
|
|
this->mAugmented = mArrayAugmented.data();
|
|
this->mQMin = mArrayQMin.data();
|
|
this->mMinRatio = mArrayMinRatio.data();
|
|
this->mRatio = mArrayRatio.data();
|
|
this->mPoly = mArrayPoly.data();
|
|
}
|
|
|
|
// If you want to know specifically why 'true' or 'false' was
|
|
// returned, pass the address of a Result variable as the last
|
|
// parameter.
|
|
bool Solve(std::array<Real, n> const& q, std::array<std::array<Real, n>, n> const& M,
|
|
std::array<Real, n>& w, std::array<Real, n>& z,
|
|
typename LCPSolverShared<Real>::Result* result = nullptr)
|
|
{
|
|
return LCPSolverShared<Real>::Solve(q.data(), M.front().data(), w.data(), z.data(), result);
|
|
}
|
|
|
|
private:
|
|
std::array<typename LCPSolverShared<Real>::Variable, n + 1> mArrayVarBasic;
|
|
std::array<typename LCPSolverShared<Real>::Variable, n + 1> mArrayVarNonbasic;
|
|
std::array<Real, 2 * (n + 1)* n> mArrayAugmented;
|
|
std::array<Real, n + 1> mArrayQMin;
|
|
std::array<Real, n + 1> mArrayMinRatio;
|
|
std::array<Real, n + 1> mArrayRatio;
|
|
std::array<Real*, n> mArrayPoly;
|
|
};
|
|
|
|
|
|
template <typename Real>
|
|
class LCPSolver<Real> : public LCPSolverShared<Real>
|
|
{
|
|
public:
|
|
// Construction. The member mMaxIterations is set by this call to the
|
|
// default value n*n.
|
|
LCPSolver(int n)
|
|
:
|
|
LCPSolverShared<Real>(n)
|
|
{
|
|
if (n > 0)
|
|
{
|
|
mVectorVarBasic.resize(n + 1);
|
|
mVectorVarNonbasic.resize(n + 1);
|
|
mVectorAugmented.resize(2 * (n + 1) * n);
|
|
mVectorQMin.resize(n + 1);
|
|
mVectorMinRatio.resize(n + 1);
|
|
mVectorRatio.resize(n + 1);
|
|
mVectorPoly.resize(n);
|
|
|
|
this->mVarBasic = mVectorVarBasic.data();
|
|
this->mVarNonbasic = mVectorVarNonbasic.data();
|
|
this->mNumCols = 2 * (n + 1);
|
|
this->mAugmented = mVectorAugmented.data();
|
|
this->mQMin = mVectorQMin.data();
|
|
this->mMinRatio = mVectorMinRatio.data();
|
|
this->mRatio = mVectorRatio.data();
|
|
this->mPoly = mVectorPoly.data();
|
|
}
|
|
}
|
|
|
|
// The input q must have n elements and the input M must be an n-by-n
|
|
// matrix stored in row-major order. The outputs w and z have n
|
|
// elements. If you want to know specifically why 'true' or 'false'
|
|
// was returned, pass the address of a Result variable as the last
|
|
// parameter.
|
|
bool Solve(std::vector<Real> const& q, std::vector<Real> const& M,
|
|
std::vector<Real>& w, std::vector<Real>& z,
|
|
typename LCPSolverShared<Real>::Result* result = nullptr)
|
|
{
|
|
if (this->mDimension > static_cast<int>(q.size())
|
|
|| this->mDimension * this->mDimension > static_cast<int>(M.size()))
|
|
{
|
|
if (result)
|
|
{
|
|
*result = this->INVALID_INPUT;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (this->mDimension > static_cast<int>(w.size()))
|
|
{
|
|
w.resize(this->mDimension);
|
|
}
|
|
|
|
if (this->mDimension > static_cast<int>(z.size()))
|
|
{
|
|
z.resize(this->mDimension);
|
|
}
|
|
|
|
return LCPSolverShared<Real>::Solve(q.data(), M.data(), w.data(), z.data(), result);
|
|
}
|
|
|
|
private:
|
|
std::vector<typename LCPSolverShared<Real>::Variable> mVectorVarBasic;
|
|
std::vector<typename LCPSolverShared<Real>::Variable> mVectorVarNonbasic;
|
|
std::vector<Real> mVectorAugmented;
|
|
std::vector<Real> mVectorQMin;
|
|
std::vector<Real> mVectorMinRatio;
|
|
std::vector<Real> mVectorRatio;
|
|
std::vector<Real*> mVectorPoly;
|
|
};
|
|
}
|
|
|