Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
278 changes: 260 additions & 18 deletions tests/games.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def read_from_file(fn: str) -> gbt.Game:


def create_efg_corresponding_to_bimatrix_game(
A: np.ndarray, B: np.ndarray, title: str
A: np.ndarray, B: np.ndarray, title: str
) -> gbt.Game:
"""
There is no direct pygambit method to create an EFG from a stategic-form game.
Expand Down Expand Up @@ -123,6 +123,238 @@ def create_2x2_zero_sum_efg(missing_term_outcome: bool = False) -> gbt.Game:
return g


def create_perfect_info_with_chance_efg() -> gbt.Game:
# Tests case in which sequence profile probabilities don't sum to 1
g = gbt.Game.new_tree(players=["1", "2"], title="2 player perfect info with chance")
g.append_move(g.root, "1", ["a", "b"])
g.append_move(g.root.children[0], g.players.chance, ["L", "R"])
g.append_move(g.root.children[0].children[0], "2", ["A", "B"])
g.append_move(g.root.children[0].children[1], "2", ["C", "D"])
g.set_outcome(
g.root.children[0].children[0].children[0], g.add_outcome([-2, 2], label="aLA")
)
g.set_outcome(
g.root.children[0].children[0].children[1], g.add_outcome([-2, 2], label="aLB")
)
g.set_outcome(
g.root.children[0].children[1].children[0], g.add_outcome([-2, 2], label="aRC")
)
g.set_outcome(
g.root.children[0].children[1].children[1], g.add_outcome([-2, 2], label="aRD")
)
g.set_outcome(g.root.children[1], g.add_outcome([-1, 1], label="b"))
return g


def create_three_action_internal_outcomes_efg(nonterm_outcomes: bool = False) -> gbt.Game:
"""
with nonterm_outcomes there are nonterminal outcomes, and missing outcomes at some leaves
"""
g = gbt.Game.new_tree(players=["1", "2"], title="")
g.append_move(g.root, g.players.chance, ["H", "L"])
for i in range(2):
g.append_move(g.root.children[i], "1", ["A", "B", "C"])
for i in range(3):
g.append_move(g.root.children[0].children[i], "2", ["X", "Y"])
g.append_infoset(g.root.children[1].children[i], g.root.children[0].children[i].infoset)
o_1 = g.add_outcome([1, -1], label="1")
o_m1 = g.add_outcome([-1, 1], label="-1")
o_2 = g.add_outcome([2, -2], label="2")
o_m2 = g.add_outcome([-2, 2], label="-2")
o_z = g.add_outcome([0, 0], label="0")
if nonterm_outcomes:
g.set_outcome(g.root.children[0].children[0], o_1)
g.set_outcome(g.root.children[1].children[2], o_m1)
g.set_outcome(g.root.children[0].children[0].children[1], o_m2)
g.set_outcome(g.root.children[0].children[1].children[0], o_m1)
g.set_outcome(g.root.children[0].children[1].children[1], o_1)
g.set_outcome(g.root.children[0].children[2].children[0], o_1)
g.set_outcome(g.root.children[1].children[0].children[1], o_1)
g.set_outcome(g.root.children[1].children[1].children[0], o_1)
g.set_outcome(g.root.children[1].children[1].children[1], o_m1)
g.set_outcome(g.root.children[1].children[2].children[1], o_2)
else:
g.set_outcome(g.root.children[0].children[0].children[0], o_1)
g.set_outcome(g.root.children[0].children[0].children[1], o_m1)
g.set_outcome(g.root.children[0].children[1].children[0], o_m1)
g.set_outcome(g.root.children[0].children[1].children[1], o_1)
g.set_outcome(g.root.children[0].children[2].children[0], o_1)
g.set_outcome(g.root.children[0].children[2].children[1], o_z)

g.set_outcome(g.root.children[1].children[0].children[0], o_z)
g.set_outcome(g.root.children[1].children[0].children[1], o_1)
g.set_outcome(g.root.children[1].children[1].children[0], o_1)
g.set_outcome(g.root.children[1].children[1].children[1], o_m1)
g.set_outcome(g.root.children[1].children[2].children[0], o_m1)
g.set_outcome(g.root.children[1].children[2].children[1], o_1)
return g


def create_entry_accomodation_efg(nonterm_outcomes: bool = False) -> gbt.Game:
g = gbt.Game.new_tree(players=["1", "2"],
title="Entry-accomodation game")
g.append_move(g.root, "1", ["S", "T"])
g.append_move(g.root.children[0], "2", ["E", "O"])
g.append_infoset(g.root.children[1], g.root.children[0].infoset)
g.append_move(g.root.children[0].children[0], "1", ["A", "F"])
g.append_move(g.root.children[1].children[0], "1", ["A", "F"])
if nonterm_outcomes:
g.set_outcome(g.root.children[0], g.add_outcome([3, 2]))
g.set_outcome(g.root.children[0].children[0].children[1], g.add_outcome([-3, -1]))
g.set_outcome(g.root.children[0].children[1], g.add_outcome([-2, 1]))
else:
g.set_outcome(g.root.children[0].children[0].children[0], g.add_outcome([3, 2]))
g.set_outcome(g.root.children[0].children[0].children[1], g.add_outcome([0, 1]))
g.set_outcome(g.root.children[0].children[1], g.add_outcome([1, 3]))
g.set_outcome(g.root.children[1].children[0].children[0], g.add_outcome([2, 3]))
g.set_outcome(g.root.children[1].children[0].children[1], g.add_outcome([1, 0]))
g.set_outcome(g.root.children[1].children[1], g.add_outcome([3, 1]))
return g


def create_non_zero_sum_lacking_outcome_efg(missing_term_outcome: bool = False) -> gbt.Game:
g = gbt.Game.new_tree(players=["1", "2"], title="Non constant-sum game lacking outcome")
g.append_move(g.root, g.players.chance, ["H", "T"])
g.set_chance_probs(g.root.infoset, ["1/2", "1/2"])
g.append_move(g.root.children[0], "1", ["A", "B"])
g.append_infoset(g.root.children[1], g.root.children[0].infoset)
g.append_move(g.root.children[0].children[0], "2", ["X", "Y"])
g.append_infoset(g.root.children[0].children[1], g.root.children[0].children[0].infoset)
g.append_infoset(g.root.children[1].children[0], g.root.children[0].children[0].infoset)
g.append_infoset(g.root.children[1].children[1], g.root.children[0].children[0].infoset)
g.set_outcome(g.root.children[0].children[0].children[0], g.add_outcome([2, 1]))
g.set_outcome(g.root.children[0].children[0].children[1], g.add_outcome([-1, 2]))
g.set_outcome(g.root.children[0].children[1].children[0], g.add_outcome([1, -1]))
if not missing_term_outcome:
g.set_outcome(g.root.children[0].children[1].children[1], g.add_outcome([0, 0]))
g.set_outcome(g.root.children[1].children[0].children[0], g.add_outcome([1, 0]))
g.set_outcome(g.root.children[1].children[0].children[1], g.add_outcome([0, 1]))
g.set_outcome(g.root.children[1].children[1].children[0], g.add_outcome([-1, 1]))
g.set_outcome(g.root.children[1].children[1].children[1], g.add_outcome([2, -1]))
return g


def create_chance_in_middle_efg(nonterm_outcomes: bool = False) -> gbt.Game:
g = gbt.Game.new_tree(players=["1", "2"],
title="Chance in middle game")
g.append_move(g.root, "1", ["A", "B"])
g.append_move(g.root.children[0], g.players.chance, ["H", "L"])
g.set_chance_probs(g.root.children[0].infoset, ["1/5", "4/5"])
g.append_move(g.root.children[1], g.players.chance, ["H", "L"])
g.set_chance_probs(g.root.children[1].infoset, ["7/10", "3/10"])
for i in range(2):
g.append_move(g.root.children[0].children[i], "2", ["X", "Y"])
ist = g.root.children[0].children[i].infoset
g.append_infoset(g.root.children[1].children[i], ist)
for i in range(2):
for j in range(2):
g.append_move(g.root.children[i].children[0].children[j], "1", ["C", "D"])
ist = g.root.children[i].children[0].children[j].infoset
g.append_infoset(g.root.children[i].children[1].children[j], ist)
o_1 = g.add_outcome([1, -1], label="1")
o_m1 = g.add_outcome([-1, 1], label="-1")
o_m2 = g.add_outcome([-2, 2], label="-2")
o_h = g.add_outcome(["1/2", "-1/2"], label="0.5")
o_mh = g.add_outcome(["-1/2", "1/2"], label="-0.5")
o_z = g.add_outcome([0, 0], label="0")
o_m3o2 = g.add_outcome(["-3/2", "3/2"], label="-1.5")
if nonterm_outcomes:
g.set_outcome(g.root.children[0].children[0], g.add_outcome([-1, 1], label="a"))
g.set_outcome(g.root.children[0].children[0].children[0].children[0], o_1)
g.set_outcome(g.root.children[0].children[0].children[0].children[1], o_m1)
g.set_outcome(g.root.children[0].children[0].children[1].children[0], o_h)
g.set_outcome(g.root.children[0].children[0].children[1].children[1], o_mh)
else:
g.set_outcome(g.root.children[0].children[0].children[0].children[0], o_z)
g.set_outcome(g.root.children[0].children[0].children[0].children[1], o_m2)
g.set_outcome(g.root.children[0].children[0].children[1].children[0], o_mh)
g.set_outcome(g.root.children[0].children[0].children[1].children[1], o_m3o2)
g.set_outcome(g.root.children[0].children[1].children[0].children[0], o_h)
g.set_outcome(g.root.children[0].children[1].children[0].children[1], o_mh)
g.set_outcome(g.root.children[0].children[1].children[1].children[0], o_1)
g.set_outcome(g.root.children[0].children[1].children[1].children[1], o_m1)
g.set_outcome(g.root.children[1].children[0].children[0].children[0], o_h)
g.set_outcome(g.root.children[1].children[0].children[0].children[1], o_mh)
g.set_outcome(g.root.children[1].children[0].children[1].children[0], o_1)
g.set_outcome(g.root.children[1].children[0].children[1].children[1], o_m1)
g.set_outcome(g.root.children[1].children[1].children[0].children[0], o_1)
g.set_outcome(g.root.children[1].children[1].children[0].children[1], o_m1)
g.set_outcome(g.root.children[1].children[1].children[1].children[0], o_h)
g.set_outcome(g.root.children[1].children[1].children[1].children[1], o_mh)
return g


def create_large_payoff_game_efg() -> gbt.Game:
g = gbt.Game.new_tree(players=["1", "2"], title="Large payoff game")
g.append_move(g.root, g.players.chance, ["L", "R"])
for i in range(2):
g.append_move(g.root.children[i], "1", ["A", "B"])
for i in range(2):
g.append_move(g.root.children[0].children[i], "2", ["X", "Y"])
g.append_infoset(g.root.children[1].children[i], g.root.children[0].children[i].infoset)
o_large = g.add_outcome([10000000000000000000, -10000000000000000000], label="large payoff")
o_1 = g.add_outcome([1, -1], label="1")
o_m1 = g.add_outcome([-1, 1], label="-1")
o_zero = g.add_outcome([0, 0], label="0")
g.set_outcome(g.root.children[0].children[0].children[0], o_large)
g.set_outcome(g.root.children[0].children[0].children[1], o_1)
g.set_outcome(g.root.children[0].children[1].children[0], o_m1)
g.set_outcome(g.root.children[0].children[1].children[1], o_zero)
g.set_outcome(g.root.children[1].children[0].children[0], o_m1)
g.set_outcome(g.root.children[1].children[0].children[1], o_1)
g.set_outcome(g.root.children[1].children[1].children[0], o_zero)
g.set_outcome(g.root.children[1].children[1].children[1], o_large)
return g


def create_3_player_with_internal_outcomes_efg(nonterm_outcomes: bool = False) -> gbt.Game:
g = gbt.Game.new_tree(players=["1", "2", "3"], title="3 player game")
g.append_move(g.root, g.players.chance, ["H", "T"])
g.set_chance_probs(g.root.infoset, ["1/2", "1/2"])
g.append_move(g.root.children[0], "1", ["a", "b"])
g.append_move(g.root.children[1], "1", ["c", "d"])
g.append_move(g.root.children[0].children[0], "2", ["A", "B"])
g.append_infoset(g.root.children[1].children[0], g.root.children[0].children[0].infoset)
g.append_move(g.root.children[0].children[1], "3", ["W", "X"])
g.append_infoset(g.root.children[1].children[1], g.root.children[0].children[1].infoset)
g.append_move(g.root.children[0].children[0].children[0], "3", ["Y", "Z"])
iset = g.root.children[0].children[0].children[0].infoset
g.append_infoset(g.root.children[0].children[0].children[1], iset)
g.append_move(g.root.children[0].children[1].children[1], "2", ["C", "D"])
o = g.add_outcome([3, 1, 4])
g.set_outcome(g.root.children[0].children[0].children[0].children[0], o)
o = g.add_outcome([4, 0, 1])
g.set_outcome(g.root.children[0].children[0].children[0].children[1], o)
o = g.add_outcome([1, 3, 2])
g.set_outcome(g.root.children[0].children[1].children[0], o)
o = g.add_outcome([2, 4, 1])
g.set_outcome(g.root.children[0].children[1].children[1].children[0], o)
o = g.add_outcome([4, 1, 3])
g.set_outcome(g.root.children[0].children[1].children[1].children[1], o)
if nonterm_outcomes:
o = g.add_outcome([1, 2, 3])
g.set_outcome(g.root.children[1], o)
o = g.add_outcome([1, 0, 1])
g.set_outcome(g.root.children[1].children[0].children[0], o)
o = g.add_outcome([2, -1, -2])
g.set_outcome(g.root.children[1].children[0].children[1], o)
o = g.add_outcome([-1, 2, -1])
g.set_outcome(g.root.children[1].children[1].children[0], o)
else:
o = g.add_outcome([2, 2, 4])
g.set_outcome(g.root.children[1].children[0].children[0], o)
o = g.add_outcome([3, 1, 1])
g.set_outcome(g.root.children[1].children[0].children[1], o)
o = g.add_outcome([0, 4, 2])
g.set_outcome(g.root.children[1].children[1].children[0], o)
o = g.add_outcome([1, 2, 3])
g.set_outcome(g.root.children[1].children[1].children[1], o)
o = g.add_outcome([0, 0, 0])
g.set_outcome(g.root.children[0].children[0].children[1].children[0], o)
g.set_outcome(g.root.children[0].children[0].children[1].children[1], o)
return g


def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game:
"""
The version with_neutral_outcome adds a (0,0) payoff outcomes at a non-terminal node.
Expand Down Expand Up @@ -211,8 +443,6 @@ def create_stripped_down_poker_efg(nonterm_outcomes: bool = False) -> gbt.Game:
g.set_outcome(bob_calls_and_loses_node, bob_calls_and_loses_outcome)
bob_calls_and_wins_node = g.root.children["Queen"].children["Bet"].children["Call"]
g.set_outcome(bob_calls_and_wins_node, bob_calls_and_wins_outcome)
# g.to_efg("stripped_down_poker_nonterminal_outcomes.efg")

return g


Expand Down Expand Up @@ -701,21 +931,33 @@ def create_seq_form_STOC_paper_zero_sum_2_player_efg() -> gbt.Game:
return g


def create_two_player_perfect_info_win_lose_efg() -> gbt.Game:
def create_two_player_perfect_info_win_lose_efg(nonterm_outcomes: bool = False) -> gbt.Game:
g = gbt.Game.new_tree(players=["1", "2"], title="2 player perfect info win lose")
g.append_move(g.root, "2", ["a", "b"])
g.append_move(g.root.children[0], "1", ["L", "R"])
g.append_move(g.root.children[1], "1", ["L", "R"])
g.append_move(g.root.children[0].children[0], "2", ["l", "r"])
g.set_outcome(
g.root.children[0].children[0].children[0], g.add_outcome([1, -1], label="aLl")
)
g.set_outcome(
g.root.children[0].children[0].children[1], g.add_outcome([-1, 1], label="aLr")
)
g.set_outcome(g.root.children[0].children[1], g.add_outcome([1, -1], label="aR"))
g.set_outcome(g.root.children[1].children[0], g.add_outcome([1, -1], label="bL"))
g.set_outcome(g.root.children[1].children[1], g.add_outcome([-1, 1], label="bR"))
if not nonterm_outcomes:
g.set_outcome(
g.root.children[0].children[0].children[0], g.add_outcome([1, -1], label="aLl")
)
g.set_outcome(
g.root.children[0].children[0].children[1], g.add_outcome([-1, 1], label="aLr")
)
g.set_outcome(g.root.children[0].children[1], g.add_outcome([1, -1], label="aR"))
g.set_outcome(g.root.children[1].children[0], g.add_outcome([1, -1], label="bL"))
g.set_outcome(g.root.children[1].children[1], g.add_outcome([-1, 1], label="bR"))
else:
g.set_outcome(g.root.children[0], g.add_outcome([-100, 50], label="a"))
g.set_outcome(
g.root.children[0].children[0].children[0], g.add_outcome([101, -51], label="aLl")
)
g.set_outcome(
g.root.children[0].children[0].children[1], g.add_outcome([99, -49], label="aLr")
)
g.set_outcome(g.root.children[0].children[1], g.add_outcome([101, -51], label="aR"))
g.set_outcome(g.root.children[1].children[0], g.add_outcome([1, -1], label="bL"))
g.set_outcome(g.root.children[1].children[1], g.add_outcome([-1, 1], label="bR"))
return g


Expand Down Expand Up @@ -990,14 +1232,14 @@ def _redu_strats(self, player, level):
first_half = tmp[:n_half]
second_half = tmp[n_half:]
n_stars = (
self.get_n_infosets(level)[1] - self.get_n_infosets(level - 1)[1] - 1
self.get_n_infosets(level)[1] - self.get_n_infosets(level - 1)[1] - 1
)
stars = "*" * n_stars
return (
["11" + t + stars for t in first_half]
+ ["12" + t + stars for t in second_half]
+ ["21" + stars + t for t in first_half]
+ ["22" + stars + t for t in second_half]
["11" + t + stars for t in first_half]
+ ["12" + t + stars for t in second_half]
+ ["21" + stars + t for t in first_half]
+ ["22" + stars + t for t in second_half]
)


Expand Down
60 changes: 60 additions & 0 deletions tests/test_extensive.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,3 +409,63 @@ def test_reduced_strategic_form(
# convert strings to rationals
exp_array = games.vectorized_make_rational(np_arrays_of_rsf[i])
assert (arrays[i] == exp_array).all()


@pytest.mark.parametrize(
"standard,modified",
[
(
games.create_two_player_perfect_info_win_lose_efg(),
games.create_two_player_perfect_info_win_lose_efg(nonterm_outcomes=True)
),
(
games.create_3_player_with_internal_outcomes_efg(),
games.create_3_player_with_internal_outcomes_efg(nonterm_outcomes=True)
),
(
games.create_chance_in_middle_efg(),
games.create_chance_in_middle_efg(nonterm_outcomes=True)
),
(
games.create_non_zero_sum_lacking_outcome_efg(),
games.create_non_zero_sum_lacking_outcome_efg(missing_term_outcome=True)
),
(
games.create_entry_accomodation_efg(),
games.create_entry_accomodation_efg(nonterm_outcomes=True)
),
(
games.create_three_action_internal_outcomes_efg(),
games.create_three_action_internal_outcomes_efg(nonterm_outcomes=True)
),
(
games.create_kuhn_poker_efg(),
games.create_kuhn_poker_efg(nonterm_outcomes=True)
),
(
games.create_stripped_down_poker_efg(),
games.create_stripped_down_poker_efg(nonterm_outcomes=True)
),
(
games.create_2x2_zero_sum_efg(),
games.create_2x2_zero_sum_efg(missing_term_outcome=True)
),
(
games.create_matching_pennies_efg(),
games.create_matching_pennies_efg(with_neutral_outcome=True)
),
],
)
def test_reduced_strategy_form_nonterminal_outcomes_consistency(standard: gbt.Game,
modified: gbt.Game):
"""
standard: game uses only non-terminal outcomes, with all non-terminal nodes having outcomes
modified: is payoff equivalent, but with non-terminal outcomes or missing terminal outcomes

The test checks that the corresponding reduced strategic forms match.
"""
arrays_s = standard.to_arrays()
arrays_m = modified.to_arrays()
assert (len(arrays_s) == len(arrays_m))
for array_s, array_m in zip(arrays_s, arrays_m, strict=True):
assert (array_s == array_m).all()
Loading