Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## [17.0.0] - unreleased

### Changed
- `lcp_solve` no longer silently absorbs internal exceptions if they occur, but instead these propagate out
to match the behaviour of all other Nash equilibrium solvers. (#996)
- Implemented bespoke XML parser that handles the subset in the de-facto legacy XML workbook .gbt format;
removes dependency on tinyxml. (#897)

Expand Down
14 changes: 0 additions & 14 deletions src/games/nash.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,6 @@ ToMixedBehaviorProfile(const std::list<MixedStrategyProfile<T>> &p_list)
return ret;
}

template <class T>
using BehaviorSolverType = std::function<std::list<MixedBehaviorProfile<T>>(const Game &)>;

//
// Exception raised when maximum number of equilibria to compute
// has been reached. A convenience for unraveling a potentially
// deep recursion.
//
class EquilibriumLimitReached : public std::runtime_error {
public:
EquilibriumLimitReached() : std::runtime_error("Reached target number of equilibria") {}
~EquilibriumLimitReached() noexcept override = default;
};

} // namespace Gambit::Nash

#endif // LIBGAMBIT_NASH_H
1 change: 1 addition & 0 deletions src/gui/dlnashmon.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <wx/txtstrm.h>
#include <wx/process.h>
#include <wx/tokenzr.h>
#include <cstring>
#include <optional>
#include <variant>
#include "wx/sheet/sheet.h"
Expand Down
4 changes: 1 addition & 3 deletions src/pygambit/gambit.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -551,9 +551,7 @@ cdef extern from "solvers/lcp/lcp.h":
stdlist[c_MixedStrategyProfile[T]] LcpStrategySolve[T](
c_Game, int p_stopAfter, int p_maxDepth
) except +RuntimeError
stdlist[c_MixedBehaviorProfile[T]] LcpBehaviorSolve[T](
c_Game, int p_stopAfter, int p_maxDepth
) except +RuntimeError
stdlist[c_MixedBehaviorProfile[T]] LcpBehaviorSolve[T](c_Game) except +RuntimeError

cdef extern from "solvers/lp/lp.h":
stdlist[c_MixedStrategyProfile[T]] LpStrategySolve[T](c_Game) except +RuntimeError
Expand Down
25 changes: 10 additions & 15 deletions src/solvers/lcp/efglcp.cc
Original file line number Diff line number Diff line change
Expand Up @@ -143,21 +143,16 @@ std::list<MixedBehaviorProfile<T>> NashLcpBehaviorSolver<T>::Solve(const Game &p
linalg::LemkeTableau<T> tab(A, b);
solution.eps = tab.Epsilon();

try {
tab.Pivot(solution.ns1 + solution.ns2 + 1, 0);
tab.SF_LCPPath(solution.ns1 + solution.ns2 + 1);
solution.AddBFS(tab);
Vector<T> sol(tab.MinRow(), tab.MaxRow());
tab.BasisVector(sol);
MixedBehaviorProfile<T> profile(p_game);
GetProfile(tab, profile, sol, p_game->GetRoot(), 1, 1, solution);
profile.UndefinedToCentroid();
solution.m_equilibria.push_back(profile);
this->m_onEquilibrium(profile, "NE");
}
catch (std::runtime_error &e) {
std::cerr << "Error: " << e.what() << std::endl;
}
tab.Pivot(solution.ns1 + solution.ns2 + 1, 0);
tab.SF_LCPPath(solution.ns1 + solution.ns2 + 1);
solution.AddBFS(tab);
Vector<T> sol(tab.MinRow(), tab.MaxRow());
tab.BasisVector(sol);
MixedBehaviorProfile<T> profile(p_game);
GetProfile(tab, profile, sol, p_game->GetRoot(), 1, 1, solution);
profile.UndefinedToCentroid();
solution.m_equilibria.push_back(profile);
this->m_onEquilibrium(profile, "NE");
return solution.m_equilibria;
}

Expand Down
74 changes: 39 additions & 35 deletions src/solvers/lcp/nfglcp.cc
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,15 @@ template <class T> class NashLcpStrategySolver {
std::list<MixedStrategyProfile<T>> Solve(const Game &) const;

private:
enum class SearchResult { Continue, PruneBranch, LimitReached };

StrategyCallbackType<T> m_onEquilibrium;
int m_stopAfter, m_maxDepth;

class Solution;

bool OnBFS(const Game &, linalg::LHTableau<T> &, Solution &) const;
void AllLemke(const Game &, int j, linalg::LHTableau<T> &, Solution &, int) const;
SearchResult OnBFS(const Game &, linalg::LHTableau<T> &, Solution &) const;
SearchResult AllLemke(const Game &, int j, linalg::LHTableau<T> &, Solution &, int) const;
};

template <class T> class NashLcpStrategySolver<T>::Solution {
Expand All @@ -145,16 +147,17 @@ template <class T> class NashLcpStrategySolver<T>::Solution {
// Function called when a CBFS is encountered.
// If it is not already in the list p_list, it is added.
// The corresponding equilibrium is computed and output.
// Returns 'true' if the CBFS is new; 'false' if it already appears in the
// list.
// Returns whether to continue from this BFS, prune the current branch,
// or stop because the requested equilibrium limit has been reached.
//
template <class T>
bool NashLcpStrategySolver<T>::OnBFS(const Game &p_game, linalg::LHTableau<T> &p_tableau,
Solution &p_solution) const
typename NashLcpStrategySolver<T>::SearchResult
NashLcpStrategySolver<T>::OnBFS(const Game &p_game, linalg::LHTableau<T> &p_tableau,
Solution &p_solution) const
{
const Gambit::linalg::BFS<T> cbfs(p_tableau.GetBFS());
if (p_solution.Contains(cbfs)) {
return false;
return SearchResult::PruneBranch;
}
p_solution.push_back(cbfs);

Expand All @@ -170,7 +173,7 @@ bool NashLcpStrategySolver<T>::OnBFS(const Game &p_game, linalg::LHTableau<T> &p
}
if (sum == (T)0) {
// This is the trivial CBFS.
return false;
return SearchResult::PruneBranch;
}

for (int j = 1; j <= n1; j++) {
Expand Down Expand Up @@ -204,10 +207,10 @@ bool NashLcpStrategySolver<T>::OnBFS(const Game &p_game, linalg::LHTableau<T> &p
p_solution.m_equilibria.push_back(profile);

if (m_stopAfter > 0 && p_solution.EquilibriumCount() >= m_stopAfter) {
throw EquilibriumLimitReached();
return SearchResult::LimitReached;
}

return true;
return SearchResult::Continue;
}

//
Expand All @@ -218,26 +221,36 @@ bool NashLcpStrategySolver<T>::OnBFS(const Game &p_game, linalg::LHTableau<T> &p
// all possible paths, adding any new equilibria to the List.
//
template <class T>
void NashLcpStrategySolver<T>::AllLemke(const Game &p_game, int j, linalg::LHTableau<T> &B,
Solution &p_solution, int depth) const
typename NashLcpStrategySolver<T>::SearchResult
NashLcpStrategySolver<T>::AllLemke(const Game &p_game, int j, linalg::LHTableau<T> &B,
Solution &p_solution, int depth) const
{
if (m_maxDepth != 0 && depth > m_maxDepth) {
return;
return SearchResult::Continue;
}

// On the initial depth=0 call, the CBFS we are at is the extraneous
// solution.
if (depth > 0 && !OnBFS(p_game, B, p_solution)) {
return;
if (depth > 0) {
const auto result = OnBFS(p_game, B, p_solution);
if (result == SearchResult::PruneBranch) {
return SearchResult::Continue;
}
if (result == SearchResult::LimitReached) {
return result;
}
}

for (int i = B.MinCol(); i <= B.MaxCol(); i++) {
if (i != j) {
linalg::LHTableau<T> Bcopy(B);
Bcopy.LemkePath(i);
AllLemke(p_game, i, Bcopy, p_solution, depth + 1);
if (AllLemke(p_game, i, Bcopy, p_solution, depth + 1) == SearchResult::LimitReached) {
return SearchResult::LimitReached;
}
}
}
return SearchResult::Continue;
}

template <class T>
Expand All @@ -252,27 +265,18 @@ std::list<MixedStrategyProfile<T>> NashLcpStrategySolver<T>::Solve(const Game &p
}
Solution solution;

try {
const Matrix<T> A1 = Make_A1<T>(p_game);
const Vector<T> b1 = Make_b1<T>(p_game);
const Matrix<T> A2 = Make_A2<T>(p_game);
const Vector<T> b2 = Make_b2<T>(p_game);
linalg::LHTableau<T> B(A1, A2, b1, b2);
const Matrix<T> A1 = Make_A1<T>(p_game);
const Vector<T> b1 = Make_b1<T>(p_game);
const Matrix<T> A2 = Make_A2<T>(p_game);
const Vector<T> b2 = Make_b2<T>(p_game);
linalg::LHTableau<T> B(A1, A2, b1, b2);

if (m_stopAfter != 1) {
AllLemke(p_game, 0, B, solution, 0);
}
else {
B.LemkePath(1);
OnBFS(p_game, B, solution);
}
}
catch (EquilibriumLimitReached &) {
// This pseudo-exception requires no additional action;
// solution contains details of all equilibria found
if (m_stopAfter != 1) {
AllLemke(p_game, 0, B, solution, 0);
}
catch (std::runtime_error &e) {
std::cerr << "ERROR: " << e.what() << std::endl;
else {
B.LemkePath(1);
OnBFS(p_game, B, solution);
}
return solution.m_equilibria;
}
Expand Down
Loading