Skip to content

Commit 1ee27e3

Browse files
committed
tests for nash solvers -- enumpoly_solve, lp_solve, lcp_solve -- in behavior stratgegies
1 parent a311874 commit 1ee27e3

6 files changed

Lines changed: 527 additions & 55 deletions

File tree

ChangeLog

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
- In the graphical interface, removed option to configure information set link drawing; information sets
77
are always drawn and indicators are always drawn if an information set spans multiple levels.
88

9+
### Added
10+
- Tests for EFG nash solvers -- `enumpoly_solve`, `lp_solve`, `lcp_solve` -- in behavior stratgegies
11+
912

1013
## [16.4.1] - unreleased
1114

pyproject.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,12 @@ max-line-length = 99
7575
[tool.pytest.ini_options]
7676
addopts = "--strict-markers"
7777
markers = [
78-
"nash_enummixed_strategy: tests of enummixed_solve in strategies",
79-
"nash_lcp_strategy: tests of lcp_solve in strategies",
80-
"nash_lcp_behavior: tests of lcp_solve in behaviors",
81-
"nash_lp_strategy: tests of lp_solve in strategies",
82-
"nash_lp_behavior: tests of lp_solve in behaviors",
78+
"nash_enummixed_strategy: tests of enummixed_solve in mixed strategies",
79+
"nash_enumpoly_behavior: tests of enumpoly_solve in behavior strategies",
80+
"nash_lcp_strategy: tests of lcp_solve in mixed strategies",
81+
"nash_lcp_behavior: tests of lcp_solve in behavior strategies",
82+
"nash_lp_strategy: tests of lp_solve in mixed strategies",
83+
"nash_lp_behavior: tests of lp_solve in behavior strategies",
8384
"nash: all tests of Nash equilibrium solvers",
8485
"slow: all time-consuming tests",
8586
]

tests/games.py

Lines changed: 291 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,31 @@ def read_from_file(fn: str) -> gbt.Game:
1818
raise ValueError(f"Unknown file extension in {fn}")
1919

2020

21+
def create_efg_corresponding_to_bimatrix_game(A: np.ndarray, B: np.ndarray, title: str
22+
) -> gbt.Game:
23+
"""
24+
There is not direct pygambit method to create an EFG from a stategic-form game.
25+
Here we create an EFG corresponding to a bimatrix game, which is given by two numpy arrays.
26+
Player 1 moves first.
27+
"""
28+
assert A.shape == B.shape
29+
m, n = A.shape
30+
g = gbt.Game.new_tree(players=["1", "2"], title=title)
31+
actions1 = [str(i) for i in range(m)]
32+
actions2 = [str(i) for i in range(n)]
33+
g.append_move(g.root, "1", actions1)
34+
for node in g.root.children:
35+
g.append_move(node, "2", actions2)
36+
iset = g.root.children[0].infoset
37+
for c in g.root.children:
38+
g.set_infoset(c, iset)
39+
from itertools import product
40+
41+
for i, j in product(range(m), range(n)):
42+
g.set_outcome(g.root.children[i].children[j], g.add_outcome([A[i, j], B[i, j]]))
43+
return g
44+
45+
2146
################################################################################################
2247
# Normal-form (aka strategic-form) games (nfg)
2348

@@ -87,10 +112,22 @@ def create_mixed_behav_game_efg() -> gbt.Game:
87112
Game
88113
Three-player extensive form game: binary tree with 3 infomation sets, one per player,
89114
with 1, 2, and 4 nodes respectively
115+
116+
Since no information is revealed this is directly equivalent to a simultaneous move game
90117
"""
91118
return read_from_file("mixed_behavior_game.efg")
92119

93120

121+
def create_1_card_poker_efg() -> gbt.Game:
122+
"""
123+
Returns
124+
-------
125+
Game
126+
One-card two-player poker game, as used in the user guide
127+
"""
128+
return read_from_file("poker.efg")
129+
130+
94131
def create_myerson_2_card_poker_efg() -> gbt.Game:
95132
"""
96133
Returns
@@ -104,6 +141,128 @@ def create_myerson_2_card_poker_efg() -> gbt.Game:
104141
return read_from_file("myerson_2_card_poker.efg")
105142

106143

144+
def create_kuhn_poker_efg() -> gbt.Game:
145+
"""
146+
Returns
147+
-------
148+
Game
149+
Kuhn poker with 3 cards and 2 players
150+
"""
151+
g = gbt.Game.new_tree(
152+
players=["Alice", "Bob"], title="Three-card poker (J, Q, K), two-player"
153+
)
154+
g.append_move(g.root, g.players.chance, ["JQ", "JK", "QJ", "QK", "KJ", "KQ"])
155+
g.set_chance_probs(g.root.infoset, [gbt.Rational(1, 6)]*6)
156+
# For each chance outcome node, append Alice's first decision: Check or Bet.
157+
for i in range(6):
158+
g.append_move(g.root.children[i], "Alice", ["Check", "Bet"])
159+
# After Alice checks, Bob moves: Check or Bet.
160+
for i in range(6):
161+
g.append_move(g.root.children[i].children[0], "Bob", ["Check", "Bet"])
162+
# If Bob bets after Alice checked (Bob's second action), then Alice can Fold or Call.
163+
# Append Alice's response nodes for the check-then-bet branch
164+
for i in range(6):
165+
g.append_move(g.root.children[i].children[0].children[1], "Alice", ["Fold", "Call"])
166+
# If Alice bets initially, Bob can Fold or Call.
167+
for i in range(6):
168+
g.append_move(g.root.children[i].children[1], "Bob", ["Fold", "Call"])
169+
# Set up information sets to represent imperfect information.
170+
# Alice dealt J
171+
g.set_infoset(g.root.children[0], g.root.children[1].infoset)
172+
# Alice dealt Q
173+
g.set_infoset(g.root.children[2], g.root.children[3].infoset)
174+
# Alice dealt K
175+
g.set_infoset(g.root.children[4], g.root.children[5].infoset)
176+
# Bob's decision after Alice checks: Bob knows his own card but not Alice's
177+
# Bob dealt Q
178+
g.set_infoset(g.root.children[0].children[0], g.root.children[5].children[0].infoset)
179+
# Bob dealt K
180+
g.set_infoset(g.root.children[1].children[0], g.root.children[3].children[0].infoset)
181+
# Bob dealt J
182+
g.set_infoset(g.root.children[2].children[0], g.root.children[4].children[0].infoset)
183+
# Bob's decision after Alice bets:
184+
# Bob dealt Q
185+
g.set_infoset(g.root.children[0].children[1], g.root.children[5].children[1].infoset)
186+
# Bob dealt K
187+
g.set_infoset(g.root.children[1].children[1], g.root.children[3].children[1].infoset)
188+
# Bob dealt J
189+
g.set_infoset(g.root.children[2].children[1], g.root.children[4].children[1].infoset)
190+
# Alice's decision after she checked and Bob then bet:
191+
# Alice dealt J
192+
g.set_infoset(g.root.children[0].children[0].children[1],
193+
g.root.children[1].children[0].children[1].infoset)
194+
# Alice dealt Q
195+
g.set_infoset(g.root.children[2].children[0].children[1],
196+
g.root.children[3].children[0].children[1].infoset)
197+
# Alice dealt K
198+
g.set_infoset(g.root.children[4].children[0].children[1],
199+
g.root.children[5].children[0].children[1].infoset)
200+
# Add outcomes at terminal nodes (net payoffs); first define four outcomes:
201+
alice_wins1 = g.add_outcome([1, -1], label="Alice wins 1")
202+
alice_wins2 = g.add_outcome([2, -2], label="Alice wins 2")
203+
bob_wins1 = g.add_outcome([-1, 1], label="Bob wins 1")
204+
bob_wins2 = g.add_outcome([-2, 2], label="Bob wins 2")
205+
# Check-Check -> Bob wins 1
206+
g.set_outcome(g.root.children[0].children[0].children[0], bob_wins1)
207+
# Check-Bet-Fold -> Bob wins 1
208+
g.set_outcome(g.root.children[0].children[0].children[1].children[0], bob_wins1)
209+
# Check-Bet-Call -> showdown -> Bob wins 2
210+
g.set_outcome(g.root.children[0].children[0].children[1].children[1], bob_wins2)
211+
# Bet-Fold -> Alice bet, Bob folds -> Alice wins pot -> Alice wins 1
212+
g.set_outcome(g.root.children[0].children[1].children[0], alice_wins1)
213+
# Bet-Call -> showdown -> Bob wins 2
214+
g.set_outcome(g.root.children[0].children[1].children[1], bob_wins2)
215+
# JK -> Bob wins showdown
216+
g.set_outcome(g.root.children[1].children[0].children[0], bob_wins1)
217+
g.set_outcome(g.root.children[1].children[0].children[1].children[0], bob_wins1)
218+
g.set_outcome(g.root.children[1].children[0].children[1].children[1], bob_wins2)
219+
g.set_outcome(g.root.children[1].children[1].children[0], alice_wins1)
220+
g.set_outcome(g.root.children[1].children[1].children[1], bob_wins2)
221+
# QJ -> Alice wins showdown
222+
g.set_outcome(g.root.children[2].children[0].children[0], alice_wins1)
223+
g.set_outcome(g.root.children[2].children[0].children[1].children[0], bob_wins1)
224+
g.set_outcome(g.root.children[2].children[0].children[1].children[1], alice_wins2)
225+
g.set_outcome(g.root.children[2].children[1].children[0], alice_wins1)
226+
g.set_outcome(g.root.children[2].children[1].children[1], alice_wins2)
227+
# QK -> Bob wins showdown
228+
g.set_outcome(g.root.children[3].children[0].children[0], bob_wins1)
229+
g.set_outcome(g.root.children[3].children[0].children[1].children[0], bob_wins1)
230+
g.set_outcome(g.root.children[3].children[0].children[1].children[1], bob_wins2)
231+
g.set_outcome(g.root.children[3].children[1].children[0], alice_wins1)
232+
g.set_outcome(g.root.children[3].children[1].children[1], bob_wins2)
233+
# KJ -> Alice wins showdown
234+
g.set_outcome(g.root.children[4].children[0].children[0], alice_wins1)
235+
g.set_outcome(g.root.children[4].children[0].children[1].children[0], bob_wins1)
236+
g.set_outcome(g.root.children[4].children[0].children[1].children[1], alice_wins2)
237+
g.set_outcome(g.root.children[4].children[1].children[0], alice_wins1)
238+
g.set_outcome(g.root.children[4].children[1].children[1], alice_wins2)
239+
# KQ -> Alice wins showdown
240+
g.set_outcome(g.root.children[5].children[0].children[0], alice_wins1)
241+
g.set_outcome(g.root.children[5].children[0].children[1].children[0], bob_wins1)
242+
g.set_outcome(g.root.children[5].children[0].children[1].children[1], alice_wins2)
243+
g.set_outcome(g.root.children[5].children[1].children[0], alice_wins1)
244+
g.set_outcome(g.root.children[5].children[1].children[1], alice_wins2)
245+
# Ensure infosets are in the same order as if game was written to efg and read back in
246+
g.sort_infosets()
247+
return g
248+
249+
250+
def create_one_shot_trust_efg() -> gbt.Game:
251+
g = gbt.Game.new_tree(
252+
players=["Buyer", "Seller"], title="One-shot trust game, after Kreps (1990)"
253+
)
254+
g.append_move(g.root, "Buyer", ["Trust", "Not trust"])
255+
g.append_move(g.root.children[0], "Seller", ["Honor", "Abuse"])
256+
g.set_outcome(
257+
g.root.children[0].children[0], g.add_outcome([1, 1], label="Trustworthy")
258+
)
259+
g.set_outcome(
260+
g.root.children[0].children[1], g.add_outcome([-1, 2], label="Untrustworthy")
261+
)
262+
g.set_outcome(g.root.children[1], g.add_outcome([0, 0], label="Opt-out"))
263+
return g
264+
265+
107266
def create_centipede_game_with_chance_efg() -> gbt.Game:
108267
"""
109268
Returns
@@ -198,7 +357,6 @@ def create_reduction_generic_payoffs_efg() -> gbt.Game:
198357
)
199358

200359
g.set_outcome(g.root.children[3], g.add_outcome([12, -12], label="d"))
201-
202360
return g
203361

204362

@@ -236,6 +394,138 @@ def create_reduction_both_players_payoff_ties_efg() -> gbt.Game:
236394
return g
237395

238396

397+
def create_seq_form_STOC_paper_zero_sum_2_player_efg() -> gbt.Game:
398+
"""
399+
Example from
400+
401+
Fast Algorithms for Finding Randomized Strategies in Game Trees (1994)
402+
Koller, Megiddo, von Stengel
403+
"""
404+
g = gbt.Game.new_tree(players=["1", "2"], title="From STOC'94 paper")
405+
g.append_move(g.root, g.players.chance, actions=["1", "2", "3", "4"])
406+
g.set_chance_probs(g.root.infoset, [0.2, 0.2, 0.2, 0.4])
407+
g.append_move(g.root.children[0], player="1", actions=["l", "r"])
408+
g.append_move(g.root.children[1], player="1", actions=["c", "d"])
409+
g.append_infoset(g.root.children[2], g.root.children[1].infoset)
410+
g.append_move(g.root.children[0].children[1], player="2", actions=["p", "q"])
411+
g.append_move(
412+
g.root.children[0].children[1].children[0], player="1", actions=["L", "R"]
413+
)
414+
g.append_infoset(
415+
g.root.children[0].children[1].children[1],
416+
g.root.children[0].children[1].children[0].infoset,
417+
)
418+
g.append_move(g.root.children[2].children[0], player="2", actions=["s", "t"])
419+
g.append_infoset(
420+
g.root.children[2].children[1], g.root.children[2].children[0].infoset
421+
)
422+
423+
g.set_outcome(
424+
g.root.children[0].children[0],
425+
outcome=g.add_outcome(payoffs=[5, -5], label="l"),
426+
)
427+
g.set_outcome(
428+
g.root.children[0].children[1].children[0].children[0],
429+
outcome=g.add_outcome(payoffs=[10, -10], label="rpL"),
430+
)
431+
g.set_outcome(
432+
g.root.children[0].children[1].children[0].children[1],
433+
outcome=g.add_outcome(payoffs=[15, -15], label="rpR"),
434+
)
435+
g.set_outcome(
436+
g.root.children[0].children[1].children[1].children[0],
437+
outcome=g.add_outcome(payoffs=[20, -20], label="rqL"),
438+
)
439+
g.set_outcome(
440+
g.root.children[0].children[1].children[1].children[1],
441+
outcome=g.add_outcome(payoffs=[-5, 5], label="rqR"),
442+
)
443+
g.set_outcome(
444+
g.root.children[1].children[0],
445+
outcome=g.add_outcome(payoffs=[10, -10], label="c"),
446+
)
447+
g.set_outcome(
448+
g.root.children[1].children[1],
449+
outcome=g.add_outcome(payoffs=[20, -20], label="d"),
450+
)
451+
g.set_outcome(
452+
g.root.children[2].children[0].children[0],
453+
outcome=g.add_outcome(payoffs=[20, -20], label="cs"),
454+
)
455+
g.set_outcome(
456+
g.root.children[2].children[0].children[1],
457+
outcome=g.add_outcome(payoffs=[50, -50], label="ct"),
458+
)
459+
g.set_outcome(
460+
g.root.children[2].children[1].children[0],
461+
outcome=g.add_outcome(payoffs=[30, -30], label="ds"),
462+
)
463+
g.set_outcome(
464+
g.root.children[2].children[1].children[1],
465+
outcome=g.add_outcome(payoffs=[15, -15], label="dt"),
466+
)
467+
g.set_outcome(
468+
g.root.children[3], outcome=g.add_outcome(payoffs=[5, -5], label="nothing")
469+
)
470+
g.root.children[0].infoset.label = "0"
471+
g.root.children[1].infoset.label = "1"
472+
g.root.children[0].children[1].infoset.label = "01"
473+
g.root.children[2].children[0].infoset.label = "20"
474+
g.root.children[0].children[1].children[0].infoset.label = "010"
475+
476+
return g
477+
478+
479+
def create_two_player_perfect_info_win_lose_efg() -> gbt.Game:
480+
g = gbt.Game.new_tree(players=["1", "2"], title="2 player perfect info win lose")
481+
g.append_move(g.root, "2", ["a", "b"])
482+
g.append_move(g.root.children[0], "1", ["L", "R"])
483+
g.append_move(g.root.children[1], "1", ["L", "R"])
484+
g.append_move(g.root.children[0].children[0], "2", ["l", "r"])
485+
g.set_outcome(
486+
g.root.children[0].children[0].children[0], g.add_outcome([1, -1], label="aLl")
487+
)
488+
g.set_outcome(
489+
g.root.children[0].children[0].children[1], g.add_outcome([-1, 1], label="aLr")
490+
)
491+
g.set_outcome(g.root.children[0].children[1], g.add_outcome([1, -1], label="aR"))
492+
g.set_outcome(g.root.children[1].children[0], g.add_outcome([1, -1], label="bL"))
493+
g.set_outcome(g.root.children[1].children[1], g.add_outcome([-1, 1], label="bR"))
494+
return g
495+
496+
497+
def create_EFG_for_nxn_bimatrix_coordination_game(n: int) -> gbt.Game:
498+
A = np.eye(n, dtype=int)
499+
B = A
500+
title = f"{n}x{n} coordination game, {2**n - 1} equilibria"
501+
return create_efg_corresponding_to_bimatrix_game(A, B, title)
502+
503+
504+
def create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq() -> gbt.Game:
505+
# 6 x 6 Payoff matrix A:
506+
A = [
507+
[-180, 72, -333, 297, -153, 270],
508+
[-30, 17, -33, 42, -3, 20],
509+
[-81, 36, -126, 126, -36, 90],
510+
[90, -36, 126, -126, 36, -81],
511+
[20, -3, 42, -33, 17, -30],
512+
[270, -153, 297, -333, 72, -180],
513+
]
514+
# 6 x 6 Payoff matrix B:
515+
B = [
516+
[72, 36, 17, -3, -36, -153],
517+
[-180, -81, -30, 20, 90, 270],
518+
[297, 126, 42, -33, -126, -333],
519+
[-333, -126, -33, 42, 126, 297],
520+
[270, 90, 20, -30, -81, -180],
521+
[-153, -36, -3, 17, 36, 72],
522+
]
523+
A = np.array(A)
524+
B = np.array(B)
525+
title = "6x6 Long Lemke-Howson Paths, unique eq"
526+
return create_efg_corresponding_to_bimatrix_game(A, B, title)
527+
528+
239529
class EfgFamilyForReducedStrategicFormTests(ABC):
240530
""" """
241531

tests/test_behav.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ def test_profile_indexing_by_infoset_and_action_labels_reference(game: gbt.Game,
229229
action_label: str,
230230
prob: typing.Union[str, float],
231231
rational_flag: bool):
232-
"""Here we use the infoset label and action label, with some exampels where the action label
232+
"""Here we use the infoset label and action label, with some examples where the action label
233233
alone throws a ValueError (checked in a separate test)
234234
"""
235235
profile = game.mixed_behavior_profile(rational=rational_flag)
@@ -259,7 +259,7 @@ def test_profile_indexing_by_player_infoset_action_labels_reference(game: gbt.Ga
259259
action_label: str,
260260
prob: typing.Union[str, float],
261261
rational_flag: bool):
262-
"""Here we use the infoset label and action label, with some exampels where the action label
262+
"""Here we use the infoset label and action label, with some examples where the action label
263263
alone throws a ValueError (checked in a separate test)
264264
"""
265265
profile = game.mixed_behavior_profile(rational=rational_flag)

tests/test_extensive.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ def test_outcome_index_exception_label():
377377
],
378378
)
379379
def test_reduced_strategic_form(
380-
game: gbt.Game, strategy_labels: list, np_arrays_of_rsf: list
380+
game: gbt.Game, strategy_labels: list, np_arrays_of_rsf: typing.Union[list, None]
381381
):
382382
"""
383383
We test two things:

0 commit comments

Comments
 (0)