Program Listing for File epec_innerapp.cpp
↰ Return to documentation for file (src/games/algorithms/EPEC/epec_innerapp.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_innerapp.h"
void Algorithms::EPEC::InnerApproximation::solve() {
this->start();
this->after();
}
void Algorithms::EPEC::InnerApproximation::start() {
// Set the initial point for all countries as 0 and solve the respective LCPs?
this->EPECObject->SolutionX.zeros(this->EPECObject->NumVariables);
bool solved = {false};
bool addRandPoly{false};
bool infeasCheck{false};
// When true, a MNE has been found. The algorithm now tries to find a PNE, at
// the cost of incrementally enumerating the remaining polyhedra, up to the
// TimeLimit (if any).
//
bool incrementalEnumeration{false};
std::vector<arma::vec> prevDevns(this->EPECObject->NumPlayers);
this->EPECObject->Stats.NumIterations = 0;
if (this->EPECObject->Stats.AlgorithmData.PolyhedraStrategy.get() ==
Data::LCP::PolyhedraStrategy::Random) {
for (unsigned int i = 0; i < this->EPECObject->NumPlayers; ++i) {
// 42 is the answer, we all know
long int seed = this->EPECObject->Stats.AlgorithmData.RandomSeed.get() < 0
? std::chrono::high_resolution_clock::now().time_since_epoch().count() +
42 + PolyLCP.at(i)->getNumRows()
: this->EPECObject->Stats.AlgorithmData.RandomSeed.get();
PolyLCP.at(i)->RandomSeed = seed;
}
}
if (this->EPECObject->Stats.AlgorithmData.TimeLimit.get() > 0)
this->EPECObject->InitTime = std::chrono::high_resolution_clock::now();
// Stay in this loop, till you find a Nash equilibrium or prove that there
// does not exist a Nash equilibrium or you run out of time.
while (!solved) {
this->EPECObject->Stats.NumIterations.set(this->EPECObject->Stats.NumIterations.get() + 1);
LOG_S(INFO) << "Algorithms::EPEC::InnerApproximation::solve: Iteration "
<< std::to_string(this->EPECObject->Stats.NumIterations.get());
if (addRandPoly) {
LOG_S(INFO) << "Algorithms::EPEC::InnerApproximation::solve: using "
"heuristic polyhedra selection";
bool success =
this->addRandomPoly2All(this->EPECObject->Stats.AlgorithmData.Aggressiveness.get(),
this->EPECObject->Stats.NumIterations.get() == 1);
if (!success) {
this->EPECObject->Stats.Status.set(ZEROStatus::NashEqNotFound);
return;
}
} else { // else we are in the case of finding deviations.
unsigned int deviatedCountry{0};
arma::vec countryDeviation{};
if (this->isSolved(&deviatedCountry, &countryDeviation)) {
this->EPECObject->Stats.Status.set(ZEROStatus::NashEqFound);
this->EPECObject->Stats.PureNashEquilibrium = this->isPureStrategy();
if ((this->EPECObject->Stats.AlgorithmData.PureNashEquilibrium.get() &&
!this->EPECObject->Stats.PureNashEquilibrium.get())) {
// We are seeking for a pure strategy. Then, here we switch between an
// incremental
// enumeration or combinations of pure strategies.
if (this->EPECObject->Stats.AlgorithmData.RecoverStrategy.get() ==
Data::EPEC::RecoverStrategy::IncrementalEnumeration) {
LOG_S(INFO) << "Algorithms::EPEC::InnerApproximation::solve: "
"triggering recover strategy "
"(IncrementalEnumeration)";
incrementalEnumeration = true;
} else if (this->EPECObject->Stats.AlgorithmData.RecoverStrategy.get() ==
Data::EPEC::RecoverStrategy::Combinatorial) {
LOG_S(INFO) << "Algorithms::EPEC::InnerApproximation::solve: triggering "
"recover strategy (Combinatorial)";
// In this case, we want to try all the combinations of pure
// strategies, except the ones between polyhedra we already tested.
std::vector<std::set<unsigned long int>> excludeList;
for (unsigned long j = 0; j < this->EPECObject->NumPlayers; ++j) {
excludeList.push_back(PolyLCP.at(j)->getAllPolyhedra()[0]);
}
Algorithms::EPEC::CombinatorialPNE combPNE(this->Env, this->EPECObject);
combPNE.solveWithExcluded(excludeList);
return;
}
} else
return;
}
// Vector of deviations for the countries
std::vector<arma::vec> devns = std::vector<arma::vec>(this->EPECObject->NumPlayers);
this->getAllDeviations(devns, this->EPECObject->SolutionX, prevDevns);
prevDevns = devns;
unsigned int addedPoly = this->addDeviatedPolyhedron(devns, infeasCheck);
if (addedPoly == 0 && this->EPECObject->Stats.NumIterations.get() > 1 &&
!incrementalEnumeration) {
LOG_S(ERROR) << " In Algorithms::EPEC::InnerApproximation::solve: Not "
"Solved, but no deviation? Error!\n This might be due to "
"Numerical issues (tolerances)";
this->EPECObject->Stats.Status.set(ZEROStatus::Numerical);
solved = true;
}
if (infeasCheck && this->EPECObject->Stats.NumIterations.get() == 1) {
LOG_S(WARNING) << " In Algorithms::EPEC::InnerApproximation::solve: Problem is "
"infeasible";
this->EPECObject->Stats.Status.set(ZEROStatus::NashEqNotFound);
return;
}
}
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) {
if (!incrementalEnumeration)
this->EPECObject->Stats.Status.set(ZEROStatus::TimeLimit);
return;
}
}
this->EPECObject->makePlayersQPs();
// TimeLimit
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();
addRandPoly = !this->EPECObject->computeNashEq(
this->EPECObject->Stats.AlgorithmData.PureNashEquilibrium.get(),
timeRemaining,
false,
true,
false) &&
!incrementalEnumeration;
} else {
// No Time Limit
addRandPoly = !this->EPECObject->computeNashEq(
this->EPECObject->Stats.AlgorithmData.PureNashEquilibrium.get(),
0,
false,
true,
false) &&
!incrementalEnumeration;
}
//Numerical issues
if (addRandPoly && this->EPECObject->Stats.Status.get() == ZEROStatus::Numerical){
return;
}
if (addRandPoly)
this->EPECObject->Stats.AlgorithmData.LostIntermediateEq.set(
this->EPECObject->Stats.AlgorithmData.LostIntermediateEq.get() + 1);
for (unsigned int i = 0; i < this->EPECObject->NumPlayers; ++i) {
LOG_S(INFO) << "Country " << i << PolyLCP.at(i)->feasabilityDetailString();
}
// This might be reached when a NashEq is found, and need to be verified.
// Anyway, we are over the TimeLimit and we should stop
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) {
if (!incrementalEnumeration)
this->EPECObject->Stats.Status.set(ZEROStatus::TimeLimit);
return;
}
}
}
}
bool Algorithms::EPEC::InnerApproximation::addRandomPoly2All(unsigned int aggressiveLevel,
bool stopOnSingleInfeasibility) {
LOG_S(1) << "Adding Random polyhedra to countries";
bool infeasible{true};
for (unsigned int i = 0; i < this->EPECObject->NumPlayers; i++) {
auto addedPoly = PolyLCP.at(i)->addAPoly(
aggressiveLevel, this->EPECObject->Stats.AlgorithmData.PolyhedraStrategy.get());
if (stopOnSingleInfeasibility && addedPoly == 0) {
LOG_S(INFO) << "Algorithms::EPEC::InnerApproximation::addRandomPoly2All: No Nash "
"equilibrium. due to "
"infeasibility of country "
<< i;
return false;
}
if (addedPoly > 0)
infeasible = false;
}
return !infeasible;
}
bool Algorithms::EPEC::InnerApproximation::getAllDeviations(
std::vector<arma::vec> & deviations,
const arma::vec & guessSol,
const std::vector<arma::vec> &prevDev) const {
deviations = std::vector<arma::vec>(this->EPECObject->NumPlayers);
for (unsigned int i = 0; i < this->EPECObject->NumPlayers; ++i) { // For each country
// If we cannot compute a deviation, it means model is infeasible!
if (this->EPECObject->bestResponse(deviations.at(i), i, guessSol, prevDev.at(i), nullptr) == GRB_INFINITY)
return false;
// cout << "Game::EPEC::getAllDeviations: deviations(i): "
// <<deviations.at(i);
}
return true;
}
unsigned int Algorithms::EPEC::InnerApproximation::addDeviatedPolyhedron(
const std::vector<arma::vec> &deviations, bool &infeasCheck) const {
infeasCheck = false;
unsigned int added = 0;
for (unsigned int i = 0; i < this->EPECObject->NumPlayers; ++i) { // For each country
bool ret = false;
if (!deviations.at(i).empty())
ret = PolyLCP.at(i)->addPolyFromX(deviations.at(i), true);
if (ret) {
LOG_S(1) << "Algorithms::EPEC::InnerApproximation::"
"addDeviatedPolyhedron: added "
"polyhedron for player "
<< i;
++added;
} else {
infeasCheck = true;
LOG_S(1) << "Algorithms::EPEC::InnerApproximation::addDeviatedPolyhedron: NO "
"polyhedron added for player "
<< i;
}
}
return added;
}