.. _program_listing_file_src_mathopt_lcp_poly_lcp.cpp:

Program Listing for File poly_lcp.cpp
=====================================

|exhale_lsh| :ref:`Return to documentation for file <file_src_mathopt_lcp_poly_lcp.cpp>` (``src/mathopt/lcp/poly_lcp.cpp``)

.. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS

.. code-block:: 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");
     }
   }