Program Listing for File poly_lcp.cpp
↰ Return to documentation for file (src/mathopt/lcp/poly_lcp.cpp
)
/* #############################################
* This file is part of
* ZERO
*
* Copyright (c) 2020
* Released under the Creative Commons
* CC BY-NC-SA 4.0 License
*
* Find out more at
* https://github.com/ds4dm/ZERO
* #############################################*/
#include "mathopt/lcp/poly_lcp.h"
#include <memory>
std::vector<short int> MathOpt::PolyLCP::solEncode(const arma::vec &x) const {
return this->solEncode(this->M * x + this->q, x);
}
std::vector<short int> MathOpt::PolyLCP::solEncode(const arma::vec &z, const arma::vec &x) const {
std::vector<short int> solEncoded(nR, 0);
for (const auto p : Compl) {
unsigned int i, j;
i = p.first;
j = p.second;
if (Utils::isEqual(z(i), 0))
solEncoded.at(i)++;
if (Utils::isEqual(x(j), 0))
solEncoded.at(i)--;
if (!Utils::isEqual(x(j), 0) && !Utils::isEqual(z(i), 0))
LOG_S(1) << "Infeasible point given! Stay alert! " << x(j) << " " << z(i) << " with i=" << i;
};
return solEncoded;
}
bool operator==(std::vector<short int> encoding1, std::vector<short int> encoding2) {
if (encoding1.size() != encoding2.size())
return false;
for (unsigned int i = 0; i < encoding1.size(); i++) {
if (encoding1.at(i) != encoding2.at(i))
return false;
}
return true;
}
bool operator<(std::vector<short int> child, std::vector<short int> father) {
if (child.size() != father.size())
return false;
else {
for (unsigned long i = 0; i < father.size(); ++i) {
if (father.at(i) != 0 && father.at(i) != 2) {
if (child.at(i) != father.at(i))
return false;
}
}
return true;
}
}
bool operator>(const std::vector<int> &encoding1, const std::vector<int> &encoding2) {
return (encoding2 < encoding1);
}
bool MathOpt::PolyLCP::addPolyFromX(const arma::vec &x, bool innerApproximation) {
const auto numCompl = this->Compl.size();
auto encoding = this->solEncode(x);
std::stringstream encStr;
for (auto vv : encoding)
encStr << vv << " ";
LOG_S(2) << "MathOpt::PolyLCP::addPolyFromX: Handling point with encoding: " << encStr.str();
// Check if the encoding polyhedron is already in the current inner approximation
for (const auto &i : this->CurrentPoly[0]) {
std::vector<short int> bin = this->numToVec(i, numCompl, true);
if (encoding < bin) {
LOG_S(2) << "MathOpt::PolyLCP::addPolyFromX: Encoding " << i
<< " already in the inner approximation! ";
return false;
}
}
LOG_S(2) << "MathOpt::PolyLCP::addPolyFromX: The encoding is not in the inner approximation!";
// If it is not in CurrentPoly
// First change any zero indices of encoding to 1
for (short &i : encoding) {
if (i == 0)
++i;
}
return this->addPolyFromEncoding(encoding, innerApproximation);
}
bool MathOpt::PolyLCP::addPolyFromEncoding(const std::vector<short int> &encoding,
bool innerApproximation,
bool checkFeas,
bool custom,
spmat_Vec * customA,
vec_Vec * customb) {
unsigned int encodingNumber = this->vecToNum(encoding, innerApproximation);
bool eval;
if (checkFeas)
eval = this->checkPolyFeas(encoding, innerApproximation);
else
eval = true;
if (eval) {
if (!innerApproximation)
this->Outer_FeasibleApproximation = true;
// Check if it was already added
if (!custom && !CurrentPoly[innerApproximation ? 0 : 1].empty()) {
if (CurrentPoly[innerApproximation ? 0 : 1].find(encodingNumber) !=
CurrentPoly[innerApproximation ? 0 : 1].end()) {
return false;
}
}
std::unique_ptr<arma::sp_mat> Aii = std::make_unique<arma::sp_mat>(nR, nC);
Aii->zeros();
std::unique_ptr<arma::vec> bii = std::make_unique<arma::vec>(nR, arma::fill::zeros);
for (unsigned int i = 0; i < this->nR; i++) {
switch (encoding.at(i)) {
// Fix z=0
case 1: {
for (auto j = this->M.begin_row(i); j != this->M.end_row(i); ++j)
if (!Utils::isEqual(*j, 0))
Aii->at(i, j.col()) = (*j); // Only mess with non-zero elements of a sparse matrix!
bii->at(i) = -this->q(i);
} break;
// Fix x=0
case -1: {
unsigned int variablePosition = (i >= this->LeadStart) ? i + this->NumberLeader : i;
Aii->at(i, variablePosition) = 1;
bii->at(i) = 0;
} break;
case 2: {
if (innerApproximation)
throw ZEROException(ZEROErrorCode::InvalidData,
"Non-allowed encoding for innerApproximation");
} break;
default:
throw ZEROException(ZEROErrorCode::InvalidData,
"Non-allowed encoding (" + std::to_string(i) + ").");
}
}
if (custom) {
customA->push_back(std::move(Aii));
customb->push_back(std::move(bii));
} else {
CurrentPoly[innerApproximation ? 0 : 1].insert(encodingNumber);
this->Ai->push_back(std::move(Aii));
this->bi->push_back(std::move(bii));
}
return true; // Successfully added
}
return false;
}
unsigned int MathOpt::PolyLCP::addPoliesFromEncoding(const std::vector<short int> &encoding,
bool innerApproximation,
bool checkFeas,
bool custom,
spmat_Vec *customA,
vec_Vec * customb) {
unsigned int added = 0; // number of added polyhedron
bool flag = false; // flag that there may be multiple polyhedra, i.e. 0 in
// some encoding entry
std::vector<short int> encodingCopy(encoding);
unsigned int i;
for (i = 0; i < this->nR; i++) {
if (encoding.at(i) == 2 && innerApproximation)
throw ZEROException(ZEROErrorCode::InvalidData,
"Non-allowed encoding for innerApproximation");
else if (encoding.at(i) == 0) {
flag = true;
break;
}
}
if (flag) {
encodingCopy[i] = 1;
added += this->addPoliesFromEncoding(
encodingCopy, innerApproximation, checkFeas, custom, customA, customb);
encodingCopy[i] = -1;
added += this->addPoliesFromEncoding(
encodingCopy, innerApproximation, checkFeas, custom, customA, customb);
} else
added += this->addPolyFromEncoding(
encoding, innerApproximation, checkFeas, custom, customA, customb);
return added;
}
unsigned long int MathOpt::PolyLCP::getNextPoly(Data::LCP::PolyhedraStrategy method) {
switch (method) {
case Data::LCP::PolyhedraStrategy::Sequential: {
while (this->Inner_SequentialPolyCounter < this->Inner_MaxPoly) {
const auto isAll =
CurrentPoly[0].find(this->Inner_SequentialPolyCounter) != CurrentPoly[0].end();
const auto isInfeas =
InfeasiblePoly[0].find(this->Inner_SequentialPolyCounter) != InfeasiblePoly[0].end();
this->Inner_SequentialPolyCounter++;
if (!isAll && !isInfeas) {
return this->Inner_SequentialPolyCounter - 1;
}
}
return this->Inner_MaxPoly;
}
case Data::LCP::PolyhedraStrategy::ReverseSequential: {
while (this->Inner_ReverseSequentialPolyCounter >= 0) {
const auto isAll =
CurrentPoly[0].find(this->Inner_ReverseSequentialPolyCounter) != CurrentPoly[0].end();
const auto isInfeas = InfeasiblePoly[0].find(this->Inner_ReverseSequentialPolyCounter) !=
InfeasiblePoly[0].end();
this->Inner_ReverseSequentialPolyCounter--;
if (!isAll && !isInfeas) {
return this->Inner_ReverseSequentialPolyCounter + 1;
}
}
return this->Inner_MaxPoly;
}
case Data::LCP::PolyhedraStrategy::Random: {
static std::mt19937 engine(this->RandomSeed);
std::uniform_int_distribution<unsigned long int> dist(0, this->Inner_MaxPoly - 1);
if ((InfeasiblePoly.size() + CurrentPoly.size()) == this->Inner_MaxPoly)
return this->Inner_MaxPoly;
while (true) {
auto randomPolyId = dist(engine);
const auto isAll = CurrentPoly[0].find(randomPolyId) != CurrentPoly[0].end();
const auto isInfeas = InfeasiblePoly[0].find(randomPolyId) != InfeasiblePoly[0].end();
if (!isAll && !isInfeas)
return randomPolyId;
}
}
}
throw ZEROException(ZEROErrorCode::Unknown,
"MathOpt::PolyLCP::getNextPoly generated a weird result.");
}
unsigned int MathOpt::PolyLCP::addAPoly(unsigned long int nPoly,
Data::LCP::PolyhedraStrategy method) {
int add = 0;
if (this->Inner_MaxPoly < nPoly) { // If you cannot add that numVariablesY polyhedra
LOG_S(WARNING) // Then issue a warning
<< "Warning in MathOpt::PolyLCP::randomPoly: "
<< "Cannot add " << nPoly << " polyhedra. Promising a maximum of " << this->Inner_MaxPoly;
nPoly = this->Inner_MaxPoly; // and update maximum possibly addable
}
if (nPoly == 0) // If nothing to be added, then nothing to be done
return 0;
while (true) {
auto choiceDecimal = this->getNextPoly(method);
if (choiceDecimal >= this->Inner_MaxPoly)
return add;
if (this->checkPolyFeas(choiceDecimal, true)) {
const std::vector<short int> choice = this->numToVec(choiceDecimal, this->Compl.size(), true);
//Disable feasibility check, since it has been already performed
auto added = this->addPolyFromEncoding(choice, true, false);
if (added) // If choice is added to All Polyhedra
{
add++;
if (add == nPoly) {
return add;
}
}
}
}
}
bool MathOpt::PolyLCP::addThePoly(const unsigned long int &decimalEncoding,
bool innerApproximation) {
if (this->Inner_MaxPoly < decimalEncoding && innerApproximation) {
// This polyhedron does not exist
LOG_S(WARNING) << "Warning in MathOpt::PolyLCP::addThePoly: Cannot add " << decimalEncoding
<< " polyhedra, since it does not exist!";
return false;
}
return this->addPolyFromEncoding(
this->numToVec(decimalEncoding, this->Compl.size(), innerApproximation), innerApproximation);
}
unsigned int MathOpt::PolyLCP::exactFullEnumeration(const bool feasibilityCheck) {
std::vector<short int> encoding = std::vector<short int>(nR, 0);
this->Ai->clear();
this->bi->clear();
this->addPoliesFromEncoding(encoding, true, feasibilityCheck);
if (this->Ai->empty()) {
LOG_S(WARNING) << "Empty vector of polyhedra given! Problem might be infeasible." << '\n';
// 0 <= -1 for infeasibility
std::unique_ptr<arma::sp_mat> A(new arma::sp_mat(1, this->M.n_cols));
std::unique_ptr<arma::vec> b(new arma::vec(1));
b->at(0) = -1;
this->Ai->push_back(std::move(A));
this->bi->push_back(std::move(b));
}
return this->Ai->size();
}
std::string MathOpt::PolyLCP::feasabilityDetailString() const {
std::stringstream ss;
ss << "\tInner Approximation: ";
for (auto vv : this->CurrentPoly[0])
ss << vv << ' ';
ss << "\n\tOuter Approximation: ";
for (auto vv : this->CurrentPoly[1])
ss << vv << ' ';
return ss.str();
}
unsigned long MathOpt::PolyLCP::convNumPoly(bool innerApproximation) const {
return this->CurrentPoly[innerApproximation ? 0 : 1].size();
}
unsigned int MathOpt::PolyLCP::convPolyPosition(const unsigned long int i,
bool innerApproximation) const {
const unsigned int nPoly = this->convNumPoly(innerApproximation);
if (i > nPoly)
throw ZEROException(ZEROErrorCode::OutOfRange, "Argument i is out of range");
const unsigned int nC = this->M.n_cols;
return nC + i * nC;
}
unsigned int MathOpt::PolyLCP::convPolyWeight(const unsigned long int i,
bool innerApproximation) const {
const unsigned int nPoly = this->convNumPoly(innerApproximation);
if (nPoly <= 1) {
return 0;
}
if (i > nPoly)
throw ZEROException(ZEROErrorCode::OutOfRange, "Argument i is out of range");
const unsigned int nC = this->M.n_cols;
return nC + nPoly * nC + i;
}
bool MathOpt::PolyLCP::checkPolyFeas(const unsigned long int &decimalEncoding,
bool innerApproximation) {
return this->checkPolyFeas(
this->numToVec(decimalEncoding, this->Compl.size(), innerApproximation), innerApproximation);
}
bool MathOpt::PolyLCP::outerApproximate(const std::vector<bool> &encoding, bool clear) {
ZEROAssert(encoding.size() == this->Compl.size());
if (clear) {
this->clearPolyhedra(false);
LOG_S(INFO) << "MathOpt::PolyLCP::outerApproximate: clearing current approximation.";
}
std::vector<short int> localEncoding = {};
// We push 0 for each complementary that has to be fixed either to +1 or -1
// And 2 for each one which is not processed (yet)
for (bool i : encoding) {
if (i)
localEncoding.push_back(0);
else
localEncoding.push_back(2);
}
return this->addPoliesFromEncoding(localEncoding, false, true) > 0;
}
bool MathOpt::PolyLCP::checkPolyFeas(const std::vector<short int> &encoding,
bool innerApproximation) {
unsigned long int encodingNumber = this->vecToNum(encoding, innerApproximation);
unsigned int index = innerApproximation ? 0 : 1;
for (const auto i : InfeasiblePoly[index]) {
if (i == encodingNumber) {
LOG_S(1) << "MathOpt::PolyLCP::checkPolyFeas: Previously known "
"infeasible polyhedron #"
<< encodingNumber;
return false;
}
if (!innerApproximation) {
// We may want to check for parents
if (encoding < this->numToVec(i, this->Compl.size(), false)) {
LOG_S(1)
<< "MathOpt::PolyLCP::checkPolyFeas: Children of an infeasible polyhedron. Infeasible #"
<< encodingNumber;
InfeasiblePoly[index].insert(encodingNumber);
return false;
}
}
}
for (const auto i : FeasiblePoly[index]) {
if (i == encodingNumber) {
LOG_S(1) << "MathOpt::PolyLCP::checkPolyFeas: Previously known "
"feasible polyhedron #"
<< encodingNumber;
return true;
}
if (!innerApproximation) {
// We may want to check for parents
if (this->numToVec(i, this->Compl.size(), false) < encoding) {
LOG_S(1) << "MathOpt::PolyLCP::checkPolyFeas: Parent of a feasible polyhedron. Feasible #"
<< encodingNumber;
FeasiblePoly[index].insert(encodingNumber);
return true;
}
}
}
unsigned int count{0};
try {
makeRelaxed();
GRBModel model(this->RelaxedModel);
for (auto i : encoding) {
switch (i) {
case 1:
model.getVarByName("z_" + std::to_string(count)).set(GRB_DoubleAttr_UB, 0);
break;
case -1:
model
.getVarByName("x_" +
std::to_string(count >= this->LeadStart ? (count + NumberLeader) : count))
.set(GRB_DoubleAttr_UB, 0);
break;
case 0: {
throw ZEROException(ZEROErrorCode::Assertion, "Non allowed encoding (0).");
} break;
case 2: {
if (innerApproximation)
throw ZEROException(ZEROErrorCode::InvalidData,
"Non-allowed encoding for innerApproximation");
} break;
default:
throw ZEROException(ZEROErrorCode::Assertion,
"Non allowed encoding (" + std::to_string(i) +
") in the inner approximation.");
}
count++;
}
model.set(GRB_IntParam_OutputFlag, 0);
model.optimize();
if (model.get(GRB_IntAttr_Status) == GRB_OPTIMAL) {
FeasiblePoly[index].insert(encodingNumber);
return true;
} else {
InfeasiblePoly[index].insert(encodingNumber);
return false;
}
} catch (GRBException &e) {
throw ZEROException(e);
}
}
unsigned long int MathOpt::PolyLCP::vecToNum(std::vector<short int> binary, bool inner) {
unsigned long int number = 0;
unsigned int posn = 1;
if (inner) {
// Add one to every entry, so that {-1,1} becomes {0,2}. Divide by 2 to obtain the bit.
while (!binary.empty()) {
short int bit = (binary.back() + 1) / 2; // The least significant bit
number += (bit * posn);
posn *= 2; // Update place value
binary.pop_back(); // Remove that bit
}
} else {
// Convert -1 to zero, so that items are from {-1,1,2} in {0,1,2}
while (!binary.empty()) {
number += ((binary.back() == -1 ? 0 : binary.back()) * posn);
posn *= 3;
binary.pop_back();
}
}
return number;
}
std::vector<short int>
MathOpt::PolyLCP::numToVec(unsigned long int number, const unsigned long nCompl, bool inner) {
std::vector<short int> binary{};
if (inner) {
for (unsigned int vv = 0; vv < nCompl; vv++) {
binary.push_back((number % 2) == 0 ? -1 : 1);
number /= 2;
}
} else {
for (unsigned int vv = 0; vv < nCompl; vv++) {
binary.push_back((number % 3) == 0 ? -1 : (number % 3));
number /= 3;
}
}
std::reverse(binary.begin(), binary.end());
return binary;
}
void MathOpt::PolyLCP::clearPolyhedra(bool inner) {
this->Ai->clear();
this->bi->clear();
this->CurrentPoly[inner ? 0 : 1].clear();
if (!inner)
this->Outer_FeasibleApproximation = false;
}
std::string std::to_string(const Data::LCP::PolyhedraStrategy add) {
switch (add) {
case Data::LCP::PolyhedraStrategy::Sequential:
return std::string("Sequential");
case Data::LCP::PolyhedraStrategy::ReverseSequential:
return std::string("ReverseSequential");
case Data::LCP::PolyhedraStrategy::Random:
return std::string("Random");
default:
return std::string("Unknown");
}
}