Program Listing for File epec_cutandplay.cpp

Return to documentation for file (src/games/algorithms/EPEC/epec_cutandplay.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 "games/algorithms/EPEC/epec_cutandplay.h"

#include <memory>


bool Algorithms::EPEC::CutAndPlay::isSolved(double tol) {
  if (this->Feasible)
     return true;
  else {
     bool cuts;
     return this->isFeasible(cuts);
  }
}


bool Algorithms::EPEC::CutAndPlay::isFeasible(bool &addedCuts) {


  // First, we have a NE from Games::computeNashEq
  if (!this->EPECObject->NashEquilibrium)
     return false;


  // The returned result
  bool result = true;

  // The best response object. filled for every players
  arma::vec bestResponse;
  // Compute payoffs in the current solution
  arma::vec incumbentPayoffs =
        this->EPECObject->TheNashGame->computeQPObjectiveValues(this->EPECObject->SolutionX, true);
  // For any player
  for (unsigned int i = 0; i < this->EPECObject->NumPlayers; ++i) {
     // Reset the feasibility
     this->Trees.at(i)->resetFeasibility();
     // Compute the payoff of the best response, as well as the best response
     auto bestModel = this->EPECObject->bestResponseProgram(
          i, this->EPECObject->SolutionX, this->Trees.at(i)->OriginalLCP.get());
     const int status = bestModel->get(GRB_IntAttr_Status);

     if (status == GRB_UNBOUNDED) {
        LOG_S(1) << "Algorithms::EPEC::CutAndPlay::isFeasible (P" << i
                    << ") Unbounded deviation";
        addedCuts = false;
        result    = false;
     }

     // Get the best response
     unsigned int Nx = this->EPECObject->PlayersLCP.at(i)->getNumCols();
     bestResponse.zeros(Nx);
     for (unsigned int j = 0; j < Nx; ++j)
        bestResponse.at(j) = bestModel->getVarByName("x_" + std::to_string(j)).get(GRB_DoubleAttr_X);
     double bestPayoff = bestModel->getObjective().getValue();


     LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::isFeasible (P" << i << ") Payoff of "
                     << incumbentPayoffs.at(i) << " vs bestResponse of " << bestPayoff;
     // Since it's minimization, difference between the incumbent and best response payoff
     double absdiff = incumbentPayoffs.at(i) - bestPayoff;



     if (!Utils::isEqual(incumbentPayoffs.at(i), bestPayoff, this->Tolerance, 1 - this->Tolerance)) {
        // Discrepancy between payoffs! Need to investigate.


        if (absdiff > 10 * this->Tolerance) {
          // It means the current payoff is more than then optimal response. Then
          // this is not a best response. Theoretically, this cannot happen from
          // an outer approximation. This can however happen for numerical reasons

          LOG_S(WARNING) << "Algorithms::EPEC::CutAndPlay::isFeasible (P" << i << ")"
                              << " No best response (" << incumbentPayoffs.at(i) << " vs " << bestPayoff
                              << " with absdiff=" << incumbentPayoffs.at(i) - bestPayoff << ")";
          this->EPECObject->Stats.Status.set(ZEROStatus::Numerical);
          ZEROException(ZEROErrorCode::Numeric, "Invalid payoffs relation (better best response).");
          return 0;
        } else
        // In any other case, we are good to go.
        {
          // It means the current payoff is less than the optimal response. The
          // approximation is not good, and this point is infeasible. Then, we can
          // generate a value-cut
          arma::vec xMinusI;
          this->EPECObject->getXMinusI(this->EPECObject->SolutionX, i, xMinusI);
          this->addValueCut(i, bestPayoff, xMinusI);
          LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::isFeasible (P" << i << ") Value cut";
          result = false;
        }
     } else {

        // Here we have a best response whose payoff coincides with the one of the
        // equilibrium. The strategy might not be feasible, though.

        // Get xOfI
        arma::vec xOfI;
        this->EPECObject->getXofI(this->EPECObject->SolutionX, i, xOfI, false);

        // Check if we need to add the point to the vertex storage.
        arma::vec vertex = bestResponse.subvec(0, xOfI.size() - 1);
        // Add the best response to the storage
        if (this->Trees.at(i)->addVertex(vertex, true)) {
          LOG_S(1) << "Algorithms::EPEC::CutAndPlay::isFeasible (P" << i
                      << ") Adding vertex (Best Response)";
        } else {
          LOG_S(1) << "Algorithms::EPEC::CutAndPlay::isFeasible (P" << i
                      << ") Already known vertex (Best Response)";
        }

        if (!Utils::isZero(xOfI - vertex, this->Tolerance)) {
          // Then, if the answers aren't equal, we need to refine the
          // approximation or determine if this strategy is anyhow feasible.
          // We search for a convex combination of best responses so that we can
          // certify the answer is inside the convex-hull (or not).

          int budget = this->EPECObject->PlayersQP.at(i)->getNumVars();
          if (!this->equilibriumOracle(xOfI, this->EPECObject->SolutionX, i, budget, addedCuts)) {
             LOG_S(1) << "Algorithms::EPEC::CutAndPlay::isFeasible (P" << i
                         << ") CutAndPlay says NO.";
             result = false;
          }
          // Otherwise, the result is true...

        } else {
          // Feasible point
          this->Trees.at(i)->setFeasible();
          this->Trees.at(i)->setPure();
          LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::isFeasible (P" << i
                          << ") Feasible strategy (Best Response)";
          return true;
        }
     }
  }
  return result;
}


void Algorithms::EPEC::CutAndPlay::updateMembership(const unsigned int &player,
                                                                                const arma::vec &   xOfI) {


  auto PlayerTree = Trees.at(player);
  MathOpt::getDualMembershipLP(PlayerTree->MembershipLP,
                                         PlayerTree->VertexCounter,
                                         PlayerTree->V,
                                         PlayerTree->RayCounter,
                                         PlayerTree->R,
                                         xOfI,
                                         PlayerTree->containsOrigin);
}



bool Algorithms::EPEC::CutAndPlay::equilibriumOracle(
     arma::vec &xOfI, arma::vec &x, unsigned int player, int budget, bool &addedCuts) {


  // Store the leaderModel outside the loop
  auto leaderModel =
        this->EPECObject->bestResponseProgram(player, x, this->Trees.at(player)->OriginalLCP.get());
  GRBVar l[xOfI.size()]; // Dual membership variables
  for (unsigned int i = 0; i < xOfI.size(); i++)
     l[i] = leaderModel->getVarByName("x_" + std::to_string(i));
  leaderModel->set(GRB_IntParam_InfUnbdInfo, 1);
  leaderModel->set(GRB_IntParam_DualReductions, 0);
  leaderModel->set(GRB_IntParam_OutputFlag, 0);
  leaderModel->set(GRB_IntParam_SolutionLimit, 100);


  // Store Membership LP outside the loop
  this->updateMembership(player, xOfI);
  auto   dualMembershipModel = this->Trees.at(player)->MembershipLP.get();
  GRBVar y[xOfI.size()]; // Dual membership variables
  for (unsigned int i = 0; i < xOfI.size(); i++)
     y[i] = dualMembershipModel->getVarByName("alpha_" + std::to_string(i));
  GRBVar betaVar = dualMembershipModel->getVarByName("beta");
  // Update the objective for the membership. Avoid doing it every time
  // Update normalization
  GRBLinExpr expr = -betaVar;
  for (int j = 0; j < xOfI.size(); ++j)
     expr += xOfI.at(j) * y[j];
  dualMembershipModel->setObjective(expr, GRB_MAXIMIZE);



  for (int k = 0; k < budget; ++k) {
     // First, we check whether the point is a convex combination of feasible
     // KNOWN points
     auto V = this->Trees.at(player)->V;

     // Avoid for the firs time. We already have it
     if (k > 0)
        this->updateMembership(player, xOfI);


     dualMembershipModel->optimize();

     int status = dualMembershipModel->get(GRB_IntAttr_Status);

     LOG_S(1) << "Algorithms::EPEC::CutAndPlay::equilibriumOracle: (P" << player << ")"
                 << " MembershipLP status is " << status
                 << " (Objective=" << dualMembershipModel->getObjective().getValue() << ")";

     if (status == GRB_OPTIMAL) {
        double dualObj = dualMembershipModel->getObjective().getValue();

        // Maximization of the dual membership gives zero. Then, the point is feasible
        if (Utils::isEqual(dualObj, 0, this->Tolerance)) {

          arma::vec sol(xOfI.size(), arma::fill::zeros);

          for (unsigned int i = 0; i < xOfI.size(); i++)
             sol.at(i) = y[i].get(GRB_DoubleAttr_X);

          LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::equilibriumOracle: (P" << player
                          << ")"
                          << " The point is a convex combination of known points!";

          this->Trees.at(player)->setFeasible();

          arma::vec support, rays;
          support.zeros(this->Trees.at(player)->getVertexCount());
          rays.zeros(this->Trees.at(player)->getRayCount());
          for (unsigned int v = 0; v < this->Trees.at(player)->getVertexCount(); ++v) {
             support.at(v) =
                  dualMembershipModel->getConstrByName("V_" + std::to_string(v)).get(GRB_DoubleAttr_Pi);
          }
          bool flag = false;
          for (unsigned int r = 0; r < this->Trees.at(player)->getRayCount(); ++r) {
             rays.at(r) =
                  dualMembershipModel->getConstrByName("R_" + std::to_string(r)).get(GRB_DoubleAttr_Pi);
             //******DEBUG********
             /*if (rays.at(r) > this->Tolerance) {

                LOG_S(WARNING) << "Algorithms::EPEC::CutAndPlay::equilibriumOracle: (P"
                                    << player << ")"
                                    << " Ray " << r << " has a positive coefficient.";
                flag = true;
             }
              */
             //******DEBUG********
          }

          //******DEBUG********
          // auto test = arma::accu(support);
          // support.print("support vertices" + std::to_string(test));
          // rays.print("support rays");
          //******DEBUG********

          // Check whether this is pure or not.
          //******DEBUG********
          // dualMembershipModel->write("Membership.lp");
          // this->Trees.at(player)->V.impl_print_dense("V");
          // this->Trees.at(player)->R.impl_print_dense("R");
          // xOfI.print("xOfI");
          //******DEBUG********

          // assert(arma::sum(support) > 1 - 1e-3);
          if (Utils::isEqual(support.max(), 1, this->Tolerance) &&
                Utils::isEqual(arma::abs(rays).max(), this->Tolerance * 10)) {
             this->Trees.at(player)->setPure();
          } else {
             if (this->isFeasiblePure(player, xOfI)) {
                this->Trees.at(player)->setPure();
                LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::equilibriumOracle: (P" << player
                                << ")"
                                << " Pure strategy.";
             } else {
                LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::equilibriumOracle: (P" << player
                                << ")"
                                << " Mixed strategy.";
             }
          }
          return true;

        } else {

          // This is not a convex combination! The dual membership objective is not zero


          // Get the Farkas' in the form of the unbounded ray of the dual of the
          // dualMembershipLP (the primal)
          LOG_S(1) << "Algorithms::EPEC::CutAndPlay::equilibriumOracle: (P" << player
                      << ")"
                          " The point is NOT a convex combination of known points!";
          arma::vec cutLHS(xOfI.size(), arma::fill::zeros);

          // Load the Farkas' certificate
          for (unsigned int i = 0; i < xOfI.size(); i++)
             cutLHS.at(i) = y[i].get(GRB_DoubleAttr_X);

          // Optimize the resulting inequality over the original feasible set
          expr = 0;
          for (unsigned int i = 0; i < xOfI.size(); ++i)
             expr += cutLHS.at(i) * l[i];


          leaderModel->setObjective(expr, GRB_MAXIMIZE);
          leaderModel->update();
          leaderModel->optimize();
          status = leaderModel->get(GRB_IntAttr_Status);


          // If the status is optimal, either a vertex or a cut
          if (status == GRB_OPTIMAL ||
                (status == GRB_SUBOPTIMAL && leaderModel->get(GRB_IntAttr_SolCount) > 0)) {

             // For any solution found
             int numSols = leaderModel->get(GRB_IntAttr_SolCount);

             for (int s = 0; s < numSols; ++s) {

                leaderModel->set(GRB_IntParam_SolutionNumber, s);
                double betaPlayer = leaderModel->getObjective().getValue();

                LOG_S(1) << "Algorithms::EPEC::CutAndPlay::equilibriumOracle: (P" << player
                            << ")"
                                " LeaderModel status = "
                            << std::to_string(status) << " with objective=" << betaPlayer;

                double betaXofI = arma::as_scalar(cutLHS.t() * xOfI); // c^T xOfI

                LOG_S(1) << "Algorithms::EPEC::CutAndPlay::equilibriumOracle: (P" << player
                            << ") c^Tv=" << betaPlayer << " -- c^TxOfI=" << betaXofI;
                double violation = betaXofI - betaPlayer;

                if (violation >= this->Tolerance) {
                  // False, but we have a cut :-)
                  // Ciao Moni
                  Utils::normalizeIneq(cutLHS, betaPlayer, true);
                  // Resize for the extra variables...
                  arma::sp_mat cutL = Utils::resizePatch(
                        arma::sp_mat{cutLHS}.t(), 1, this->PolyLCP.at(player)->getNumCols());

                  // Add the cut and return false
                  this->PolyLCP.at(player)->addCustomCuts(cutL, arma::vec{betaPlayer});
                  LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::equilibriumOracle: (P" << player
                                  << ") adding a cut";
                  addedCuts = true;
                  return false;
                } else {

                  // We found a new vertex
                  arma::vec v(V.n_cols, arma::fill::zeros);
                  for (unsigned int i = 0; i < V.n_cols; ++i)
                     v[i] = l[i].get(GRB_DoubleAttr_X);

                  this->Trees.at(player)->addVertex(v, false);
                  LOG_S(1) << "Algorithms::EPEC::CutAndPlay::equilibriumOracle: (P" << player
                              << ") adding vertex for Player. " << (budget - k - 1) << "/" << budget
                              << " iterations left";
                }
             }


          } // status optimal for leaderModel
          else if (status == GRB_UNBOUNDED) {
             auto relaxed = leaderModel->relax();
             relaxed.optimize();
             // Well, we have a ray. But let's normalize it...
             try {
                for (unsigned int i = 0; i < cutLHS.size(); ++i)
                  cutLHS[i] =
                        relaxed.getVarByName("x_" + std::to_string(i)).get(GRB_DoubleAttr_UnbdRay);
             } catch (GRBException &e) {
                throw ZEROException(e);
             }
             cutLHS = Utils::normalizeVec(cutLHS);
             LOG_S(1) << "Algorithms::EPEC::CutAndPlay::equilibriumOracle: (P" << player
                         << ") new ray";
             // Add the ray and repeat
             this->Trees.at(player)->addRay(cutLHS);
          } // status unbounded for leaderModel

          // Otherwise, we don't know what happened
          else {
             throw ZEROException(ZEROErrorCode::Assertion,
                                        "Unknown status (" + std::to_string(status) +
                                             ") for leaderModel for player " + std::to_string(player));
          }
          // no separation
        }
     } else {
        throw ZEROException(ZEROErrorCode::Assertion,
                                  "Unknown status for dualMembershipModel for player " +
                                        std::to_string(player));
     }
  }
  return false;
}


void Algorithms::EPEC::CutAndPlay::addValueCut(const unsigned int player,
                                                                         const double       RHS,
                                                                         const arma::vec &  xMinusI) {

  arma::vec LHS = (this->EPECObject->LeaderObjective.at(player)->c +
                         this->EPECObject->LeaderObjective.at(player)->C * xMinusI);

  // Constant!
  if (Utils::isEqual(arma::max(LHS), 0)) {
     LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::addValueCut: "
                         "Constant cut. Discarding. ";
     return;
  }

  double trueRHS;
  if (Utils::nonzeroDecimals(RHS, 6) >= 6) {
     LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::addValueCut: "
                         "Numerically unstable cut. This may cause numerical problems. "
                     << player;
     // Let's try to save what we can.
     for (unsigned int i = 0; i < LHS.size(); ++i)
        LHS.at(i) = Utils::round_nplaces(LHS.at(i), 5);
     trueRHS = Utils::round_nplaces(RHS, 5);
     Utils::normalizeIneq(LHS, trueRHS, true);
  } else {
     trueRHS = Utils::round_nplaces(RHS, 6);
     //  LHS.print("LHS with RHS of " + std::to_string(trueRHS));
     Utils::normalizeIneq(LHS, trueRHS, false);
  }


  LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::addValueCut: "
                      "adding cut for Player "
                  << player;

  // Resize
  arma::sp_mat cutLHS =
        Utils::resizePatch(arma::sp_mat{LHS}.t(), 1, this->PolyLCP.at(player)->getNumCols());

  this->PolyLCP.at(player)->addCustomCuts(-cutLHS, arma::vec{-trueRHS});
}


void Algorithms::EPEC::CutAndPlay::originFeasibility(unsigned int player) {

  arma::vec zeros(this->EPECObject->LeaderObjective.at(player)->C.n_cols, arma::fill::zeros);
  auto      origin = this->EPECObject->PlayersLCP.at(player).get()->LCPasMILP(
      this->EPECObject->LeaderObjective.at(player)->C,
      this->EPECObject->LeaderObjective.at(player)->c,
      zeros,
      false);

  for (unsigned int j = 0; j < this->EPECObject->LeaderObjective.at(player)->c.size(); j++) {
     origin->addConstr(
          origin->getVarByName("x_" + std::to_string(j)), GRB_EQUAL, 0, "Fix_x_" + std::to_string(j));
  }
  origin->update();
  origin->optimize();
  if (origin->get(GRB_IntAttr_Status) == GRB_OPTIMAL) {
     this->Trees.at(player)->containsOrigin = true;
     LOG_S(1) << "Algorithms::EPEC::CutAndPlay::originFeasibility: "
                     "Feasible origin for player "
                 << player;
  } else {
     LOG_S(1) << "Algorithms::EPEC::CutAndPlay::originFeasibility: "
                     "Infeasible origin for player "
                 << player;
  }
}
void Algorithms::EPEC::CutAndPlay::solve() {
  // Set the initial point for all countries as 0 and solve the respective LCPs?
  this->EPECObject->SolutionX.zeros(this->EPECObject->NumVariables);
  bool solved = {false};
  if (this->EPECObject->Stats.AlgorithmData.TimeLimit.get() > 0)
     this->EPECObject->InitTime = std::chrono::high_resolution_clock::now();

  this->EPECObject->Stats.NumIterations.set(0);


  // Initialize Trees
  // We actually do not use the complex tree structure for this vanilla-version, but we nevertheless
  // give the user the capability of doing so.
  this->Trees     = std::vector<OuterTree *>(this->EPECObject->NumPlayers, nullptr);
  this->Incumbent = std::vector<OuterTree::Node *>(this->EPECObject->NumPlayers, nullptr);
  for (unsigned int i = 0; i < this->EPECObject->NumPlayers; i++) {
     Trees.at(i) = new OuterTree(this->PolyLCP.at(i)->getNumRows(), this->Env);
     this->Trees.at(i)->OriginalLCP =
          std::make_unique<MathOpt::PolyLCP>(this->Env, *EPECObject->PlayersLowerLevels.at(i).get());
     Incumbent.at(i) = Trees.at(i)->getRoot();
     this->originFeasibility(i);
  }

  bool branch = true;
  // In this case, branchingLocations is a vector of locations with the length
  // of this->EPECObject->NumPlayers
  std::vector<int>      branchingLocations;
  std::vector<int>      branchingCandidatesNumber;
  int                   cumulativeBranchingCandidates = 0;
  unsigned int          branchingChoices              = 0;
  std::vector<long int> branches;
  while (!solved) {
     branchingLocations.clear();
     this->EPECObject->Stats.NumIterations.set(this->EPECObject->Stats.NumIterations.get() + 1);
     LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::solve: Iteration "
                     << std::to_string(this->EPECObject->Stats.NumIterations.get());

     branchingLocations            = std::vector<int>(this->EPECObject->NumPlayers, -1);
     branchingCandidatesNumber     = std::vector<int>(this->EPECObject->NumPlayers, 0);
     cumulativeBranchingCandidates = 0;

     for (int j = 0; j < this->EPECObject->NumPlayers; ++j) {
        branchingCandidatesNumber.at(j) =
             Trees.at(j)->getEncodingSize() - Incumbent.at(j)->getCumulativeBranches();
        cumulativeBranchingCandidates += branchingCandidatesNumber.at(j);
     }

     bool infeasibilityDetection = false;
     branchingChoices            = 0;
     if (branch) {
        for (int j = 0; j < this->EPECObject->NumPlayers && !infeasibilityDetection; ++j) {
          // Check if we can branch
          if (branchingCandidatesNumber.at(j) != 0) {
             // In the first iteration, no complex branching rule.
             if (this->EPECObject->Stats.NumIterations.get() == 1) {
                branchingLocations.at(j) = this->getFirstBranchLocation(j, Incumbent.at(j));
                // Check if we detected infeasibility
                if (branchingLocations.at(j) < 0) {
                  LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::solve: "
                                      "firstBranching proves infeasibility for player  "
                                  << j;
                  infeasibilityDetection = true;
                  break;
                }
             } else {
                if (this->EPECObject->Stats.AlgorithmData.BranchingStrategy.get() ==
                     Data::EPEC::BranchingStrategy::HybridBranching)
                  branchingLocations.at(j) = this->hybridBranching(j, Incumbent.at(j));
                else if (this->EPECObject->Stats.AlgorithmData.BranchingStrategy.get() ==
                            Data::EPEC::BranchingStrategy::DeviationBranching) {
                  branchingLocations.at(j) = this->deviationBranching(j, Incumbent.at(j));
                  if (branchingLocations.at(j) == -1)
                     branchingLocations.at(j) = this->getFirstBranchLocation(j, Incumbent.at(j));
                }
                // Check if we detected infeasibility
                if (branchingLocations.at(j) == -2) {
                  LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::solve: "
                                      "hybridBranching proves infeasibility for player "
                                  << j;
                  infeasibilityDetection = true;
                  break;
                }
             }
          }
        }

        if (infeasibilityDetection) {
          LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::solve: "
                              "Solved without any equilibrium. Proven infeasibility";
          this->EPECObject->Stats.Status.set(ZEROStatus::NashEqNotFound);
          solved = true;
          break;
        }


        // Check that there is at least a player has a branching selection with
        // hybrid branching
        if (*std::max_element(branchingLocations.begin(), branchingLocations.end()) < 0) {

          // No branching candidates.
          LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::solve: "
                              "No more hybrid branching candidates for "
                              "any player. Checking if "
                              "any complementarities are left.";
          this->printCurrentApprox();
          for (int j = 0; j < this->EPECObject->NumPlayers; ++j)
             branchingLocations.at(j) = this->getFirstBranchLocation(j, Incumbent.at(j));

          if (*std::max_element(branchingLocations.begin(), branchingLocations.end()) < 0) {
             LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::solve: "
                                 "No more branching candidates.";
             this->EPECObject->Stats.Status.set(ZEROStatus::NashEqNotFound);
             break;
          }
        }
     }

     for (int j = 0; j < this->EPECObject->NumPlayers; ++j) {
        if (branchingLocations.at(j) > -1) {
          branchingChoices   = branchingChoices + 1;
          branches           = Trees.at(j)->singleBranch(branchingLocations.at(j), *Incumbent.at(j));
          auto childEncoding = this->Trees.at(j)->getNodes()->at(branches.at(0)).getEncoding();
          this->PolyLCP.at(j)->outerApproximate(childEncoding, true);
          // By definition of hybrid branching, the node should be feasible
          Incumbent.at(j) = &(this->Trees.at(j)->getNodes()->at(branches.at(0)));
          LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::solve: "
                              "branching candidate for player "
                          << j << " is " << branchingLocations.at(j);
        } else if (!branch) {
          // if we don't branch.
          this->PolyLCP.at(j)->outerApproximate(Incumbent.at(j)->getEncoding(), true);
          LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::solve: "
                              "No branching for player "
                          << j;
        }
     }

     this->printCurrentApprox();
     this->EPECObject->makePlayersQPs();
     this->Feasible   = false;
     int branchesLeft = cumulativeBranchingCandidates - branchingChoices;
     if (this->EPECObject->Stats.AlgorithmData.TimeLimit.get() > 0) {
        // Then we should take care of time. Also, let's use an heuristic to compute the time for the
        // current outer approximation.
        const std::chrono::duration<double> timeElapsed =
             std::chrono::high_resolution_clock::now() - this->EPECObject->InitTime;
        const double timeRemaining =
             this->EPECObject->Stats.AlgorithmData.TimeLimit.get() - timeElapsed.count();


        double timeForNextIteration = timeRemaining * 0.98;

        //@todo Currently not used.
        /*
        if (branchesLeft > 0 && false)
          timeForNextIteration = (timeRemaining * 0.2) / (cumulativeBranchingCandidates - 1);
          */

        LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::solve: Allocating "
                        << timeForNextIteration << "s for the next iteration (" << branchesLeft
                        << " complementarities left).";
        this->EPECObject->computeNashEq(
             this->EPECObject->Stats.AlgorithmData.PureNashEquilibrium.get(),
             timeForNextIteration,
             false,
             true,
             false);
     } else {
        this->EPECObject->computeNashEq(
             this->EPECObject->Stats.AlgorithmData.PureNashEquilibrium.get(), 0, false, true, false);
     }

     if (!this->EPECObject->NashEquilibrium && branchesLeft == 0) {
        if (this->EPECObject->Stats.Status.get() == ZEROStatus::TimeLimit) {
          LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::solve: "
                              "Time Limit Hit.";
          solved = true;
          break;
        }
        if (this->EPECObject->Stats.Status.get() == ZEROStatus::NashEqNotFound) {
          LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::solve: "
                              "Solved without any equilibrium.";
          solved = true;
          break;
        }
     }

     this->Feasible = false;
     if (this->EPECObject->NashEquilibrium) {
        bool addedCuts{false};
        if (this->isFeasible(addedCuts)) {
          this->Feasible = true;
          this->EPECObject->Stats.Status.set(ZEROStatus::NashEqFound);
          LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::solve: "
                              "Solved. ";
          this->after();
          return;
        } else {
          if (addedCuts) {
             branch = false;
             LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::solve: "
                                 "Cuts were added. Skipping next branching phase. ";
          } else {
             branch = true;
          }
        }
     } else {
        branch = true;
     }
     if (this->EPECObject->Stats.AlgorithmData.TimeLimit.get() > 0) {
        const std::chrono::duration<double> timeElapsed =
             std::chrono::high_resolution_clock::now() - this->EPECObject->InitTime;
        const double timeRemaining =
             this->EPECObject->Stats.AlgorithmData.TimeLimit.get() - timeElapsed.count();
        if (timeRemaining <= 0) {
          this->EPECObject->Stats.Status.set(ZEROStatus::TimeLimit);
          this->after();
          return;
        }
     }
  }
  this->after();
}


std::unique_ptr<GRBModel>
Algorithms::EPEC::CutAndPlay::getFeasibilityQP(const unsigned int player,
                                                                         const arma::vec &  x) {


  arma::vec xMinusI;
  this->EPECObject->getXMinusI(this->EPECObject->SolutionX, player, xMinusI);
  xMinusI.resize(this->EPECObject->PlayersQP.at(player)->getNumParams());
  auto model = this->EPECObject->PlayersQP.at(player)->solveFixed(xMinusI, false);
  // Enforce QP::y to be x, namely the point to belong to the feasible region
  for (unsigned int j = 0; j < x.size(); j++)
     model->addConstr(model->getVarByName("y_" + std::to_string(j)),
                            GRB_EQUAL,
                            x.at(j),
                            "Fix_y_" + std::to_string(j));
  // Reset the objective
  model->setObjective(GRBLinExpr{0}, GRB_MINIMIZE);
  return model;
}


bool Algorithms::EPEC::CutAndPlay::isFeasiblePure(const unsigned int player,
                                                                             const arma::vec &  x) {


  auto model = this->PolyLCP.at(player)->LCPasMIP(false, -1, 1, 1);
  for (unsigned int j = 0; j < x.size(); j++)
     model->addConstr(model->getVarByName("x_" + std::to_string(j)),
                            GRB_EQUAL,
                            x.at(j),
                            "Fix_x_" + std::to_string(j));
  // Reset the objective
  model->setObjective(GRBLinExpr{0}, GRB_MINIMIZE);
  model->optimize();
  const int status = model->get(GRB_IntAttr_Status);
  if (status == GRB_INFEASIBLE)
     return false;
  else
     return true;
}


int Algorithms::EPEC::CutAndPlay::hybridBranching(const unsigned int player,
                                                                             OuterTree::Node *  node) {

  LOG_S(INFO) << "CutAndPlay::hybridBranching: Player " << player;

  int bestId = -1;
  if (this->EPECObject->NashEquilibrium) {
     arma::vec zeros, x;

     this->EPECObject->getXofI(this->EPECObject->SolutionX, player, x);
     if (x.size() != this->EPECObject->LeaderObjective.at(player)->c.n_rows)
        throw ZEROException(ZEROErrorCode::Assertion, "wrong dimensioned x^i");

     auto              currentEncoding = node->getEncoding();
     std::vector<bool> incumbentApproximation;
     double            bestScore = -1.0;

     for (unsigned int i = 0; i < currentEncoding.size(); i++) {
        // For each complementarity


        if (node->getAllowedBranchings().at(i)) {
          // Consider it if it is a good candidate for branching (namely, we
          // didn't branch on it, or it wasn't proven to be infeasible)
          incumbentApproximation = currentEncoding;
          // Include this complementarity in the approximation
          incumbentApproximation.at(i) = true;
          // Build the approximation
          this->PolyLCP.at(player)->outerApproximate(incumbentApproximation, true);
          // If the approximation is infeasible, the
          if (!this->PolyLCP.at(player)->getFeasOuterApp()) {
             // The problem is infeasible!
             LOG_S(INFO) << "CutAndPlay::hybridBranching: Player " << player
                             << " has an infeasible problem (outer relaxation induction)";
             for (unsigned int j = 0; j < currentEncoding.size(); j++) {
                Trees.at(player)->denyBranchingLocation(*node, j);
             }
             return -2;
          } else {
             // In this case, we can check if the solution belongs to the outer
             // approximation
             this->EPECObject->makePlayerQP(player);
             // Get the QP model with other players decision QP::x fixed to zero
             // (since they only appear in the objective);
             auto model = this->getFeasibilityQP(player, x);
             model->optimize();
             const int status = model->get(GRB_IntAttr_Status);
             if (status == GRB_INFEASIBLE) {
                // If the status is infeasible, bingo! We want to get a measure of
                // the constraint violations given by the current x
                model->feasRelax(1, false, false, true);
                model->optimize();
                if (model->get(GRB_IntAttr_Status) == GRB_OPTIMAL) {
                  if (model->getObjective().getValue() > bestScore) {
                     bestId    = i;
                     bestScore = model->getObjective().getValue();
                     LOG_S(INFO) << "CutAndPlay::hybridBranching: Player " << player
                                     << " has violation of " << bestScore << " with complementarity " << i;
                  } else {
                     LOG_S(INFO) << "CutAndPlay::hybridBranching: Player " << player
                                     << " has violation of " << model->getObjective().getValue()
                                     << " with complementarity " << i;
                  }
                } else {
                  LOG_S(WARNING)
                        << "CutAndPlay::hybridBranching: Numerical difficulties in evaluating "
                        << std::to_string(i);
                }
             } else {
                LOG_S(INFO) << "CutAndPlay::hybridBranching: Player " << player
                                << " has no violation with complementarity " << i;
             }
          }
        }
     }
  }
  return bestId;
}


int Algorithms::EPEC::CutAndPlay::infeasibleBranching(const unsigned int     player,
                                                                                  const OuterTree::Node *node) {

  int result = -1;
  if (this->EPECObject->NashEquilibrium) {
     // There exists a Nash Equilibrium for the outer approximation, which is not
     // a Nash Equilibrium for the game
     arma::vec x, z;
     this->EPECObject->getXWithoutHull(this->EPECObject->SolutionX, x);
     z                                      = this->PolyLCP.at(player)->zFromX(x);
     std::vector<short int> currentSolution = this->PolyLCP.at(player)->solEncode(x);

     double maxInfeas = 0;

     //"The most infeasible" branching
     for (unsigned int i = 0; i < currentSolution.size(); i++) {
        unsigned int varPos = i >= this->PolyLCP.at(player)->getLStart()
                                          ? i + this->PolyLCP.at(player)->getNumberLeader()
                                          : i;
        if (x(varPos) > 0 && z(i) > 0 && node->getAllowedBranchings().at(i) &&
             currentSolution.at(i) == 0) {
          if ((x(varPos) + z(i)) > maxInfeas) {
             maxInfeas = x(varPos) + z(i);
             result    = i;
          }
        }
     }
  }
  return result;
}

int Algorithms::EPEC::CutAndPlay::deviationBranching(const unsigned int     player,
                                                                                 const OuterTree::Node *node) {


  int result = -1;
  if (this->EPECObject->NashEquilibrium) {
     // There exists a Nash Equilibrium for the outer approximation, which is not
     // a Nash Equilibrium for the game
     arma::vec dev;
     arma::vec x;
     this->EPECObject->getXWithoutHull(this->EPECObject->SolutionX, x);
     std::vector<short int> currentSolution = this->PolyLCP.at(player)->solEncode(x);
     this->EPECObject->bestResponse(
          dev, player, this->EPECObject->SolutionX, {}, this->Trees.at(player)->OriginalLCP.get());
     auto encoding = this->PolyLCP.at(player)->solEncode(dev);

     for (unsigned int i = 0; i < encoding.size(); i++) {
        if (encoding.at(i) > 0 && node->getAllowedBranchings().at(i) && currentSolution.at(i) == 0) {
          result = i;
        }
     }
  }
  return result;
}


int Algorithms::EPEC::CutAndPlay::getFirstBranchLocation(const unsigned int player,
                                                                                      OuterTree::Node *  node) {
  if (node->getCumulativeBranches() == Trees.at(player)->getEncodingSize())
     return -1;
  auto         model = this->PolyLCP.at(player)->LCPasMIP(true, -1, 1, 1);
  unsigned int nR    = this->PolyLCP.at(player)->getNumRows();
  int          pos   = -nR;
  arma::vec    z, x;
  if (this->PolyLCP.at(player)->extractSols(
             model.get(), z, x, true)) // If already infeasible, nothing to branch!
  {
     std::vector<short int> v1 = this->PolyLCP.at(player)->solEncode(z, x);

     double       maxvalx{-1}, maxvalz{-1};
     unsigned int maxposx{0}, maxposz{0};
     for (unsigned int i = 0; i < nR; i++) {
        unsigned int varPos = i >= this->PolyLCP.at(player)->getLStart()
                                          ? i + this->PolyLCP.at(player)->getNumberLeader()
                                          : i;
        if (x(varPos) > maxvalx && node->getAllowedBranchings().at(i)) {
          maxvalx = x(varPos);
          maxposx = i;
        }
        if (z(i) > maxvalz && node->getAllowedBranchings().at(i)) {
          maxvalz = z(i);
          maxposz = i;
        }
     }
     pos = maxvalz > maxvalx ? maxposz : maxposx;
  } else {
     // The problem is infeasible!
     LOG_S(INFO) << "CutAndPlay::getFirstBranchLocation: Player " << player
                     << " has an infeasible problem (outer relaxation induction)";
     for (unsigned int j = 0; j < node->getEncoding().size(); j++) {
        Trees.at(player)->denyBranchingLocation(*node, j);
     }
     return -1;
  }
  return pos;
}



[[maybe_unused]] std::vector<int>
Algorithms::EPEC::CutAndPlay::getNextBranchLocation(const unsigned int player,
                                                                                OuterTree::Node *  node) {

  std::vector<int> decisions = {-1, -1, -1, -1};
  decisions.at(0)            = this->infeasibleBranching(player, node);
  decisions.at(1)            = this->deviationBranching(player, node);
  decisions.at(2)            = this->hybridBranching(player, node);

  if (decisions.at(0) < 0 && decisions.at(1) < 0 && decisions.at(2) < 0) {
     LOG_S(INFO) << "Player " << player
                     << ": branching with FirstBranchLocation is the only available choice";
     decisions.at(3) = this->getFirstBranchLocation(player, node);
  }

  LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::getNextBranchinglocation: "
                      "given decisions are: ";
  LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::"
                      "getNextBranchinglocation:\t Infeasible="
                  << decisions.at(0);
  LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::"
                      "getNextBranchinglocation:\t Deviation="
                  << decisions.at(1);
  LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::"
                      "getNextBranchinglocation:\t Hybrid="
                  << decisions.at(2);
  LOG_S(INFO) << "Algorithms::EPEC::CutAndPlay::"
                      "getNextBranchinglocation:\t First="
                  << decisions.at(3);
  return decisions;
}


void Algorithms::EPEC::CutAndPlay::printCurrentApprox() {
  LOG_S(INFO) << "Current Node Approximation:";
  for (unsigned int p = 0; p < this->EPECObject->NumPlayers; ++p) {
     std::stringstream msg;
     msg << "\tPlayer " << p << ":";
     for (unsigned int i = 0; i < this->Incumbent.at(p)->getEncoding().size(); i++) {
        msg << "\t" << this->Incumbent.at(p)->getEncoding().at(i);
     }
     LOG_S(INFO) << msg.str();
  }
}

void Algorithms::EPEC::CutAndPlay::printBranchingLog(std::vector<int> vector) {
  LOG_S(INFO) << "Current Branching Log:";
  LOG_S(INFO) << "\tInfeasibleBranching: " << vector.at(0);
  LOG_S(INFO) << "\tDeviationBranching: " << vector.at(1);
  LOG_S(INFO) << "\tHybridBranching: " << vector.at(2);
  LOG_S(INFO) << "\tFirstAvail: " << vector.at(3);
}

bool Algorithms::EPEC::CutAndPlay::isPureStrategy(double tol) const {
  if (!this->Feasible)
     return false;
  else {
     for (unsigned int i = 0; i < this->EPECObject->NumPlayers; ++i)
        if (!Trees.at(i)->getPure())
          return false;

     return true;
  }
}
void Algorithms::EPEC::CutAndPlay::after() {
  bool                      pureStrategy = true;
  std::vector<unsigned int> numComps;
  for (unsigned int i = 0; i < this->EPECObject->getNumPlayers(); ++i) {
     if (!this->Trees.at(i)->getPure()) {
        pureStrategy = false;
     }
     unsigned int counter = 0;
     for (unsigned int j = 0; j < this->Incumbent.at(i)->getEncoding().size(); j++)
        counter += this->Incumbent.at(i)->getEncoding().at(j);
     numComps.push_back(counter);
  }
  this->EPECObject->Stats.PureNashEquilibrium.set(pureStrategy);
  this->EPECObject->Stats.AlgorithmData.OuterComplementarities.set(numComps);
  LOG_S(3) << "Algorithms::EPEC::CutAndPlay::after: post-processing results.";
}



Algorithms::EPEC::OuterTree::Node::Node(Node &parent, unsigned int idComp, unsigned long int id) {
  this->IdComps                      = std::vector<unsigned int>{idComp};
  this->Encoding                     = parent.Encoding;
  this->Encoding.at(idComp)          = true;
  this->AllowedBranchings            = parent.AllowedBranchings;
  this->AllowedBranchings.at(idComp) = false;
  this->Id                           = id;
  this->Parent                       = &parent;
}

Algorithms::EPEC::OuterTree::Node::Node(unsigned int encSize) {
  this->Encoding          = std::vector<bool>(encSize, false);
  this->Id                = 0;
  this->AllowedBranchings = std::vector<bool>(encSize, true);
}


void Algorithms::EPEC::OuterTree::denyBranchingLocation(Algorithms::EPEC::OuterTree::Node &node,
                                                                          const unsigned int &location) const {
  if (location >= this->EncodingSize)
     throw ZEROException(ZEROErrorCode::OutOfRange, "idComp is larger than the encoding size");
  if (!node.AllowedBranchings.at(location))
     LOG_S(WARNING) << "Algorithms::EPEC::OuterTree::denyBranchingLocation: location " << location
                         << "has been already denied.";

  LOG_S(INFO) << "Algorithms::EPEC::OuterTree::denyBranchingLocation: location " << location
                  << "denied.";
  node.AllowedBranchings.at(location) = false;
}


std::vector<long int>
Algorithms::EPEC::OuterTree::singleBranch(const unsigned int                 idComp,
                                                        Algorithms::EPEC::OuterTree::Node &t) {
  if (idComp >= this->EncodingSize)
     throw ZEROException(ZEROErrorCode::OutOfRange, "idComp is larger than the encoding size");
  if (t.Encoding.at(idComp) != 0) {
     LOG_S(WARNING) << "OuterTree: cannot branch on this complementary, since it already "
                             "has been processed.";
     return std::vector<long int>{-1};
  }
  auto child = Node(t, idComp, this->nextIdentifier());

  this->Nodes.push_back(child);
  return std::vector<long int>{this->NodeCounter - 1};
}

bool Algorithms::EPEC::OuterTree::addVertex(const arma::vec &vertex, bool checkDuplicates) {
  if (vertex.size() != this->V.n_cols && this->V.n_rows > 0)
     throw ZEROException(ZEROErrorCode::OutOfRange, "Ill-dimensioned vertex");

  bool go = false;
  if (checkDuplicates)
     go = Utils::containsRow(this->V, vertex, 1e-5);


  if (!go) {
     int nCols = this->V.n_cols < 1 ? vertex.size() : this->V.n_cols;
     this->V.resize(this->V.n_rows + 1, nCols);
     this->V.row(this->V.n_rows - 1) = vertex.t();
     return true;
  }
  return false;
}

void Algorithms::EPEC::OuterTree::addRay(const arma::vec &ray) {
  if (ray.size() != this->R.n_cols && this->R.n_rows > 0)
     throw ZEROException(ZEROErrorCode::OutOfRange, "Ill-dimensioned ray");
  int nCols = this->R.n_cols < 1 ? ray.size() : this->R.n_cols;
  this->R.resize(this->R.n_rows + 1, nCols);
  this->R.row(this->R.n_rows - 1) = ray.t();
}

Algorithms::EPEC::OuterTree::Node::Node(Node &            parent,
                                                     std::vector<int>  idsComp,
                                                     unsigned long int id) {
  this->IdComps           = std::vector<unsigned int>();
  this->Encoding          = parent.Encoding;
  this->AllowedBranchings = parent.AllowedBranchings;
  for (auto &idComp : idsComp) {
     if (idComp < 0)
        throw ZEROException(ZEROErrorCode::Assertion, "idComp is negative");
     this->Encoding.at(idComp)          = true;
     this->AllowedBranchings.at(idComp) = false;
     this->IdComps.push_back(idComp);
  }
  this->Id     = id;
  this->Parent = &parent;
}