Skip to content

Commit

Permalink
Significant extension to test_mixed.py: new profile order tests, new …
Browse files Browse the repository at this point in the history
…indexing tests, better coverage of game types, including a 3-player nfg
  • Loading branch information
rahulsavani authored and tturocy committed Feb 8, 2024
1 parent b9e777d commit 40966e7
Show file tree
Hide file tree
Showing 9 changed files with 1,357 additions and 853 deletions.
2 changes: 0 additions & 2 deletions src/pygambit/strategy.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ class Strategy:

@label.setter
def label(self, value: str) -> None:
if value in [i.label for i in self.player.strategies]:
warnings.warn("This player has another strategy with an identical label")
self.strategy.deref().SetLabel(value.encode("ascii"))

@property
Expand Down
106 changes: 83 additions & 23 deletions tests/games.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,67 @@ def read_from_file(fn: str) -> gbt.Game:
return gbt.Game.read_game(pathlib.Path("tests/test_games")/fn)


def create_mixed_behav_game() -> gbt.Game:
################################################################################################
# Normal-form (aka strategic-form) games (nfg)

def create_2x2_zero_nfg() -> gbt.Game:
"""
Returns
-------
Game
2x2 all-zero-payoffs bimatrix, with player names and a duplicate label set intentionally
for testing purposes
"""
game = gbt.Game.new_table([2, 2])

game.players[0].label = "Joe"
game.players["Joe"].strategies[0].label = "cooperate"
game.players["Joe"].strategies[1].label = "defect"

game.players[1].label = "Dan"
game.players["Dan"].strategies[0].label = "defect"
# intentional duplicate label for player (generates warning):
game.players["Dan"].strategies[1].label = "defect"

return game


def create_2x2x2_nfg() -> gbt.Game:
"""
- This comes from a local max cut instance:
players {1,2,3} are nodes; edge weight{1,2} = 2; weight{1,3} = -1; weight{2,3} = 2
- Pure strategies {a,b} encode if respective player is on left or right of the cut
- The payoff to a player is the sum of their incident edges across the implied cut
- Pure equilibrium iff local max cuts; in addition, uniform mixture is an equilibrium
- Equilibrium analysis for pure profiles:
a a a: 0 0 0 -- Not Nash (2 can deviate and get 4)
b a a: 1 2 -1 -- Not Nash (3 can deviate and get 2)
a b a: 2 4 2 -- Nash (global max cut)
b b a: -1 2 1 -- Not Nash (1 can deviate and get 2)
a a b: -1 2 1 -- Not Nash (1 can deviate and get 2)
b a b: 2 4 2 -- Nash (global max cut)
a b b: 1 2 -1 -- Not Nash (3 can deviate and get 2)
b b b: 0 0 0 -- Not Nash (2 can deviate and get 4)
"""
return read_from_file("2x2x2_nfg_with_two_pure_one_mixed_eq.nfg")


def create_coord_4x4_nfg(outcome_version: bool = False) -> gbt.Game:
"""
Returns
-------
Game
4x4 coordination game, either via reading in a payoff version nfg, or an
outcome version nfg, which has strategy labels useful for testing
"""
version = "outcome" if outcome_version else "payoff"
return read_from_file(f"coordination_4x4_{version}.nfg")


################################################################################################
# Extensive-form games (efg)

def create_mixed_behav_game_efg() -> gbt.Game:
"""
Returns
-------
Expand All @@ -19,7 +79,7 @@ def create_mixed_behav_game() -> gbt.Game:
return read_from_file("mixed_behavior_game.efg")


def create_myerson_2_card_poker() -> gbt.Game:
def create_myerson_2_card_poker_efg() -> gbt.Game:
"""
Returns
-------
Expand All @@ -31,31 +91,31 @@ def create_myerson_2_card_poker() -> gbt.Game:
return read_from_file("myerson_2_card_poker.efg")


def create_strategic_game() -> gbt.Game:
game = gbt.Game.new_table([2, 2])
game.players[0].label = "Joe"
game.players["Joe"].strategies[0].label = "cooperate"
game.players[1].label = "Dan"
game.players["Dan"].strategies[1].label = "defect"
return game


def create_coord_4x4_nfg() -> gbt.Game:
return read_from_file("coordination_4x4.nfg")


def create_zero_matrix_nfg() -> gbt.Game:
"""4-players with three strategies each"""
return read_from_file("zero.nfg")


def create_centipede_game_with_chance() -> gbt.Game:
def create_centipede_game_with_chance_efg() -> gbt.Game:
"""
Returns
-------
Game
2-player Centipede Game with 3 innings and a probability of altruism
"""
return read_from_file("cent3.efg")


def create_el_farol_bar_game() -> gbt.Game:
def create_el_farol_bar_game_efg() -> gbt.Game:
"""
Returns
-------
Game
5-player El Farol Bar Game
"""
return read_from_file("el_farol_bar.efg")


def create_selten_horse_game() -> gbt.Game:
def create_selten_horse_game_efg() -> gbt.Game:
"""
Returns
-------
Game
5-player Selten's Horse Game
"""
return read_from_file("e01.efg")
28 changes: 14 additions & 14 deletions tests/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

@pytest.mark.parametrize(
"game,label",
[(games.create_myerson_2_card_poker(), "random label")]
[(games.create_myerson_2_card_poker_efg(), "random label")]
)
def test_set_action_label(game: gbt.Game, label: str):
game.root.infoset.actions[0].label = label
Expand All @@ -15,9 +15,9 @@ def test_set_action_label(game: gbt.Game, label: str):

@pytest.mark.parametrize(
"game,inprobs,outprobs",
[(games.create_myerson_2_card_poker(),
[(games.create_myerson_2_card_poker_efg(),
[0.75, 0.25], [0.75, 0.25]),
(games.create_myerson_2_card_poker(),
(games.create_myerson_2_card_poker_efg(),
["16/17", "1/17"], [gbt.Rational("16/17"), gbt.Rational("1/17")])]
)
def test_set_chance_valid_probability(game: gbt.Game, inprobs: list, outprobs: list):
Expand All @@ -28,9 +28,9 @@ def test_set_chance_valid_probability(game: gbt.Game, inprobs: list, outprobs: l

@pytest.mark.parametrize(
"game,inprobs",
[(games.create_myerson_2_card_poker(), [0.75, -0.10]),
(games.create_myerson_2_card_poker(), [0.75, 0.40]),
(games.create_myerson_2_card_poker(), ["foo", "bar"])]
[(games.create_myerson_2_card_poker_efg(), [0.75, -0.10]),
(games.create_myerson_2_card_poker_efg(), [0.75, 0.40]),
(games.create_myerson_2_card_poker_efg(), ["foo", "bar"])]
)
def test_set_chance_improper_probability(game: gbt.Game, inprobs: list):
with pytest.raises(ValueError):
Expand All @@ -39,8 +39,8 @@ def test_set_chance_improper_probability(game: gbt.Game, inprobs: list):

@pytest.mark.parametrize(
"game,inprobs",
[(games.create_myerson_2_card_poker(), [0.25, 0.75, 0.25]),
(games.create_myerson_2_card_poker(), [1.00])]
[(games.create_myerson_2_card_poker_efg(), [0.25, 0.75, 0.25]),
(games.create_myerson_2_card_poker_efg(), [1.00])]
)
def test_set_chance_bad_dimension(game: gbt.Game, inprobs: list):
with pytest.raises(IndexError):
Expand All @@ -49,7 +49,7 @@ def test_set_chance_bad_dimension(game: gbt.Game, inprobs: list):

@pytest.mark.parametrize(
"game",
[games.create_myerson_2_card_poker()]
[games.create_myerson_2_card_poker_efg()]
)
def test_set_chance_personal(game: gbt.Game):
with pytest.raises(gbt.UndefinedOperationError):
Expand All @@ -58,7 +58,7 @@ def test_set_chance_personal(game: gbt.Game):

@pytest.mark.parametrize(
"game",
[games.create_myerson_2_card_poker()]
[games.create_myerson_2_card_poker_efg()]
)
def test_action_precedes(game: gbt.Game):
child = game.root.children[0]
Expand All @@ -68,7 +68,7 @@ def test_action_precedes(game: gbt.Game):

@pytest.mark.parametrize(
"game",
[games.create_myerson_2_card_poker()]
[games.create_myerson_2_card_poker_efg()]
)
def test_action_precedes_nonnode(game: gbt.Game):
with pytest.raises(TypeError):
Expand All @@ -77,7 +77,7 @@ def test_action_precedes_nonnode(game: gbt.Game):

@pytest.mark.parametrize(
"game",
[games.create_myerson_2_card_poker()]
[games.create_myerson_2_card_poker_efg()]
)
def test_action_delete_personal(game: gbt.Game):
node = game.players[0].infosets[0].members[0]
Expand All @@ -89,7 +89,7 @@ def test_action_delete_personal(game: gbt.Game):

@pytest.mark.parametrize(
"game",
[games.create_myerson_2_card_poker()]
[games.create_myerson_2_card_poker_efg()]
)
def test_action_delete_last(game: gbt.Game):
node = game.players[0].infosets[0].members[0]
Expand All @@ -102,7 +102,7 @@ def test_action_delete_last(game: gbt.Game):
@pytest.mark.parametrize(
"game",
[games.read_from_file("chance_root_3_moves_only_one_nonzero_prob.efg"),
games.create_myerson_2_card_poker(),
games.create_myerson_2_card_poker_efg(),
games.read_from_file("chance_root_5_moves_no_nonterm_player_nodes.efg")]
)
def test_action_delete_chance(game: gbt.Game):
Expand Down
Loading

0 comments on commit 40966e7

Please sign in to comment.