Skip to content

Commit 322934a

Browse files
committed
Lemke on trees stops after finding one eq; stop_after/max_depth not used here
1 parent 1cb6a02 commit 322934a

6 files changed

Lines changed: 59 additions & 114 deletions

File tree

doc/tools.lcp.rst

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@
66

77
:program:`gambit-lcp` reads a two-player game on standard input and
88
computes Nash equilibria by finding solutions to a linear
9-
complementarity problem. For extensive games, the program uses the
9+
complementarity problem.
10+
11+
For extensive games, the program uses the
1012
sequence form representation of the extensive game, as defined by
1113
Koller, Megiddo, and von Stengel [KolMegSte94]_, and applies the
12-
algorithm developed by Lemke. For strategic games, the program uses
13-
the method of Lemke and Howson [LemHow64]_. There exist strategic
14-
games for which some equilibria cannot be located by this method; see
15-
Shapley [Sha74]_.
14+
algorithm developed by Lemke. In that case, the method will find
15+
one Nash equilibrium.
16+
17+
For strategic games, the program uses the method of Lemke and Howson
18+
[LemHow64]_. In this case, the method will find all "accessible"
19+
equilibria, i.e., those that can be found as concatenations of Lemke-Howson
20+
paths that start at the artificial equilibrium.
21+
There exist strategic-form games for which some equilibria cannot be found
22+
by this method, i.e., some equilibria are inaccessible; see Shapley [Sha74]_.
1623

1724
In a two-player strategic game, the set of Nash equilibria can be expressed
1825
as the union of convex sets. This program will find extreme points
@@ -53,9 +60,11 @@ game.
5360

5461
.. cmdoption:: -e EQA
5562

56-
By default, the program will find all equilibria accessible from
57-
the origin of the polytopes. This switch instructs the program
58-
to terminate when EQA equilibria have been found.
63+
By default, when working with the reduced strategic game, the program
64+
will find all equilibria accessible from the origin of the polytopes.
65+
This switch instructs the program to terminate when EQA equilibria
66+
have been found. This has no effect when using the extensive representation
67+
of a game, in which case the method always only returns one equilibrium.
5968

6069
.. cmdoption:: -h
6170

src/pygambit/nash.pxi

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,15 @@ def _enummixed_strategy_solve_rational(game: Game) -> list[MixedStrategyProfileR
7474

7575

7676
def _lcp_behavior_solve_double(
77-
game: Game, stop_after: int, max_depth: int
77+
game: Game
7878
) -> list[MixedBehaviorProfileDouble]:
79-
return _convert_mbpd(LcpBehaviorSolve[double](game.game, stop_after, max_depth))
79+
return _convert_mbpd(LcpBehaviorSolve[double](game.game))
8080

8181

8282
def _lcp_behavior_solve_rational(
83-
game: Game, stop_after: int, max_depth: int
83+
game: Game
8484
) -> list[MixedBehaviorProfileRational]:
85-
return _convert_mbpr(LcpBehaviorSolve[c_Rational](game.game, stop_after, max_depth))
85+
return _convert_mbpr(LcpBehaviorSolve[c_Rational](game.game))
8686

8787

8888
def _lcp_strategy_solve_double(

src/pygambit/nash.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,13 @@ def lcp_solve(
184184
representation even if the game's native representation is extensive.
185185
186186
stop_after : int, optional
187-
Maximum number of equilibria to compute. If not specified, computes all
188-
accessible equilibria.
187+
Maximum number of equilibria to compute when using the strategic representation.
188+
If not specified, computes all accessible equilibria.
189189
190190
max_depth : int, optional
191-
Maximum depth of recursion. If specified, will limit the recursive search,
192-
but may result in some accessible equilibria not being found.
191+
Maximum depth of recursion when using the strategic representation.
192+
If specified, will limit the recursive search, but may result in some accessible
193+
equilibria not being found.
193194
194195
Returns
195196
-------
@@ -200,7 +201,19 @@ def lcp_solve(
200201
------
201202
RuntimeError
202203
If game has more than two players.
204+
205+
ValueError
206+
If stop_after or max_depth are supplied for use on the tree representation.
203207
"""
208+
if game.is_tree and not use_strategic:
209+
if stop_after:
210+
raise ValueError(
211+
"lcp_solve(): stop_after can only be used on the strategic representation"
212+
)
213+
if max_depth:
214+
raise ValueError(
215+
"lcp_solve(): max_depth can only be used on the strategic representation"
216+
)
204217
if stop_after is None:
205218
stop_after = 0
206219
elif stop_after < 0:
@@ -215,9 +228,9 @@ def lcp_solve(
215228
else:
216229
equilibria = libgbt._lcp_strategy_solve_double(game, stop_after or 0, max_depth or 0)
217230
elif rational:
218-
equilibria = libgbt._lcp_behavior_solve_rational(game, stop_after or 0, max_depth or 0)
231+
equilibria = libgbt._lcp_behavior_solve_rational(game)
219232
else:
220-
equilibria = libgbt._lcp_behavior_solve_double(game, stop_after or 0, max_depth or 0)
233+
equilibria = libgbt._lcp_behavior_solve_double(game)
221234
return NashComputationResult(
222235
game=game,
223236
method="lcp",

src/solvers/lcp/efglcp.cc

Lines changed: 16 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@ namespace Gambit::Nash {
2929

3030
template <class T> class NashLcpBehaviorSolver {
3131
public:
32-
NashLcpBehaviorSolver(int p_stopAfter, int p_maxDepth,
33-
BehaviorCallbackType<T> p_onEquilibrium = NullBehaviorCallback<T>)
34-
: m_onEquilibrium(p_onEquilibrium), m_stopAfter(p_stopAfter), m_maxDepth(p_maxDepth)
32+
NashLcpBehaviorSolver(BehaviorCallbackType<T> p_onEquilibrium = NullBehaviorCallback<T>)
33+
: m_onEquilibrium(p_onEquilibrium)
3534
{
3635
}
3736
~NashLcpBehaviorSolver() = default;
@@ -144,98 +143,23 @@ std::list<MixedBehaviorProfile<T>> NashLcpBehaviorSolver<T>::Solve(const Game &p
144143
solution.eps = tab.Epsilon();
145144

146145
try {
147-
if (m_stopAfter != 1) {
148-
try {
149-
AllLemke(p_game, solution.ns1 + solution.ns2 + 1, tab, 0, A, solution);
150-
}
151-
catch (EquilibriumLimitReached &) {
152-
// Handle this silently; equilibria are recorded as found so no action needed
153-
}
154-
}
155-
else {
156-
tab.Pivot(solution.ns1 + solution.ns2 + 1, 0);
157-
tab.SF_LCPPath(solution.ns1 + solution.ns2 + 1);
158-
solution.AddBFS(tab);
159-
Vector<T> sol(tab.MinRow(), tab.MaxRow());
160-
tab.BasisVector(sol);
161-
MixedBehaviorProfile<T> profile(p_game);
162-
GetProfile(tab, profile, sol, p_game->GetRoot(), 1, 1, solution);
163-
profile.UndefinedToCentroid();
164-
solution.m_equilibria.push_back(profile);
165-
this->m_onEquilibrium(profile, "NE");
166-
}
146+
tab.Pivot(solution.ns1 + solution.ns2 + 1, 0);
147+
tab.SF_LCPPath(solution.ns1 + solution.ns2 + 1);
148+
solution.AddBFS(tab);
149+
Vector<T> sol(tab.MinRow(), tab.MaxRow());
150+
tab.BasisVector(sol);
151+
MixedBehaviorProfile<T> profile(p_game);
152+
GetProfile(tab, profile, sol, p_game->GetRoot(), 1, 1, solution);
153+
profile.UndefinedToCentroid();
154+
solution.m_equilibria.push_back(profile);
155+
this->m_onEquilibrium(profile, "NE");
167156
}
168157
catch (std::runtime_error &e) {
169158
std::cerr << "Error: " << e.what() << std::endl;
170159
}
171160
return solution.m_equilibria;
172161
}
173162

174-
//
175-
// All_Lemke finds all accessible Nash equilibria by recursively
176-
// calling itself. List maintains the list of basic variables
177-
// for the equilibria that have already been found.
178-
// From each new accessible equilibrium, it follows
179-
// all possible paths, adding any new equilibria to the List.
180-
//
181-
template <class T>
182-
void NashLcpBehaviorSolver<T>::AllLemke(const Game &p_game, int j, linalg::LemkeTableau<T> &B,
183-
int depth, Matrix<T> &A, Solution &p_solution) const
184-
{
185-
if (m_maxDepth != 0 && depth > m_maxDepth) {
186-
return;
187-
}
188-
189-
Vector<T> sol(B.MinRow(), B.MaxRow());
190-
MixedBehaviorProfile<T> profile(p_game);
191-
192-
bool newsol = false;
193-
for (int i = B.MinRow(); i <= B.MaxRow() && !newsol; i++) {
194-
if (i == j) {
195-
continue;
196-
}
197-
198-
linalg::LemkeTableau<T> BCopy(B);
199-
// Perturb tableau by a small number
200-
A(i, 0) = static_cast<T>(-1) / static_cast<T>(1000);
201-
BCopy.Refactor();
202-
203-
int missing;
204-
if (depth == 0) {
205-
BCopy.Pivot(j, 0);
206-
missing = -j;
207-
}
208-
else {
209-
missing = BCopy.SF_PivotIn(0);
210-
}
211-
212-
newsol = false;
213-
214-
if (BCopy.SF_LCPPath(-missing) == 1) {
215-
newsol = p_solution.AddBFS(BCopy);
216-
BCopy.BasisVector(sol);
217-
GetProfile(BCopy, profile, sol, p_game->GetRoot(), 1, 1, p_solution);
218-
profile.UndefinedToCentroid();
219-
if (newsol) {
220-
m_onEquilibrium(profile, "NE");
221-
p_solution.m_equilibria.push_back(profile);
222-
if (m_stopAfter > 0 && p_solution.EquilibriumCount() >= m_stopAfter) {
223-
throw EquilibriumLimitReached();
224-
}
225-
}
226-
}
227-
else {
228-
// Dead end
229-
}
230-
231-
A(i, 0) = static_cast<T>(-1);
232-
if (newsol) {
233-
BCopy.Refactor();
234-
AllLemke(p_game, i, BCopy, depth + 1, A, p_solution);
235-
}
236-
}
237-
}
238-
239163
template <class T>
240164
void NashLcpBehaviorSolver<T>::FillTableau(Matrix<T> &A, const GameNode &n, T prob, int s1, int s2,
241165
Solution &p_solution) const
@@ -341,16 +265,15 @@ void NashLcpBehaviorSolver<T>::GetProfile(const linalg::LemkeTableau<T> &tab,
341265
}
342266

343267
template <class T>
344-
std::list<MixedBehaviorProfile<T>> LcpBehaviorSolve(const Game &p_game, int p_stopAfter,
345-
int p_maxDepth,
268+
std::list<MixedBehaviorProfile<T>> LcpBehaviorSolve(const Game &p_game,
346269
BehaviorCallbackType<T> p_onEquilibrium)
347270
{
348-
return NashLcpBehaviorSolver<T>(p_stopAfter, p_maxDepth, p_onEquilibrium).Solve(p_game);
271+
return NashLcpBehaviorSolver<T>(p_onEquilibrium).Solve(p_game);
349272
}
350273

351-
template std::list<MixedBehaviorProfile<double>> LcpBehaviorSolve(const Game &, int, int,
274+
template std::list<MixedBehaviorProfile<double>> LcpBehaviorSolve(const Game &,
352275
BehaviorCallbackType<double>);
353276
template std::list<MixedBehaviorProfile<Rational>>
354-
LcpBehaviorSolve(const Game &, int, int, BehaviorCallbackType<Rational>);
277+
LcpBehaviorSolve(const Game &, BehaviorCallbackType<Rational>);
355278

356279
} // end namespace Gambit::Nash

src/solvers/lcp/lcp.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ LcpStrategySolve(const Game &p_game, int p_stopAfter, int p_maxDepth,
3434

3535
template <class T>
3636
std::list<MixedBehaviorProfile<T>>
37-
LcpBehaviorSolve(const Game &p_game, int p_stopAfter, int p_maxDepth,
37+
LcpBehaviorSolve(const Game &p_game,
3838
BehaviorCallbackType<T> p_onEquilibrium = NullBehaviorCallback<T>);
3939

4040
} // end namespace Gambit::Nash

src/tools/lcp/lcp.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,14 @@ int main(int argc, char *argv[])
149149
if (useFloat) {
150150
auto renderer =
151151
MakeMixedBehaviorProfileRenderer<double>(std::cout, numDecimals, printDetail);
152-
LcpBehaviorSolve<double>(game, stopAfter, maxDepth,
152+
LcpBehaviorSolve<double>(game,
153153
[&](const MixedBehaviorProfile<double> &p,
154154
const std::string &label) { renderer->Render(p, label); });
155155
}
156156
else {
157157
auto renderer =
158158
MakeMixedBehaviorProfileRenderer<Rational>(std::cout, numDecimals, printDetail);
159-
LcpBehaviorSolve<Rational>(game, stopAfter, maxDepth,
159+
LcpBehaviorSolve<Rational>(game,
160160
[&](const MixedBehaviorProfile<Rational> &p,
161161
const std::string &label) { renderer->Render(p, label); });
162162
}

0 commit comments

Comments
 (0)