From 156083bcfc148c6d890a652f004d0dd863035489 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Tue, 23 Dec 2025 15:45:09 +0000 Subject: [PATCH 01/44] Draft structure of organising test cases as dataclasses, using subtests. --- tests/requirements.txt | 1 + tests/test_nash.py | 108 ++++++++++++++++++++++++++++------------- 2 files changed, 75 insertions(+), 34 deletions(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index 545fd9b00..37cbc5ded 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,4 +1,5 @@ pytest +pytest-subtests nbformat nbclient ipykernel diff --git a/tests/test_nash.py b/tests/test_nash.py index adf5552df..4967528e7 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -7,6 +7,11 @@ lp_solve, lcp_solve, and enumpoly_solve, all in mixed behaviors. """ +import dataclasses +import functools +import typing +from fractions import Fraction as Q + import pytest import pygambit as gbt @@ -37,49 +42,84 @@ def test_enummixed_double(): # For floating-point results are not exact, so we skip testing exact values for now +def d(*probs) -> tuple: + """Helper function to let us write d() to be suggestive of + "probability distribution on simplex" ("Delta") + """ + return tuple(probs) + + +@dataclasses.dataclass +class EquilibriumTestCase: + """Summarising the data relevant for a test fixture of a call to an equilibrium solver.""" + label: str + factory: typing.Callable[[], gbt.Game] + expected: list + + +NASH_ENUMMIXED_RATIONAL_CASES = [ + EquilibriumTestCase( + label="test1", + factory=games.create_stripped_down_poker_efg, + expected=[ + [d(Q("1/3"), Q("2/3"), 0, 0), d(Q("2/3"), Q("1/3"))], + ] + ), + EquilibriumTestCase( + label="test2", + factory=games.create_one_shot_trust_efg, + expected=[ + [d(0, 1), d(Q("1/2"), Q("1/2"))], + [d(0, 1), d(0, 1)], + ] + ), + EquilibriumTestCase( + label="test3", + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), + expected=[ + [d(1, 0, 0), d(1, 0, 0)], + [d(Q("1/2"), Q("1/2"), 0), d(Q("1/2"), Q("1/2"), 0)], + [d(Q("1/3"), Q("1/3"), Q("1/3")), d(Q("1/3"), Q("1/3"), Q("1/3"))], + [d(Q("1/2"), 0, Q("1/2")), d(Q("1/2"), 0, Q("1/2"))], + [d(0, 1, 0), d(0, 1, 0)], + [d(0, Q("1/2"), Q("1/2")), d(0, Q("1/2"), Q("1/2"))], + [d(0, 0, 1), d(0, 0, 1)], + ] + ), + EquilibriumTestCase( + label="test4", + factory=games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq, + expected=[ + [d(Q("1/30"), Q("1/6"), Q("3/10"), Q("3/10"), Q("1/6"), Q("1/30")), + d(Q("1/6"), Q("1/30"), Q("3/10"), Q("3/10"), Q("1/30"), Q("1/6"))], + ] + ), +] + + @pytest.mark.nash @pytest.mark.nash_enummixed_strategy @pytest.mark.parametrize( - "game,mixed_strategy_prof_data", - [ - # Zero-sum games - (games.create_stripped_down_poker_efg(), [[["1/3", "2/3", 0, 0], ["2/3", "1/3"]]]), - # Non-zero-sum games - (games.create_one_shot_trust_efg(), [[[0, 1], ["1/2", "1/2"]], - [[0, 1], [0, 1]]]), - ( - games.create_EFG_for_nxn_bimatrix_coordination_game(3), - [ - [[1, 0, 0], [1, 0, 0]], - [["1/2", "1/2", 0], ["1/2", "1/2", 0]], - [["1/3", "1/3", "1/3"], ["1/3", "1/3", "1/3"]], - [["1/2", 0, "1/2"], ["1/2", 0, "1/2"]], - [[0, 1, 0], [0, 1, 0]], - [[0, "1/2", "1/2"], [0, "1/2", "1/2"]], - [[0, 0, 1], [0, 0, 1]], - ], - ), - ( - games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq(), - [ - [["1/30", "1/6", "3/10", "3/10", "1/6", "1/30"], - ["1/6", "1/30", "3/10", "3/10", "1/30", "1/6"]], - ], - ), - ] + "test_case", NASH_ENUMMIXED_RATIONAL_CASES, ids=lambda c: c.label ) -def test_enummixed_rational(game: gbt.Game, mixed_strategy_prof_data: list): +def test_enummixed_rational( + test_case: EquilibriumTestCase, + subtests, +) -> None: """Test calls of enumeration of extreme mixed strategy equilibria, rational precision Tests max regret being zero (internal consistency) and compares the computed sequence of - extreme equilibria to a previosuly computed sequence (regression test) + extreme equilibria to a previously-computed sequence (regression test) """ + game = test_case.factory() result = gbt.nash.enummixed_solve(game, rational=True) - assert len(result.equilibria) == len(mixed_strategy_prof_data) - for eq, exp in zip(result.equilibria, mixed_strategy_prof_data, strict=True): - assert eq.max_regret() == 0 - expected = game.mixed_strategy_profile(rational=True, data=exp) - assert eq == expected + with subtests.test("number of equilibria found"): + assert len(result.equilibria) == len(test_case.expected) + for (i, (eq, exp)) in enumerate(zip(result.equilibria, test_case.expected, strict=True)): + with subtests.test(eq=i, check="max_regret"): + assert eq.max_regret() == 0 + with subtests.test(eq=i, check="strategy_profile"): + assert eq == game.mixed_strategy_profile(rational=True, data=exp) @pytest.mark.nash From 549b2e6f64b5188fa0c0078da35f7674e675b573 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Tue, 23 Dec 2025 16:10:49 +0000 Subject: [PATCH 02/44] Suggestion for writing a generic Nash tester. --- tests/test_nash.py | 113 ++++++++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 47 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 4967528e7..ec196f0c7 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -10,11 +10,11 @@ import dataclasses import functools import typing -from fractions import Fraction as Q import pytest import pygambit as gbt +from pygambit import Rational as Q from . import games @@ -52,74 +52,93 @@ def d(*probs) -> tuple: @dataclasses.dataclass class EquilibriumTestCase: """Summarising the data relevant for a test fixture of a call to an equilibrium solver.""" - label: str factory: typing.Callable[[], gbt.Game] + solver: typing.Callable[[gbt.Game], gbt.nash.NashComputationResult] expected: list + regret_tol: float | gbt.Rational = Q(0) + prob_tol: float | gbt.Rational = Q(0) NASH_ENUMMIXED_RATIONAL_CASES = [ - EquilibriumTestCase( - label="test1", - factory=games.create_stripped_down_poker_efg, - expected=[ - [d(Q("1/3"), Q("2/3"), 0, 0), d(Q("2/3"), Q("1/3"))], - ] - ), - EquilibriumTestCase( - label="test2", - factory=games.create_one_shot_trust_efg, - expected=[ - [d(0, 1), d(Q("1/2"), Q("1/2"))], - [d(0, 1), d(0, 1)], - ] + pytest.param( + EquilibriumTestCase( + factory=games.create_stripped_down_poker_efg, + solver=functools.partial(gbt.nash.enummixed_solve, rational=True), + expected=[ + [d(Q("1/3"), Q("2/3"), 0, 0), d(Q("2/3"), Q("1/3"))], + ], + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test1", ), - EquilibriumTestCase( - label="test3", - factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), - expected=[ - [d(1, 0, 0), d(1, 0, 0)], - [d(Q("1/2"), Q("1/2"), 0), d(Q("1/2"), Q("1/2"), 0)], - [d(Q("1/3"), Q("1/3"), Q("1/3")), d(Q("1/3"), Q("1/3"), Q("1/3"))], - [d(Q("1/2"), 0, Q("1/2")), d(Q("1/2"), 0, Q("1/2"))], - [d(0, 1, 0), d(0, 1, 0)], - [d(0, Q("1/2"), Q("1/2")), d(0, Q("1/2"), Q("1/2"))], - [d(0, 0, 1), d(0, 0, 1)], - ] + pytest.param( + EquilibriumTestCase( + factory=games.create_one_shot_trust_efg, + solver=functools.partial(gbt.nash.enummixed_solve, rational=True), + expected=[ + [d(0, 1), d(Q("1/2"), Q("1/2"))], + [d(0, 1), d(0, 1)], + ], + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test2", ), - EquilibriumTestCase( - label="test4", - factory=games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq, - expected=[ - [d(Q("1/30"), Q("1/6"), Q("3/10"), Q("3/10"), Q("1/6"), Q("1/30")), - d(Q("1/6"), Q("1/30"), Q("3/10"), Q("3/10"), Q("1/30"), Q("1/6"))], - ] + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), + solver=functools.partial(gbt.nash.enummixed_solve, rational=True), + expected=[ + [d(1, 0, 0), d(1, 0, 0)], + [d(Q("1/2"), Q("1/2"), 0), d(Q("1/2"), Q("1/2"), 0)], + [d(Q("1/3"), Q("1/3"), Q("1/3")), d(Q("1/3"), Q("1/3"), Q("1/3"))], + [d(Q("1/2"), 0, Q("1/2")), d(Q("1/2"), 0, Q("1/2"))], + [d(0, 1, 0), d(0, 1, 0)], + [d(0, Q("1/2"), Q("1/2")), d(0, Q("1/2"), Q("1/2"))], + [d(0, 0, 1), d(0, 0, 1)], + ] + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test3", ), + pytest.param( + EquilibriumTestCase( + factory=games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq, + solver=functools.partial(gbt.nash.enummixed_solve, rational=True), + expected=[ + [d(Q("1/30"), Q("1/6"), Q("3/10"), Q("3/10"), Q("1/6"), Q("1/30")), + d(Q("1/6"), Q("1/30"), Q("3/10"), Q("3/10"), Q("1/30"), Q("1/6"))], + ] + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test4", + ) ] @pytest.mark.nash -@pytest.mark.nash_enummixed_strategy @pytest.mark.parametrize( "test_case", NASH_ENUMMIXED_RATIONAL_CASES, ids=lambda c: c.label ) -def test_enummixed_rational( - test_case: EquilibriumTestCase, - subtests, -) -> None: - """Test calls of enumeration of extreme mixed strategy equilibria, rational precision - - Tests max regret being zero (internal consistency) and compares the computed sequence of - extreme equilibria to a previously-computed sequence (regression test) +def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: + """Test calls of Nash solvers. + + Subtests: + - Max regret no more than `test_case.regret_tol` + - Equilibria are output in the expected order. Equilibria are deemed to match if the maximum + difference in probabilities is no more than `test_case.prob_tol` """ game = test_case.factory() - result = gbt.nash.enummixed_solve(game, rational=True) + result = test_case.solver(game) with subtests.test("number of equilibria found"): assert len(result.equilibria) == len(test_case.expected) for (i, (eq, exp)) in enumerate(zip(result.equilibria, test_case.expected, strict=True)): with subtests.test(eq=i, check="max_regret"): - assert eq.max_regret() == 0 + assert eq.max_regret() <= test_case.regret_tol with subtests.test(eq=i, check="strategy_profile"): - assert eq == game.mixed_strategy_profile(rational=True, data=exp) + expected = game.mixed_strategy_profile(rational=True, data=exp) + for player in game.players: + for strategy in player.strategies: + assert abs(eq[strategy] - expected[strategy]) <= test_case.prob_tol @pytest.mark.nash From fc4ccf037b60a370766fe3dda0a807374a3405b4 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 10:27:27 +0000 Subject: [PATCH 03/44] removed create_2x2x2_nfg from games.py --- tests/games.py | 20 - ...g_from_local_max_cut_2_pure_1_mixed_eq.nfg | 20 + .../2x2x2_nfg_with_two_pure_one_mixed_eq.nfg | 4 - tests/test_io.py | 22 +- tests/test_mixed.py | 2183 +++++++++++------ tests/test_players.py | 2 +- 6 files changed, 1488 insertions(+), 763 deletions(-) create mode 100644 tests/test_games/2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg delete mode 100644 tests/test_games/2x2x2_nfg_with_two_pure_one_mixed_eq.nfg diff --git a/tests/games.py b/tests/games.py index deb1eb580..dbadfe033 100644 --- a/tests/games.py +++ b/tests/games.py @@ -66,26 +66,6 @@ def create_2x2_zero_nfg() -> gbt.Game: 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 (regrets: 1, 4, 1) - b a a: 1 2 -1 -- Not Nash (regrets: 0, 0, 3) - a b a: 2 4 2 -- Nash (global max cut) - b b a: -1 2 1 -- Not Nash (regrets: 3, 0, 0) - a a b: -1 2 1 -- Not Nash (regrets: 3, 0, 0) - b a b: 2 4 2 -- Nash (global max cut) - a b b: 1 2 -1 -- Not Nash (regrets: 0, 0, 3) - b b b: 0 0 0 -- Not Nash (regrets: 1, 4, 1) - """ - 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 diff --git a/tests/test_games/2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg b/tests/test_games/2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg new file mode 100644 index 000000000..f28f234f6 --- /dev/null +++ b/tests/test_games/2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg @@ -0,0 +1,20 @@ +NFG 1 R "2x2x2 game with 2 pure and 1 mixed equilibrium" +{ "Player 1" "Player 2" "Player 3" } { 2 2 2 } +" +- 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 (regrets: 1, 4, 1) + b a a: 1 2 -1 -- Not Nash (regrets: 0, 0, 3) + a b a: 2 4 2 -- Nash (global max cut) + b b a: -1 2 1 -- Not Nash (regrets: 3, 0, 0) + a a b: -1 2 1 -- Not Nash (regrets: 3, 0, 0) + b a b: 2 4 2 -- Nash (global max cut) + a b b: 1 2 -1 -- Not Nash (regrets: 0, 0, 3) + b b b: 0 0 0 -- Not Nash (regrets: 1, 4, 1) +" + +0 0 0 1 2 -1 2 4 2 -1 2 1 -1 2 1 2 4 2 1 2 -1 0 0 0 diff --git a/tests/test_games/2x2x2_nfg_with_two_pure_one_mixed_eq.nfg b/tests/test_games/2x2x2_nfg_with_two_pure_one_mixed_eq.nfg deleted file mode 100644 index 8c4e999d3..000000000 --- a/tests/test_games/2x2x2_nfg_with_two_pure_one_mixed_eq.nfg +++ /dev/null @@ -1,4 +0,0 @@ -NFG 1 R "2x2x2 game with 2 pure and 1 mixed equilibrium" -{ "Player 1" "Player 2" "Player 3" } { 2 2 2 } - -0 0 0 1 2 -1 2 4 2 -1 2 1 -1 2 1 2 4 2 1 2 -1 0 0 0 diff --git a/tests/test_io.py b/tests/test_io.py index 1a6c328de..05046b8fa 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -16,7 +16,9 @@ def test_read_efg(game_path): def test_read_efg_invalid(): - game_path = os.path.join("tests", "test_games", "2x2x2_nfg_with_two_pure_one_mixed_eq.nfg") + game_path = os.path.join( + "tests", "test_games", "2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg" + ) with pytest.raises(ValueError): gbt.read_efg(game_path) @@ -40,13 +42,17 @@ def test_read_agg(game_path): def test_read_agg_invalid(): - game_path = os.path.join("tests", "test_games", "2x2x2_nfg_with_two_pure_one_mixed_eq.nfg") + game_path = os.path.join( + "tests", "test_games", "2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg" + ) with pytest.raises(ValueError): gbt.read_agg(game_path) def test_read_gbt_invalid(): - game_path = os.path.join("tests", "test_games", "2x2x2_nfg_with_two_pure_one_mixed_eq.nfg") + game_path = os.path.join( + "tests", "test_games", "2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg" + ) with pytest.raises(ValueError): gbt.read_gbt(game_path) @@ -116,8 +122,9 @@ def test_read_write_efg(): def test_read_write_nfg(): nfg_game = create_2x2_zero_nfg() serialized_nfg_game = nfg_game.to_nfg() - deserialized_nfg_game = gbt.read_nfg(io.BytesIO(serialized_nfg_game.encode()), - normalize_labels=False) + deserialized_nfg_game = gbt.read_nfg( + io.BytesIO(serialized_nfg_game.encode()), normalize_labels=False + ) double_serialized_nfg_game = deserialized_nfg_game.to_nfg() assert serialized_nfg_game == double_serialized_nfg_game @@ -125,7 +132,8 @@ def test_read_write_nfg(): def test_read_write_nfg_normalize(): nfg_game = create_2x2_zero_nfg() serialized_nfg_game = nfg_game.to_nfg() - deserialized_nfg_game = gbt.read_nfg(io.BytesIO(serialized_nfg_game.encode()), - normalize_labels=True) + deserialized_nfg_game = gbt.read_nfg( + io.BytesIO(serialized_nfg_game.encode()), normalize_labels=True + ) double_serialized_nfg_game = deserialized_nfg_game.to_nfg() assert serialized_nfg_game != double_serialized_nfg_game diff --git a/tests/test_mixed.py b/tests/test_mixed.py index be6935634..975e26ee5 100644 --- a/tests/test_mixed.py +++ b/tests/test_mixed.py @@ -76,49 +76,64 @@ def test_normalize_neg_entry_value_error(game, profile_data, rational_flag): [ ############################################################################### # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), [[1, 2, 3, 14], [1, 1, 1, 1]], - [["1/20", "2/20", "3/20", "14/20"], ["1/4", "1/4", "1/4", "1/4"]], True), - (games.create_coord_4x4_nfg(), [[1.0, 2.0, 3.0, 14.0], [1, 1, 1, 1]], - [[1 / 20, 2 / 20, 3 / 20, 14 / 20], [0.25, 0.25, 0.25, 0.25]], False), + ( + games.create_coord_4x4_nfg(), + [[1, 2, 3, 14], [1, 1, 1, 1]], + [["1/20", "2/20", "3/20", "14/20"], ["1/4", "1/4", "1/4", "1/4"]], + True, + ), + ( + games.create_coord_4x4_nfg(), + [[1.0, 2.0, 3.0, 14.0], [1, 1, 1, 1]], + [[1 / 20, 2 / 20, 3 / 20, 14 / 20], [0.25, 0.25, 0.25, 0.25]], + False, + ), ############################################################################### # centipede with chance efg - (games.create_centipede_game_with_chance_efg(), [[1, 2, 3, 14], [1, 1, 1, 1]], - [["1/20", "2/20", "3/20", "14/20"], ["1/4", "1/4", "1/4", "1/4"]], True), - (games.create_centipede_game_with_chance_efg(), [[1.0, 2.0, 3.0, 14.0], [1, 1, 1, 1]], - [[1 / 20, 2 / 20, 3 / 20, 14 / 20], [0.25, 0.25, 0.25, 0.25]], False), + ( + games.create_centipede_game_with_chance_efg(), + [[1, 2, 3, 14], [1, 1, 1, 1]], + [["1/20", "2/20", "3/20", "14/20"], ["1/4", "1/4", "1/4", "1/4"]], + True, + ), + ( + games.create_centipede_game_with_chance_efg(), + [[1.0, 2.0, 3.0, 14.0], [1, 1, 1, 1]], + [[1 / 20, 2 / 20, 3 / 20, 14 / 20], [0.25, 0.25, 0.25, 0.25]], + False, + ), ], ) def test_normalize(game, profile_data, expected_data, rational_flag): - assert ( - game.mixed_strategy_profile(data=profile_data, rational=rational_flag).normalize() == - game.mixed_strategy_profile(data=expected_data, rational=rational_flag) - ) + assert game.mixed_strategy_profile( + data=profile_data, rational=rational_flag + ).normalize() == game.mixed_strategy_profile(data=expected_data, rational=rational_flag) @pytest.mark.parametrize( "game,strategy_label,rational_flag,prob", [ - ############################################################################## - # zero matrix nfg - (games.create_2x2_zero_nfg(), "cooperate", False, 0.72), - (games.create_2x2_zero_nfg(), "cooperate", True, "7/9"), - ############################################################################### - # coordination 4x4 nfg outcome version with strategy labels - (games.create_coord_4x4_nfg(outcome_version=True), "1-1", 0.25, False), - (games.create_coord_4x4_nfg(outcome_version=True), "1-1", "1/4", True), - ############################################################################### - # stripped-down poker efg - (games.create_stripped_down_poker_efg(), "11", 0.25, False), - (games.create_stripped_down_poker_efg(), "12", 0.15, False), - (games.create_stripped_down_poker_efg(), "21", 0.99, False), - (games.create_stripped_down_poker_efg(), "11", "1/4", True), - (games.create_stripped_down_poker_efg(), "12", "3/4", True), - (games.create_stripped_down_poker_efg(), "21", "7/9", True), + ############################################################################## + # zero matrix nfg + (games.create_2x2_zero_nfg(), "cooperate", False, 0.72), + (games.create_2x2_zero_nfg(), "cooperate", True, "7/9"), + ############################################################################### + # coordination 4x4 nfg outcome version with strategy labels + (games.create_coord_4x4_nfg(outcome_version=True), "1-1", 0.25, False), + (games.create_coord_4x4_nfg(outcome_version=True), "1-1", "1/4", True), + ############################################################################### + # stripped-down poker efg + (games.create_stripped_down_poker_efg(), "11", 0.25, False), + (games.create_stripped_down_poker_efg(), "12", 0.15, False), + (games.create_stripped_down_poker_efg(), "21", 0.99, False), + (games.create_stripped_down_poker_efg(), "11", "1/4", True), + (games.create_stripped_down_poker_efg(), "12", "3/4", True), + (games.create_stripped_down_poker_efg(), "21", "7/9", True), ], ) -def test_set_and_get_probability_by_strategy_label(game: gbt.Game, strategy_label: str, - rational_flag: bool, - prob: float | str): +def test_set_and_get_probability_by_strategy_label( + game: gbt.Game, strategy_label: str, rational_flag: bool, prob: float | str +): profile = game.mixed_strategy_profile(rational=rational_flag) prob = gbt.Rational(prob) if rational_flag else prob profile[strategy_label] = prob @@ -128,24 +143,25 @@ def test_set_and_get_probability_by_strategy_label(game: gbt.Game, strategy_labe @pytest.mark.parametrize( "game,player_label,rational_flag,profile_data", [ - ############################################################################## - # zero matrix nfg - (games.create_2x2_zero_nfg(), "Joe", False, [0.72, 0.28]), - (games.create_2x2_zero_nfg(), "Joe", True, ["7/9", "2/9"]), - ############################################################################## - # coordination 4x4 nfg outcome version with strategy labels - (games.create_coord_4x4_nfg(), P1, False, [0.25, 0, 0, 0.75]), - (games.create_coord_4x4_nfg(), P1, True, ["1/4", 0, 0, "3/4"]), - ############################################################################## - # stripped-down poker efg - (games.create_stripped_down_poker_efg(), "Alice", False, [0.25, 0.75, 0, 0]), - (games.create_stripped_down_poker_efg(), "Bob", False, [1, 0]), - (games.create_stripped_down_poker_efg(), "Alice", True, ["1/4", "3/4", 0, 0]), - (games.create_stripped_down_poker_efg(), "Bob", True, [1, 0]), + ############################################################################## + # zero matrix nfg + (games.create_2x2_zero_nfg(), "Joe", False, [0.72, 0.28]), + (games.create_2x2_zero_nfg(), "Joe", True, ["7/9", "2/9"]), + ############################################################################## + # coordination 4x4 nfg outcome version with strategy labels + (games.create_coord_4x4_nfg(), P1, False, [0.25, 0, 0, 0.75]), + (games.create_coord_4x4_nfg(), P1, True, ["1/4", 0, 0, "3/4"]), + ############################################################################## + # stripped-down poker efg + (games.create_stripped_down_poker_efg(), "Alice", False, [0.25, 0.75, 0, 0]), + (games.create_stripped_down_poker_efg(), "Bob", False, [1, 0]), + (games.create_stripped_down_poker_efg(), "Alice", True, ["1/4", "3/4", 0, 0]), + (games.create_stripped_down_poker_efg(), "Bob", True, [1, 0]), ], ) -def test_set_and_get_probabilities_by_player_label(game: gbt.Game, player_label: str, - rational_flag: bool, profile_data: list): +def test_set_and_get_probabilities_by_player_label( + game: gbt.Game, player_label: str, rational_flag: bool, profile_data: list +): profile_data = [gbt.Rational(p) for p in profile_data] if rational_flag else profile_data profile = game.mixed_strategy_profile(rational=rational_flag) profile[player_label] = profile_data @@ -155,32 +171,31 @@ def test_set_and_get_probabilities_by_player_label(game: gbt.Game, player_label: @pytest.mark.parametrize( "game,player_label,strategy_label,prob,rational_flag", [ - ############################################################################## - # stripped-down poker efg - # Player 1 - (games.create_stripped_down_poker_efg(), "Alice", "11", 0.25, False), - (games.create_stripped_down_poker_efg(), "Alice", "12", 0.25, False), - (games.create_stripped_down_poker_efg(), "Alice", "21", 0.25, False), - (games.create_stripped_down_poker_efg(), "Alice", "22", 0.25, False), - (games.create_stripped_down_poker_efg(), "Alice", "11", "1/4", True), - (games.create_stripped_down_poker_efg(), "Alice", "12", "1/4", True), - (games.create_stripped_down_poker_efg(), "Alice", "21", "1/4", True), - (games.create_stripped_down_poker_efg(), "Alice", "22", "1/4", True), - # Player 2 - (games.create_stripped_down_poker_efg(), "Bob", "1", 0.5, False), - (games.create_stripped_down_poker_efg(), "Bob", "2", 0.5, False), - (games.create_stripped_down_poker_efg(), "Bob", "1", "1/2", True), - (games.create_stripped_down_poker_efg(), "Bob", "2", "1/2", True), - ############################################################################## - # coordination 4x4 nfg outcome version with strategy labels - (games.create_coord_4x4_nfg(outcome_version=True), P1, "1-1", "1/4", True), - (games.create_coord_4x4_nfg(outcome_version=True), P2, "2-1", "1/4", True), - ] + ############################################################################## + # stripped-down poker efg + # Player 1 + (games.create_stripped_down_poker_efg(), "Alice", "11", 0.25, False), + (games.create_stripped_down_poker_efg(), "Alice", "12", 0.25, False), + (games.create_stripped_down_poker_efg(), "Alice", "21", 0.25, False), + (games.create_stripped_down_poker_efg(), "Alice", "22", 0.25, False), + (games.create_stripped_down_poker_efg(), "Alice", "11", "1/4", True), + (games.create_stripped_down_poker_efg(), "Alice", "12", "1/4", True), + (games.create_stripped_down_poker_efg(), "Alice", "21", "1/4", True), + (games.create_stripped_down_poker_efg(), "Alice", "22", "1/4", True), + # Player 2 + (games.create_stripped_down_poker_efg(), "Bob", "1", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob", "2", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob", "1", "1/2", True), + (games.create_stripped_down_poker_efg(), "Bob", "2", "1/2", True), + ############################################################################## + # coordination 4x4 nfg outcome version with strategy labels + (games.create_coord_4x4_nfg(outcome_version=True), P1, "1-1", "1/4", True), + (games.create_coord_4x4_nfg(outcome_version=True), P2, "2-1", "1/4", True), + ], ) -def test_profile_indexing_by_player_and_strategy_label_reference(game: gbt.Game, player_label: str, - strategy_label: str, - prob: str | float, - rational_flag: bool): +def test_profile_indexing_by_player_and_strategy_label_reference( + game: gbt.Game, player_label: str, strategy_label: str, prob: str | float, rational_flag: bool +): profile = game.mixed_strategy_profile(rational=rational_flag) prob = gbt.Rational(prob) if rational_flag else prob assert profile[player_label][strategy_label] == prob @@ -189,24 +204,23 @@ def test_profile_indexing_by_player_and_strategy_label_reference(game: gbt.Game, @pytest.mark.parametrize( "game,player_label,strategy_label,rational_flag", [ - ############################################################################## - # stripped-down poker efg - (games.create_stripped_down_poker_efg(), "Bob", "11", True), - (games.create_stripped_down_poker_efg(), "Bob", "11", False), - (games.create_stripped_down_poker_efg(), "Alice", "1", True), - (games.create_stripped_down_poker_efg(), "Alice", "1", False), - (games.create_stripped_down_poker_efg(), "Alice", "2", True), - (games.create_stripped_down_poker_efg(), "Alice", "2", False), - ############################################################################## - # coordination 4x4 nfg outcome version with strategy labels - (games.create_coord_4x4_nfg(outcome_version=True), P1, "2-1", True), - (games.create_coord_4x4_nfg(outcome_version=True), P2, "1-1", True), - ] + ############################################################################## + # stripped-down poker efg + (games.create_stripped_down_poker_efg(), "Bob", "11", True), + (games.create_stripped_down_poker_efg(), "Bob", "11", False), + (games.create_stripped_down_poker_efg(), "Alice", "1", True), + (games.create_stripped_down_poker_efg(), "Alice", "1", False), + (games.create_stripped_down_poker_efg(), "Alice", "2", True), + (games.create_stripped_down_poker_efg(), "Alice", "2", False), + ############################################################################## + # coordination 4x4 nfg outcome version with strategy labels + (games.create_coord_4x4_nfg(outcome_version=True), P1, "2-1", True), + (games.create_coord_4x4_nfg(outcome_version=True), P2, "1-1", True), + ], ) -def test_profile_indexing_by_player_and_invalid_strategy_label(game: gbt.Game, - player_label: str, - strategy_label: str, - rational_flag: bool): +def test_profile_indexing_by_player_and_invalid_strategy_label( + game: gbt.Game, player_label: str, strategy_label: str, rational_flag: bool +): """Test that we get a KeyError and that "player" appears in the error message""" with pytest.raises(KeyError, match="for player"): game.mixed_strategy_profile(rational=rational_flag)[player_label][strategy_label] @@ -215,22 +229,25 @@ def test_profile_indexing_by_player_and_invalid_strategy_label(game: gbt.Game, @pytest.mark.parametrize( "game,strategy_label,rational_flag,error,message", [ - ############################################################################## - # stripped-down poker efg - (games.create_stripped_down_poker_efg(), "13", True, KeyError, "player or strategy"), - ############################################################################## - # coordination 4x4 nfg payoff version (default strategy labels created with duplicates) - (games.create_coord_4x4_nfg(), "1", True, ValueError, "multiple strategies"), - (games.create_coord_4x4_nfg(), "2", True, ValueError, "multiple strategies"), - (games.create_coord_4x4_nfg(), "3", True, ValueError, "multiple strategies"), - (games.create_coord_4x4_nfg(), "4", True, ValueError, "multiple strategies"), - (games.create_coord_4x4_nfg(), "5", True, KeyError, "player or strategy"), - ] + ############################################################################## + # stripped-down poker efg + (games.create_stripped_down_poker_efg(), "13", True, KeyError, "player or strategy"), + ############################################################################## + # coordination 4x4 nfg payoff version (default strategy labels created with duplicates) + (games.create_coord_4x4_nfg(), "1", True, ValueError, "multiple strategies"), + (games.create_coord_4x4_nfg(), "2", True, ValueError, "multiple strategies"), + (games.create_coord_4x4_nfg(), "3", True, ValueError, "multiple strategies"), + (games.create_coord_4x4_nfg(), "4", True, ValueError, "multiple strategies"), + (games.create_coord_4x4_nfg(), "5", True, KeyError, "player or strategy"), + ], ) -def test_profile_indexing_by_invalid_strategy_label(game: gbt.Game, strategy_label: str, - rational_flag: bool, - error: ValueError | KeyError, - message: str | None): +def test_profile_indexing_by_invalid_strategy_label( + game: gbt.Game, + strategy_label: str, + rational_flag: bool, + error: ValueError | KeyError, + message: str | None, +): """Check that we get a ValueError for an ambigious strategy label and a KeyError for one that is neither a player or strategy label in the game """ @@ -248,35 +265,35 @@ def test_profile_indexing_by_player_and_duplicate_strategy_label(): @pytest.mark.parametrize( "game,strategy_label,prob,rational_flag", [ - ########################################################################### - # stripped-down poker efg - # Player 1 - (games.create_stripped_down_poker_efg(), "11", 0.25, False), - (games.create_stripped_down_poker_efg(), "12", 0.25, False), - (games.create_stripped_down_poker_efg(), "21", 0.25, False), - (games.create_stripped_down_poker_efg(), "22", 0.25, False), - (games.create_stripped_down_poker_efg(), "11", "1/4", True), - (games.create_stripped_down_poker_efg(), "12", "1/4", True), - (games.create_stripped_down_poker_efg(), "21", "1/4", True), - (games.create_stripped_down_poker_efg(), "22", "1/4", True), - # Player 2 - (games.create_stripped_down_poker_efg(), "1", 0.5, False), - (games.create_stripped_down_poker_efg(), "2", 0.5, False), - (games.create_stripped_down_poker_efg(), "1", "1/2", True), - (games.create_stripped_down_poker_efg(), "2", "1/2", True), - ############################################################################ - # coordination 4x4 nfg outcome version with strategy labels - # Player 1 - (games.create_coord_4x4_nfg(outcome_version=True), "1-1", "1/4", True), - (games.create_coord_4x4_nfg(outcome_version=True), "1-1", 0.25, False), - # Player 2 - (games.create_coord_4x4_nfg(outcome_version=True), "2-1", "1/4", True), - (games.create_coord_4x4_nfg(outcome_version=True), "2-1", 0.25, False), - ] + ########################################################################### + # stripped-down poker efg + # Player 1 + (games.create_stripped_down_poker_efg(), "11", 0.25, False), + (games.create_stripped_down_poker_efg(), "12", 0.25, False), + (games.create_stripped_down_poker_efg(), "21", 0.25, False), + (games.create_stripped_down_poker_efg(), "22", 0.25, False), + (games.create_stripped_down_poker_efg(), "11", "1/4", True), + (games.create_stripped_down_poker_efg(), "12", "1/4", True), + (games.create_stripped_down_poker_efg(), "21", "1/4", True), + (games.create_stripped_down_poker_efg(), "22", "1/4", True), + # Player 2 + (games.create_stripped_down_poker_efg(), "1", 0.5, False), + (games.create_stripped_down_poker_efg(), "2", 0.5, False), + (games.create_stripped_down_poker_efg(), "1", "1/2", True), + (games.create_stripped_down_poker_efg(), "2", "1/2", True), + ############################################################################ + # coordination 4x4 nfg outcome version with strategy labels + # Player 1 + (games.create_coord_4x4_nfg(outcome_version=True), "1-1", "1/4", True), + (games.create_coord_4x4_nfg(outcome_version=True), "1-1", 0.25, False), + # Player 2 + (games.create_coord_4x4_nfg(outcome_version=True), "2-1", "1/4", True), + (games.create_coord_4x4_nfg(outcome_version=True), "2-1", 0.25, False), + ], ) -def test_profile_indexing_by_strategy_label_reference(game: gbt.Game, strategy_label: str, - prob: str | float, - rational_flag: bool): +def test_profile_indexing_by_strategy_label_reference( + game: gbt.Game, strategy_label: str, prob: str | float, rational_flag: bool +): profile = game.mixed_strategy_profile(rational=rational_flag) prob = gbt.Rational(prob) if rational_flag else prob assert profile[strategy_label] == prob @@ -285,30 +302,31 @@ def test_profile_indexing_by_strategy_label_reference(game: gbt.Game, strategy_l @pytest.mark.parametrize( "game,player_label,strategy_data,rational_flag", [ - ############################################################################ - # mixed behav efg - (games.create_mixed_behav_game_efg(), P1, [0.5, 0.5], False), - (games.create_mixed_behav_game_efg(), P2, [0.5, 0.5], False), - (games.create_mixed_behav_game_efg(), P3, [0.5, 0.5], False), - (games.create_mixed_behav_game_efg(), P1, ["1/2", "1/2"], True), - (games.create_mixed_behav_game_efg(), P2, ["1/2", "1/2"], True), - (games.create_mixed_behav_game_efg(), P3, ["1/2", "1/2"], True), - ############################################################################ - # stripped-down poker efg - (games.create_stripped_down_poker_efg(), "Alice", [0.25, 0.25, 0.25, 0.25], False), - (games.create_stripped_down_poker_efg(), "Bob", [0.5, 0.5], False), - (games.create_stripped_down_poker_efg(), "Alice", ["1/4", "1/4", "1/4", "1/4"], True), - (games.create_stripped_down_poker_efg(), "Bob", ["1/2", "1/2"], True), - ############################################################################ - # coordination 4x4 nfg - (games.create_coord_4x4_nfg(), P1, [0.25, 0.25, 0.25, 0.25], False), - (games.create_coord_4x4_nfg(), P2, [0.25, 0.25, 0.25, 0.25], False), - (games.create_coord_4x4_nfg(), P1, ["1/4", "1/4", "1/4", "1/4"], True), - (games.create_coord_4x4_nfg(), P2, ["1/4", "1/4", "1/4", "1/4"], True), - ] + ############################################################################ + # mixed behav efg + (games.create_mixed_behav_game_efg(), P1, [0.5, 0.5], False), + (games.create_mixed_behav_game_efg(), P2, [0.5, 0.5], False), + (games.create_mixed_behav_game_efg(), P3, [0.5, 0.5], False), + (games.create_mixed_behav_game_efg(), P1, ["1/2", "1/2"], True), + (games.create_mixed_behav_game_efg(), P2, ["1/2", "1/2"], True), + (games.create_mixed_behav_game_efg(), P3, ["1/2", "1/2"], True), + ############################################################################ + # stripped-down poker efg + (games.create_stripped_down_poker_efg(), "Alice", [0.25, 0.25, 0.25, 0.25], False), + (games.create_stripped_down_poker_efg(), "Bob", [0.5, 0.5], False), + (games.create_stripped_down_poker_efg(), "Alice", ["1/4", "1/4", "1/4", "1/4"], True), + (games.create_stripped_down_poker_efg(), "Bob", ["1/2", "1/2"], True), + ############################################################################ + # coordination 4x4 nfg + (games.create_coord_4x4_nfg(), P1, [0.25, 0.25, 0.25, 0.25], False), + (games.create_coord_4x4_nfg(), P2, [0.25, 0.25, 0.25, 0.25], False), + (games.create_coord_4x4_nfg(), P1, ["1/4", "1/4", "1/4", "1/4"], True), + (games.create_coord_4x4_nfg(), P2, ["1/4", "1/4", "1/4", "1/4"], True), + ], ) -def test_profile_indexing_by_player_label_reference(game: gbt.Game, player_label: str, - strategy_data: list, rational_flag: bool): +def test_profile_indexing_by_player_label_reference( + game: gbt.Game, player_label: str, strategy_data: list, rational_flag: bool +): profile = game.mixed_strategy_profile(rational=rational_flag) if rational_flag: strategy_data = [gbt.Rational(prob) for prob in strategy_data] @@ -318,55 +336,62 @@ def test_profile_indexing_by_player_label_reference(game: gbt.Game, player_label @pytest.mark.parametrize( "game,rational_flag,profile_data,label,payoff", [ - ######################################################################### - # zero matrix nfg - (games.create_2x2_zero_nfg(), False, None, "Joe", 0), - (games.create_2x2_zero_nfg(), True, None, "Joe", 0), - ######################################################################### - # coordination 4x4 nfg - (games.create_coord_4x4_nfg(), False, None, P1, 0.25), - (games.create_coord_4x4_nfg(), True, None, P1, "1/4"), - (games.create_coord_4x4_nfg(), False, None, P2, 0.25), - (games.create_coord_4x4_nfg(), True, None, P2, "1/4"), - (games.create_coord_4x4_nfg(), False, [[1, 0, 0, 0], [1, 0, 0, 0]], P1, 1), - (games.create_coord_4x4_nfg(), True, [[1, 0, 0, 0], [1, 0, 0, 0]], P1, 1), - (games.create_coord_4x4_nfg(), False, [[1, 0, 0, 0], [1, 0, 0, 0]], P2, 1), - (games.create_coord_4x4_nfg(), True, [[1, 0, 0, 0], [1, 0, 0, 0]], P2, 1), - (games.create_coord_4x4_nfg(), False, [[1, 0, 0, 0], [0, 1, 0, 0]], P1, 0), - (games.create_coord_4x4_nfg(), True, [[1, 0, 0, 0], [0, 1, 0, 0]], P1, 0), - (games.create_coord_4x4_nfg(), False, [[1, 0, 0, 0], [0, 1, 0, 0]], P2, 0), - (games.create_coord_4x4_nfg(), True, [[1, 0, 0, 0], [0, 1, 0, 0]], P2, 0), - ######################################################################### - # stripped-down poker efg - (games.create_stripped_down_poker_efg(), False, None, "Alice", -0.25), - (games.create_stripped_down_poker_efg(), False, None, "Bob", 0.25), - (games.create_stripped_down_poker_efg(), True, None, "Alice", "-1/4"), - (games.create_stripped_down_poker_efg(), True, None, "Bob", "1/4"), - # Bet/Call - (games.create_stripped_down_poker_efg(), False, [[1, 0, 0, 0], [1, 0]], "Alice", 0), - (games.create_stripped_down_poker_efg(), False, [[1, 0, 0, 0], [1, 0]], "Bob", 0), - (games.create_stripped_down_poker_efg(), True, [[1, 0, 0, 0], [1, 0]], "Alice", 0), - (games.create_stripped_down_poker_efg(), True, [[1, 0, 0, 0], [1, 0]], "Bob", 0), - # Fold/Fold for player 1 (player 2's strategy is payoff-irrelevant) - (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [1, 0]], "Alice", -1), - (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [1, 0]], "Bob", 1), - (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], [1, 0]], "Alice", -1), - (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], [1, 0]], "Bob", 1), - (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [0.5, 0.5]], "Alice", -1), - (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [0.5, 0.5]], "Bob", 1), - (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], ["1/2", "1/2"]], "Alice", -1), - (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], ["1/2", "1/2"]], "Bob", 1), - ######################################################################### - (games.create_mixed_behav_game_efg(), False, None, P1, 3.0), - (games.create_mixed_behav_game_efg(), False, None, P2, 3.0), - (games.create_mixed_behav_game_efg(), False, None, P3, 3.25), - (games.create_mixed_behav_game_efg(), True, None, P1, 3), - (games.create_mixed_behav_game_efg(), True, None, P2, 3), - (games.create_mixed_behav_game_efg(), True, None, P3, "13/4"), - ] + ######################################################################### + # zero matrix nfg + (games.create_2x2_zero_nfg(), False, None, "Joe", 0), + (games.create_2x2_zero_nfg(), True, None, "Joe", 0), + ######################################################################### + # coordination 4x4 nfg + (games.create_coord_4x4_nfg(), False, None, P1, 0.25), + (games.create_coord_4x4_nfg(), True, None, P1, "1/4"), + (games.create_coord_4x4_nfg(), False, None, P2, 0.25), + (games.create_coord_4x4_nfg(), True, None, P2, "1/4"), + (games.create_coord_4x4_nfg(), False, [[1, 0, 0, 0], [1, 0, 0, 0]], P1, 1), + (games.create_coord_4x4_nfg(), True, [[1, 0, 0, 0], [1, 0, 0, 0]], P1, 1), + (games.create_coord_4x4_nfg(), False, [[1, 0, 0, 0], [1, 0, 0, 0]], P2, 1), + (games.create_coord_4x4_nfg(), True, [[1, 0, 0, 0], [1, 0, 0, 0]], P2, 1), + (games.create_coord_4x4_nfg(), False, [[1, 0, 0, 0], [0, 1, 0, 0]], P1, 0), + (games.create_coord_4x4_nfg(), True, [[1, 0, 0, 0], [0, 1, 0, 0]], P1, 0), + (games.create_coord_4x4_nfg(), False, [[1, 0, 0, 0], [0, 1, 0, 0]], P2, 0), + (games.create_coord_4x4_nfg(), True, [[1, 0, 0, 0], [0, 1, 0, 0]], P2, 0), + ######################################################################### + # stripped-down poker efg + (games.create_stripped_down_poker_efg(), False, None, "Alice", -0.25), + (games.create_stripped_down_poker_efg(), False, None, "Bob", 0.25), + (games.create_stripped_down_poker_efg(), True, None, "Alice", "-1/4"), + (games.create_stripped_down_poker_efg(), True, None, "Bob", "1/4"), + # Bet/Call + (games.create_stripped_down_poker_efg(), False, [[1, 0, 0, 0], [1, 0]], "Alice", 0), + (games.create_stripped_down_poker_efg(), False, [[1, 0, 0, 0], [1, 0]], "Bob", 0), + (games.create_stripped_down_poker_efg(), True, [[1, 0, 0, 0], [1, 0]], "Alice", 0), + (games.create_stripped_down_poker_efg(), True, [[1, 0, 0, 0], [1, 0]], "Bob", 0), + # Fold/Fold for player 1 (player 2's strategy is payoff-irrelevant) + (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [1, 0]], "Alice", -1), + (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [1, 0]], "Bob", 1), + (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], [1, 0]], "Alice", -1), + (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], [1, 0]], "Bob", 1), + (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [0.5, 0.5]], "Alice", -1), + (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [0.5, 0.5]], "Bob", 1), + ( + games.create_stripped_down_poker_efg(), + True, + [[0, 0, 0, 1], ["1/2", "1/2"]], + "Alice", + -1, + ), + (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], ["1/2", "1/2"]], "Bob", 1), + ######################################################################### + (games.create_mixed_behav_game_efg(), False, None, P1, 3.0), + (games.create_mixed_behav_game_efg(), False, None, P2, 3.0), + (games.create_mixed_behav_game_efg(), False, None, P3, 3.25), + (games.create_mixed_behav_game_efg(), True, None, P1, 3), + (games.create_mixed_behav_game_efg(), True, None, P2, 3), + (games.create_mixed_behav_game_efg(), True, None, P3, "13/4"), + ], ) -def test_payoff_by_label_reference(game: gbt.Game, rational_flag: bool, profile_data: list, - label: str, payoff: float | str): +def test_payoff_by_label_reference( + game: gbt.Game, rational_flag: bool, profile_data: list, label: str, payoff: float | str +): payoff = gbt.Rational(payoff) if rational_flag else payoff profile = game.mixed_strategy_profile(rational=rational_flag, data=profile_data) assert profile.payoff(label) == payoff @@ -375,28 +400,29 @@ def test_payoff_by_label_reference(game: gbt.Game, rational_flag: bool, profile_ @pytest.mark.parametrize( "game,rational_flag,label,value", [ - ############################################################################## - # zero matrix nfg - (games.create_2x2_zero_nfg(), False, "cooperate", 0), - (games.create_2x2_zero_nfg(), True, "cooperate", 0), - ############################################################################## - # coordination 4x4 nfg - (games.create_coord_4x4_nfg(outcome_version=True), False, "1-1", 0.25), - (games.create_coord_4x4_nfg(outcome_version=True), True, "1-1", "1/4"), - ############################################################################## - # stripped-down poker efg - (games.create_stripped_down_poker_efg(), False, "11", 0.5), # Bet/Bet - (games.create_stripped_down_poker_efg(), False, "12", 0.25), # Bet King/Fold Queen - (games.create_stripped_down_poker_efg(), False, "21", -0.75), # Fold King/Bet Queen - (games.create_stripped_down_poker_efg(), False, "22", -1), # Fold/Fold - (games.create_stripped_down_poker_efg(), True, "11", "1/2"), - (games.create_stripped_down_poker_efg(), True, "12", "1/4"), - (games.create_stripped_down_poker_efg(), True, "21", "-3/4"), - (games.create_stripped_down_poker_efg(), True, "22", -1), - ] + ############################################################################## + # zero matrix nfg + (games.create_2x2_zero_nfg(), False, "cooperate", 0), + (games.create_2x2_zero_nfg(), True, "cooperate", 0), + ############################################################################## + # coordination 4x4 nfg + (games.create_coord_4x4_nfg(outcome_version=True), False, "1-1", 0.25), + (games.create_coord_4x4_nfg(outcome_version=True), True, "1-1", "1/4"), + ############################################################################## + # stripped-down poker efg + (games.create_stripped_down_poker_efg(), False, "11", 0.5), # Bet/Bet + (games.create_stripped_down_poker_efg(), False, "12", 0.25), # Bet King/Fold Queen + (games.create_stripped_down_poker_efg(), False, "21", -0.75), # Fold King/Bet Queen + (games.create_stripped_down_poker_efg(), False, "22", -1), # Fold/Fold + (games.create_stripped_down_poker_efg(), True, "11", "1/2"), + (games.create_stripped_down_poker_efg(), True, "12", "1/4"), + (games.create_stripped_down_poker_efg(), True, "21", "-3/4"), + (games.create_stripped_down_poker_efg(), True, "22", -1), + ], ) -def test_strategy_value_by_label_reference(game: gbt.Game, rational_flag: bool, label: str, - value: float | str): +def test_strategy_value_by_label_reference( + game: gbt.Game, rational_flag: bool, label: str, value: float | str +): value = gbt.Rational(value) if rational_flag else value assert game.mixed_strategy_profile(rational=rational_flag).strategy_value(label) == value @@ -408,13 +434,12 @@ def test_strategy_value_by_label_reference(game: gbt.Game, rational_flag: bool, (games.create_mixed_behav_game_efg(), True), (games.create_centipede_game_with_chance_efg(), False), (games.create_centipede_game_with_chance_efg(), True), - ] + ], ) def test_as_behavior_roundtrip(game: gbt.Game, rational_flag: bool): - assert ( - game.mixed_strategy_profile(rational=rational_flag).as_behavior().as_strategy() == - game.mixed_strategy_profile(rational=rational_flag) - ) + assert game.mixed_strategy_profile( + rational=rational_flag + ).as_behavior().as_strategy() == game.mixed_strategy_profile(rational=rational_flag) @pytest.mark.parametrize( @@ -424,7 +449,7 @@ def test_as_behavior_roundtrip(game: gbt.Game, rational_flag: bool): (games.create_2x2_zero_nfg(), True), (games.create_coord_4x4_nfg(), False), (games.create_coord_4x4_nfg(), True), - ] + ], ) def test_as_behavior_error(game: gbt.Game, rational_flag: bool): with pytest.raises(gbt.UndefinedOperationError): @@ -434,27 +459,56 @@ def test_as_behavior_error(game: gbt.Game, rational_flag: bool): @pytest.mark.parametrize( "game,profile_data,rational_flag,payoffs", [ - ############################################################################### - # zero matrix nfg - (games.create_2x2_zero_nfg(), None, True, (0, 0)), - ############################################################################### - # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), None, False, (0.25, 0.25)), - (games.create_coord_4x4_nfg(), None, True, ("1/4", "1/4")), - (games.create_coord_4x4_nfg(), - [["1/3", "1/3", "1/3", 0], ["1/3", "1/3", "1/3", 0]], True, ("1/3", "1/3")), - (games.create_coord_4x4_nfg(), - [["1/3", "1/3", 0, "1/3"], ["1/3", "1/3", "1/3", 0]], True, ("2/9", "2/9")), - ############################################################################### - # 2x2x2 nfg - (games.create_2x2x2_nfg(), None, True, ("4/8", "16/8", "4/8")), - (games.create_2x2x2_nfg(), [[1, 0], [1, 0], [1, 0]], True, (0, 0, 0)), - (games.create_2x2x2_nfg(), [[0, 1], [1, 0], [1, 0]], True, (1, 2, -1)), - (games.create_2x2x2_nfg(), [["1/2", "1/2"], [1, 0], [1, 0]], True, ("1/2", 1, "-1/2")), + ############################################################################### + # zero matrix nfg + (games.create_2x2_zero_nfg(), None, True, (0, 0)), + ############################################################################### + # 4x4 coordination nfg + (games.create_coord_4x4_nfg(), None, False, (0.25, 0.25)), + (games.create_coord_4x4_nfg(), None, True, ("1/4", "1/4")), + ( + games.create_coord_4x4_nfg(), + [["1/3", "1/3", "1/3", 0], ["1/3", "1/3", "1/3", 0]], + True, + ("1/3", "1/3"), + ), + ( + games.create_coord_4x4_nfg(), + [["1/3", "1/3", 0, "1/3"], ["1/3", "1/3", "1/3", 0]], + True, + ("2/9", "2/9"), + ), + ############################################################################### + # 2x2x2 nfg + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + None, + True, + ("4/8", "16/8", "4/8"), + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [1, 0], [1, 0]], + True, + (0, 0, 0), + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[0, 1], [1, 0], [1, 0]], + True, + (1, 2, -1), + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [["1/2", "1/2"], [1, 0], [1, 0]], + True, + ("1/2", 1, "-1/2"), + ), ], ) -def test_payoffs_reference(game: gbt.Game, profile_data: list, rational_flag: bool, - payoffs: tuple): +def test_payoffs_reference( + game: gbt.Game, profile_data: list, rational_flag: bool, payoffs: tuple +): profile = game.mixed_strategy_profile(rational=rational_flag, data=profile_data) for payoff, player in zip(payoffs, profile.game.players, strict=True): payoff = gbt.Rational(payoff) if rational_flag else payoff @@ -464,35 +518,61 @@ def test_payoffs_reference(game: gbt.Game, profile_data: list, rational_flag: bo @pytest.mark.parametrize( "game,profile_data,rational_flag,strategy_values", [ - ############################################################################### - # zero matrix nfg - (games.create_2x2_zero_nfg(), None, False, ([0, 0], [0, 0])), - (games.create_2x2_zero_nfg(), None, True, ([0, 0], [0, 0])), - ############################################################################### - # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), None, False, - ([0.25, 0.25, 0.25, 0.25], [0.25, 0.25, 0.25, 0.25])), - (games.create_coord_4x4_nfg(), None, True, - ([0.25, 0.25, 0.25, 0.25], [0.25, 0.25, 0.25, 0.25])), - (games.create_coord_4x4_nfg(), [["1", "0", "0", "0"], ["1", "0", "0", "0"]], - True, (["1", "0", "0", "0"], ["1", "0", "0", "0"])), - (games.create_coord_4x4_nfg(), [["3/7", "0", "0", "4/7"], ["1/3", "1/3", "1/3", "0"]], - True, (["1/3", "1/3", "1/3", "0"], ["3/7", "0", "0", "4/7"])), - ############################################################################### - # 2x2x2 nfg - (games.create_2x2x2_nfg(), None, True, (["1/2", "1/2"], [2, 2], ["1/2", "1/2"])), - (games.create_2x2x2_nfg(), [[1, 0], [1, 0], [1, 0]], True, ([0, 1], [0, 4], [0, 1])), - ############################################################################### - # stripped-down poker efg - (games.create_stripped_down_poker_efg(), None, False, [(0.5, 0.25, -0.75, -1), (0.5, 0)]), - ] + ############################################################################### + # zero matrix nfg + (games.create_2x2_zero_nfg(), None, False, ([0, 0], [0, 0])), + (games.create_2x2_zero_nfg(), None, True, ([0, 0], [0, 0])), + ############################################################################### + # 4x4 coordination nfg + ( + games.create_coord_4x4_nfg(), + None, + False, + ([0.25, 0.25, 0.25, 0.25], [0.25, 0.25, 0.25, 0.25]), + ), + ( + games.create_coord_4x4_nfg(), + None, + True, + ([0.25, 0.25, 0.25, 0.25], [0.25, 0.25, 0.25, 0.25]), + ), + ( + games.create_coord_4x4_nfg(), + [["1", "0", "0", "0"], ["1", "0", "0", "0"]], + True, + (["1", "0", "0", "0"], ["1", "0", "0", "0"]), + ), + ( + games.create_coord_4x4_nfg(), + [["3/7", "0", "0", "4/7"], ["1/3", "1/3", "1/3", "0"]], + True, + (["1/3", "1/3", "1/3", "0"], ["3/7", "0", "0", "4/7"]), + ), + ############################################################################### + # 2x2x2 nfg + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + None, + True, + (["1/2", "1/2"], [2, 2], ["1/2", "1/2"]), + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [1, 0], [1, 0]], + True, + ([0, 1], [0, 4], [0, 1]), + ), + ############################################################################### + # stripped-down poker efg + (games.create_stripped_down_poker_efg(), None, False, [(0.5, 0.25, -0.75, -1), (0.5, 0)]), + ], ) -def test_strategy_value_reference(game: gbt.Game, profile_data: list, rational_flag: bool, - strategy_values: list): +def test_strategy_value_reference( + game: gbt.Game, profile_data: list, rational_flag: bool, strategy_values: list +): profile = game.mixed_strategy_profile(rational=rational_flag, data=profile_data) for strategy_values_for_player, player in zip( - strategy_values, profile.game.players, - strict=True + strategy_values, profile.game.players, strict=True ): for i, s in enumerate(player.strategies): sv = strategy_values_for_player[i] @@ -503,55 +583,148 @@ def test_strategy_value_reference(game: gbt.Game, profile_data: list, rational_f @pytest.mark.parametrize( "game,profile_data,liap_exp,tol,rational_flag", [ - ############################################################################## - # Zero matrix nfg, all liap_values are zero - (games.create_2x2_zero_nfg(), [["3/4", "1/4"], ["2/5", "3/5"]], 0, ZERO, True), - (games.create_2x2_zero_nfg(), [["1/2", "1/2"], ["1/2", "1/2"]], 0, ZERO, True), - (games.create_2x2_zero_nfg(), [[1, 0], [1, 0]], 0, ZERO, True), - (games.create_2x2_zero_nfg(), [[1/4, 3/4], [2/5, 3/5]], 0, TOL, False), - ############################################################################## - # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), None, 0, ZERO, True), - (games.create_coord_4x4_nfg(), None, 0, TOL, False), - (games.create_coord_4x4_nfg(), [[1, 0, 0, 0], [1, 0, 0, 0]], 0, ZERO, True), - (games.create_coord_4x4_nfg(), [[1, 0, 0, 0], [1, 0, 0, 0]], 0, TOL, False), - (games.create_coord_4x4_nfg(), - [["1/3", "1/2", "1/12", "1/12"], ["3/8", "1/8", "1/4", "1/4"]], - "245/2304", ZERO, True), - (games.create_coord_4x4_nfg(), [[1/3, 1/2, 1/12, 1/12], [3/8, 1/8, 1/4, 1/4]], - 245/2304, TOL, False), - (games.create_coord_4x4_nfg(), - [["1/3", 0, 0, "2/3"], [1, 0, 0, 0]], "5/9", ZERO, True), - (games.create_coord_4x4_nfg(), - [[1/3, 0, 0, 2/3], [1, 0, 0, 0]], 5/9, TOL, False), - ############################################################################## - # El Farol bar game efg - (games.create_el_farol_bar_game_efg(), - [["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"]], - 0, ZERO, True), - (games.create_el_farol_bar_game_efg(), [[1, 0], [1, 0], [0, 1], [0, 1], [0, 1]], - 0, ZERO, True), - ############################################################################## - # # 2x2x2 nfg with 2 pure and 1 mixed eq - # Pure non-Nash eq: - (games.create_2x2x2_nfg(), [[1, 0], [1, 0], [1, 0]], 18, ZERO, True), # 4^2+1+1 - (games.create_2x2x2_nfg(), [[0, 1], [0, 1], [0, 1]], 18, ZERO, True), # 4^2+1+1 - (games.create_2x2x2_nfg(), [[1, 0], [0, 1], [0, 1]], 9, ZERO, True), # 3^2 - (games.create_2x2x2_nfg(), [[0, 1], [1, 0], [1, 0]], 9, ZERO, True), # 3^2 - (games.create_2x2x2_nfg(), [[1, 0], [0, 1], [0, 1]], 9, ZERO, True), # 3^2 - (games.create_2x2x2_nfg(), [[1, 1], [1, 0], [0, 0]], 9, ZERO, True), # 3^2 - # Non-pure non-Nash eq: - (games.create_2x2x2_nfg(), [["1/2", "1/2"], [1, 0], [1, 0]], "33/4", ZERO, True), - (games.create_2x2x2_nfg(), [[1, 0], ["1/2", "1/2"], [1, 0]], 4, ZERO, True), - (games.create_2x2x2_nfg(), [[1, 0], [1, 0], ["1/2", "1/2"]], "33/4", ZERO, True), - # Nash eq: - (games.create_2x2x2_nfg(), [[1, 0], [0, 1], [1, 0]], 0, ZERO, True), - (games.create_2x2x2_nfg(), [[0, 1], [1, 0], [0, 1]], 0, ZERO, True), - (games.create_2x2x2_nfg(), None, 0, ZERO, True), # uniform is Nash - ] + ############################################################################## + # Zero matrix nfg, all liap_values are zero + (games.create_2x2_zero_nfg(), [["3/4", "1/4"], ["2/5", "3/5"]], 0, ZERO, True), + (games.create_2x2_zero_nfg(), [["1/2", "1/2"], ["1/2", "1/2"]], 0, ZERO, True), + (games.create_2x2_zero_nfg(), [[1, 0], [1, 0]], 0, ZERO, True), + (games.create_2x2_zero_nfg(), [[1 / 4, 3 / 4], [2 / 5, 3 / 5]], 0, TOL, False), + ############################################################################## + # 4x4 coordination nfg + (games.create_coord_4x4_nfg(), None, 0, ZERO, True), + (games.create_coord_4x4_nfg(), None, 0, TOL, False), + (games.create_coord_4x4_nfg(), [[1, 0, 0, 0], [1, 0, 0, 0]], 0, ZERO, True), + (games.create_coord_4x4_nfg(), [[1, 0, 0, 0], [1, 0, 0, 0]], 0, TOL, False), + ( + games.create_coord_4x4_nfg(), + [["1/3", "1/2", "1/12", "1/12"], ["3/8", "1/8", "1/4", "1/4"]], + "245/2304", + ZERO, + True, + ), + ( + games.create_coord_4x4_nfg(), + [[1 / 3, 1 / 2, 1 / 12, 1 / 12], [3 / 8, 1 / 8, 1 / 4, 1 / 4]], + 245 / 2304, + TOL, + False, + ), + (games.create_coord_4x4_nfg(), [["1/3", 0, 0, "2/3"], [1, 0, 0, 0]], "5/9", ZERO, True), + (games.create_coord_4x4_nfg(), [[1 / 3, 0, 0, 2 / 3], [1, 0, 0, 0]], 5 / 9, TOL, False), + ############################################################################## + # El Farol bar game efg + ( + games.create_el_farol_bar_game_efg(), + [["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"]], + 0, + ZERO, + True, + ), + ( + games.create_el_farol_bar_game_efg(), + [[1, 0], [1, 0], [0, 1], [0, 1], [0, 1]], + 0, + ZERO, + True, + ), + ############################################################################## + # # 2x2x2 nfg with 2 pure and 1 mixed eq + # Pure non-Nash eq: + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [1, 0], [1, 0]], + 18, + ZERO, + True, + ), # 4^2+1+1 + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[0, 1], [0, 1], [0, 1]], + 18, + ZERO, + True, + ), # 4^2+1+1 + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [0, 1], [0, 1]], + 9, + ZERO, + True, + ), # 3^2 + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[0, 1], [1, 0], [1, 0]], + 9, + ZERO, + True, + ), # 3^2 + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [0, 1], [0, 1]], + 9, + ZERO, + True, + ), # 3^2 + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 1], [1, 0], [0, 0]], + 9, + ZERO, + True, + ), # 3^2 + # Non-pure non-Nash eq: + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [["1/2", "1/2"], [1, 0], [1, 0]], + "33/4", + ZERO, + True, + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], ["1/2", "1/2"], [1, 0]], + 4, + ZERO, + True, + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [1, 0], ["1/2", "1/2"]], + "33/4", + ZERO, + True, + ), + # Nash eq: + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [0, 1], [1, 0]], + 0, + ZERO, + True, + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[0, 1], [1, 0], [0, 1]], + 0, + ZERO, + True, + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + None, + 0, + ZERO, + True, + ), # uniform is Nash + ], ) -def test_liap_value_reference(game: gbt.Game, profile_data: list, liap_exp: float | str, - tol: float | gbt.Rational | int, rational_flag: bool): +def test_liap_value_reference( + game: gbt.Game, + profile_data: list, + liap_exp: float | str, + tol: float | gbt.Rational | int, + rational_flag: bool, +): profile = game.mixed_strategy_profile(rational=rational_flag, data=profile_data) liap_exp = gbt.Rational(liap_exp) if rational_flag else liap_exp assert abs(profile.liap_value() - liap_exp) <= tol @@ -560,57 +733,160 @@ def test_liap_value_reference(game: gbt.Game, profile_data: list, liap_exp: floa @pytest.mark.parametrize( "game,profile_data,player_regrets_exp,tol,rational_flag", [ - ############################################################################## - # Zero matrix nfg, all liap_values are zero - (games.create_2x2_zero_nfg(), [["3/4", "1/4"], ["2/5", "3/5"]], [0]*2, ZERO, True), - (games.create_2x2_zero_nfg(), [["1/2", "1/2"], ["1/2", "1/2"]], [0]*2, ZERO, True), - (games.create_2x2_zero_nfg(), [[1, 0], [1, 0]], [0]*2, ZERO, True), - (games.create_2x2_zero_nfg(), [[1/4, 3/4], [2/5, 3/5]], [0]*2, TOL, False), - ############################################################################## - # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), None, [0]*2, ZERO, True), - (games.create_coord_4x4_nfg(), None, [0]*2, TOL, False), - (games.create_coord_4x4_nfg(), [[1, 0, 0, 0], [1, 0, 0, 0]], [0]*2, ZERO, True), - (games.create_coord_4x4_nfg(), [[1, 0, 0, 0], [1, 0, 0, 0]], [0]*2, TOL, False), - (games.create_coord_4x4_nfg(), - [["1/3", "1/2", "1/12", "1/12"], ["3/8", "1/8", "1/4", "1/4"]], - ["7/48", "13/48"], ZERO, True), - (games.create_coord_4x4_nfg(), [[1/3, 1/2, 1/12, 1/12], [3/8, 1/8, 1/4, 1/4]], - [7/48, 13/48], TOL, False), - (games.create_coord_4x4_nfg(), - [["1/3", 0, 0, "2/3"], [1, 0, 0, 0]], ["2/3", "1/3"], ZERO, True), - (games.create_coord_4x4_nfg(), - [[1/3, 0, 0, 2/3], [1, 0, 0, 0]], [2/3, 1/3], TOL, False), - ############################################################################## - # El Farol bar game efg - (games.create_el_farol_bar_game_efg(), - [["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"]], - [0]*5, ZERO, True), - (games.create_el_farol_bar_game_efg(), [[1, 0], [1, 0], [0, 1], [0, 1], [0, 1]], - [0]*5, ZERO, True), - ############################################################################## - # 2x2x2 nfg with 2 pure and 1 mixed eq - # Pure non-Nash - (games.create_2x2x2_nfg(), [[1, 0], [1, 0], [1, 0]], [1, 4, 1], ZERO, True), # 111 - (games.create_2x2x2_nfg(), [[0, 1], [0, 1], [0, 1]], [1, 4, 1], ZERO, True), # 000 - (games.create_2x2x2_nfg(), [[1, 0], [0, 1], [0, 1]], [0, 0, 3], ZERO, True), # 100 - (games.create_2x2x2_nfg(), [[0, 1], [1, 0], [1, 0]], [0, 0, 3], ZERO, True), # 011 - (games.create_2x2x2_nfg(), [[0, 1], [0, 1], [1, 0]], [3, 0, 0], ZERO, True), # 001 - (games.create_2x2x2_nfg(), [[1, 0], [1, 0], [0, 1]], [3, 0, 0], ZERO, True), # 110 - # Mixed non-Nash - (games.create_2x2x2_nfg(), [["1/2", "1/2"], [1, 0], [1, 0]], ["1/2", 2, 2], ZERO, True), - (games.create_2x2x2_nfg(), [[1, 0], ["1/2", "1/2"], [1, 0]], [0, 2, 0], ZERO, True), - (games.create_2x2x2_nfg(), [[1, 0], [1, 0], ["1/2", "1/2"]], [2, 2, "1/2"], ZERO, True), - # Nash eq: - (games.create_2x2x2_nfg(), [[1, 0], [0, 1], [1, 0]], [0]*3, ZERO, True), # 101 - (games.create_2x2x2_nfg(), [[0, 1], [1, 0], [0, 1]], [0]*3, ZERO, True), # 010 - (games.create_2x2x2_nfg(), None, [0]*3, ZERO, True), # uniform is Nash - ] + ############################################################################## + # Zero matrix nfg, all liap_values are zero + (games.create_2x2_zero_nfg(), [["3/4", "1/4"], ["2/5", "3/5"]], [0] * 2, ZERO, True), + (games.create_2x2_zero_nfg(), [["1/2", "1/2"], ["1/2", "1/2"]], [0] * 2, ZERO, True), + (games.create_2x2_zero_nfg(), [[1, 0], [1, 0]], [0] * 2, ZERO, True), + (games.create_2x2_zero_nfg(), [[1 / 4, 3 / 4], [2 / 5, 3 / 5]], [0] * 2, TOL, False), + ############################################################################## + # 4x4 coordination nfg + (games.create_coord_4x4_nfg(), None, [0] * 2, ZERO, True), + (games.create_coord_4x4_nfg(), None, [0] * 2, TOL, False), + (games.create_coord_4x4_nfg(), [[1, 0, 0, 0], [1, 0, 0, 0]], [0] * 2, ZERO, True), + (games.create_coord_4x4_nfg(), [[1, 0, 0, 0], [1, 0, 0, 0]], [0] * 2, TOL, False), + ( + games.create_coord_4x4_nfg(), + [["1/3", "1/2", "1/12", "1/12"], ["3/8", "1/8", "1/4", "1/4"]], + ["7/48", "13/48"], + ZERO, + True, + ), + ( + games.create_coord_4x4_nfg(), + [[1 / 3, 1 / 2, 1 / 12, 1 / 12], [3 / 8, 1 / 8, 1 / 4, 1 / 4]], + [7 / 48, 13 / 48], + TOL, + False, + ), + ( + games.create_coord_4x4_nfg(), + [["1/3", 0, 0, "2/3"], [1, 0, 0, 0]], + ["2/3", "1/3"], + ZERO, + True, + ), + ( + games.create_coord_4x4_nfg(), + [[1 / 3, 0, 0, 2 / 3], [1, 0, 0, 0]], + [2 / 3, 1 / 3], + TOL, + False, + ), + ############################################################################## + # El Farol bar game efg + ( + games.create_el_farol_bar_game_efg(), + [["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"]], + [0] * 5, + ZERO, + True, + ), + ( + games.create_el_farol_bar_game_efg(), + [[1, 0], [1, 0], [0, 1], [0, 1], [0, 1]], + [0] * 5, + ZERO, + True, + ), + ############################################################################## + # 2x2x2 nfg with 2 pure and 1 mixed eq + # Pure non-Nash + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [1, 0], [1, 0]], + [1, 4, 1], + ZERO, + True, + ), # 111 + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[0, 1], [0, 1], [0, 1]], + [1, 4, 1], + ZERO, + True, + ), # 000 + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [0, 1], [0, 1]], + [0, 0, 3], + ZERO, + True, + ), # 100 + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[0, 1], [1, 0], [1, 0]], + [0, 0, 3], + ZERO, + True, + ), # 011 + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[0, 1], [0, 1], [1, 0]], + [3, 0, 0], + ZERO, + True, + ), # 001 + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [1, 0], [0, 1]], + [3, 0, 0], + ZERO, + True, + ), # 110 + # Mixed non-Nash + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [["1/2", "1/2"], [1, 0], [1, 0]], + ["1/2", 2, 2], + ZERO, + True, + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], ["1/2", "1/2"], [1, 0]], + [0, 2, 0], + ZERO, + True, + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [1, 0], ["1/2", "1/2"]], + [2, 2, "1/2"], + ZERO, + True, + ), + # Nash eq: + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [0, 1], [1, 0]], + [0] * 3, + ZERO, + True, + ), # 101 + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[0, 1], [1, 0], [0, 1]], + [0] * 3, + ZERO, + True, + ), # 010 + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + None, + [0] * 3, + ZERO, + True, + ), # uniform is Nash + ], ) -def test_player_regret_max_regret_reference(game: gbt.Game, profile_data: list, - player_regrets_exp: list, - tol: float | gbt.Rational | int, - rational_flag: bool): +def test_player_regret_max_regret_reference( + game: gbt.Game, + profile_data: list, + player_regrets_exp: list, + tol: float | gbt.Rational | int, + rational_flag: bool, +): profile = game.mixed_strategy_profile(rational=rational_flag, data=profile_data) if rational_flag: player_regrets_exp = [gbt.Rational(r) for r in player_regrets_exp] @@ -622,119 +898,216 @@ def test_player_regret_max_regret_reference(game: gbt.Game, profile_data: list, @pytest.mark.parametrize( "game,rational_flag", [ - ################################################################################# - # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), False), - (games.create_coord_4x4_nfg(), True), - ################################################################################# - # Zero matrix nfg - (games.create_2x2_zero_nfg(), False), - (games.create_2x2_zero_nfg(), True), - ################################################################################# - # El Farol bar game efg - (games.create_el_farol_bar_game_efg(), False), - (games.create_el_farol_bar_game_efg(), True), - ################################################################################# - # Centipede with chance efg - (games.create_centipede_game_with_chance_efg(), False), - (games.create_centipede_game_with_chance_efg(), True), - ################################################################################# - # 2x2x2 nfg - (games.create_2x2x2_nfg(), False), - (games.create_2x2x2_nfg(), True), - ] + ################################################################################# + # 4x4 coordination nfg + (games.create_coord_4x4_nfg(), False), + (games.create_coord_4x4_nfg(), True), + ################################################################################# + # Zero matrix nfg + (games.create_2x2_zero_nfg(), False), + (games.create_2x2_zero_nfg(), True), + ################################################################################# + # El Farol bar game efg + (games.create_el_farol_bar_game_efg(), False), + (games.create_el_farol_bar_game_efg(), True), + ################################################################################# + # Centipede with chance efg + (games.create_centipede_game_with_chance_efg(), False), + (games.create_centipede_game_with_chance_efg(), True), + ################################################################################# + # 2x2x2 nfg + (games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), False), + (games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), True), + ], ) def test_strategy_regret_consistency(game: gbt.Game, rational_flag: bool): profile = game.mixed_strategy_profile(rational=False) for player in game.players: for strategy in player.strategies: - assert ( - profile.strategy_regret(strategy) == - ( - max(profile.strategy_value(s) for s in player.strategies) - - profile.strategy_value(strategy) - ) + assert profile.strategy_regret(strategy) == ( + max(profile.strategy_value(s) for s in player.strategies) + - profile.strategy_value(strategy) ) @pytest.mark.parametrize( "game,profile_data,tol,rational_flag", [ - ################################################################################# - # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), - [["1/5", "2/5", "0/5", "2/5"], ["3/8", "1/4", "3/8", "0/4"]], ZERO, True), - (games.create_coord_4x4_nfg(), - [[1/3, 1/3, 0/3, 1/3], [1/4, 1/4, 3/8, 1/8]], TOL, False), - ################################################################################# - # Centipede with chance efg - (games.create_centipede_game_with_chance_efg(), - [["1/3", "1/3", "1/3", "0/1"], ["1/10", "3/5", "3/10", 0]], ZERO, True), - (games.create_centipede_game_with_chance_efg(), - [[1/3, 1/3, 1/3, 0], [.10, 3/5, .3, 0]], TOL, False), - ################################################################################# - # El Farol bar game efg - (games.create_el_farol_bar_game_efg(), - [[1, 0], ["1/2", "1/2"], ["1/3", "2/3"], ["1/5", "4/5"], ["1/8", "7/8"]], ZERO, True), - (games.create_el_farol_bar_game_efg(), - [[1, 0], [1/2, 1/2], [1/3, 2/3], [1/5, 4/5], [1/8, 7/8]], TOL, False), - ################################################################################# - # 2x2x2 nfg with 2 pure and 1 mixed eq - (games.create_2x2x2_nfg(), None, ZERO, True), - (games.create_2x2x2_nfg(), [[1, 0], [1, 0], [1, 0]], ZERO, True), - (games.create_2x2x2_nfg(), None, TOL, False), - (games.create_2x2x2_nfg(), [[1, 0], [1, 0], [1, 0]], TOL, False), - ] + ################################################################################# + # 4x4 coordination nfg + ( + games.create_coord_4x4_nfg(), + [["1/5", "2/5", "0/5", "2/5"], ["3/8", "1/4", "3/8", "0/4"]], + ZERO, + True, + ), + ( + games.create_coord_4x4_nfg(), + [[1 / 3, 1 / 3, 0 / 3, 1 / 3], [1 / 4, 1 / 4, 3 / 8, 1 / 8]], + TOL, + False, + ), + ################################################################################# + # Centipede with chance efg + ( + games.create_centipede_game_with_chance_efg(), + [["1/3", "1/3", "1/3", "0/1"], ["1/10", "3/5", "3/10", 0]], + ZERO, + True, + ), + ( + games.create_centipede_game_with_chance_efg(), + [[1 / 3, 1 / 3, 1 / 3, 0], [0.10, 3 / 5, 0.3, 0]], + TOL, + False, + ), + ################################################################################# + # El Farol bar game efg + ( + games.create_el_farol_bar_game_efg(), + [[1, 0], ["1/2", "1/2"], ["1/3", "2/3"], ["1/5", "4/5"], ["1/8", "7/8"]], + ZERO, + True, + ), + ( + games.create_el_farol_bar_game_efg(), + [[1, 0], [1 / 2, 1 / 2], [1 / 3, 2 / 3], [1 / 5, 4 / 5], [1 / 8, 7 / 8]], + TOL, + False, + ), + ################################################################################# + # 2x2x2 nfg with 2 pure and 1 mixed eq + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + None, + ZERO, + True, + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [1, 0], [1, 0]], + ZERO, + True, + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + None, + TOL, + False, + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [1, 0], [1, 0]], + TOL, + False, + ), + ], ) -def test_liap_value_consistency(game: gbt.Game, profile_data: list, - tol: float | gbt.Rational, - rational_flag: bool): +def test_liap_value_consistency( + game: gbt.Game, profile_data: list, tol: float | gbt.Rational, rational_flag: bool +): profile = game.mixed_strategy_profile(rational=rational_flag, data=profile_data) assert ( - abs(profile.liap_value() - - sum([max(profile.strategy_value(strategy) - profile.payoff(player), 0)**2 - for player in game.players for strategy in player.strategies])) <= tol + abs( + profile.liap_value() + - sum( + [ + max(profile.strategy_value(strategy) - profile.payoff(player), 0) ** 2 + for player in game.players + for strategy in player.strategies + ] + ) + ) + <= tol ) @pytest.mark.parametrize( "game,profile_data,tol,rational_flag", [ - ################################################################################# - # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), - [["1/5", "2/5", "0/5", "2/5"], ["3/8", "1/4", "3/8", "0/4"]], ZERO, True), - (games.create_coord_4x4_nfg(), - [[1/3, 1/3, 0/3, 1/3], [1/4, 1/4, 3/8, 1/8]], TOL, False), - ################################################################################# - # Centipede with chance efg - (games.create_centipede_game_with_chance_efg(), - [["1/3", "1/3", "1/3", "0/1"], ["1/10", "3/5", "3/10", 0]], ZERO, True), - (games.create_centipede_game_with_chance_efg(), - [[1/3, 1/3, 1/3, 0], [.10, 3/5, .3, 0]], TOL, False), - ################################################################################# - # El Farol bar game efg - (games.create_el_farol_bar_game_efg(), - [[1, 0], ["1/2", "1/2"], ["1/3", "2/3"], ["1/5", "4/5"], ["1/8", "7/8"]], ZERO, True), - (games.create_el_farol_bar_game_efg(), - [[1, 0], [1/2, 1/2], [1/3, 2/3], [1/5, 4/5], [1/8, 7/8]], TOL, False), - ################################################################################# - # 2x2x2 nfg with 2 pure and 1 mixed eq - (games.create_2x2x2_nfg(), None, ZERO, True), - (games.create_2x2x2_nfg(), [[1, 0], [1, 0], [1, 0]], ZERO, True), - (games.create_2x2x2_nfg(), None, TOL, False), - (games.create_2x2x2_nfg(), [[1, 0], [1, 0], [1, 0]], TOL, False), - ] + ################################################################################# + # 4x4 coordination nfg + ( + games.create_coord_4x4_nfg(), + [["1/5", "2/5", "0/5", "2/5"], ["3/8", "1/4", "3/8", "0/4"]], + ZERO, + True, + ), + ( + games.create_coord_4x4_nfg(), + [[1 / 3, 1 / 3, 0 / 3, 1 / 3], [1 / 4, 1 / 4, 3 / 8, 1 / 8]], + TOL, + False, + ), + ################################################################################# + # Centipede with chance efg + ( + games.create_centipede_game_with_chance_efg(), + [["1/3", "1/3", "1/3", "0/1"], ["1/10", "3/5", "3/10", 0]], + ZERO, + True, + ), + ( + games.create_centipede_game_with_chance_efg(), + [[1 / 3, 1 / 3, 1 / 3, 0], [0.10, 3 / 5, 0.3, 0]], + TOL, + False, + ), + ################################################################################# + # El Farol bar game efg + ( + games.create_el_farol_bar_game_efg(), + [[1, 0], ["1/2", "1/2"], ["1/3", "2/3"], ["1/5", "4/5"], ["1/8", "7/8"]], + ZERO, + True, + ), + ( + games.create_el_farol_bar_game_efg(), + [[1, 0], [1 / 2, 1 / 2], [1 / 3, 2 / 3], [1 / 5, 4 / 5], [1 / 8, 7 / 8]], + TOL, + False, + ), + ################################################################################# + # 2x2x2 nfg with 2 pure and 1 mixed eq + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + None, + ZERO, + True, + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [1, 0], [1, 0]], + ZERO, + True, + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + None, + TOL, + False, + ), + ( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + [[1, 0], [1, 0], [1, 0]], + TOL, + False, + ), + ], ) -def test_player_regret_max_regret_consistency(game: gbt.Game, profile_data: list, - tol: float | gbt.Rational, - rational_flag: bool): +def test_player_regret_max_regret_consistency( + game: gbt.Game, profile_data: list, tol: float | gbt.Rational, rational_flag: bool +): profile = game.mixed_strategy_profile(rational=rational_flag, data=profile_data) player_regrets = [] for p in game.players: - p_regret = max([max(profile.strategy_value(strategy) - profile.payoff(p), 0) - for strategy in p.strategies]) + p_regret = max( + [ + max(profile.strategy_value(strategy) - profile.payoff(p), 0) + for strategy in p.strategies + ] + ) player_regrets.append(p_regret) assert abs(profile.player_regret(p) - p_regret) <= tol assert abs(profile.max_regret() - max(player_regrets)) <= tol @@ -743,147 +1116,262 @@ def test_player_regret_max_regret_consistency(game: gbt.Game, profile_data: list @pytest.mark.parametrize( "game,profile1,profile2,alpha,tol,rational_flag", [ - ################################################################################# - # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), - [["1/5", "2/5", "0/5", "2/5"], ["3/8", "1/4", "3/8", "0/4"]], - [["1/5", "2/5", "0/5", "2/5"], ["1/4", "3/8", "0/4", "3/8"]], - gbt.Rational("3/5"), ZERO, True), - (games.create_coord_4x4_nfg(), - [[1/5, 2/5, 0/5, 2/5], [3/8, 1/4, 3/8, 0/4]], [[1/5, 2/5, 0/5, 2/5], [1/4, 3/8, 0/4, 3/8]], - 3/5, TOL, False), - ################################################################################# - # Zero matrix nfg - (games.create_2x2_zero_nfg(), - [["1/4", "3/4"], ["3/5", "2/5"]], [["1/2", "1/2"], ["3/5", "2/5"]], - gbt.Rational("5/6"), ZERO, True), - ################################################################################# - # Centipede game with chance - (games.create_centipede_game_with_chance_efg(), - [["1/3", "1/3", "1/3", "0/1"], ["1/10", "3/5", "3/10", "0/1"]], - [["1/3", "1/3", "1/3", "0/1"], ["1/5", "2/5", "1/5", "1/5"]], - gbt.Rational("1/12"), ZERO, True), - (games.create_centipede_game_with_chance_efg(), - [[1/3, 1/3, 1/3, 0/1], [1/10, 3/5, 3/10, 0/1]], [[1/3, 1/3, 1/3, 0/1], [1/5, 2/5, 1/5, 1/5]], - 1/12, TOL, False), - ################################################################################# - # Selten's horse game - (games.create_selten_horse_game_efg(), - [["4/9", "5/9"], ["1/11", "10/11"], ["8/9", "1/9"]], - [["4/9", "5/9"], ["10/11", "1/11"], ["8/9", "1/9"]], - gbt.Rational("4/9"), ZERO, True), - ################################################################################# - # El Farol bar game - (games.create_el_farol_bar_game_efg(), - [["4/9", "5/9"], ["1/3", "2/3"], ["1/2", "1/2"], ["11/12", "1/12"], ["1/2", "1/2"]], - [["4/9", "5/9"], ["1/3", "2/3"], ["1/2", "1/2"], ["1/12", "11/12"], ["1/2", "1/2"]], - gbt.Rational("1/2"), ZERO, True), - ] + ################################################################################# + # 4x4 coordination nfg + ( + games.create_coord_4x4_nfg(), + [["1/5", "2/5", "0/5", "2/5"], ["3/8", "1/4", "3/8", "0/4"]], + [["1/5", "2/5", "0/5", "2/5"], ["1/4", "3/8", "0/4", "3/8"]], + gbt.Rational("3/5"), + ZERO, + True, + ), + ( + games.create_coord_4x4_nfg(), + [[1 / 5, 2 / 5, 0 / 5, 2 / 5], [3 / 8, 1 / 4, 3 / 8, 0 / 4]], + [[1 / 5, 2 / 5, 0 / 5, 2 / 5], [1 / 4, 3 / 8, 0 / 4, 3 / 8]], + 3 / 5, + TOL, + False, + ), + ################################################################################# + # Zero matrix nfg + ( + games.create_2x2_zero_nfg(), + [["1/4", "3/4"], ["3/5", "2/5"]], + [["1/2", "1/2"], ["3/5", "2/5"]], + gbt.Rational("5/6"), + ZERO, + True, + ), + ################################################################################# + # Centipede game with chance + ( + games.create_centipede_game_with_chance_efg(), + [["1/3", "1/3", "1/3", "0/1"], ["1/10", "3/5", "3/10", "0/1"]], + [["1/3", "1/3", "1/3", "0/1"], ["1/5", "2/5", "1/5", "1/5"]], + gbt.Rational("1/12"), + ZERO, + True, + ), + ( + games.create_centipede_game_with_chance_efg(), + [[1 / 3, 1 / 3, 1 / 3, 0 / 1], [1 / 10, 3 / 5, 3 / 10, 0 / 1]], + [[1 / 3, 1 / 3, 1 / 3, 0 / 1], [1 / 5, 2 / 5, 1 / 5, 1 / 5]], + 1 / 12, + TOL, + False, + ), + ################################################################################# + # Selten's horse game + ( + games.create_selten_horse_game_efg(), + [["4/9", "5/9"], ["1/11", "10/11"], ["8/9", "1/9"]], + [["4/9", "5/9"], ["10/11", "1/11"], ["8/9", "1/9"]], + gbt.Rational("4/9"), + ZERO, + True, + ), + ################################################################################# + # El Farol bar game + ( + games.create_el_farol_bar_game_efg(), + [["4/9", "5/9"], ["1/3", "2/3"], ["1/2", "1/2"], ["11/12", "1/12"], ["1/2", "1/2"]], + [["4/9", "5/9"], ["1/3", "2/3"], ["1/2", "1/2"], ["1/12", "11/12"], ["1/2", "1/2"]], + gbt.Rational("1/2"), + ZERO, + True, + ), + ], ) -def test_linearity_payoff_property(game: gbt.Game, profile1: list, profile2: list, - alpha: float | gbt.Rational, - tol: float | gbt.Rational, rational_flag: bool): +def test_linearity_payoff_property( + game: gbt.Game, + profile1: list, + profile2: list, + alpha: float | gbt.Rational, + tol: float | gbt.Rational, + rational_flag: bool, +): profile1 = game.mixed_strategy_profile(rational=rational_flag, data=profile1) profile2 = game.mixed_strategy_profile(rational=rational_flag, data=profile2) - profile_data = [[alpha*profile1[strategy] + (1-alpha)*profile2[strategy] - for strategy in player.strategies] for player in game.players] + profile_data = [ + [ + alpha * profile1[strategy] + (1 - alpha) * profile2[strategy] + for strategy in player.strategies + ] + for player in game.players + ] profile3 = game.mixed_strategy_profile(rational=rational_flag, data=profile_data) for player in game.players: assert ( - abs(alpha*profile1.payoff(player) + (1 - alpha)*profile2.payoff(player) - - profile3.payoff(player)) <= tol + abs( + alpha * profile1.payoff(player) + + (1 - alpha) * profile2.payoff(player) + - profile3.payoff(player) + ) + <= tol ) @pytest.mark.parametrize( "game,profile_data,tol,rational_flag", [ - ################################################################################# - # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), - [["1/5", "2/5", "0/5", "2/5"], ["1/4", "3/8", "0/4", "3/8"]], ZERO, True), - (games.create_coord_4x4_nfg(), [[0.2, 0.4, 0, 0.4], [1/4, 3/8, 0, 3/8]], TOL, False), - (gbt.Game.from_arrays([[1, 2], [-3, 4]], [[-4, 3], [2, 1]]), [[1/2, 1/2], [3/5, 2/5]], - TOL, False), - ################################################################################# - # Zero matrix nfg - (games.create_2x2_zero_nfg(), [["4/5", "1/5"], ["4/7", "3/7"]], ZERO, True), - ################################################################################# - # Centipede game with chance - (games.create_centipede_game_with_chance_efg(), - [["1/5", "2/5", "1/5", "1/5"], ["1/10", "3/5", "3/10", "0/1"]], ZERO, True), - (games.create_centipede_game_with_chance_efg(), - [[1/3, 1/3, 1/3, 0/1], [1/10, 3/5, 3/10, 0/1]], TOL, False), - ################################################################################# - # Selten's horse - (games.create_selten_horse_game_efg(), [["4/9", "5/9"], ["6/11", "5/11"], ["4/7", "3/7"]], - ZERO, True), - (games.create_selten_horse_game_efg(), [[4/9, 5/9], [6/11, 5/11], [4/7, 3/7]], TOL, False), - ################################################################################# - # El Farol bar game - (games.create_el_farol_bar_game_efg(), - [["4/9", "5/9"], ["1/3", "2/3"], ["0/1", "1/1"], ["11/12", "1/12"], ["1/3", "2/3"]], - ZERO, True), - ] + ################################################################################# + # 4x4 coordination nfg + ( + games.create_coord_4x4_nfg(), + [["1/5", "2/5", "0/5", "2/5"], ["1/4", "3/8", "0/4", "3/8"]], + ZERO, + True, + ), + (games.create_coord_4x4_nfg(), [[0.2, 0.4, 0, 0.4], [1 / 4, 3 / 8, 0, 3 / 8]], TOL, False), + ( + gbt.Game.from_arrays([[1, 2], [-3, 4]], [[-4, 3], [2, 1]]), + [[1 / 2, 1 / 2], [3 / 5, 2 / 5]], + TOL, + False, + ), + ################################################################################# + # Zero matrix nfg + (games.create_2x2_zero_nfg(), [["4/5", "1/5"], ["4/7", "3/7"]], ZERO, True), + ################################################################################# + # Centipede game with chance + ( + games.create_centipede_game_with_chance_efg(), + [["1/5", "2/5", "1/5", "1/5"], ["1/10", "3/5", "3/10", "0/1"]], + ZERO, + True, + ), + ( + games.create_centipede_game_with_chance_efg(), + [[1 / 3, 1 / 3, 1 / 3, 0 / 1], [1 / 10, 3 / 5, 3 / 10, 0 / 1]], + TOL, + False, + ), + ################################################################################# + # Selten's horse + ( + games.create_selten_horse_game_efg(), + [["4/9", "5/9"], ["6/11", "5/11"], ["4/7", "3/7"]], + ZERO, + True, + ), + ( + games.create_selten_horse_game_efg(), + [[4 / 9, 5 / 9], [6 / 11, 5 / 11], [4 / 7, 3 / 7]], + TOL, + False, + ), + ################################################################################# + # El Farol bar game + ( + games.create_el_farol_bar_game_efg(), + [["4/9", "5/9"], ["1/3", "2/3"], ["0/1", "1/1"], ["11/12", "1/12"], ["1/3", "2/3"]], + ZERO, + True, + ), + ], ) -def test_payoff_and_strategy_value_consistency(game: gbt.Game, profile_data: list, - tol: float | gbt.Rational, - rational_flag: bool): +def test_payoff_and_strategy_value_consistency( + game: gbt.Game, profile_data: list, tol: float | gbt.Rational, rational_flag: bool +): profile = game.mixed_strategy_profile(rational=rational_flag, data=profile_data) for player in game.players: assert ( - abs(sum([profile[player][strategy] * profile.strategy_value(strategy) - for strategy in player.strategies]) - profile.payoff(player)) <= tol + abs( + sum( + [ + profile[player][strategy] * profile.strategy_value(strategy) + for strategy in player.strategies + ] + ) + - profile.payoff(player) + ) + <= tol ) @pytest.mark.parametrize( "game,profile1,profile2,alpha,rational_flag,tol", [ - ################################################################################# - # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), - [["1/1111", "10/1111", "100/1111", "1000/1111"], ["1/4", "1/8", "3/8", "1/4"]], - [["1/1111", "10/1111", "99/1111", "1001/1111"], ["1/4", "1/8", "3/8", "1/4"]], "1/2", True, - ZERO), - (games.create_coord_4x4_nfg(), - [[1/1111, 10/1111, 100/1111, 1000/1111], [1/4, 1/8, 3/8, 1/4]], - [[1/1111, 10/1111, 99/1111, 1001/1111], [1/4, 1/8, 3/8, 1/4]], 1/2, False, TOL), - ################################################################################# - # centipede game with chance - (games.create_centipede_game_with_chance_efg(), - [["1/3", "1/3", "1/3", "0"], ["1/10", "3/5", "3/10", "0"]], - [["1/3", "1/3", "1/3", "0"], ["1/10", "3/5", "3/10", "0"]], "82943/62500", True, ZERO), - (games.create_centipede_game_with_chance_efg(), - [[1/3, 1/3, 1/3, 0], [1/10, 3/5, 3/10, 0]], - [[1/3, 1/3, 1/3, 0], [1/10, 3/5, 3/10, 0]], 82943/62500, False, TOL), - ] + ################################################################################# + # 4x4 coordination nfg + ( + games.create_coord_4x4_nfg(), + [["1/1111", "10/1111", "100/1111", "1000/1111"], ["1/4", "1/8", "3/8", "1/4"]], + [["1/1111", "10/1111", "99/1111", "1001/1111"], ["1/4", "1/8", "3/8", "1/4"]], + "1/2", + True, + ZERO, + ), + ( + games.create_coord_4x4_nfg(), + [[1 / 1111, 10 / 1111, 100 / 1111, 1000 / 1111], [1 / 4, 1 / 8, 3 / 8, 1 / 4]], + [[1 / 1111, 10 / 1111, 99 / 1111, 1001 / 1111], [1 / 4, 1 / 8, 3 / 8, 1 / 4]], + 1 / 2, + False, + TOL, + ), + ################################################################################# + # centipede game with chance + ( + games.create_centipede_game_with_chance_efg(), + [["1/3", "1/3", "1/3", "0"], ["1/10", "3/5", "3/10", "0"]], + [["1/3", "1/3", "1/3", "0"], ["1/10", "3/5", "3/10", "0"]], + "82943/62500", + True, + ZERO, + ), + ( + games.create_centipede_game_with_chance_efg(), + [[1 / 3, 1 / 3, 1 / 3, 0], [1 / 10, 3 / 5, 3 / 10, 0]], + [[1 / 3, 1 / 3, 1 / 3, 0], [1 / 10, 3 / 5, 3 / 10, 0]], + 82943 / 62500, + False, + TOL, + ), + ], ) -def test_property_linearity_strategy_value(game: gbt.Game, profile1: list, profile2: list, - alpha: float | str, rational_flag: bool, - tol: float | gbt.Rational): - +def test_property_linearity_strategy_value( + game: gbt.Game, + profile1: list, + profile2: list, + alpha: float | str, + rational_flag: bool, + tol: float | gbt.Rational, +): alpha = gbt.Rational(alpha) if rational_flag else alpha profile1 = game.mixed_strategy_profile(rational=rational_flag, data=profile1) profile2 = game.mixed_strategy_profile(rational=rational_flag, data=profile2) - profile_data = [[alpha*profile1[strategy] + (1-alpha)*profile2[strategy] - for strategy in player.strategies] for player in game.players] + profile_data = [ + [ + alpha * profile1[strategy] + (1 - alpha) * profile2[strategy] + for strategy in player.strategies + ] + for player in game.players + ] profile3 = game.mixed_strategy_profile(rational=rational_flag, data=profile_data) for player in game.players: for strategy in player.strategies: - convex_comb = alpha * profile1.strategy_value(strategy) +\ - (1-alpha) * profile2.strategy_value(strategy) + convex_comb = alpha * profile1.strategy_value(strategy) + ( + 1 - alpha + ) * profile2.strategy_value(strategy) assert abs(profile3.strategy_value(strategy) - convex_comb) <= tol -def _get_answers_one_order(game: gbt.Game, action_probs_1st: tuple, action_probs_2nd: tuple, - rational_flag: bool, func_to_test: typing.Callable, - object_to_test_on: typing.Any): +def _get_answers_one_order( + game: gbt.Game, + action_probs_1st: tuple, + action_probs_2nd: tuple, + rational_flag: bool, + func_to_test: typing.Callable, + object_to_test_on: typing.Any, +): """helper function for the 'profile_order' caching tests""" ret = dict() profile = game.mixed_strategy_profile(rational=rational_flag) @@ -894,14 +1382,27 @@ def _get_answers_one_order(game: gbt.Game, action_probs_1st: tuple, action_probs return ret -def _get_and_check_answers(game: gbt.Game, action_probs1: tuple, action_probs2: tuple, - rational_flag: bool, func_to_test: typing.Callable, - objects_to_test_on: typing.Collection): +def _get_and_check_answers( + game: gbt.Game, + action_probs1: tuple, + action_probs2: tuple, + rational_flag: bool, + func_to_test: typing.Callable, + objects_to_test_on: typing.Collection, +): """helper function for the 'profile_order' caching tests""" - order1_answers = {o: _get_answers_one_order(game, action_probs1, action_probs2, rational_flag, - func_to_test, o) for o in objects_to_test_on} - order2_answers = {o: _get_answers_one_order(game, action_probs2, action_probs1, rational_flag, - func_to_test, o) for o in objects_to_test_on} + order1_answers = { + o: _get_answers_one_order( + game, action_probs1, action_probs2, rational_flag, func_to_test, o + ) + for o in objects_to_test_on + } + order2_answers = { + o: _get_answers_one_order( + game, action_probs2, action_probs1, rational_flag, func_to_test, o + ) + for o in objects_to_test_on + } assert order1_answers == order2_answers @@ -920,163 +1421,383 @@ def _get_and_check_answers(game: gbt.Game, action_probs1: tuple, action_probs2: @pytest.mark.parametrize( "game,action_probs1,action_probs2,rational_flag,func_to_test,objects_to_test", [ - ################################################################################# - # payoffs (for players) - ####################### - # 4x4 coordination nfg - pytest.param(games.create_coord_4x4_nfg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda profile, player: profile.payoff(player), lambda game: game.players, - id="payoffs_coord_doub"), - pytest.param(games.create_coord_4x4_nfg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda profile, player: profile.payoff(player), lambda game: game.players, - id="payoffs_coord_rat"), - # 2x2x2 nfg - pytest.param(games.create_2x2x2_nfg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda profile, player: profile.payoff(player), lambda game: game.players, - id="payoffs_2x2x2_doub"), - pytest.param(games.create_2x2x2_nfg(), PROBS_1B_rat, PROBS_2B_rat, True, - lambda profile, player: profile.payoff(player), lambda game: game.players, - id="payoffs_2x2x2_rat"), - # stripped-down poker - pytest.param(games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda profile, player: profile.payoff(player), lambda game: game.players, - id="payoffs_poker_doub"), - pytest.param(games.create_stripped_down_poker_efg(), PROBS_1B_rat, PROBS_2B_rat, True, - lambda profile, player: profile.payoff(player), lambda game: game.players, - id="payoffs_poker_rat"), - ################################################################################# - # regret (for strategies) - # 4x4 coordination nfg - pytest.param(games.create_coord_4x4_nfg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda profile, strategy: profile.strategy_regret(strategy), - lambda game: game.strategies, id="regret_coord_doub"), - pytest.param(games.create_coord_4x4_nfg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda profile, strategy: profile.strategy_regret(strategy), - lambda game: game.strategies, id="regret_coord_rat"), - # 2x2x2 nfg - pytest.param(games.create_2x2x2_nfg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda profile, strategy: profile.strategy_regret(strategy), - lambda game: game.strategies, id="regret_2x2x2_doub"), - pytest.param(games.create_2x2x2_nfg(), PROBS_1B_rat, PROBS_2B_rat, True, - lambda profile, strategy: profile.strategy_regret(strategy), - lambda game: game.strategies, id="regret_2x2x2_rat"), - # stripped-down poker - pytest.param(games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda profile, strategy: profile.strategy_regret(strategy), - lambda game: game.strategies, id="regret_poker_doub"), - pytest.param(games.create_stripped_down_poker_efg(), PROBS_1B_rat, PROBS_2B_rat, True, - lambda profile, strategy: profile.strategy_regret(strategy), - lambda game: game.strategies, id="regret_poker_rat"), - ################################################################################# - # strategy_value (for strategies) - # 4x4 coordination nfg - pytest.param(games.create_coord_4x4_nfg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda profile, strategy: profile.strategy_value(strategy), - lambda game: game.strategies, id="strat_value_coord_doub"), - pytest.param(games.create_coord_4x4_nfg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda profile, strategy: profile.strategy_value(strategy), - lambda game: game.strategies, id="strat_value_coord_rat"), - # 2x2x2 nfg - pytest.param(games.create_2x2x2_nfg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda profile, strategy: profile.strategy_value(strategy), - lambda game: game.strategies, id="strat_value_2x2x2_doub"), - pytest.param(games.create_2x2x2_nfg(), PROBS_1B_rat, PROBS_2B_rat, True, - lambda profile, strategy: profile.strategy_value(strategy), - lambda game: game.strategies, id="strat_value_2x2x2_rat"), - # stripped-down poker - pytest.param(games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda profile, strategy: profile.strategy_value(strategy), - lambda game: game.strategies, id="strat_value_poker_doub"), - pytest.param(games.create_stripped_down_poker_efg(), PROBS_1B_rat, PROBS_2B_rat, True, - lambda profile, strategy: profile.strategy_value(strategy), - lambda game: game.strategies, id="strat_value_poker_rat"), - ################################################################################# - # strategy_value_deriv (for strategies * strategies) - # 4x4 coordination nfg - pytest.param(games.create_coord_4x4_nfg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda profile, strat_pair: profile.strategy_value_deriv(strategy=strat_pair[0], - other=strat_pair[1]), - lambda game: list(product(game.strategies, game.strategies)), - id="strat_value_deriv_coord_doub"), - pytest.param(games.create_coord_4x4_nfg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda profile, strat_pair: profile.strategy_value_deriv(strategy=strat_pair[0], - other=strat_pair[1]), - lambda game: list(product(game.strategies, game.strategies)), - id="strat_value_deriv_coord_rat"), - # 2x2x2 nfg - pytest.param(games.create_2x2x2_nfg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda profile, strat_pair: profile.strategy_value_deriv(strategy=strat_pair[0], - other=strat_pair[1]), - lambda game: list(product(game.strategies, game.strategies)), - id="strat_value_deriv_2x2x2_doub"), - pytest.param(games.create_2x2x2_nfg(), PROBS_1B_rat, PROBS_2B_rat, True, - lambda profile, strat_pair: profile.strategy_value_deriv(strategy=strat_pair[0], - other=strat_pair[1]), - lambda game: list(product(game.strategies, game.strategies)), - id="strat_value_deriv_2x2x2_rat"), - # stripped-down poker - pytest.param(games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda profile, strat_pair: profile.strategy_value_deriv(strategy=strat_pair[0], - other=strat_pair[1]), - lambda game: list(product(game.strategies, game.strategies)), - id="strat_value_deriv_poker_doub"), - pytest.param(games.create_stripped_down_poker_efg(), PROBS_1B_rat, PROBS_2B_rat, True, - lambda profile, strat_pair: profile.strategy_value_deriv(strategy=strat_pair[0], - other=strat_pair[1]), - lambda game: list(product(game.strategies, game.strategies)), - id="strat_value_deriv_poker_rat"), - ################################################################################# - # liap_value (of profile, hence [1] for objects_to_test, any singleton collection would do) - # 4x4 coordination nfg - pytest.param(games.create_coord_4x4_nfg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda profile, y: profile.liap_value(), lambda x: [1], - id="liap_value_coord_doub"), - pytest.param(games.create_coord_4x4_nfg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda profile, y: profile.liap_value(), lambda x: [1], - id="liap_value_coord_rat"), - # 2x2x2 nfg - pytest.param(games.create_2x2x2_nfg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda profile, y: profile.liap_value(), lambda x: [1], - id="liap_value_2x2x2_doub"), - pytest.param(games.create_2x2x2_nfg(), PROBS_1B_rat, PROBS_2B_rat, True, - lambda profile, y: profile.liap_value(), lambda x: [1], - id="liap_value_2x2x2_rat"), - # stripped-down poker - pytest.param(games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda profile, y: profile.liap_value(), lambda x: [1], - id="liap_value_poker_doub"), - pytest.param(games.create_stripped_down_poker_efg(), PROBS_1B_rat, PROBS_2B_rat, True, - lambda profile, y: profile.liap_value(), lambda x: [1], - id="liap_value_poker_rat"), - ################################################################################# - # max_regret (of profile, hence [1] for objects_to_test, any singleton collection would do) - # 4x4 coordination nfg - pytest.param(games.create_coord_4x4_nfg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda profile, y: profile.max_regret(), lambda x: [1], - id="max_regret_coord_doub"), - pytest.param(games.create_coord_4x4_nfg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda profile, y: profile.max_regret(), lambda x: [1], - id="max_regret_coord_rat"), - # 2x2x2 nfg - pytest.param(games.create_2x2x2_nfg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda profile, y: profile.max_regret(), lambda x: [1], - id="max_regret_2x2x2_doub"), - pytest.param(games.create_2x2x2_nfg(), PROBS_1B_rat, PROBS_2B_rat, True, - lambda profile, y: profile.max_regret(), lambda x: [1], - id="max_regret_2x2x2_rat"), - # stripped-down poker - pytest.param(games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda profile, y: profile.max_regret(), lambda x: [1], - id="max_regret_poker_doub"), - pytest.param(games.create_stripped_down_poker_efg(), PROBS_1B_rat, PROBS_2B_rat, True, - lambda profile, y: profile.max_regret(), lambda x: [1], - id="max_regret_poker_rat"), - ] + ################################################################################# + # payoffs (for players) + ####################### + # 4x4 coordination nfg + pytest.param( + games.create_coord_4x4_nfg(), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda profile, player: profile.payoff(player), + lambda game: game.players, + id="payoffs_coord_doub", + ), + pytest.param( + games.create_coord_4x4_nfg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda profile, player: profile.payoff(player), + lambda game: game.players, + id="payoffs_coord_rat", + ), + # 2x2x2 nfg + pytest.param( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda profile, player: profile.payoff(player), + lambda game: game.players, + id="payoffs_2x2x2_doub", + ), + pytest.param( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + PROBS_1B_rat, + PROBS_2B_rat, + True, + lambda profile, player: profile.payoff(player), + lambda game: game.players, + id="payoffs_2x2x2_rat", + ), + # stripped-down poker + pytest.param( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda profile, player: profile.payoff(player), + lambda game: game.players, + id="payoffs_poker_doub", + ), + pytest.param( + games.create_stripped_down_poker_efg(), + PROBS_1B_rat, + PROBS_2B_rat, + True, + lambda profile, player: profile.payoff(player), + lambda game: game.players, + id="payoffs_poker_rat", + ), + ################################################################################# + # regret (for strategies) + # 4x4 coordination nfg + pytest.param( + games.create_coord_4x4_nfg(), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda profile, strategy: profile.strategy_regret(strategy), + lambda game: game.strategies, + id="regret_coord_doub", + ), + pytest.param( + games.create_coord_4x4_nfg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda profile, strategy: profile.strategy_regret(strategy), + lambda game: game.strategies, + id="regret_coord_rat", + ), + # 2x2x2 nfg + pytest.param( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda profile, strategy: profile.strategy_regret(strategy), + lambda game: game.strategies, + id="regret_2x2x2_doub", + ), + pytest.param( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + PROBS_1B_rat, + PROBS_2B_rat, + True, + lambda profile, strategy: profile.strategy_regret(strategy), + lambda game: game.strategies, + id="regret_2x2x2_rat", + ), + # stripped-down poker + pytest.param( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda profile, strategy: profile.strategy_regret(strategy), + lambda game: game.strategies, + id="regret_poker_doub", + ), + pytest.param( + games.create_stripped_down_poker_efg(), + PROBS_1B_rat, + PROBS_2B_rat, + True, + lambda profile, strategy: profile.strategy_regret(strategy), + lambda game: game.strategies, + id="regret_poker_rat", + ), + ################################################################################# + # strategy_value (for strategies) + # 4x4 coordination nfg + pytest.param( + games.create_coord_4x4_nfg(), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda profile, strategy: profile.strategy_value(strategy), + lambda game: game.strategies, + id="strat_value_coord_doub", + ), + pytest.param( + games.create_coord_4x4_nfg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda profile, strategy: profile.strategy_value(strategy), + lambda game: game.strategies, + id="strat_value_coord_rat", + ), + # 2x2x2 nfg + pytest.param( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda profile, strategy: profile.strategy_value(strategy), + lambda game: game.strategies, + id="strat_value_2x2x2_doub", + ), + pytest.param( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + PROBS_1B_rat, + PROBS_2B_rat, + True, + lambda profile, strategy: profile.strategy_value(strategy), + lambda game: game.strategies, + id="strat_value_2x2x2_rat", + ), + # stripped-down poker + pytest.param( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda profile, strategy: profile.strategy_value(strategy), + lambda game: game.strategies, + id="strat_value_poker_doub", + ), + pytest.param( + games.create_stripped_down_poker_efg(), + PROBS_1B_rat, + PROBS_2B_rat, + True, + lambda profile, strategy: profile.strategy_value(strategy), + lambda game: game.strategies, + id="strat_value_poker_rat", + ), + ################################################################################# + # strategy_value_deriv (for strategies * strategies) + # 4x4 coordination nfg + pytest.param( + games.create_coord_4x4_nfg(), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda profile, strat_pair: profile.strategy_value_deriv( + strategy=strat_pair[0], other=strat_pair[1] + ), + lambda game: list(product(game.strategies, game.strategies)), + id="strat_value_deriv_coord_doub", + ), + pytest.param( + games.create_coord_4x4_nfg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda profile, strat_pair: profile.strategy_value_deriv( + strategy=strat_pair[0], other=strat_pair[1] + ), + lambda game: list(product(game.strategies, game.strategies)), + id="strat_value_deriv_coord_rat", + ), + # 2x2x2 nfg + pytest.param( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda profile, strat_pair: profile.strategy_value_deriv( + strategy=strat_pair[0], other=strat_pair[1] + ), + lambda game: list(product(game.strategies, game.strategies)), + id="strat_value_deriv_2x2x2_doub", + ), + pytest.param( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + PROBS_1B_rat, + PROBS_2B_rat, + True, + lambda profile, strat_pair: profile.strategy_value_deriv( + strategy=strat_pair[0], other=strat_pair[1] + ), + lambda game: list(product(game.strategies, game.strategies)), + id="strat_value_deriv_2x2x2_rat", + ), + # stripped-down poker + pytest.param( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda profile, strat_pair: profile.strategy_value_deriv( + strategy=strat_pair[0], other=strat_pair[1] + ), + lambda game: list(product(game.strategies, game.strategies)), + id="strat_value_deriv_poker_doub", + ), + pytest.param( + games.create_stripped_down_poker_efg(), + PROBS_1B_rat, + PROBS_2B_rat, + True, + lambda profile, strat_pair: profile.strategy_value_deriv( + strategy=strat_pair[0], other=strat_pair[1] + ), + lambda game: list(product(game.strategies, game.strategies)), + id="strat_value_deriv_poker_rat", + ), + ################################################################################# + # liap_value (of profile, hence [1] for objects_to_test, any singleton collection would do) + # 4x4 coordination nfg + pytest.param( + games.create_coord_4x4_nfg(), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda profile, y: profile.liap_value(), + lambda x: [1], + id="liap_value_coord_doub", + ), + pytest.param( + games.create_coord_4x4_nfg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda profile, y: profile.liap_value(), + lambda x: [1], + id="liap_value_coord_rat", + ), + # 2x2x2 nfg + pytest.param( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda profile, y: profile.liap_value(), + lambda x: [1], + id="liap_value_2x2x2_doub", + ), + pytest.param( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + PROBS_1B_rat, + PROBS_2B_rat, + True, + lambda profile, y: profile.liap_value(), + lambda x: [1], + id="liap_value_2x2x2_rat", + ), + # stripped-down poker + pytest.param( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda profile, y: profile.liap_value(), + lambda x: [1], + id="liap_value_poker_doub", + ), + pytest.param( + games.create_stripped_down_poker_efg(), + PROBS_1B_rat, + PROBS_2B_rat, + True, + lambda profile, y: profile.liap_value(), + lambda x: [1], + id="liap_value_poker_rat", + ), + ################################################################################# + # max_regret (of profile, hence [1] for objects_to_test, any singleton collection would do) + # 4x4 coordination nfg + pytest.param( + games.create_coord_4x4_nfg(), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda profile, y: profile.max_regret(), + lambda x: [1], + id="max_regret_coord_doub", + ), + pytest.param( + games.create_coord_4x4_nfg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda profile, y: profile.max_regret(), + lambda x: [1], + id="max_regret_coord_rat", + ), + # 2x2x2 nfg + pytest.param( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda profile, y: profile.max_regret(), + lambda x: [1], + id="max_regret_2x2x2_doub", + ), + pytest.param( + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + PROBS_1B_rat, + PROBS_2B_rat, + True, + lambda profile, y: profile.max_regret(), + lambda x: [1], + id="max_regret_2x2x2_rat", + ), + # stripped-down poker + pytest.param( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda profile, y: profile.max_regret(), + lambda x: [1], + id="max_regret_poker_doub", + ), + pytest.param( + games.create_stripped_down_poker_efg(), + PROBS_1B_rat, + PROBS_2B_rat, + True, + lambda profile, y: profile.max_regret(), + lambda x: [1], + id="max_regret_poker_rat", + ), + ], ) -def test_profile_order_consistency(game: gbt.Game, - action_probs1: tuple, - action_probs2: tuple, rational_flag: bool, - func_to_test: typing.Callable, - objects_to_test: typing.Callable): - _get_and_check_answers(game, action_probs1, action_probs2, rational_flag, func_to_test, - objects_to_test(game)) +def test_profile_order_consistency( + game: gbt.Game, + action_probs1: tuple, + action_probs2: tuple, + rational_flag: bool, + func_to_test: typing.Callable, + objects_to_test: typing.Callable, +): + _get_and_check_answers( + game, action_probs1, action_probs2, rational_flag, func_to_test, objects_to_test(game) + ) diff --git a/tests/test_players.py b/tests/test_players.py index dd0257f0a..339ae1de2 100644 --- a/tests/test_players.py +++ b/tests/test_players.py @@ -152,7 +152,7 @@ def test_player_strategy_bad_type(): [ # NFGs ( - games.read_from_file("2x2x2_nfg_with_two_pure_one_mixed_eq.nfg"), + games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), [-1, 0, -1], [2, 4, 2] ), From fa705670c176ff190d4ccb139d9782ebe35e290e Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 13:34:29 +0000 Subject: [PATCH 04/44] removed create_coord_4x4_nfg from games.py --- tests/games.py | 12 -- tests/test_behav.py | 4 +- tests/test_mixed.py | 335 ++++++++++++++++++++++++++++++++------------ 3 files changed, 244 insertions(+), 107 deletions(-) diff --git a/tests/games.py b/tests/games.py index dbadfe033..96bccffa1 100644 --- a/tests/games.py +++ b/tests/games.py @@ -66,18 +66,6 @@ def create_2x2_zero_nfg() -> gbt.Game: return game -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) diff --git a/tests/test_behav.py b/tests/test_behav.py index c3468ef0c..77c3f8e43 100644 --- a/tests/test_behav.py +++ b/tests/test_behav.py @@ -1345,9 +1345,9 @@ def test_profile_data_error(game: gbt.Game, rational_flag: bool, data: list): @pytest.mark.parametrize( "game,rational_flag,data", - [(games.create_coord_4x4_nfg(), True, + [(games.read_from_file("coordination_4x4_payoff.nfg"), True, [["1/5", "2/5", 0, "2/5"], ["1/4", "3/8", "1/4", "3/8"]]), - (games.create_coord_4x4_nfg(), False, + (games.read_from_file("coordination_4x4_payoff.nfg"), False, [[1/5, 2/5, 0/5, 2/5], [1/4, 3/8, 1/4, 3/8]]), ] ) diff --git a/tests/test_mixed.py b/tests/test_mixed.py index 975e26ee5..48d7fe10f 100644 --- a/tests/test_mixed.py +++ b/tests/test_mixed.py @@ -28,10 +28,22 @@ def _set_action_probs(profile: gbt.MixedStrategyProfile, probs: list, rational_f [ ############################################################################### # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), [[0, 0, 0, 0], ["1/3", "1/3", "1/3", 0]], True), - (games.create_coord_4x4_nfg(), [[1, 0, 0, 0], [0, 0, 0, 0]], True), - (games.create_coord_4x4_nfg(), [[0, 0, 0, 0], [1.0, 1.0, 1.0, 1.0]], False), - (games.create_coord_4x4_nfg(), [[1.0, 1.0, 1.0, 1.0], [0, 0, 0, 0]], False), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + [[0, 0, 0, 0], ["1/3", "1/3", "1/3", 0]], + True, + ), + (games.read_from_file("coordination_4x4_payoff.nfg"), [[1, 0, 0, 0], [0, 0, 0, 0]], True), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + [[0, 0, 0, 0], [1.0, 1.0, 1.0, 1.0]], + False, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + [[1.0, 1.0, 1.0, 1.0], [0, 0, 0, 0]], + False, + ), ############################################################################### # centipede with chance efg (games.create_centipede_game_with_chance_efg(), [[0, 0, 0, 0], [1, 0, 0, 0]], True), @@ -51,8 +63,16 @@ def test_normalize_zero_value_error(game, profile_data, rational_flag): [ ############################################################################### # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), [[1, 1, 0, -1], ["1/3", "1/3", "1/3", 0]], True), - (games.create_coord_4x4_nfg(), [[0, 0, 0, -1.0], [1.0, 1.0, 1.0, 1.0]], False), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + [[1, 1, 0, -1], ["1/3", "1/3", "1/3", 0]], + True, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + [[0, 0, 0, -1.0], [1.0, 1.0, 1.0, 1.0]], + False, + ), ############################################################################### # zero matrix nfg (games.create_2x2_zero_nfg(), [[1, 0], [0, -1]], True), @@ -77,13 +97,13 @@ def test_normalize_neg_entry_value_error(game, profile_data, rational_flag): ############################################################################### # 4x4 coordination nfg ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [[1, 2, 3, 14], [1, 1, 1, 1]], [["1/20", "2/20", "3/20", "14/20"], ["1/4", "1/4", "1/4", "1/4"]], True, ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [[1.0, 2.0, 3.0, 14.0], [1, 1, 1, 1]], [[1 / 20, 2 / 20, 3 / 20, 14 / 20], [0.25, 0.25, 0.25, 0.25]], False, @@ -119,8 +139,8 @@ def test_normalize(game, profile_data, expected_data, rational_flag): (games.create_2x2_zero_nfg(), "cooperate", True, "7/9"), ############################################################################### # coordination 4x4 nfg outcome version with strategy labels - (games.create_coord_4x4_nfg(outcome_version=True), "1-1", 0.25, False), - (games.create_coord_4x4_nfg(outcome_version=True), "1-1", "1/4", True), + (games.read_from_file("coordination_4x4_outcome.nfg"), "1-1", 0.25, False), + (games.read_from_file("coordination_4x4_outcome.nfg"), "1-1", "1/4", True), ############################################################################### # stripped-down poker efg (games.create_stripped_down_poker_efg(), "11", 0.25, False), @@ -149,8 +169,8 @@ def test_set_and_get_probability_by_strategy_label( (games.create_2x2_zero_nfg(), "Joe", True, ["7/9", "2/9"]), ############################################################################## # coordination 4x4 nfg outcome version with strategy labels - (games.create_coord_4x4_nfg(), P1, False, [0.25, 0, 0, 0.75]), - (games.create_coord_4x4_nfg(), P1, True, ["1/4", 0, 0, "3/4"]), + (games.read_from_file("coordination_4x4_payoff.nfg"), P1, False, [0.25, 0, 0, 0.75]), + (games.read_from_file("coordination_4x4_payoff.nfg"), P1, True, ["1/4", 0, 0, "3/4"]), ############################################################################## # stripped-down poker efg (games.create_stripped_down_poker_efg(), "Alice", False, [0.25, 0.75, 0, 0]), @@ -189,8 +209,8 @@ def test_set_and_get_probabilities_by_player_label( (games.create_stripped_down_poker_efg(), "Bob", "2", "1/2", True), ############################################################################## # coordination 4x4 nfg outcome version with strategy labels - (games.create_coord_4x4_nfg(outcome_version=True), P1, "1-1", "1/4", True), - (games.create_coord_4x4_nfg(outcome_version=True), P2, "2-1", "1/4", True), + (games.read_from_file("coordination_4x4_outcome.nfg"), P1, "1-1", "1/4", True), + (games.read_from_file("coordination_4x4_outcome.nfg"), P2, "2-1", "1/4", True), ], ) def test_profile_indexing_by_player_and_strategy_label_reference( @@ -214,8 +234,8 @@ def test_profile_indexing_by_player_and_strategy_label_reference( (games.create_stripped_down_poker_efg(), "Alice", "2", False), ############################################################################## # coordination 4x4 nfg outcome version with strategy labels - (games.create_coord_4x4_nfg(outcome_version=True), P1, "2-1", True), - (games.create_coord_4x4_nfg(outcome_version=True), P2, "1-1", True), + (games.read_from_file("coordination_4x4_outcome.nfg"), P1, "2-1", True), + (games.read_from_file("coordination_4x4_outcome.nfg"), P2, "1-1", True), ], ) def test_profile_indexing_by_player_and_invalid_strategy_label( @@ -234,11 +254,41 @@ def test_profile_indexing_by_player_and_invalid_strategy_label( (games.create_stripped_down_poker_efg(), "13", True, KeyError, "player or strategy"), ############################################################################## # coordination 4x4 nfg payoff version (default strategy labels created with duplicates) - (games.create_coord_4x4_nfg(), "1", True, ValueError, "multiple strategies"), - (games.create_coord_4x4_nfg(), "2", True, ValueError, "multiple strategies"), - (games.create_coord_4x4_nfg(), "3", True, ValueError, "multiple strategies"), - (games.create_coord_4x4_nfg(), "4", True, ValueError, "multiple strategies"), - (games.create_coord_4x4_nfg(), "5", True, KeyError, "player or strategy"), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + "1", + True, + ValueError, + "multiple strategies", + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + "2", + True, + ValueError, + "multiple strategies", + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + "3", + True, + ValueError, + "multiple strategies", + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + "4", + True, + ValueError, + "multiple strategies", + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + "5", + True, + KeyError, + "player or strategy", + ), ], ) def test_profile_indexing_by_invalid_strategy_label( @@ -284,11 +334,11 @@ def test_profile_indexing_by_player_and_duplicate_strategy_label(): ############################################################################ # coordination 4x4 nfg outcome version with strategy labels # Player 1 - (games.create_coord_4x4_nfg(outcome_version=True), "1-1", "1/4", True), - (games.create_coord_4x4_nfg(outcome_version=True), "1-1", 0.25, False), + (games.read_from_file("coordination_4x4_outcome.nfg"), "1-1", "1/4", True), + (games.read_from_file("coordination_4x4_outcome.nfg"), "1-1", 0.25, False), # Player 2 - (games.create_coord_4x4_nfg(outcome_version=True), "2-1", "1/4", True), - (games.create_coord_4x4_nfg(outcome_version=True), "2-1", 0.25, False), + (games.read_from_file("coordination_4x4_outcome.nfg"), "2-1", "1/4", True), + (games.read_from_file("coordination_4x4_outcome.nfg"), "2-1", 0.25, False), ], ) def test_profile_indexing_by_strategy_label_reference( @@ -318,10 +368,20 @@ def test_profile_indexing_by_strategy_label_reference( (games.create_stripped_down_poker_efg(), "Bob", ["1/2", "1/2"], True), ############################################################################ # coordination 4x4 nfg - (games.create_coord_4x4_nfg(), P1, [0.25, 0.25, 0.25, 0.25], False), - (games.create_coord_4x4_nfg(), P2, [0.25, 0.25, 0.25, 0.25], False), - (games.create_coord_4x4_nfg(), P1, ["1/4", "1/4", "1/4", "1/4"], True), - (games.create_coord_4x4_nfg(), P2, ["1/4", "1/4", "1/4", "1/4"], True), + (games.read_from_file("coordination_4x4_payoff.nfg"), P1, [0.25, 0.25, 0.25, 0.25], False), + (games.read_from_file("coordination_4x4_payoff.nfg"), P2, [0.25, 0.25, 0.25, 0.25], False), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + P1, + ["1/4", "1/4", "1/4", "1/4"], + True, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + P2, + ["1/4", "1/4", "1/4", "1/4"], + True, + ), ], ) def test_profile_indexing_by_player_label_reference( @@ -342,18 +402,66 @@ def test_profile_indexing_by_player_label_reference( (games.create_2x2_zero_nfg(), True, None, "Joe", 0), ######################################################################### # coordination 4x4 nfg - (games.create_coord_4x4_nfg(), False, None, P1, 0.25), - (games.create_coord_4x4_nfg(), True, None, P1, "1/4"), - (games.create_coord_4x4_nfg(), False, None, P2, 0.25), - (games.create_coord_4x4_nfg(), True, None, P2, "1/4"), - (games.create_coord_4x4_nfg(), False, [[1, 0, 0, 0], [1, 0, 0, 0]], P1, 1), - (games.create_coord_4x4_nfg(), True, [[1, 0, 0, 0], [1, 0, 0, 0]], P1, 1), - (games.create_coord_4x4_nfg(), False, [[1, 0, 0, 0], [1, 0, 0, 0]], P2, 1), - (games.create_coord_4x4_nfg(), True, [[1, 0, 0, 0], [1, 0, 0, 0]], P2, 1), - (games.create_coord_4x4_nfg(), False, [[1, 0, 0, 0], [0, 1, 0, 0]], P1, 0), - (games.create_coord_4x4_nfg(), True, [[1, 0, 0, 0], [0, 1, 0, 0]], P1, 0), - (games.create_coord_4x4_nfg(), False, [[1, 0, 0, 0], [0, 1, 0, 0]], P2, 0), - (games.create_coord_4x4_nfg(), True, [[1, 0, 0, 0], [0, 1, 0, 0]], P2, 0), + (games.read_from_file("coordination_4x4_payoff.nfg"), False, None, P1, 0.25), + (games.read_from_file("coordination_4x4_payoff.nfg"), True, None, P1, "1/4"), + (games.read_from_file("coordination_4x4_payoff.nfg"), False, None, P2, 0.25), + (games.read_from_file("coordination_4x4_payoff.nfg"), True, None, P2, "1/4"), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + False, + [[1, 0, 0, 0], [1, 0, 0, 0]], + P1, + 1, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + True, + [[1, 0, 0, 0], [1, 0, 0, 0]], + P1, + 1, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + False, + [[1, 0, 0, 0], [1, 0, 0, 0]], + P2, + 1, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + True, + [[1, 0, 0, 0], [1, 0, 0, 0]], + P2, + 1, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + False, + [[1, 0, 0, 0], [0, 1, 0, 0]], + P1, + 0, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + True, + [[1, 0, 0, 0], [0, 1, 0, 0]], + P1, + 0, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + False, + [[1, 0, 0, 0], [0, 1, 0, 0]], + P2, + 0, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + True, + [[1, 0, 0, 0], [0, 1, 0, 0]], + P2, + 0, + ), ######################################################################### # stripped-down poker efg (games.create_stripped_down_poker_efg(), False, None, "Alice", -0.25), @@ -406,8 +514,8 @@ def test_payoff_by_label_reference( (games.create_2x2_zero_nfg(), True, "cooperate", 0), ############################################################################## # coordination 4x4 nfg - (games.create_coord_4x4_nfg(outcome_version=True), False, "1-1", 0.25), - (games.create_coord_4x4_nfg(outcome_version=True), True, "1-1", "1/4"), + (games.read_from_file("coordination_4x4_outcome.nfg"), False, "1-1", 0.25), + (games.read_from_file("coordination_4x4_outcome.nfg"), True, "1-1", "1/4"), ############################################################################## # stripped-down poker efg (games.create_stripped_down_poker_efg(), False, "11", 0.5), # Bet/Bet @@ -447,8 +555,8 @@ def test_as_behavior_roundtrip(game: gbt.Game, rational_flag: bool): [ (games.create_2x2_zero_nfg(), False), (games.create_2x2_zero_nfg(), True), - (games.create_coord_4x4_nfg(), False), - (games.create_coord_4x4_nfg(), True), + (games.read_from_file("coordination_4x4_payoff.nfg"), False), + (games.read_from_file("coordination_4x4_payoff.nfg"), True), ], ) def test_as_behavior_error(game: gbt.Game, rational_flag: bool): @@ -464,16 +572,16 @@ def test_as_behavior_error(game: gbt.Game, rational_flag: bool): (games.create_2x2_zero_nfg(), None, True, (0, 0)), ############################################################################### # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), None, False, (0.25, 0.25)), - (games.create_coord_4x4_nfg(), None, True, ("1/4", "1/4")), + (games.read_from_file("coordination_4x4_payoff.nfg"), None, False, (0.25, 0.25)), + (games.read_from_file("coordination_4x4_payoff.nfg"), None, True, ("1/4", "1/4")), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [["1/3", "1/3", "1/3", 0], ["1/3", "1/3", "1/3", 0]], True, ("1/3", "1/3"), ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [["1/3", "1/3", 0, "1/3"], ["1/3", "1/3", "1/3", 0]], True, ("2/9", "2/9"), @@ -525,25 +633,25 @@ def test_payoffs_reference( ############################################################################### # 4x4 coordination nfg ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), None, False, ([0.25, 0.25, 0.25, 0.25], [0.25, 0.25, 0.25, 0.25]), ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), None, True, ([0.25, 0.25, 0.25, 0.25], [0.25, 0.25, 0.25, 0.25]), ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [["1", "0", "0", "0"], ["1", "0", "0", "0"]], True, (["1", "0", "0", "0"], ["1", "0", "0", "0"]), ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [["3/7", "0", "0", "4/7"], ["1/3", "1/3", "1/3", "0"]], True, (["1/3", "1/3", "1/3", "0"], ["3/7", "0", "0", "4/7"]), @@ -591,26 +699,50 @@ def test_strategy_value_reference( (games.create_2x2_zero_nfg(), [[1 / 4, 3 / 4], [2 / 5, 3 / 5]], 0, TOL, False), ############################################################################## # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), None, 0, ZERO, True), - (games.create_coord_4x4_nfg(), None, 0, TOL, False), - (games.create_coord_4x4_nfg(), [[1, 0, 0, 0], [1, 0, 0, 0]], 0, ZERO, True), - (games.create_coord_4x4_nfg(), [[1, 0, 0, 0], [1, 0, 0, 0]], 0, TOL, False), + (games.read_from_file("coordination_4x4_payoff.nfg"), None, 0, ZERO, True), + (games.read_from_file("coordination_4x4_payoff.nfg"), None, 0, TOL, False), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + [[1, 0, 0, 0], [1, 0, 0, 0]], + 0, + ZERO, + True, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + [[1, 0, 0, 0], [1, 0, 0, 0]], + 0, + TOL, + False, + ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [["1/3", "1/2", "1/12", "1/12"], ["3/8", "1/8", "1/4", "1/4"]], "245/2304", ZERO, True, ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [[1 / 3, 1 / 2, 1 / 12, 1 / 12], [3 / 8, 1 / 8, 1 / 4, 1 / 4]], 245 / 2304, TOL, False, ), - (games.create_coord_4x4_nfg(), [["1/3", 0, 0, "2/3"], [1, 0, 0, 0]], "5/9", ZERO, True), - (games.create_coord_4x4_nfg(), [[1 / 3, 0, 0, 2 / 3], [1, 0, 0, 0]], 5 / 9, TOL, False), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + [["1/3", 0, 0, "2/3"], [1, 0, 0, 0]], + "5/9", + ZERO, + True, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + [[1 / 3, 0, 0, 2 / 3], [1, 0, 0, 0]], + 5 / 9, + TOL, + False, + ), ############################################################################## # El Farol bar game efg ( @@ -741,33 +873,45 @@ def test_liap_value_reference( (games.create_2x2_zero_nfg(), [[1 / 4, 3 / 4], [2 / 5, 3 / 5]], [0] * 2, TOL, False), ############################################################################## # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), None, [0] * 2, ZERO, True), - (games.create_coord_4x4_nfg(), None, [0] * 2, TOL, False), - (games.create_coord_4x4_nfg(), [[1, 0, 0, 0], [1, 0, 0, 0]], [0] * 2, ZERO, True), - (games.create_coord_4x4_nfg(), [[1, 0, 0, 0], [1, 0, 0, 0]], [0] * 2, TOL, False), + (games.read_from_file("coordination_4x4_payoff.nfg"), None, [0] * 2, ZERO, True), + (games.read_from_file("coordination_4x4_payoff.nfg"), None, [0] * 2, TOL, False), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), + [[1, 0, 0, 0], [1, 0, 0, 0]], + [0] * 2, + ZERO, + True, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + [[1, 0, 0, 0], [1, 0, 0, 0]], + [0] * 2, + TOL, + False, + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), [["1/3", "1/2", "1/12", "1/12"], ["3/8", "1/8", "1/4", "1/4"]], ["7/48", "13/48"], ZERO, True, ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [[1 / 3, 1 / 2, 1 / 12, 1 / 12], [3 / 8, 1 / 8, 1 / 4, 1 / 4]], [7 / 48, 13 / 48], TOL, False, ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [["1/3", 0, 0, "2/3"], [1, 0, 0, 0]], ["2/3", "1/3"], ZERO, True, ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [[1 / 3, 0, 0, 2 / 3], [1, 0, 0, 0]], [2 / 3, 1 / 3], TOL, @@ -900,8 +1044,8 @@ def test_player_regret_max_regret_reference( [ ################################################################################# # 4x4 coordination nfg - (games.create_coord_4x4_nfg(), False), - (games.create_coord_4x4_nfg(), True), + (games.read_from_file("coordination_4x4_payoff.nfg"), False), + (games.read_from_file("coordination_4x4_payoff.nfg"), True), ################################################################################# # Zero matrix nfg (games.create_2x2_zero_nfg(), False), @@ -936,13 +1080,13 @@ def test_strategy_regret_consistency(game: gbt.Game, rational_flag: bool): ################################################################################# # 4x4 coordination nfg ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [["1/5", "2/5", "0/5", "2/5"], ["3/8", "1/4", "3/8", "0/4"]], ZERO, True, ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [[1 / 3, 1 / 3, 0 / 3, 1 / 3], [1 / 4, 1 / 4, 3 / 8, 1 / 8]], TOL, False, @@ -1029,13 +1173,13 @@ def test_liap_value_consistency( ################################################################################# # 4x4 coordination nfg ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [["1/5", "2/5", "0/5", "2/5"], ["3/8", "1/4", "3/8", "0/4"]], ZERO, True, ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [[1 / 3, 1 / 3, 0 / 3, 1 / 3], [1 / 4, 1 / 4, 3 / 8, 1 / 8]], TOL, False, @@ -1119,7 +1263,7 @@ def test_player_regret_max_regret_consistency( ################################################################################# # 4x4 coordination nfg ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [["1/5", "2/5", "0/5", "2/5"], ["3/8", "1/4", "3/8", "0/4"]], [["1/5", "2/5", "0/5", "2/5"], ["1/4", "3/8", "0/4", "3/8"]], gbt.Rational("3/5"), @@ -1127,7 +1271,7 @@ def test_player_regret_max_regret_consistency( True, ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [[1 / 5, 2 / 5, 0 / 5, 2 / 5], [3 / 8, 1 / 4, 3 / 8, 0 / 4]], [[1 / 5, 2 / 5, 0 / 5, 2 / 5], [1 / 4, 3 / 8, 0 / 4, 3 / 8]], 3 / 5, @@ -1221,12 +1365,17 @@ def test_linearity_payoff_property( ################################################################################# # 4x4 coordination nfg ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [["1/5", "2/5", "0/5", "2/5"], ["1/4", "3/8", "0/4", "3/8"]], ZERO, True, ), - (games.create_coord_4x4_nfg(), [[0.2, 0.4, 0, 0.4], [1 / 4, 3 / 8, 0, 3 / 8]], TOL, False), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + [[0.2, 0.4, 0, 0.4], [1 / 4, 3 / 8, 0, 3 / 8]], + TOL, + False, + ), ( gbt.Game.from_arrays([[1, 2], [-3, 4]], [[-4, 3], [2, 1]]), [[1 / 2, 1 / 2], [3 / 5, 2 / 5]], @@ -1299,7 +1448,7 @@ def test_payoff_and_strategy_value_consistency( ################################################################################# # 4x4 coordination nfg ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [["1/1111", "10/1111", "100/1111", "1000/1111"], ["1/4", "1/8", "3/8", "1/4"]], [["1/1111", "10/1111", "99/1111", "1001/1111"], ["1/4", "1/8", "3/8", "1/4"]], "1/2", @@ -1307,7 +1456,7 @@ def test_payoff_and_strategy_value_consistency( ZERO, ), ( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), [[1 / 1111, 10 / 1111, 100 / 1111, 1000 / 1111], [1 / 4, 1 / 8, 3 / 8, 1 / 4]], [[1 / 1111, 10 / 1111, 99 / 1111, 1001 / 1111], [1 / 4, 1 / 8, 3 / 8, 1 / 4]], 1 / 2, @@ -1426,7 +1575,7 @@ def _get_and_check_answers( ####################### # 4x4 coordination nfg pytest.param( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), PROBS_1A_doub, PROBS_2A_doub, False, @@ -1435,7 +1584,7 @@ def _get_and_check_answers( id="payoffs_coord_doub", ), pytest.param( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), PROBS_1A_rat, PROBS_2A_rat, True, @@ -1485,7 +1634,7 @@ def _get_and_check_answers( # regret (for strategies) # 4x4 coordination nfg pytest.param( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), PROBS_1A_doub, PROBS_2A_doub, False, @@ -1494,7 +1643,7 @@ def _get_and_check_answers( id="regret_coord_doub", ), pytest.param( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), PROBS_1A_rat, PROBS_2A_rat, True, @@ -1544,7 +1693,7 @@ def _get_and_check_answers( # strategy_value (for strategies) # 4x4 coordination nfg pytest.param( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), PROBS_1A_doub, PROBS_2A_doub, False, @@ -1553,7 +1702,7 @@ def _get_and_check_answers( id="strat_value_coord_doub", ), pytest.param( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), PROBS_1A_rat, PROBS_2A_rat, True, @@ -1603,7 +1752,7 @@ def _get_and_check_answers( # strategy_value_deriv (for strategies * strategies) # 4x4 coordination nfg pytest.param( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), PROBS_1A_doub, PROBS_2A_doub, False, @@ -1614,7 +1763,7 @@ def _get_and_check_answers( id="strat_value_deriv_coord_doub", ), pytest.param( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), PROBS_1A_rat, PROBS_2A_rat, True, @@ -1674,7 +1823,7 @@ def _get_and_check_answers( # liap_value (of profile, hence [1] for objects_to_test, any singleton collection would do) # 4x4 coordination nfg pytest.param( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), PROBS_1A_doub, PROBS_2A_doub, False, @@ -1683,7 +1832,7 @@ def _get_and_check_answers( id="liap_value_coord_doub", ), pytest.param( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), PROBS_1A_rat, PROBS_2A_rat, True, @@ -1733,7 +1882,7 @@ def _get_and_check_answers( # max_regret (of profile, hence [1] for objects_to_test, any singleton collection would do) # 4x4 coordination nfg pytest.param( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), PROBS_1A_doub, PROBS_2A_doub, False, @@ -1742,7 +1891,7 @@ def _get_and_check_answers( id="max_regret_coord_doub", ), pytest.param( - games.create_coord_4x4_nfg(), + games.read_from_file("coordination_4x4_payoff.nfg"), PROBS_1A_rat, PROBS_2A_rat, True, From 543c937fe1d08dfb8960d5c08101b3768ffc0036 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 13:50:07 +0000 Subject: [PATCH 05/44] removed create_2x2_zero_nfg from games.py --- tests/games.py | 28 ----------- tests/test_io.py | 8 ++-- tests/test_mixed.py | 113 +++++++++++++++++++++++++++++++++----------- 3 files changed, 89 insertions(+), 60 deletions(-) diff --git a/tests/games.py b/tests/games.py index 96bccffa1..8197f8d93 100644 --- a/tests/games.py +++ b/tests/games.py @@ -5,7 +5,6 @@ from abc import ABC, abstractmethod import numpy as np -import pytest import pygambit as gbt @@ -39,33 +38,6 @@ def create_efg_corresponding_to_bimatrix_game( return g -################################################################################################ -# 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 - with pytest.warns(FutureWarning): - game.players["Dan"].strategies[1].label = "defect" - - return game - - ################################################################################################ # Extensive-form games (efg) diff --git a/tests/test_io.py b/tests/test_io.py index 05046b8fa..c0bf876ec 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -6,7 +6,7 @@ import pygambit as gbt -from .games import create_2x2_zero_nfg, create_selten_horse_game_efg +from . import games @pytest.mark.parametrize("game_path", glob(os.path.join("tests", "test_games", "*.efg"))) @@ -112,7 +112,7 @@ def test_write_latex(): def test_read_write_efg(): - efg_game = create_selten_horse_game_efg() + efg_game = games.create_selten_horse_game_efg() serialized_efg_game = efg_game.to_efg() deserialized_efg_game = gbt.read_efg(io.BytesIO(serialized_efg_game.encode())) double_serialized_efg_game = deserialized_efg_game.to_efg() @@ -120,7 +120,7 @@ def test_read_write_efg(): def test_read_write_nfg(): - nfg_game = create_2x2_zero_nfg() + nfg_game = games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg") serialized_nfg_game = nfg_game.to_nfg() deserialized_nfg_game = gbt.read_nfg( io.BytesIO(serialized_nfg_game.encode()), normalize_labels=False @@ -130,7 +130,7 @@ def test_read_write_nfg(): def test_read_write_nfg_normalize(): - nfg_game = create_2x2_zero_nfg() + nfg_game = games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg") serialized_nfg_game = nfg_game.to_nfg() deserialized_nfg_game = gbt.read_nfg( io.BytesIO(serialized_nfg_game.encode()), normalize_labels=True diff --git a/tests/test_mixed.py b/tests/test_mixed.py index 48d7fe10f..731f6f35f 100644 --- a/tests/test_mixed.py +++ b/tests/test_mixed.py @@ -75,8 +75,12 @@ def test_normalize_zero_value_error(game, profile_data, rational_flag): ), ############################################################################### # zero matrix nfg - (games.create_2x2_zero_nfg(), [[1, 0], [0, -1]], True), - (games.create_2x2_zero_nfg(), [[1.0, 1.0], [0, -1.0]], False), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), [[1, 0], [0, -1]], True), + ( + games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), + [[1.0, 1.0], [0, -1.0]], + False, + ), ############################################################################### # centipede with chance efg (games.create_centipede_game_with_chance_efg(), [[-1, 0, 0, 0], [1, 0, 0, 0]], True), @@ -135,8 +139,8 @@ def test_normalize(game, profile_data, expected_data, rational_flag): [ ############################################################################## # zero matrix nfg - (games.create_2x2_zero_nfg(), "cooperate", False, 0.72), - (games.create_2x2_zero_nfg(), "cooperate", True, "7/9"), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), "cooperate", False, 0.72), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), "cooperate", True, "7/9"), ############################################################################### # coordination 4x4 nfg outcome version with strategy labels (games.read_from_file("coordination_4x4_outcome.nfg"), "1-1", 0.25, False), @@ -165,8 +169,8 @@ def test_set_and_get_probability_by_strategy_label( [ ############################################################################## # zero matrix nfg - (games.create_2x2_zero_nfg(), "Joe", False, [0.72, 0.28]), - (games.create_2x2_zero_nfg(), "Joe", True, ["7/9", "2/9"]), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), "Joe", False, [0.72, 0.28]), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), "Joe", True, ["7/9", "2/9"]), ############################################################################## # coordination 4x4 nfg outcome version with strategy labels (games.read_from_file("coordination_4x4_payoff.nfg"), P1, False, [0.25, 0, 0, 0.75]), @@ -306,7 +310,7 @@ def test_profile_indexing_by_invalid_strategy_label( def test_profile_indexing_by_player_and_duplicate_strategy_label(): - game = games.create_2x2_zero_nfg() + game = games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg") profile = game.mixed_strategy_profile() with pytest.raises(ValueError): profile["Dan"]["defect"] @@ -398,8 +402,8 @@ def test_profile_indexing_by_player_label_reference( [ ######################################################################### # zero matrix nfg - (games.create_2x2_zero_nfg(), False, None, "Joe", 0), - (games.create_2x2_zero_nfg(), True, None, "Joe", 0), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), False, None, "Joe", 0), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), True, None, "Joe", 0), ######################################################################### # coordination 4x4 nfg (games.read_from_file("coordination_4x4_payoff.nfg"), False, None, P1, 0.25), @@ -510,8 +514,8 @@ def test_payoff_by_label_reference( [ ############################################################################## # zero matrix nfg - (games.create_2x2_zero_nfg(), False, "cooperate", 0), - (games.create_2x2_zero_nfg(), True, "cooperate", 0), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), False, "cooperate", 0), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), True, "cooperate", 0), ############################################################################## # coordination 4x4 nfg (games.read_from_file("coordination_4x4_outcome.nfg"), False, "1-1", 0.25), @@ -553,8 +557,8 @@ def test_as_behavior_roundtrip(game: gbt.Game, rational_flag: bool): @pytest.mark.parametrize( "game,rational_flag", [ - (games.create_2x2_zero_nfg(), False), - (games.create_2x2_zero_nfg(), True), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), False), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), True), (games.read_from_file("coordination_4x4_payoff.nfg"), False), (games.read_from_file("coordination_4x4_payoff.nfg"), True), ], @@ -569,7 +573,7 @@ def test_as_behavior_error(game: gbt.Game, rational_flag: bool): [ ############################################################################### # zero matrix nfg - (games.create_2x2_zero_nfg(), None, True, (0, 0)), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), None, True, (0, 0)), ############################################################################### # 4x4 coordination nfg (games.read_from_file("coordination_4x4_payoff.nfg"), None, False, (0.25, 0.25)), @@ -628,8 +632,8 @@ def test_payoffs_reference( [ ############################################################################### # zero matrix nfg - (games.create_2x2_zero_nfg(), None, False, ([0, 0], [0, 0])), - (games.create_2x2_zero_nfg(), None, True, ([0, 0], [0, 0])), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), None, False, ([0, 0], [0, 0])), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), None, True, ([0, 0], [0, 0])), ############################################################################### # 4x4 coordination nfg ( @@ -693,10 +697,34 @@ def test_strategy_value_reference( [ ############################################################################## # Zero matrix nfg, all liap_values are zero - (games.create_2x2_zero_nfg(), [["3/4", "1/4"], ["2/5", "3/5"]], 0, ZERO, True), - (games.create_2x2_zero_nfg(), [["1/2", "1/2"], ["1/2", "1/2"]], 0, ZERO, True), - (games.create_2x2_zero_nfg(), [[1, 0], [1, 0]], 0, ZERO, True), - (games.create_2x2_zero_nfg(), [[1 / 4, 3 / 4], [2 / 5, 3 / 5]], 0, TOL, False), + ( + games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), + [["3/4", "1/4"], ["2/5", "3/5"]], + 0, + ZERO, + True, + ), + ( + games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), + [["1/2", "1/2"], ["1/2", "1/2"]], + 0, + ZERO, + True, + ), + ( + games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), + [[1, 0], [1, 0]], + 0, + ZERO, + True, + ), + ( + games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), + [[1 / 4, 3 / 4], [2 / 5, 3 / 5]], + 0, + TOL, + False, + ), ############################################################################## # 4x4 coordination nfg (games.read_from_file("coordination_4x4_payoff.nfg"), None, 0, ZERO, True), @@ -867,10 +895,34 @@ def test_liap_value_reference( [ ############################################################################## # Zero matrix nfg, all liap_values are zero - (games.create_2x2_zero_nfg(), [["3/4", "1/4"], ["2/5", "3/5"]], [0] * 2, ZERO, True), - (games.create_2x2_zero_nfg(), [["1/2", "1/2"], ["1/2", "1/2"]], [0] * 2, ZERO, True), - (games.create_2x2_zero_nfg(), [[1, 0], [1, 0]], [0] * 2, ZERO, True), - (games.create_2x2_zero_nfg(), [[1 / 4, 3 / 4], [2 / 5, 3 / 5]], [0] * 2, TOL, False), + ( + games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), + [["3/4", "1/4"], ["2/5", "3/5"]], + [0] * 2, + ZERO, + True, + ), + ( + games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), + [["1/2", "1/2"], ["1/2", "1/2"]], + [0] * 2, + ZERO, + True, + ), + ( + games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), + [[1, 0], [1, 0]], + [0] * 2, + ZERO, + True, + ), + ( + games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), + [[1 / 4, 3 / 4], [2 / 5, 3 / 5]], + [0] * 2, + TOL, + False, + ), ############################################################################## # 4x4 coordination nfg (games.read_from_file("coordination_4x4_payoff.nfg"), None, [0] * 2, ZERO, True), @@ -1048,8 +1100,8 @@ def test_player_regret_max_regret_reference( (games.read_from_file("coordination_4x4_payoff.nfg"), True), ################################################################################# # Zero matrix nfg - (games.create_2x2_zero_nfg(), False), - (games.create_2x2_zero_nfg(), True), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), False), + (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), True), ################################################################################# # El Farol bar game efg (games.create_el_farol_bar_game_efg(), False), @@ -1281,7 +1333,7 @@ def test_player_regret_max_regret_consistency( ################################################################################# # Zero matrix nfg ( - games.create_2x2_zero_nfg(), + games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), [["1/4", "3/4"], ["3/5", "2/5"]], [["1/2", "1/2"], ["3/5", "2/5"]], gbt.Rational("5/6"), @@ -1384,7 +1436,12 @@ def test_linearity_payoff_property( ), ################################################################################# # Zero matrix nfg - (games.create_2x2_zero_nfg(), [["4/5", "1/5"], ["4/7", "3/7"]], ZERO, True), + ( + games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), + [["4/5", "1/5"], ["4/7", "3/7"]], + ZERO, + True, + ), ################################################################################# # Centipede game with chance ( From c8f1752593497f7eefed85e86285ac46b9cbce7b Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 14:03:29 +0000 Subject: [PATCH 06/44] removed create_STOC_simplified{,2} from games.py (which were not used anyway) --- tests/games.py | 86 -------------------------------------------------- 1 file changed, 86 deletions(-) diff --git a/tests/games.py b/tests/games.py index 8197f8d93..8c33fe015 100644 --- a/tests/games.py +++ b/tests/games.py @@ -801,92 +801,6 @@ def create_problem_example_efg() -> gbt.Game: return g -def create_STOC_simplified() -> gbt.Game: - """ - """ - g = gbt.Game.new_tree(players=["1", "2"], title="") - g.append_move(g.root, g.players.chance, actions=["1", "2"]) - g.set_chance_probs(g.root.infoset, [0.2, 0.8]) - g.append_move(g.root.children[0], player="1", actions=["l", "r"]) - g.append_move(g.root.children[1], player="1", actions=["c", "d"]) - g.append_move(g.root.children[0].children[1], player="2", actions=["p", "q"]) - g.append_move( - g.root.children[0].children[1].children[0], player="1", actions=["L", "R"] - ) - g.append_infoset( - g.root.children[0].children[1].children[1], - g.root.children[0].children[1].children[0].infoset, - ) - g.set_outcome( - g.root.children[0].children[0], - outcome=g.add_outcome(payoffs=[5, -5], label="l"), - ) - g.set_outcome( - g.root.children[0].children[1].children[0].children[0], - outcome=g.add_outcome(payoffs=[10, -10], label="rpL"), - ) - g.set_outcome( - g.root.children[0].children[1].children[0].children[1], - outcome=g.add_outcome(payoffs=[15, -15], label="rpR"), - ) - g.set_outcome( - g.root.children[0].children[1].children[1].children[0], - outcome=g.add_outcome(payoffs=[20, -20], label="rqL"), - ) - g.set_outcome( - g.root.children[0].children[1].children[1].children[1], - outcome=g.add_outcome(payoffs=[-5, 5], label="rqR"), - ) - g.set_outcome( - g.root.children[1].children[0], - outcome=g.add_outcome(payoffs=[10, -10], label="c"), - ) - g.set_outcome( - g.root.children[1].children[1], - outcome=g.add_outcome(payoffs=[20, -20], label="d"), - ) - return g - - -def create_STOC_simplified2() -> gbt.Game: - """ - """ - g = gbt.Game.new_tree(players=["1", "2"], title="") - g.append_move(g.root, g.players.chance, actions=["1", "2"]) - g.set_chance_probs(g.root.infoset, [0.2, 0.8]) - g.append_move(g.root.children[0], player="1", actions=["r"]) - g.append_move(g.root.children[1], player="1", actions=["c"]) - g.append_move(g.root.children[0].children[0], player="2", actions=["p", "q"]) - g.append_move( - g.root.children[0].children[0].children[0], player="1", actions=["L", "R"] - ) - g.append_infoset( - g.root.children[0].children[0].children[1], - g.root.children[0].children[0].children[0].infoset, - ) - g.set_outcome( - g.root.children[0].children[0].children[0].children[0], - outcome=g.add_outcome(payoffs=[10, -10], label="rpL"), - ) - g.set_outcome( - g.root.children[0].children[0].children[0].children[1], - outcome=g.add_outcome(payoffs=[15, -15], label="rpR"), - ) - g.set_outcome( - g.root.children[0].children[0].children[1].children[0], - outcome=g.add_outcome(payoffs=[20, -20], label="rqL"), - ) - g.set_outcome( - g.root.children[0].children[0].children[1].children[1], - outcome=g.add_outcome(payoffs=[-5, 5], label="rqR"), - ) - g.set_outcome( - g.root.children[1].children[0], - outcome=g.add_outcome(payoffs=[10, -10], label="c"), - ) - return g - - def create_seq_form_STOC_paper_zero_sum_2_player_efg() -> gbt.Game: """ Example from From 68870996a1be9850c6ff86b661726750e43fa5a8 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 14:11:52 +0000 Subject: [PATCH 07/44] added new .nfg files --- .../2x2_bimatrix_all_zero_payoffs.nfg | 14 +++++++++++ tests/test_games/STOC.efg | 24 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 tests/test_games/2x2_bimatrix_all_zero_payoffs.nfg create mode 100644 tests/test_games/STOC.efg diff --git a/tests/test_games/2x2_bimatrix_all_zero_payoffs.nfg b/tests/test_games/2x2_bimatrix_all_zero_payoffs.nfg new file mode 100644 index 000000000..7a290820d --- /dev/null +++ b/tests/test_games/2x2_bimatrix_all_zero_payoffs.nfg @@ -0,0 +1,14 @@ +NFG 1 R "2x2 bimatrix game with all zero payoffs, with strategy labels" { "Joe" "Dan" } + +{ { "cooperate" "defect" } +{ "defect" "defect" } +} +"" + +{ +{ "" 0, 0 } +{ "" 0, 0 } +{ "" 0, 0 } +{ "" 0, 0 } +} +1 2 3 4 diff --git a/tests/test_games/STOC.efg b/tests/test_games/STOC.efg new file mode 100644 index 000000000..73c230af5 --- /dev/null +++ b/tests/test_games/STOC.efg @@ -0,0 +1,24 @@ +EFG 2 R "From STOC'94 paper" { "1" "2" } +"" + +c "" 1 "" { "1" 0.2 "2" 0.2 "3" 0.2 "4" 0.4 } 0 +p "" 1 1 "0" { "l" "r" } 0 +t "" 1 "l" { 5, -5 } +p "" 2 1 "01" { "p" "q" } 0 +p "" 1 3 "010" { "L" "R" } 0 +t "" 2 "rpL" { 10, -10 } +t "" 3 "rpR" { 15, -15 } +p "" 1 3 "010" { "L" "R" } 0 +t "" 4 "rqL" { 20, -20 } +t "" 5 "rqR" { -5, 5 } +p "" 1 2 "1" { "c" "d" } 0 +t "" 6 "c" { 10, -10 } +t "" 7 "d" { 20, -20 } +p "" 1 2 "1" { "c" "d" } 0 +p "" 2 2 "20" { "s" "t" } 0 +t "" 8 "cs" { 20, -20 } +t "" 9 "ct" { 50, -50 } +p "" 2 2 "20" { "s" "t" } 0 +t "" 10 "ds" { 30, -30 } +t "" 11 "dt" { 15, -15 } +t "" 12 "nothing" { 5, -5 } From 00b28c308ca3c8f22484e9a6b975a9a36c569dea Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 14:20:55 +0000 Subject: [PATCH 08/44] removed create_seq_form_STOC_paper_zero_sum_2_player_efg from games.py --- tests/games.py | 81 ------------------- ...m_efg_from_sequence_form_STOC94_paper.efg} | 9 ++- tests/test_nash.py | 2 +- 3 files changed, 8 insertions(+), 84 deletions(-) rename tests/test_games/{STOC.efg => zerosum_efg_from_sequence_form_STOC94_paper.efg} (73%) diff --git a/tests/games.py b/tests/games.py index 8c33fe015..20f997f7b 100644 --- a/tests/games.py +++ b/tests/games.py @@ -801,87 +801,6 @@ def create_problem_example_efg() -> gbt.Game: return g -def create_seq_form_STOC_paper_zero_sum_2_player_efg() -> gbt.Game: - """ - Example from - - Fast Algorithms for Finding Randomized Strategies in Game Trees (1994) - Koller, Megiddo, von Stengel - """ - g = gbt.Game.new_tree(players=["1", "2"], title="From STOC'94 paper") - g.append_move(g.root, g.players.chance, actions=["1", "2", "3", "4"]) - g.set_chance_probs(g.root.infoset, [0.2, 0.2, 0.2, 0.4]) - g.append_move(g.root.children[0], player="1", actions=["l", "r"]) - g.append_move(g.root.children[1], player="1", actions=["c", "d"]) - g.append_infoset(g.root.children[2], g.root.children[1].infoset) - g.append_move(g.root.children[0].children[1], player="2", actions=["p", "q"]) - g.append_move( - g.root.children[0].children[1].children[0], player="1", actions=["L", "R"] - ) - g.append_infoset( - g.root.children[0].children[1].children[1], - g.root.children[0].children[1].children[0].infoset, - ) - g.append_move(g.root.children[2].children[0], player="2", actions=["s", "t"]) - g.append_infoset( - g.root.children[2].children[1], g.root.children[2].children[0].infoset - ) - - g.set_outcome( - g.root.children[0].children[0], - outcome=g.add_outcome(payoffs=[5, -5], label="l"), - ) - g.set_outcome( - g.root.children[0].children[1].children[0].children[0], - outcome=g.add_outcome(payoffs=[10, -10], label="rpL"), - ) - g.set_outcome( - g.root.children[0].children[1].children[0].children[1], - outcome=g.add_outcome(payoffs=[15, -15], label="rpR"), - ) - g.set_outcome( - g.root.children[0].children[1].children[1].children[0], - outcome=g.add_outcome(payoffs=[20, -20], label="rqL"), - ) - g.set_outcome( - g.root.children[0].children[1].children[1].children[1], - outcome=g.add_outcome(payoffs=[-5, 5], label="rqR"), - ) - g.set_outcome( - g.root.children[1].children[0], - outcome=g.add_outcome(payoffs=[10, -10], label="c"), - ) - g.set_outcome( - g.root.children[1].children[1], - outcome=g.add_outcome(payoffs=[20, -20], label="d"), - ) - g.set_outcome( - g.root.children[2].children[0].children[0], - outcome=g.add_outcome(payoffs=[20, -20], label="cs"), - ) - g.set_outcome( - g.root.children[2].children[0].children[1], - outcome=g.add_outcome(payoffs=[50, -50], label="ct"), - ) - g.set_outcome( - g.root.children[2].children[1].children[0], - outcome=g.add_outcome(payoffs=[30, -30], label="ds"), - ) - g.set_outcome( - g.root.children[2].children[1].children[1], - outcome=g.add_outcome(payoffs=[15, -15], label="dt"), - ) - g.set_outcome( - g.root.children[3], outcome=g.add_outcome(payoffs=[5, -5], label="nothing") - ) - g.root.children[0].infoset.label = "0" - g.root.children[1].infoset.label = "1" - g.root.children[0].children[1].infoset.label = "01" - g.root.children[2].children[0].infoset.label = "20" - g.root.children[0].children[1].children[0].infoset.label = "010" - return g - - 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"]) diff --git a/tests/test_games/STOC.efg b/tests/test_games/zerosum_efg_from_sequence_form_STOC94_paper.efg similarity index 73% rename from tests/test_games/STOC.efg rename to tests/test_games/zerosum_efg_from_sequence_form_STOC94_paper.efg index 73c230af5..750ff2de7 100644 --- a/tests/test_games/STOC.efg +++ b/tests/test_games/zerosum_efg_from_sequence_form_STOC94_paper.efg @@ -1,5 +1,10 @@ -EFG 2 R "From STOC'94 paper" { "1" "2" } -"" +EFG 2 R "Two-player zero-sum EFG from sequence form STOC'94 paper" { "1" "2" } +" +Example from + +Fast Algorithms for Finding Randomized Strategies in Game Trees (STOC 1994) +Koller, Megiddo, von Stengel +" c "" 1 "" { "1" 0.2 "2" 0.2 "3" 0.2 "4" 0.4 } 0 p "" 1 1 "0" { "l" "r" } 0 diff --git a/tests/test_nash.py b/tests/test_nash.py index f70ac4f2d..d635ffee1 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -758,7 +758,7 @@ def test_lp_behavior_double(): ], ), ( - games.create_seq_form_STOC_paper_zero_sum_2_player_efg(), + games.read_from_file("zerosum_efg_from_sequence_form_STOC94_paper.efg"), [ [[0, 1], ["2/3", "1/3"], ["1/3", "2/3"]], [["5/6", "1/6"], ["5/9", "4/9"]], From 2cfa8a1b4a726d47046905aa493f2d76fb149416 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 14:22:57 +0000 Subject: [PATCH 09/44] removed create_problem_example_efg from games.py --- tests/games.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/games.py b/tests/games.py index 20f997f7b..90c8c6563 100644 --- a/tests/games.py +++ b/tests/games.py @@ -788,19 +788,6 @@ def create_reduction_both_players_payoff_ties_efg() -> gbt.Game: return g -def create_problem_example_efg() -> gbt.Game: - g = gbt.Game.new_tree(players=["1", "2"], title="") - g.append_move(g.root, player="1", actions=["L", "R"]) - # do the second child first on purpose to diverge from sort infosets order - g.append_move(g.root.children[1], "2", actions=["l2", "r2"]) - g.append_move(g.root.children[0], "2", actions=["l1", "r1"]) - g.set_outcome(g.root.children[0].children[0], outcome=g.add_outcome(payoffs=[5, -5])) - g.set_outcome(g.root.children[0].children[1], outcome=g.add_outcome(payoffs=[2, -2])) - g.set_outcome(g.root.children[1].children[0], outcome=g.add_outcome(payoffs=[-5, 5])) - g.set_outcome(g.root.children[1].children[1], outcome=g.add_outcome(payoffs=[-2, 2])) - return g - - 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"]) From f2a80394f8b45bfafeaa7a858d773a61e72b706d Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 15:51:52 +0000 Subject: [PATCH 10/44] removed create_chance_in_middle_efg from games.py --- tests/games.py | 69 ++++++++++++----------------------------- tests/test_extensive.py | 4 +-- tests/test_nash.py | 12 +++---- 3 files changed, 27 insertions(+), 58 deletions(-) diff --git a/tests/games.py b/tests/games.py index 90c8c6563..12e9a91ce 100644 --- a/tests/games.py +++ b/tests/games.py @@ -44,6 +44,8 @@ def create_efg_corresponding_to_bimatrix_game( def create_2x2_zero_sum_efg(missing_term_outcome: bool = False) -> gbt.Game: """ + TODO: use create_efg_corresponding_to_bimatrix_game + EFG corresponding to 2x2 zero-sum game (I,-I). If missing_term_outcome, the terminal node after "T" then "r" does not have an outcome. """ @@ -85,6 +87,7 @@ def create_perfect_info_with_chance_efg() -> gbt.Game: 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")) + g.to_efg("perfect_info_with_chance.efg") return g @@ -129,6 +132,8 @@ def create_three_action_internal_outcomes_efg(nonterm_outcomes: bool = False) -> 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) + tmp = "_nonterm_outcomes_and_missing_term_outcomes" if nonterm_outcomes else "" + g.to_efg(f"2_player_chance_3_actions{tmp}.efg") return g @@ -151,6 +156,8 @@ def create_entry_accomodation_efg(nonterm_outcomes: bool = False) -> gbt.Game: 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])) + tmp = "_with_nonterm_outcomes" if nonterm_outcomes else "" + g.to_efg(f"entry_accomodation{tmp}.efg") return g @@ -173,56 +180,8 @@ def create_non_zero_sum_lacking_outcome_efg(missing_term_outcome: bool = False) 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) + tmp = "_missing_term_outcome" if missing_term_outcome else "" + g.to_efg(f"non_zero_sum_2_player{tmp}.efg") return g @@ -246,6 +205,7 @@ def create_large_payoff_game_efg() -> gbt.Game: 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) + g.to_efg("large_payoff_game.efg") return g @@ -294,11 +254,15 @@ def create_3_player_with_internal_outcomes_efg(nonterm_outcomes: bool = False) - 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) + tmp = "_with_nonterm_outcomes" if nonterm_outcomes else "" + g.to_efg(f"3_player{tmp}.efg") return g def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game: """ + TODO: use create_efg_corresponding_to_bimatrix_game + The version with_neutral_outcome adds a (0,0) payoff outcomes at a non-terminal node. """ g = gbt.Game.new_tree( @@ -751,6 +715,7 @@ def create_reduction_generic_payoffs_efg() -> gbt.Game: ) g.set_outcome(g.root.children[3], g.add_outcome([12, -12], label="d")) + g.to_efg("reduction_generic_payoffs.efg") return g @@ -763,6 +728,7 @@ def create_reduction_one_player_generic_payoffs_efg() -> gbt.Game: g.set_outcome(g.root.children[1], g.add_outcome([3])) g.set_outcome(g.root.children[2], g.add_outcome([4])) g.set_outcome(g.root.children[3], g.add_outcome([5])) + g.to_efg("reduction_one_player_generic_payoffs.efg") return g @@ -785,6 +751,7 @@ def create_reduction_both_players_payoff_ties_efg() -> gbt.Game: g.set_outcome(g.root.children[2].children[1].children[0], g.add_outcome([7, 8])) g.set_outcome(g.root.children[2].children[1].children[1], g.add_outcome([2, 2])) g.set_outcome(g.root.children[3], g.add_outcome([6, 4])) + g.to_efg("reduction_both_players_payoff_ties_GTE_survey.efg") return g @@ -815,6 +782,8 @@ def create_two_player_perfect_info_win_lose_efg(nonterm_outcomes: bool = False) 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")) + tmp = "_with_nonterm_outcomes" if nonterm_outcomes else "" + g.to_efg(f"two_player_perfect_info_win_lose{tmp}.efg") return g diff --git a/tests/test_extensive.py b/tests/test_extensive.py index 41528afbe..59d0458ac 100644 --- a/tests/test_extensive.py +++ b/tests/test_extensive.py @@ -409,8 +409,8 @@ def test_reduced_strategic_form( 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.read_from_file("chance_in_middle.efg"), + games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg") ), ( games.create_non_zero_sum_lacking_outcome_efg(), diff --git a/tests/test_nash.py b/tests/test_nash.py index d635ffee1..2eebeb07c 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -279,14 +279,14 @@ def test_enummixed_rational(game: gbt.Game, mixed_strategy_prof_data: list): ############################################################################## ############################################################################## ( - games.create_chance_in_middle_efg(), + games.read_from_file("chance_in_middle.efg"), [[[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], ], # [[[1, 0], [1, 0], [1, 0], [0, 0], [0, 0]], [[0, 1], [1, 0]]], # [[[0, 1], [0, 0], [0, 0], [1, 0], [1, 0]], [[1, 0], [0, 1]]], 1, # subsequent eqs have undefined infosets; include after #issue 660 ), ( - games.create_chance_in_middle_efg(nonterm_outcomes=True), + games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg"), [[[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], ], # [[[1, 0], [1, 0], [1, 0], [0, 0], [0, 0]], [[0, 1], [1, 0]]], # [[[0, 1], [0, 0], [0, 0], [1, 0], [1, 0]], [[1, 0], [0, 1]]], @@ -587,14 +587,14 @@ def test_lcp_behavior_double(): ], ), ( - games.create_chance_in_middle_efg(), + games.read_from_file("chance_in_middle.efg"), [ [["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]] ] ), ( - games.create_chance_in_middle_efg(nonterm_outcomes=True), + games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg"), [ [["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]] @@ -791,14 +791,14 @@ def test_lp_behavior_double(): ], ), ( - games.create_chance_in_middle_efg(), + games.read_from_file("chance_in_middle.efg"), [ [["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]] ], ), ( - games.create_chance_in_middle_efg(nonterm_outcomes=True), + games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg"), [ [["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]] From e0f16fb8a7ebbe3047c7491409de85e0dfa26953 Mon Sep 17 00:00:00 2001 From: rahulsavani Date: Wed, 14 Jan 2026 16:42:54 +0000 Subject: [PATCH 11/44] chance_in_middle efgs --- tests/test_games/chance_in_middle.efg | 34 +++++++++++++++++++ ...chance_in_middle_with_nonterm_outcomes.efg | 34 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 tests/test_games/chance_in_middle.efg create mode 100644 tests/test_games/chance_in_middle_with_nonterm_outcomes.efg diff --git a/tests/test_games/chance_in_middle.efg b/tests/test_games/chance_in_middle.efg new file mode 100644 index 000000000..c874b3f99 --- /dev/null +++ b/tests/test_games/chance_in_middle.efg @@ -0,0 +1,34 @@ +EFG 2 R "Chance in middle game" { "1" "2" } +"" + +p "" 1 1 "" { "A" "B" } 0 +c "" 1 "" { "H" 1/5 "L" 4/5 } 0 +p "" 2 1 "" { "X" "Y" } 0 +p "" 1 2 "" { "C" "D" } 0 +t "" 6 "0" { 0, 0 } +t "" 3 "-2" { -2, 2 } +p "" 1 3 "" { "C" "D" } 0 +t "" 5 "-0.5" { -1/2, 1/2 } +t "" 7 "-1.5" { -3/2, 3/2 } +p "" 2 2 "" { "X" "Y" } 0 +p "" 1 2 "" { "C" "D" } 0 +t "" 4 "0.5" { 1/2, -1/2 } +t "" 5 "-0.5" { -1/2, 1/2 } +p "" 1 3 "" { "C" "D" } 0 +t "" 1 "1" { 1, -1 } +t "" 2 "-1" { -1, 1 } +c "" 2 "" { "H" 7/10 "L" 3/10 } 0 +p "" 2 1 "" { "X" "Y" } 0 +p "" 1 4 "" { "C" "D" } 0 +t "" 4 "0.5" { 1/2, -1/2 } +t "" 5 "-0.5" { -1/2, 1/2 } +p "" 1 5 "" { "C" "D" } 0 +t "" 1 "1" { 1, -1 } +t "" 2 "-1" { -1, 1 } +p "" 2 2 "" { "X" "Y" } 0 +p "" 1 4 "" { "C" "D" } 0 +t "" 1 "1" { 1, -1 } +t "" 2 "-1" { -1, 1 } +p "" 1 5 "" { "C" "D" } 0 +t "" 4 "0.5" { 1/2, -1/2 } +t "" 5 "-0.5" { -1/2, 1/2 } diff --git a/tests/test_games/chance_in_middle_with_nonterm_outcomes.efg b/tests/test_games/chance_in_middle_with_nonterm_outcomes.efg new file mode 100644 index 000000000..7f7cd2132 --- /dev/null +++ b/tests/test_games/chance_in_middle_with_nonterm_outcomes.efg @@ -0,0 +1,34 @@ +EFG 2 R "Chance in middle game" { "1" "2" } +"" + +p "" 1 1 "" { "A" "B" } 0 +c "" 1 "" { "H" 1/5 "L" 4/5 } 0 +p "" 2 1 "" { "X" "Y" } 8 "a" { -1, 1 } +p "" 1 2 "" { "C" "D" } 0 +t "" 1 "1" { 1, -1 } +t "" 2 "-1" { -1, 1 } +p "" 1 3 "" { "C" "D" } 0 +t "" 4 "0.5" { 1/2, -1/2 } +t "" 5 "-0.5" { -1/2, 1/2 } +p "" 2 2 "" { "X" "Y" } 0 +p "" 1 2 "" { "C" "D" } 0 +t "" 4 "0.5" { 1/2, -1/2 } +t "" 5 "-0.5" { -1/2, 1/2 } +p "" 1 3 "" { "C" "D" } 0 +t "" 1 "1" { 1, -1 } +t "" 2 "-1" { -1, 1 } +c "" 2 "" { "H" 7/10 "L" 3/10 } 0 +p "" 2 1 "" { "X" "Y" } 0 +p "" 1 4 "" { "C" "D" } 0 +t "" 4 "0.5" { 1/2, -1/2 } +t "" 5 "-0.5" { -1/2, 1/2 } +p "" 1 5 "" { "C" "D" } 0 +t "" 1 "1" { 1, -1 } +t "" 2 "-1" { -1, 1 } +p "" 2 2 "" { "X" "Y" } 0 +p "" 1 4 "" { "C" "D" } 0 +t "" 1 "1" { 1, -1 } +t "" 2 "-1" { -1, 1 } +p "" 1 5 "" { "C" "D" } 0 +t "" 4 "0.5" { 1/2, -1/2 } +t "" 5 "-0.5" { -1/2, 1/2 } From 39332acb4e435456a19438850e3fd1cdacc56b86 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 17:37:30 +0000 Subject: [PATCH 12/44] removed create_two_player_perfect_info_win_lose_efg from games.py --- tests/games.py | 32 ------------------- tests/test_extensive.py | 4 +-- .../two_player_perfect_info_win_lose.efg | 12 +++++++ ...ct_info_win_lose_with_nonterm_outcomes.efg | 12 +++++++ tests/test_nash.py | 29 +++++++++-------- 5 files changed, 41 insertions(+), 48 deletions(-) create mode 100644 tests/test_games/two_player_perfect_info_win_lose.efg create mode 100644 tests/test_games/two_player_perfect_info_win_lose_with_nonterm_outcomes.efg diff --git a/tests/games.py b/tests/games.py index 12e9a91ce..af8277498 100644 --- a/tests/games.py +++ b/tests/games.py @@ -755,38 +755,6 @@ def create_reduction_both_players_payoff_ties_efg() -> gbt.Game: return g -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"]) - 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")) - tmp = "_with_nonterm_outcomes" if nonterm_outcomes else "" - g.to_efg(f"two_player_perfect_info_win_lose{tmp}.efg") - return g - - def create_EFG_for_nxn_bimatrix_coordination_game(n: int) -> gbt.Game: A = np.eye(n, dtype=int) B = A diff --git a/tests/test_extensive.py b/tests/test_extensive.py index 59d0458ac..5f02ce964 100644 --- a/tests/test_extensive.py +++ b/tests/test_extensive.py @@ -401,8 +401,8 @@ def test_reduced_strategic_form( "standard,modified", [ ( - games.create_two_player_perfect_info_win_lose_efg(), - games.create_two_player_perfect_info_win_lose_efg(nonterm_outcomes=True) + games.read_from_file("two_player_perfect_info_win_lose.efg"), + games.read_from_file("two_player_perfect_info_win_lose_with_nonterm_outcomes.efg") ), ( games.create_3_player_with_internal_outcomes_efg(), diff --git a/tests/test_games/two_player_perfect_info_win_lose.efg b/tests/test_games/two_player_perfect_info_win_lose.efg new file mode 100644 index 000000000..aeb5fa62e --- /dev/null +++ b/tests/test_games/two_player_perfect_info_win_lose.efg @@ -0,0 +1,12 @@ +EFG 2 R "2 player perfect info win lose" { "1" "2" } +"" + +p "" 2 1 "" { "a" "b" } 0 +p "" 1 1 "" { "L" "R" } 0 +p "" 2 2 "" { "l" "r" } 0 +t "" 1 "aLl" { 1, -1 } +t "" 2 "aLr" { -1, 1 } +t "" 3 "aR" { 1, -1 } +p "" 1 2 "" { "L" "R" } 0 +t "" 4 "bL" { 1, -1 } +t "" 5 "bR" { -1, 1 } diff --git a/tests/test_games/two_player_perfect_info_win_lose_with_nonterm_outcomes.efg b/tests/test_games/two_player_perfect_info_win_lose_with_nonterm_outcomes.efg new file mode 100644 index 000000000..d2ebceb41 --- /dev/null +++ b/tests/test_games/two_player_perfect_info_win_lose_with_nonterm_outcomes.efg @@ -0,0 +1,12 @@ +EFG 2 R "2 player perfect info win lose" { "1" "2" } +"" + +p "" 2 1 "" { "a" "b" } 0 +p "" 1 1 "" { "L" "R" } 1 "a" { -100, 50 } +p "" 2 2 "" { "l" "r" } 0 +t "" 2 "aLl" { 101, -51 } +t "" 3 "aLr" { 99, -49 } +t "" 4 "aR" { 101, -51 } +p "" 1 2 "" { "L" "R" } 0 +t "" 5 "bL" { 1, -1 } +t "" 6 "bR" { -1, 1 } diff --git a/tests/test_nash.py b/tests/test_nash.py index 2eebeb07c..2dd356bac 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -23,7 +23,7 @@ [ # Zero-sum games ( - games.create_two_player_perfect_info_win_lose_efg(), + games.read_from_file("two_player_perfect_info_win_lose.efg"), [ [[0, 0, 1, 0], [1, 0, 0]], [[0, 0, 1, 0], [0, 1, 0]], @@ -78,7 +78,7 @@ def test_enumpure_strategy(game: gbt.Game, pure_strategy_prof_data: list): ############################################################# # Zero-sum games ( - games.create_two_player_perfect_info_win_lose_efg(), + games.read_from_file("two_player_perfect_info_win_lose.efg"), [ [[[1, 0], [1, 0]], [[0, 1], [1, 0]]], [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], @@ -557,11 +557,11 @@ def test_lcp_behavior_double(): [[[0, 1]], [[0, 1], [0, 1]]], ), ( - games.create_two_player_perfect_info_win_lose_efg(), + games.read_from_file("two_player_perfect_info_win_lose.efg"), [[[0, 1], [1, 0]], [[0, 1], ["1/2", "1/2"]]], ), ( - games.create_two_player_perfect_info_win_lose_efg(nonterm_outcomes=True), + games.read_from_file("two_player_perfect_info_win_lose_with_nonterm_outcomes.efg"), [[[0, 1], [1, 0]], [[0, 1], ["1/2", "1/2"]]], ), ( @@ -707,30 +707,31 @@ def test_lp_behavior_double(): "game,mixed_behav_prof_data", [ ( - games.create_two_player_perfect_info_win_lose_efg(), - [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], + games.read_from_file("two_player_perfect_info_win_lose.efg"), + [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], ), ( - games.create_two_player_perfect_info_win_lose_efg(nonterm_outcomes=True), - [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], + games.read_from_file("two_player_perfect_info_win_lose_with_nonterm_outcomes.efg"), + [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], ), ( - games.create_2x2_zero_sum_efg(missing_term_outcome=False), - [[["1/2", "1/2"]], [["1/2", "1/2"]]] + games.create_2x2_zero_sum_efg(missing_term_outcome=False), + [[["1/2", "1/2"]], [["1/2", "1/2"]]] ), ( games.create_2x2_zero_sum_efg(missing_term_outcome=True), [[["1/2", "1/2"]], [["1/2", "1/2"]]], ), - (games.create_matching_pennies_efg(with_neutral_outcome=False), - [[["1/2", "1/2"]], [["1/2", "1/2"]]]), + ( + games.create_matching_pennies_efg(with_neutral_outcome=False), + [[["1/2", "1/2"]], [["1/2", "1/2"]]]), ( games.create_matching_pennies_efg(with_neutral_outcome=True), [[["1/2", "1/2"]], [["1/2", "1/2"]]], ), ( - games.create_stripped_down_poker_efg(), - [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]], + games.create_stripped_down_poker_efg(), + [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]], ), ( games.create_stripped_down_poker_efg(nonterm_outcomes=True), From 47b65002bc48b990c8bd9ed31bb0123d3dcac9c5 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 17:44:07 +0000 Subject: [PATCH 13/44] removed create_reduction_both_players_payoff_ties_efg from games.py --- tests/test_extensive.py | 2 +- ...on_both_players_payoff_ties_GTE_survey.efg | 20 +++++++++++++++++++ tests/test_nash.py | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 tests/test_games/reduction_both_players_payoff_ties_GTE_survey.efg diff --git a/tests/test_extensive.py b/tests/test_extensive.py index 5f02ce964..b3527765b 100644 --- a/tests/test_extensive.py +++ b/tests/test_extensive.py @@ -210,7 +210,7 @@ def test_outcome_index_exception_label(): ), # # 2-player game from GTE survey; reduction for both players; payoff ties ( - games.create_reduction_both_players_payoff_ties_efg(), + games.read_from_file("reduction_both_players_payoff_ties_GTE_survey.efg"), [ ["1*", "2*", "31", "32", "4*"], [ diff --git a/tests/test_games/reduction_both_players_payoff_ties_GTE_survey.efg b/tests/test_games/reduction_both_players_payoff_ties_GTE_survey.efg new file mode 100644 index 000000000..1c4adf21c --- /dev/null +++ b/tests/test_games/reduction_both_players_payoff_ties_GTE_survey.efg @@ -0,0 +1,20 @@ +EFG 2 R "From GTE survey" { "1" "2" } +"A reduction in pure strategies is possible for both players" + +p "" 1 1 "" { "A" "B" "C" "D" } 0 +p "" 2 1 "" { "a" "b" } 0 +t "" 1 "" { 2, 8 } +p "" 2 4 "" { "g" "h" } 0 +t "" 2 "" { 0, 1 } +t "" 3 "" { 5, 2 } +p "" 2 2 "" { "c" "d" } 0 +t "" 4 "" { 7, 6 } +t "" 5 "" { 4, 2 } +p "" 2 3 "" { "e" "f" } 0 +p "" 1 2 "" { "E" "F" } 0 +t "" 6 "" { 3, 7 } +t "" 7 "" { 8, 3 } +p "" 1 2 "" { "E" "F" } 0 +t "" 8 "" { 7, 8 } +t "" 9 "" { 2, 2 } +t "" 10 "" { 6, 4 } diff --git a/tests/test_nash.py b/tests/test_nash.py index 2dd356bac..d0c94c9ff 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -602,7 +602,7 @@ def test_lcp_behavior_double(): ), # Non-zero-sum games ( - games.create_reduction_both_players_payoff_ties_efg(), + games.read_from_file("reduction_both_players_payoff_ties_GTE_survey.efg"), [[[0, 0, 1, 0], [1, 0]], [[0, 1], [0, 1], [0, 1], [0, 1]]], ), ( From 255afa44e291fc0e48517860c92bb064a7c944c2 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 18:04:18 +0000 Subject: [PATCH 14/44] removed create_3_player_with_internal_outcomes_efg and create_large_payoff_game from games.py --- tests/games.py | 74 ------------------- tests/test_behav.py | 4 +- tests/test_extensive.py | 4 +- tests/test_games/3_player.efg | 24 ++++++ .../3_player_with_nonterm_outcomes.efg | 24 ++++++ tests/test_games/large_payoff_game.efg | 18 +++++ tests/test_nash.py | 8 +- 7 files changed, 74 insertions(+), 82 deletions(-) create mode 100644 tests/test_games/3_player.efg create mode 100644 tests/test_games/3_player_with_nonterm_outcomes.efg create mode 100644 tests/test_games/large_payoff_game.efg diff --git a/tests/games.py b/tests/games.py index af8277498..5847d3c47 100644 --- a/tests/games.py +++ b/tests/games.py @@ -185,80 +185,6 @@ def create_non_zero_sum_lacking_outcome_efg(missing_term_outcome: bool = False) 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) - g.to_efg("large_payoff_game.efg") - 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) - tmp = "_with_nonterm_outcomes" if nonterm_outcomes else "" - g.to_efg(f"3_player{tmp}.efg") - return g - - def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game: """ TODO: use create_efg_corresponding_to_bimatrix_game diff --git a/tests/test_behav.py b/tests/test_behav.py index 77c3f8e43..c24cf5361 100644 --- a/tests/test_behav.py +++ b/tests/test_behav.py @@ -843,8 +843,8 @@ def test_infoset_regret_consistency(game: gbt.Game, rational_flag: bool): (games.create_stripped_down_poker_efg(), True), (games.create_kuhn_poker_efg(), False), (games.create_kuhn_poker_efg(), True), - (games.create_3_player_with_internal_outcomes_efg(), False), - (games.create_3_player_with_internal_outcomes_efg(), True) + (games.read_from_file("3_player.efg"), False), + (games.read_from_file("3_player.efg"), True) ] ) def test_max_regret_consistency(game: gbt.Game, rational_flag: bool): diff --git a/tests/test_extensive.py b/tests/test_extensive.py index b3527765b..638adf49c 100644 --- a/tests/test_extensive.py +++ b/tests/test_extensive.py @@ -405,8 +405,8 @@ def test_reduced_strategic_form( games.read_from_file("two_player_perfect_info_win_lose_with_nonterm_outcomes.efg") ), ( - games.create_3_player_with_internal_outcomes_efg(), - games.create_3_player_with_internal_outcomes_efg(nonterm_outcomes=True) + games.read_from_file("3_player.efg"), + games.read_from_file("3_player_with_nonterm_outcomes.efg") ), ( games.read_from_file("chance_in_middle.efg"), diff --git a/tests/test_games/3_player.efg b/tests/test_games/3_player.efg new file mode 100644 index 000000000..c1c89ab57 --- /dev/null +++ b/tests/test_games/3_player.efg @@ -0,0 +1,24 @@ +EFG 2 R "3 player game" { "1" "2" "3" } +"" + +c "" 1 "" { "H" 1/2 "T" 1/2 } 0 +p "" 1 1 "" { "a" "b" } 0 +p "" 2 1 "" { "A" "B" } 0 +p "" 3 2 "" { "Y" "Z" } 0 +t "" 1 "" { 3, 1, 4 } +t "" 2 "" { 4, 0, 1 } +p "" 3 2 "" { "Y" "Z" } 0 +t "" 10 "" { 0, 0, 0 } +t "" 10 "" { 0, 0, 0 } +p "" 3 1 "" { "W" "X" } 0 +t "" 3 "" { 1, 3, 2 } +p "" 2 2 "" { "C" "D" } 0 +t "" 4 "" { 2, 4, 1 } +t "" 5 "" { 4, 1, 3 } +p "" 1 2 "" { "c" "d" } 0 +p "" 2 1 "" { "A" "B" } 0 +t "" 6 "" { 2, 2, 4 } +t "" 7 "" { 3, 1, 1 } +p "" 3 1 "" { "W" "X" } 0 +t "" 8 "" { 0, 4, 2 } +t "" 9 "" { 1, 2, 3 } diff --git a/tests/test_games/3_player_with_nonterm_outcomes.efg b/tests/test_games/3_player_with_nonterm_outcomes.efg new file mode 100644 index 000000000..85c11d24a --- /dev/null +++ b/tests/test_games/3_player_with_nonterm_outcomes.efg @@ -0,0 +1,24 @@ +EFG 2 R "3 player game" { "1" "2" "3" } +"" + +c "" 1 "" { "H" 1/2 "T" 1/2 } 0 +p "" 1 1 "" { "a" "b" } 0 +p "" 2 1 "" { "A" "B" } 0 +p "" 3 2 "" { "Y" "Z" } 0 +t "" 1 "" { 3, 1, 4 } +t "" 2 "" { 4, 0, 1 } +p "" 3 2 "" { "Y" "Z" } 0 +t "" 0 +t "" 0 +p "" 3 1 "" { "W" "X" } 0 +t "" 3 "" { 1, 3, 2 } +p "" 2 2 "" { "C" "D" } 0 +t "" 4 "" { 2, 4, 1 } +t "" 5 "" { 4, 1, 3 } +p "" 1 2 "" { "c" "d" } 6 "" { 1, 2, 3 } +p "" 2 1 "" { "A" "B" } 0 +t "" 7 "" { 1, 0, 1 } +t "" 8 "" { 2, -1, -2 } +p "" 3 1 "" { "W" "X" } 0 +t "" 9 "" { -1, 2, -1 } +t "" 0 diff --git a/tests/test_games/large_payoff_game.efg b/tests/test_games/large_payoff_game.efg new file mode 100644 index 000000000..51d304418 --- /dev/null +++ b/tests/test_games/large_payoff_game.efg @@ -0,0 +1,18 @@ +EFG 2 R "Large payoff game" { "1" "2" } +"" + +c "" 1 "" { "L" 1/2 "R" 1/2 } 0 +p "" 1 1 "" { "A" "B" } 0 +p "" 2 1 "" { "X" "Y" } 0 +t "" 1 "large payoff" { 10000000000000000000, -10000000000000000000 } +t "" 2 "1" { 1, -1 } +p "" 2 2 "" { "X" "Y" } 0 +t "" 3 "-1" { -1, 1 } +t "" 4 "0" { 0, 0 } +p "" 1 2 "" { "A" "B" } 0 +p "" 2 1 "" { "X" "Y" } 0 +t "" 3 "-1" { -1, 1 } +t "" 2 "1" { 1, -1 } +p "" 2 2 "" { "X" "Y" } 0 +t "" 4 "0" { 0, 0 } +t "" 1 "large payoff" { 10000000000000000000, -10000000000000000000 } diff --git a/tests/test_nash.py b/tests/test_nash.py index d0c94c9ff..796fea3d8 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -249,7 +249,7 @@ def test_enummixed_rational(game: gbt.Game, mixed_strategy_prof_data: list): ############################################################################## ############################################################################## ( - games.create_3_player_with_internal_outcomes_efg(), + games.read_from_file("3_player.efg"), [ [[[1, 0], [1, 0]], [[1, 0], ["1/2", "1/2"]], [[1, 0], [0, 1]]], [[[1, 0], [1, 0]], [[1, 0], [0, 1]], @@ -257,7 +257,7 @@ def test_enummixed_rational(game: gbt.Game, mixed_strategy_prof_data: list): 2, ), ( - games.create_3_player_with_internal_outcomes_efg(nonterm_outcomes=True), + games.read_from_file("3_player_with_nonterm_outcomes.efg"), [ [[[1, 0], [1, 0]], [[1, 0], ["1/2", "1/2"]], [[1, 0], [0, 1]]], [[[1, 0], [1, 0]], [[1, 0], [0, 1]], @@ -579,7 +579,7 @@ def test_lcp_behavior_double(): ], ), ( - games.create_large_payoff_game_efg(), + games.read_from_file("large_payoff_game.efg"), [ [[1, 0], [1, 0]], [[0, 1], ["9999999999999999999/10000000000000000000", @@ -784,7 +784,7 @@ def test_lp_behavior_double(): ], ), ( - games.create_large_payoff_game_efg(), + games.read_from_file("large_payoff_game.efg"), [ [[1, 0], [1, 0]], [[0, 1], ["9999999999999999999/10000000000000000000", From f8c32f49a258b9012601594aa6877b1a1593c6c2 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 18:13:33 +0000 Subject: [PATCH 15/44] removed create_reduction_one_player_generic_payoffs_efg from games.py --- tests/games.py | 13 ------------- tests/test_extensive.py | 2 +- .../reduction_one_player_generic_payoffs.efg | 10 ++++++++++ 3 files changed, 11 insertions(+), 14 deletions(-) create mode 100644 tests/test_games/reduction_one_player_generic_payoffs.efg diff --git a/tests/games.py b/tests/games.py index 5847d3c47..6d4175a65 100644 --- a/tests/games.py +++ b/tests/games.py @@ -645,19 +645,6 @@ def create_reduction_generic_payoffs_efg() -> gbt.Game: return g -def create_reduction_one_player_generic_payoffs_efg() -> gbt.Game: - g = gbt.Game.new_tree(players=["1"], title="One player reduction generic payoffs") - g.append_move(g.root, "1", ["a", "b", "c", "d"]) - g.append_move(g.root.children[0], "1", ["e", "f"]) - g.set_outcome(g.root.children[0].children[0], g.add_outcome([1])) - g.set_outcome(g.root.children[0].children[1], g.add_outcome([2])) - g.set_outcome(g.root.children[1], g.add_outcome([3])) - g.set_outcome(g.root.children[2], g.add_outcome([4])) - g.set_outcome(g.root.children[3], g.add_outcome([5])) - g.to_efg("reduction_one_player_generic_payoffs.efg") - return g - - def create_reduction_both_players_payoff_ties_efg() -> gbt.Game: g = gbt.Game.new_tree(players=["1", "2"], title="From GTE survey") g.append_move(g.root, "1", ["A", "B", "C", "D"]) diff --git a/tests/test_extensive.py b/tests/test_extensive.py index 638adf49c..31cdc2c43 100644 --- a/tests/test_extensive.py +++ b/tests/test_extensive.py @@ -123,7 +123,7 @@ def test_outcome_index_exception_label(): ############################################################################### # 1 player; reduction; generic payoffs ( - games.create_reduction_one_player_generic_payoffs_efg(), + games.read_from_file("reduction_one_player_generic_payoffs.efg"), [["11", "12", "2*", "3*", "4*"]], [np.array(range(1, 6))], ), diff --git a/tests/test_games/reduction_one_player_generic_payoffs.efg b/tests/test_games/reduction_one_player_generic_payoffs.efg new file mode 100644 index 000000000..7844ed6c0 --- /dev/null +++ b/tests/test_games/reduction_one_player_generic_payoffs.efg @@ -0,0 +1,10 @@ +EFG 2 R "One player reduction generic payoffs" { "1" } +"" + +p "" 1 1 "" { "a" "b" "c" "d" } 0 +p "" 1 2 "" { "e" "f" } 0 +t "" 1 "" { 1 } +t "" 2 "" { 2 } +t "" 3 "" { 3 } +t "" 4 "" { 4 } +t "" 5 "" { 5 } From ce73a369e3850403257b8c252fe31064dcaa95ae Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 18:16:23 +0000 Subject: [PATCH 16/44] removed create_reduction_generic_payoffs_efg from games.py --- tests/games.py | 68 ----------------------------------------- tests/test_extensive.py | 2 +- 2 files changed, 1 insertion(+), 69 deletions(-) diff --git a/tests/games.py b/tests/games.py index 6d4175a65..1521477fb 100644 --- a/tests/games.py +++ b/tests/games.py @@ -577,74 +577,6 @@ def create_selten_horse_game_efg() -> gbt.Game: return read_from_file("e01.efg") -def create_reduction_generic_payoffs_efg() -> gbt.Game: - # tree with only root - g = gbt.Game.new_tree( - players=["1", "2"], title="2 player reduction generic payoffs" - ) - - # add four children - g.append_move(g.root, "2", ["a", "b", "c", "d"]) - - # add L and R after a - g.append_move(g.root.children[0], "1", ["L", "R"]) - - # add C and D to single infoset after b and c - nodes = [g.root.children[1], g.root.children[2]] - g.append_move(nodes, "1", ["C", "D"]) - - # add s and t from single infoset after rightmost C and D - g.append_move(g.root.children[2].children, "2", ["s", "t"]) - - # add p and q - g.append_move(g.root.children[0].children[1], "2", ["p", "q"]) - - # add U and V in a single infoset after p and q - g.append_move(g.root.children[0].children[1].children, "1", ["U", "V"]) - - # Set outcomes - - g.set_outcome(g.root.children[0].children[0], g.add_outcome([1, -1], label="aL")) - g.set_outcome( - g.root.children[0].children[1].children[0].children[0], - g.add_outcome([2, -2], label="aRpU"), - ) - g.set_outcome( - g.root.children[0].children[1].children[0].children[1], - g.add_outcome([3, -3], label="aRpV"), - ) - g.set_outcome( - g.root.children[0].children[1].children[1].children[0], - g.add_outcome([4, -4], label="aRqU"), - ) - g.set_outcome( - g.root.children[0].children[1].children[1].children[1], - g.add_outcome([5, -5], label="aRqV"), - ) - - g.set_outcome(g.root.children[1].children[0], g.add_outcome([6, -6], label="bC")) - g.set_outcome(g.root.children[1].children[1], g.add_outcome([7, -7], label="bD")) - - g.set_outcome( - g.root.children[2].children[0].children[0], g.add_outcome([8, -8], label="cCs") - ) - g.set_outcome( - g.root.children[2].children[0].children[1], g.add_outcome([9, -9], label="cCt") - ) - g.set_outcome( - g.root.children[2].children[1].children[0], - g.add_outcome([10, -10], label="cDs"), - ) - g.set_outcome( - g.root.children[2].children[1].children[1], - g.add_outcome([11, -11], label="cDt"), - ) - - g.set_outcome(g.root.children[3], g.add_outcome([12, -12], label="d")) - g.to_efg("reduction_generic_payoffs.efg") - return g - - def create_reduction_both_players_payoff_ties_efg() -> gbt.Game: g = gbt.Game.new_tree(players=["1", "2"], title="From GTE survey") g.append_move(g.root, "1", ["A", "B", "C", "D"]) diff --git a/tests/test_extensive.py b/tests/test_extensive.py index 31cdc2c43..33c06bd6f 100644 --- a/tests/test_extensive.py +++ b/tests/test_extensive.py @@ -168,7 +168,7 @@ def test_outcome_index_exception_label(): ), # 2-player (zero-sum) game; reduction for both players; generic payoffs ( - games.create_reduction_generic_payoffs_efg(), + games.read_from_file("reduction_generic_payoffs.efg"), [ ["1*1", "1*2", "211", "212", "221", "222"], ["11*", "12*", "2**", "3*1", "3*2", "4**"], From 9307709422098c854adada107e90263b0a5f585c Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 18:19:27 +0000 Subject: [PATCH 17/44] removed create_perfect_info_with_chance_efg from games.py --- tests/games.py | 47 ------------------- tests/test_games/perfect_info_with_chance.efg | 12 +++++ .../test_games/reduction_generic_payoffs.efg | 24 ++++++++++ tests/test_nash.py | 4 +- 4 files changed, 38 insertions(+), 49 deletions(-) create mode 100644 tests/test_games/perfect_info_with_chance.efg create mode 100644 tests/test_games/reduction_generic_payoffs.efg diff --git a/tests/games.py b/tests/games.py index 1521477fb..e684b6880 100644 --- a/tests/games.py +++ b/tests/games.py @@ -67,30 +67,6 @@ 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")) - g.to_efg("perfect_info_with_chance.efg") - 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 @@ -577,29 +553,6 @@ def create_selten_horse_game_efg() -> gbt.Game: return read_from_file("e01.efg") -def create_reduction_both_players_payoff_ties_efg() -> gbt.Game: - g = gbt.Game.new_tree(players=["1", "2"], title="From GTE survey") - g.append_move(g.root, "1", ["A", "B", "C", "D"]) - g.append_move(g.root.children[0], "2", ["a", "b"]) - g.append_move(g.root.children[1], "2", ["c", "d"]) - g.append_move(g.root.children[2], "2", ["e", "f"]) - g.append_move(g.root.children[0].children[1], "2", ["g", "h"]) - g.append_move(g.root.children[2].children, "1", ["E", "F"]) - - g.set_outcome(g.root.children[0].children[0], g.add_outcome([2, 8])) - g.set_outcome(g.root.children[0].children[1].children[0], g.add_outcome([0, 1])) - g.set_outcome(g.root.children[0].children[1].children[1], g.add_outcome([5, 2])) - g.set_outcome(g.root.children[1].children[0], g.add_outcome([7, 6])) - g.set_outcome(g.root.children[1].children[1], g.add_outcome([4, 2])) - g.set_outcome(g.root.children[2].children[0].children[0], g.add_outcome([3, 7])) - g.set_outcome(g.root.children[2].children[0].children[1], g.add_outcome([8, 3])) - g.set_outcome(g.root.children[2].children[1].children[0], g.add_outcome([7, 8])) - g.set_outcome(g.root.children[2].children[1].children[1], g.add_outcome([2, 2])) - g.set_outcome(g.root.children[3], g.add_outcome([6, 4])) - g.to_efg("reduction_both_players_payoff_ties_GTE_survey.efg") - return g - - def create_EFG_for_nxn_bimatrix_coordination_game(n: int) -> gbt.Game: A = np.eye(n, dtype=int) B = A diff --git a/tests/test_games/perfect_info_with_chance.efg b/tests/test_games/perfect_info_with_chance.efg new file mode 100644 index 000000000..4b7c58ea0 --- /dev/null +++ b/tests/test_games/perfect_info_with_chance.efg @@ -0,0 +1,12 @@ +EFG 2 R "2 player perfect info with chance" { "1" "2" } +"" + +p "" 1 1 "" { "a" "b" } 0 +c "" 1 "" { "L" 1/2 "R" 1/2 } 0 +p "" 2 1 "" { "A" "B" } 0 +t "" 1 "aLA" { -2, 2 } +t "" 2 "aLB" { -2, 2 } +p "" 2 2 "" { "C" "D" } 0 +t "" 3 "aRC" { -2, 2 } +t "" 4 "aRD" { -2, 2 } +t "" 5 "b" { -1, 1 } diff --git a/tests/test_games/reduction_generic_payoffs.efg b/tests/test_games/reduction_generic_payoffs.efg new file mode 100644 index 000000000..7dcac053a --- /dev/null +++ b/tests/test_games/reduction_generic_payoffs.efg @@ -0,0 +1,24 @@ +EFG 2 R "2 player reduction generic payoffs" { "1" "2" } +"" + +p "" 2 1 "" { "a" "b" "c" "d" } 0 +p "" 1 1 "" { "L" "R" } 0 +t "" 1 "aL" { 1, -1 } +p "" 2 3 "" { "p" "q" } 0 +p "" 1 3 "" { "U" "V" } 0 +t "" 2 "aRpU" { 2, -2 } +t "" 3 "aRpV" { 3, -3 } +p "" 1 3 "" { "U" "V" } 0 +t "" 4 "aRqU" { 4, -4 } +t "" 5 "aRqV" { 5, -5 } +p "" 1 2 "" { "C" "D" } 0 +t "" 6 "bC" { 6, -6 } +t "" 7 "bD" { 7, -7 } +p "" 1 2 "" { "C" "D" } 0 +p "" 2 2 "" { "s" "t" } 0 +t "" 8 "cCs" { 8, -8 } +t "" 9 "cCt" { 9, -9 } +p "" 2 2 "" { "s" "t" } 0 +t "" 10 "cDs" { 10, -10 } +t "" 11 "cDt" { 11, -11 } +t "" 12 "d" { 12, -12 } diff --git a/tests/test_nash.py b/tests/test_nash.py index 796fea3d8..a07bdf5d1 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -553,7 +553,7 @@ def test_lcp_behavior_double(): # In the next test case: # 1/2-1/2 for l/r is determined by MixedBehaviorProfile.UndefinedToCentroid() ( - games.create_perfect_info_with_chance_efg(), + games.read_from_file("perfect_info_with_chance.efg"), [[[0, 1]], [[0, 1], [0, 1]]], ), ( @@ -766,7 +766,7 @@ def test_lp_behavior_double(): ], ), ( - games.create_perfect_info_with_chance_efg(), + games.read_from_file("perfect_info_with_chance.efg"), [[[0, 1]], [[1, 0], [1, 0]]], ), ( From 78e337558cbc0244c5920004e90d10b1a0a3b2b6 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 18:24:41 +0000 Subject: [PATCH 18/44] removed create_entry_accomodation_efg from games.py --- tests/games.py | 24 ------------------- tests/test_extensive.py | 4 ++-- tests/test_games/entry_accommodation.efg | 14 +++++++++++ ...ry_accommodation_with_nonterm_outcomes.efg | 14 +++++++++++ tests/test_nash.py | 4 ++-- 5 files changed, 32 insertions(+), 28 deletions(-) create mode 100644 tests/test_games/entry_accommodation.efg create mode 100644 tests/test_games/entry_accommodation_with_nonterm_outcomes.efg diff --git a/tests/games.py b/tests/games.py index e684b6880..764ae4df0 100644 --- a/tests/games.py +++ b/tests/games.py @@ -113,30 +113,6 @@ def create_three_action_internal_outcomes_efg(nonterm_outcomes: bool = False) -> 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])) - tmp = "_with_nonterm_outcomes" if nonterm_outcomes else "" - g.to_efg(f"entry_accomodation{tmp}.efg") - 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"]) diff --git a/tests/test_extensive.py b/tests/test_extensive.py index 33c06bd6f..b7b917755 100644 --- a/tests/test_extensive.py +++ b/tests/test_extensive.py @@ -417,8 +417,8 @@ def test_reduced_strategic_form( 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.read_from_file("entry_accommodation.efg"), + games.read_from_file("entry_accommodation_with_nonterm_outcomes.efg") ), ( games.create_three_action_internal_outcomes_efg(), diff --git a/tests/test_games/entry_accommodation.efg b/tests/test_games/entry_accommodation.efg new file mode 100644 index 000000000..dfd383ba7 --- /dev/null +++ b/tests/test_games/entry_accommodation.efg @@ -0,0 +1,14 @@ +EFG 2 R "Entry-accomodation game" { "1" "2" } +"" + +p "" 1 1 "" { "S" "T" } 0 +p "" 2 1 "" { "E" "O" } 0 +p "" 1 2 "" { "A" "F" } 0 +t "" 1 "" { 3, 2 } +t "" 2 "" { 0, 1 } +t "" 3 "" { 1, 3 } +p "" 2 1 "" { "E" "O" } 0 +p "" 1 3 "" { "A" "F" } 0 +t "" 4 "" { 2, 3 } +t "" 5 "" { 1, 0 } +t "" 6 "" { 3, 1 } diff --git a/tests/test_games/entry_accommodation_with_nonterm_outcomes.efg b/tests/test_games/entry_accommodation_with_nonterm_outcomes.efg new file mode 100644 index 000000000..9453a4ff8 --- /dev/null +++ b/tests/test_games/entry_accommodation_with_nonterm_outcomes.efg @@ -0,0 +1,14 @@ +EFG 2 R "Entry-accomodation game" { "1" "2" } +"" + +p "" 1 1 "" { "S" "T" } 0 +p "" 2 1 "" { "E" "O" } 1 "" { 3, 2 } +p "" 1 2 "" { "A" "F" } 0 +t "" 0 +t "" 2 "" { -3, -1 } +t "" 3 "" { -2, 1 } +p "" 2 1 "" { "E" "O" } 0 +p "" 1 3 "" { "A" "F" } 0 +t "" 4 "" { 2, 3 } +t "" 5 "" { 1, 0 } +t "" 6 "" { 3, 1 } diff --git a/tests/test_nash.py b/tests/test_nash.py index a07bdf5d1..fe97559e9 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -618,11 +618,11 @@ def test_lcp_behavior_double(): [[[0, 0, 0, 1]], [[0, 0, 0, 1]]], ), ( - games.create_entry_accomodation_efg(), + games.read_from_file("entry_accommodation.efg"), [[["2/3", "1/3"], [1, 0], [1, 0]], [["2/3", "1/3"]]] ), ( - games.create_entry_accomodation_efg(nonterm_outcomes=True), + games.read_from_file("entry_accommodation_with_nonterm_outcomes.efg"), [[["2/3", "1/3"], [1, 0], [1, 0]], [["2/3", "1/3"]]], ), ( From 22fafa73811b6a3482f0e21a9939ced90167d4c2 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 18:35:10 +0000 Subject: [PATCH 19/44] removed create_non_zero_sum_lacking_outcome_efg from games.py --- tests/games.py | 24 ------------------- tests/test_extensive.py | 4 ++-- tests/test_games/2_player_non_zero_sum.efg | 18 ++++++++++++++ ...ayer_non_zero_sum_missing_term_outcome.efg | 18 ++++++++++++++ tests/test_nash.py | 8 +++---- 5 files changed, 42 insertions(+), 30 deletions(-) create mode 100644 tests/test_games/2_player_non_zero_sum.efg create mode 100644 tests/test_games/2_player_non_zero_sum_missing_term_outcome.efg diff --git a/tests/games.py b/tests/games.py index 764ae4df0..b16b4aacb 100644 --- a/tests/games.py +++ b/tests/games.py @@ -113,30 +113,6 @@ def create_three_action_internal_outcomes_efg(nonterm_outcomes: bool = False) -> 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])) - tmp = "_missing_term_outcome" if missing_term_outcome else "" - g.to_efg(f"non_zero_sum_2_player{tmp}.efg") - return g - - def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game: """ TODO: use create_efg_corresponding_to_bimatrix_game diff --git a/tests/test_extensive.py b/tests/test_extensive.py index b7b917755..0f2e1fc28 100644 --- a/tests/test_extensive.py +++ b/tests/test_extensive.py @@ -413,8 +413,8 @@ def test_reduced_strategic_form( games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg") ), ( - games.create_non_zero_sum_lacking_outcome_efg(), - games.create_non_zero_sum_lacking_outcome_efg(missing_term_outcome=True) + games.read_from_file("2_player_non_zero_sum.efg"), + games.read_from_file("2_player_non_zero_sum_missing_term_outcome.efg"), ), ( games.read_from_file("entry_accommodation.efg"), diff --git a/tests/test_games/2_player_non_zero_sum.efg b/tests/test_games/2_player_non_zero_sum.efg new file mode 100644 index 000000000..87a9e7adf --- /dev/null +++ b/tests/test_games/2_player_non_zero_sum.efg @@ -0,0 +1,18 @@ +EFG 2 R "Non constant-sum game lacking outcome" { "1" "2" } +"" + +c "" 1 "" { "H" 1/2 "T" 1/2 } 0 +p "" 1 1 "" { "A" "B" } 0 +p "" 2 1 "" { "X" "Y" } 0 +t "" 1 "" { 2, 1 } +t "" 2 "" { -1, 2 } +p "" 2 1 "" { "X" "Y" } 0 +t "" 3 "" { 1, -1 } +t "" 4 "" { 0, 0 } +p "" 1 1 "" { "A" "B" } 0 +p "" 2 1 "" { "X" "Y" } 0 +t "" 5 "" { 1, 0 } +t "" 6 "" { 0, 1 } +p "" 2 1 "" { "X" "Y" } 0 +t "" 7 "" { -1, 1 } +t "" 8 "" { 2, -1 } diff --git a/tests/test_games/2_player_non_zero_sum_missing_term_outcome.efg b/tests/test_games/2_player_non_zero_sum_missing_term_outcome.efg new file mode 100644 index 000000000..a7fb080e1 --- /dev/null +++ b/tests/test_games/2_player_non_zero_sum_missing_term_outcome.efg @@ -0,0 +1,18 @@ +EFG 2 R "Non constant-sum game lacking outcome" { "1" "2" } +"" + +c "" 1 "" { "H" 1/2 "T" 1/2 } 0 +p "" 1 1 "" { "A" "B" } 0 +p "" 2 1 "" { "X" "Y" } 0 +t "" 1 "" { 2, 1 } +t "" 2 "" { -1, 2 } +p "" 2 1 "" { "X" "Y" } 0 +t "" 3 "" { 1, -1 } +t "" 0 +p "" 1 1 "" { "A" "B" } 0 +p "" 2 1 "" { "X" "Y" } 0 +t "" 4 "" { 1, 0 } +t "" 5 "" { 0, 1 } +p "" 2 1 "" { "X" "Y" } 0 +t "" 6 "" { -1, 1 } +t "" 7 "" { 2, -1 } diff --git a/tests/test_nash.py b/tests/test_nash.py index fe97559e9..343d5208e 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -267,12 +267,12 @@ def test_enummixed_rational(game: gbt.Game, mixed_strategy_prof_data: list): ############################################################################## ############################################################################## ( - games.create_non_zero_sum_lacking_outcome_efg(), + games.read_from_file("2_player_non_zero_sum.efg"), [[[["1/3", "2/3"]], [["1/2", "1/2"]]]], 1, ), ( - games.create_non_zero_sum_lacking_outcome_efg(missing_term_outcome=True), + games.read_from_file("2_player_non_zero_sum_missing_term_outcome.efg"), [[[["1/3", "2/3"]], [["1/2", "1/2"]]]], 1, ), @@ -626,11 +626,11 @@ def test_lcp_behavior_double(): [[["2/3", "1/3"], [1, 0], [1, 0]], [["2/3", "1/3"]]], ), ( - games.create_non_zero_sum_lacking_outcome_efg(), + games.read_from_file("2_player_non_zero_sum.efg"), [[["1/3", "2/3"]], [["1/2", "1/2"]]] ), ( - games.create_non_zero_sum_lacking_outcome_efg(missing_term_outcome=True), + games.read_from_file("2_player_non_zero_sum_missing_term_outcome.efg"), [[["1/3", "2/3"]], [["1/2", "1/2"]]], ), ], From e275daf096f982278a52e82bca3177f79b35871c Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 18:47:09 +0000 Subject: [PATCH 20/44] create_matching_pennies_efg uses create_efg_corresponding_to_bimatrix_game and removed create_three_action_internal_outcomes_efg --- tests/games.py | 67 +++---------------- tests/test_extensive.py | 4 +- tests/test_games/2_player_chance.efg | 24 +++++++ ...erm_outcomes_and_missing_term_outcomes.efg | 24 +++++++ tests/test_nash.py | 8 +-- 5 files changed, 62 insertions(+), 65 deletions(-) create mode 100644 tests/test_games/2_player_chance.efg create mode 100644 tests/test_games/2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg diff --git a/tests/games.py b/tests/games.py index b16b4aacb..40b115d40 100644 --- a/tests/games.py +++ b/tests/games.py @@ -67,71 +67,20 @@ def create_2x2_zero_sum_efg(missing_term_outcome: bool = False) -> gbt.Game: 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) - tmp = "_nonterm_outcomes_and_missing_term_outcomes" if nonterm_outcomes else "" - g.to_efg(f"2_player_chance_3_actions{tmp}.efg") - return g - - def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game: """ - TODO: use create_efg_corresponding_to_bimatrix_game - The version with_neutral_outcome adds a (0,0) payoff outcomes at a non-terminal node. """ - g = gbt.Game.new_tree( - players=["Alice", "Bob"], title="Matching pennies") - g.append_move(g.root, "Alice", ["H", "T"]) - g.append_move(g.root.children, "Bob", ["h", "t"]) - alice_lose = g.add_outcome([-1, 1], label="Alice lose") - alice_win = g.add_outcome([1, -1], label="Alice win") + title = "Matching Pennies" + if with_neutral_outcome: + title += " with nonterminal neutral outcome" + A = np.array([[1, -1], [-1, 1]]) + B = -A + g = create_efg_corresponding_to_bimatrix_game(A, B, title) if with_neutral_outcome: neutral = g.add_outcome([0, 0], label="neutral") - g.set_outcome(g.root.children["H"], neutral) - g.set_outcome(g.root.children["H"].children["h"], alice_win) - g.set_outcome(g.root.children["T"].children["t"], alice_win) - g.set_outcome(g.root.children["H"].children["t"], alice_lose) - g.set_outcome(g.root.children["T"].children["h"], alice_lose) + g.set_outcome(g.root.children[0], neutral) + g.to_efg(f"tmp2_{with_neutral_outcome}.efg") return g diff --git a/tests/test_extensive.py b/tests/test_extensive.py index 0f2e1fc28..f97584844 100644 --- a/tests/test_extensive.py +++ b/tests/test_extensive.py @@ -421,8 +421,8 @@ def test_reduced_strategic_form( games.read_from_file("entry_accommodation_with_nonterm_outcomes.efg") ), ( - games.create_three_action_internal_outcomes_efg(), - games.create_three_action_internal_outcomes_efg(nonterm_outcomes=True) + games.read_from_file("2_player_chance.efg"), + games.read_from_file("2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg"), ), ( games.create_kuhn_poker_efg(), diff --git a/tests/test_games/2_player_chance.efg b/tests/test_games/2_player_chance.efg new file mode 100644 index 000000000..3362390d5 --- /dev/null +++ b/tests/test_games/2_player_chance.efg @@ -0,0 +1,24 @@ +EFG 2 R "" { "1" "2" } +"" + +c "" 1 "" { "H" 1/2 "L" 1/2 } 0 +p "" 1 1 "" { "A" "B" "C" } 0 +p "" 2 1 "" { "X" "Y" } 0 +t "" 1 "1" { 1, -1 } +t "" 2 "-1" { -1, 1 } +p "" 2 2 "" { "X" "Y" } 0 +t "" 2 "-1" { -1, 1 } +t "" 1 "1" { 1, -1 } +p "" 2 3 "" { "X" "Y" } 0 +t "" 1 "1" { 1, -1 } +t "" 5 "0" { 0, 0 } +p "" 1 2 "" { "A" "B" "C" } 0 +p "" 2 1 "" { "X" "Y" } 0 +t "" 5 "0" { 0, 0 } +t "" 1 "1" { 1, -1 } +p "" 2 2 "" { "X" "Y" } 0 +t "" 1 "1" { 1, -1 } +t "" 2 "-1" { -1, 1 } +p "" 2 3 "" { "X" "Y" } 0 +t "" 2 "-1" { -1, 1 } +t "" 1 "1" { 1, -1 } diff --git a/tests/test_games/2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg b/tests/test_games/2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg new file mode 100644 index 000000000..2a9099273 --- /dev/null +++ b/tests/test_games/2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg @@ -0,0 +1,24 @@ +EFG 2 R "" { "1" "2" } +"" + +c "" 1 "" { "H" 1/2 "L" 1/2 } 0 +p "" 1 1 "" { "A" "B" "C" } 0 +p "" 2 1 "" { "X" "Y" } 1 "1" { 1, -1 } +t "" 0 +t "" 4 "-2" { -2, 2 } +p "" 2 2 "" { "X" "Y" } 0 +t "" 2 "-1" { -1, 1 } +t "" 1 "1" { 1, -1 } +p "" 2 3 "" { "X" "Y" } 0 +t "" 1 "1" { 1, -1 } +t "" 0 +p "" 1 2 "" { "A" "B" "C" } 0 +p "" 2 1 "" { "X" "Y" } 0 +t "" 0 +t "" 1 "1" { 1, -1 } +p "" 2 2 "" { "X" "Y" } 0 +t "" 1 "1" { 1, -1 } +t "" 2 "-1" { -1, 1 } +p "" 2 3 "" { "X" "Y" } 2 "-1" { -1, 1 } +t "" 0 +t "" 3 "2" { 2, -2 } diff --git a/tests/test_nash.py b/tests/test_nash.py index 343d5208e..52343490e 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -565,14 +565,14 @@ def test_lcp_behavior_double(): [[[0, 1], [1, 0]], [[0, 1], ["1/2", "1/2"]]], ), ( - games.create_three_action_internal_outcomes_efg(), + games.read_from_file("2_player_chance.efg"), [ [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], [["2/3", "1/3"], ["1/3", "2/3"], ["1/3", "2/3"]], ] ), ( - games.create_three_action_internal_outcomes_efg(nonterm_outcomes=True), + games.read_from_file("2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg"), [ [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], [["2/3", "1/3"], ["1/3", "2/3"], ["1/3", "2/3"]], @@ -770,14 +770,14 @@ def test_lp_behavior_double(): [[[0, 1]], [[1, 0], [1, 0]]], ), ( - games.create_three_action_internal_outcomes_efg(), + games.read_from_file("2_player_chance.efg"), [ [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], [["2/3", "1/3"], ["2/3", "1/3"], ["1/3", "2/3"]], ] ), ( - games.create_three_action_internal_outcomes_efg(nonterm_outcomes=True), + games.read_from_file("2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg"), [ [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], [["2/3", "1/3"], ["2/3", "1/3"], ["1/3", "2/3"]], From 25b42cd8c5adaf4a8cb195d70875640479443d39 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 18:52:10 +0000 Subject: [PATCH 21/44] removed temp to_efg call --- tests/games.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/games.py b/tests/games.py index 40b115d40..01b5bc2a6 100644 --- a/tests/games.py +++ b/tests/games.py @@ -80,7 +80,6 @@ def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game: if with_neutral_outcome: neutral = g.add_outcome([0, 0], label="neutral") g.set_outcome(g.root.children[0], neutral) - g.to_efg(f"tmp2_{with_neutral_outcome}.efg") return g From 02d1d8646a5e422df1e00a8871fc6c0c84706e20 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 19:00:29 +0000 Subject: [PATCH 22/44] removed create_mixed_behav_game_efg from games.py --- tests/games.py | 13 - tests/test_behav.py | 2464 ++++++++++++++-------- tests/test_games/mixed_behavior_game.efg | 7 +- tests/test_mixed.py | 28 +- tests/test_nash.py | 8 +- 5 files changed, 1626 insertions(+), 894 deletions(-) diff --git a/tests/games.py b/tests/games.py index 01b5bc2a6..da398bf0a 100644 --- a/tests/games.py +++ b/tests/games.py @@ -83,19 +83,6 @@ def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game: return g -def create_mixed_behav_game_efg() -> gbt.Game: - """ - Returns - ------- - Game - Three-player extensive form game: binary tree with 3 infomation sets, one per player, - with 1, 2, and 4 nodes respectively - - Since no information is revealed this is directly equivalent to a simultaneous move game - """ - return read_from_file("mixed_behavior_game.efg") - - def create_stripped_down_poker_efg(nonterm_outcomes: bool = False) -> gbt.Game: """ Returns diff --git a/tests/test_behav.py b/tests/test_behav.py index c24cf5361..6097d342e 100644 --- a/tests/test_behav.py +++ b/tests/test_behav.py @@ -22,20 +22,22 @@ def _set_action_probs(profile: gbt.MixedBehaviorProfile, probs: list, rational_f @pytest.mark.parametrize( "game,player_idx,payoff,rational_flag", - [(games.create_mixed_behav_game_efg(), 0, 3.0, False), - (games.create_mixed_behav_game_efg(), 1, 3.0, False), - (games.create_mixed_behav_game_efg(), 2, 3.25, False), - (games.create_mixed_behav_game_efg(), 0, "3", True), - (games.create_mixed_behav_game_efg(), 1, "3", True), - (games.create_mixed_behav_game_efg(), 2, "13/4", True), - (games.create_stripped_down_poker_efg(), 0, -0.25, False), - (games.create_stripped_down_poker_efg(), 1, 0.25, True), - (games.create_stripped_down_poker_efg(), 0, "-1/4", True), - (games.create_stripped_down_poker_efg(), 1, "1/4", True) - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), 0, 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 1, 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 2, 3.25, False), + (games.read_from_file("mixed_behavior_game.efg"), 0, "3", True), + (games.read_from_file("mixed_behavior_game.efg"), 1, "3", True), + (games.read_from_file("mixed_behavior_game.efg"), 2, "13/4", True), + (games.create_stripped_down_poker_efg(), 0, -0.25, False), + (games.create_stripped_down_poker_efg(), 1, 0.25, True), + (games.create_stripped_down_poker_efg(), 0, "-1/4", True), + (games.create_stripped_down_poker_efg(), 1, "1/4", True), + ], ) -def test_payoff_reference(game: gbt.Game, player_idx: int, payoff: str | float, - rational_flag: bool): +def test_payoff_reference( + game: gbt.Game, player_idx: int, payoff: str | float, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) payoff = gbt.Rational(payoff) if rational_flag else payoff assert profile.payoff(game.players[player_idx]) == payoff @@ -43,20 +45,22 @@ def test_payoff_reference(game: gbt.Game, player_idx: int, payoff: str | float, @pytest.mark.parametrize( "game,label,payoff,rational_flag", - [(games.create_mixed_behav_game_efg(), "Player 1", 3.0, False), - (games.create_mixed_behav_game_efg(), "Player 2", 3.0, False), - (games.create_mixed_behav_game_efg(), "Player 3", 3.25, False), - (games.create_mixed_behav_game_efg(), "Player 1", "3", True), - (games.create_mixed_behav_game_efg(), "Player 2", "3", True), - (games.create_mixed_behav_game_efg(), "Player 3", "13/4", True), - (games.create_stripped_down_poker_efg(), "Alice", -0.25, False), - (games.create_stripped_down_poker_efg(), "Bob", 0.25, False), - (games.create_stripped_down_poker_efg(), "Alice", "-1/4", True), - (games.create_stripped_down_poker_efg(), "Bob", "1/4", True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), "Player 1", 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), "Player 2", 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), "Player 3", 3.25, False), + (games.read_from_file("mixed_behavior_game.efg"), "Player 1", "3", True), + (games.read_from_file("mixed_behavior_game.efg"), "Player 2", "3", True), + (games.read_from_file("mixed_behavior_game.efg"), "Player 3", "13/4", True), + (games.create_stripped_down_poker_efg(), "Alice", -0.25, False), + (games.create_stripped_down_poker_efg(), "Bob", 0.25, False), + (games.create_stripped_down_poker_efg(), "Alice", "-1/4", True), + (games.create_stripped_down_poker_efg(), "Bob", "1/4", True), + ], ) -def test_payoff_by_label_reference(game: gbt.Game, label: str, payoff: str | float, - rational_flag: bool): +def test_payoff_by_label_reference( + game: gbt.Game, label: str, payoff: str | float, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) payoff = gbt.Rational(payoff) if rational_flag else payoff assert profile.payoff(label) == payoff @@ -64,11 +68,12 @@ def test_payoff_by_label_reference(game: gbt.Game, label: str, payoff: str | flo @pytest.mark.parametrize( "game,rational_flag", - [(games.create_mixed_behav_game_efg(), False), - (games.create_mixed_behav_game_efg(), True), - (games.create_stripped_down_poker_efg(), False), - (games.create_stripped_down_poker_efg(), True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), False), + (games.read_from_file("mixed_behavior_game.efg"), True), + (games.create_stripped_down_poker_efg(), False), + (games.create_stripped_down_poker_efg(), True), + ], ) def test_is_defined_at(game: gbt.Game, rational_flag: bool): """Test to check if infoset are all defined""" @@ -79,19 +84,20 @@ def test_is_defined_at(game: gbt.Game, rational_flag: bool): @pytest.mark.parametrize( "game,label,rational_flag", - [(games.create_mixed_behav_game_efg(), "Infoset 1:1", False), - (games.create_mixed_behav_game_efg(), "Infoset 2:1", False), - (games.create_mixed_behav_game_efg(), "Infoset 3:1", False), - (games.create_mixed_behav_game_efg(), "Infoset 1:1", True), - (games.create_mixed_behav_game_efg(), "Infoset 2:1", True), - (games.create_mixed_behav_game_efg(), "Infoset 3:1", True), - (games.create_stripped_down_poker_efg(), "Alice has King", False), - (games.create_stripped_down_poker_efg(), "Alice has Queen", False), - (games.create_stripped_down_poker_efg(), "Bob's response", False), - (games.create_stripped_down_poker_efg(), "Alice has King", True), - (games.create_stripped_down_poker_efg(), "Alice has Queen", True), - (games.create_stripped_down_poker_efg(), "Bob's response", True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 1:1", False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 2:1", False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 3:1", False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 1:1", True), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 2:1", True), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 3:1", True), + (games.create_stripped_down_poker_efg(), "Alice has King", False), + (games.create_stripped_down_poker_efg(), "Alice has Queen", False), + (games.create_stripped_down_poker_efg(), "Bob's response", False), + (games.create_stripped_down_poker_efg(), "Alice has King", True), + (games.create_stripped_down_poker_efg(), "Alice has Queen", True), + (games.create_stripped_down_poker_efg(), "Bob's response", True), + ], ) def test_is_defined_at_by_label(game: gbt.Game, label: str, rational_flag: bool): """Test to check if an infoset is defined by string labels""" @@ -101,37 +107,41 @@ def test_is_defined_at_by_label(game: gbt.Game, label: str, rational_flag: bool) @pytest.mark.parametrize( "game,player_idx,infoset_idx,action_idx,prob,rational_flag", - [(games.create_mixed_behav_game_efg(), 0, 0, 0, 0.5, False), - (games.create_mixed_behav_game_efg(), 0, 0, 1, 0.5, False), - (games.create_mixed_behav_game_efg(), 1, 0, 0, 0.5, False), - (games.create_mixed_behav_game_efg(), 1, 0, 1, 0.5, False), - (games.create_mixed_behav_game_efg(), 2, 0, 0, 0.5, False), - (games.create_mixed_behav_game_efg(), 2, 0, 1, 0.5, False), - (games.create_mixed_behav_game_efg(), 0, 0, 0, "1/2", True), - (games.create_mixed_behav_game_efg(), 0, 0, 1, "1/2", True), - (games.create_mixed_behav_game_efg(), 1, 0, 0, "1/2", True), - (games.create_mixed_behav_game_efg(), 1, 0, 1, "1/2", True), - (games.create_mixed_behav_game_efg(), 2, 0, 0, "1/2", True), - (games.create_mixed_behav_game_efg(), 2, 0, 1, "1/2", True), - (games.create_stripped_down_poker_efg(), 0, 0, 0, 0.5, False), - (games.create_stripped_down_poker_efg(), 0, 0, 1, 0.5, False), - (games.create_stripped_down_poker_efg(), 0, 1, 0, 0.5, False), - (games.create_stripped_down_poker_efg(), 0, 1, 1, 0.5, False), - (games.create_stripped_down_poker_efg(), 1, 0, 0, 0.5, False), - (games.create_stripped_down_poker_efg(), 1, 0, 1, 0.5, False), - (games.create_stripped_down_poker_efg(), 0, 0, 0, "1/2", True), - (games.create_stripped_down_poker_efg(), 0, 0, 1, "1/2", True), - (games.create_stripped_down_poker_efg(), 0, 1, 0, "1/2", True), - (games.create_stripped_down_poker_efg(), 0, 1, 1, "1/2", True), - (games.create_stripped_down_poker_efg(), 1, 0, 0, "1/2", True), - (games.create_stripped_down_poker_efg(), 1, 0, 1, "1/2", True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, 0, 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, 1, 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, 0, 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, 1, 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, 0, 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, 1, 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, 0, "1/2", True), + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, 1, "1/2", True), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, 0, "1/2", True), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, 1, "1/2", True), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, 0, "1/2", True), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, 1, "1/2", True), + (games.create_stripped_down_poker_efg(), 0, 0, 0, 0.5, False), + (games.create_stripped_down_poker_efg(), 0, 0, 1, 0.5, False), + (games.create_stripped_down_poker_efg(), 0, 1, 0, 0.5, False), + (games.create_stripped_down_poker_efg(), 0, 1, 1, 0.5, False), + (games.create_stripped_down_poker_efg(), 1, 0, 0, 0.5, False), + (games.create_stripped_down_poker_efg(), 1, 0, 1, 0.5, False), + (games.create_stripped_down_poker_efg(), 0, 0, 0, "1/2", True), + (games.create_stripped_down_poker_efg(), 0, 0, 1, "1/2", True), + (games.create_stripped_down_poker_efg(), 0, 1, 0, "1/2", True), + (games.create_stripped_down_poker_efg(), 0, 1, 1, "1/2", True), + (games.create_stripped_down_poker_efg(), 1, 0, 0, "1/2", True), + (games.create_stripped_down_poker_efg(), 1, 0, 1, "1/2", True), + ], ) -def test_profile_indexing_by_player_infoset_action_idx_reference(game: gbt.Game, player_idx: int, - infoset_idx: int, - action_idx: int, - prob: str | float, - rational_flag: bool): +def test_profile_indexing_by_player_infoset_action_idx_reference( + game: gbt.Game, + player_idx: int, + infoset_idx: int, + action_idx: int, + prob: str | float, + rational_flag: bool, +): profile = game.mixed_behavior_profile(rational=rational_flag) action = game.players[player_idx].infosets[infoset_idx].actions[action_idx] prob = gbt.Rational(prob) if rational_flag else prob @@ -140,25 +150,26 @@ def test_profile_indexing_by_player_infoset_action_idx_reference(game: gbt.Game, @pytest.mark.parametrize( "game,action_label,prob,rational_flag", - [(games.create_mixed_behav_game_efg(), "U1", 0.5, False), - (games.create_mixed_behav_game_efg(), "D1", 0.5, False), - (games.create_mixed_behav_game_efg(), "U2", 0.5, False), - (games.create_mixed_behav_game_efg(), "D2", 0.5, False), - (games.create_mixed_behav_game_efg(), "U3", 0.5, False), - (games.create_mixed_behav_game_efg(), "D3", 0.5, False), - (games.create_mixed_behav_game_efg(), "U1", "1/2", True), - (games.create_mixed_behav_game_efg(), "D1", "1/2", True), - (games.create_mixed_behav_game_efg(), "U2", "1/2", True), - (games.create_mixed_behav_game_efg(), "D2", "1/2", True), - (games.create_mixed_behav_game_efg(), "U3", "1/2", True), - (games.create_mixed_behav_game_efg(), "D3", "1/2", True), - (games.create_stripped_down_poker_efg(), "Call", 0.5, False), - (games.create_stripped_down_poker_efg(), "Call", "1/2", True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), "U1", 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), "D1", 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), "U2", 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), "D2", 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), "U3", 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), "D3", 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), "U1", "1/2", True), + (games.read_from_file("mixed_behavior_game.efg"), "D1", "1/2", True), + (games.read_from_file("mixed_behavior_game.efg"), "U2", "1/2", True), + (games.read_from_file("mixed_behavior_game.efg"), "D2", "1/2", True), + (games.read_from_file("mixed_behavior_game.efg"), "U3", "1/2", True), + (games.read_from_file("mixed_behavior_game.efg"), "D3", "1/2", True), + (games.create_stripped_down_poker_efg(), "Call", 0.5, False), + (games.create_stripped_down_poker_efg(), "Call", "1/2", True), + ], ) -def test_profile_indexing_by_action_label_reference(game: gbt.Game, action_label: str, - prob: str | float, - rational_flag: bool): +def test_profile_indexing_by_action_label_reference( + game: gbt.Game, action_label: str, prob: str | float, rational_flag: bool +): """Here we only use the action label, which are all valid""" profile = game.mixed_behavior_profile(rational=rational_flag) prob = gbt.Rational(prob) if rational_flag else prob @@ -167,36 +178,33 @@ def test_profile_indexing_by_action_label_reference(game: gbt.Game, action_label @pytest.mark.parametrize( "game,action_label,rational_flag,error", - [(games.create_mixed_behav_game_efg(), "U4", True, KeyError), - (games.create_mixed_behav_game_efg(), "U4", False, KeyError), - (games.create_stripped_down_poker_efg(), "Bet", True, ValueError), - (games.create_stripped_down_poker_efg(), "Bet", False, ValueError), - (games.create_stripped_down_poker_efg(), "Fold", True, ValueError), - (games.create_stripped_down_poker_efg(), "Fold", False, ValueError), - (games.create_stripped_down_poker_efg(), "BetFold", True, KeyError), - (games.create_stripped_down_poker_efg(), "BetFold", False, KeyError), - (games.create_stripped_down_poker_efg(), "MISSING", True, KeyError), - (games.create_stripped_down_poker_efg(), "MISSING", False, KeyError), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), "U4", True, KeyError), + (games.read_from_file("mixed_behavior_game.efg"), "U4", False, KeyError), + (games.create_stripped_down_poker_efg(), "Bet", True, ValueError), + (games.create_stripped_down_poker_efg(), "Bet", False, ValueError), + (games.create_stripped_down_poker_efg(), "Fold", True, ValueError), + (games.create_stripped_down_poker_efg(), "Fold", False, ValueError), + (games.create_stripped_down_poker_efg(), "BetFold", True, KeyError), + (games.create_stripped_down_poker_efg(), "BetFold", False, KeyError), + (games.create_stripped_down_poker_efg(), "MISSING", True, KeyError), + (games.create_stripped_down_poker_efg(), "MISSING", False, KeyError), + ], ) -def test_profile_indexing_by_invalid_action_label(game: gbt.Game, action_label: str, - rational_flag: bool, - error: ValueError | KeyError): - """Test that we get a KeyError for a missing label, and a ValueError for an ambigiuous label - """ +def test_profile_indexing_by_invalid_action_label( + game: gbt.Game, action_label: str, rational_flag: bool, error: ValueError | KeyError +): + """Test that we get a KeyError for a missing label, and a ValueError for an ambigiuous label""" with pytest.raises(error): game.mixed_behavior_profile(rational=rational_flag)[action_label] -@pytest.mark.parametrize( - "rational_flag", - [True, False] - ) +@pytest.mark.parametrize("rational_flag", [True, False]) def test_profile_indexing_by_invalid_infoset_label(rational_flag: bool): """Create a duplicate infoset label and check we get a ValueError for this ambiguous label, and a KeyError for the now missing label that was overwritten """ - game = games.create_mixed_behav_game_efg() + game = games.read_from_file("mixed_behavior_game.efg") profile = game.mixed_behavior_profile(rational=rational_flag) assert profile["Infoset 1:1"] game.infosets["Infoset 1:1"].label = "Infoset 2:1" @@ -208,25 +216,24 @@ def test_profile_indexing_by_invalid_infoset_label(rational_flag: bool): @pytest.mark.parametrize( "game,infoset_label,action_label,prob,rational_flag", - [(games.create_mixed_behav_game_efg(), "Infoset 1:1", "U1", 0.5, False), - (games.create_mixed_behav_game_efg(), "Infoset 1:1", "D1", 0.5, False), - (games.create_mixed_behav_game_efg(), "Infoset 1:1", "U1", "1/2", True), - (games.create_mixed_behav_game_efg(), "Infoset 1:1", "D1", "1/2", True), - (games.create_stripped_down_poker_efg(), "Alice has King", "Bet", 0.5, False), - (games.create_stripped_down_poker_efg(), "Alice has King", "Fold", 0.5, False), - (games.create_stripped_down_poker_efg(), "Alice has Queen", "Bet", 0.5, False), - (games.create_stripped_down_poker_efg(), "Alice has Queen", "Fold", 0.5, False), - (games.create_stripped_down_poker_efg(), "Bob's response", "Call", 0.5, False), - (games.create_stripped_down_poker_efg(), "Bob's response", "Fold", 0.5, False), - (games.create_stripped_down_poker_efg(), "Bob's response", "Call", "1/2", True), - (games.create_stripped_down_poker_efg(), "Bob's response", "Fold", "1/2", True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 1:1", "U1", 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 1:1", "D1", 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 1:1", "U1", "1/2", True), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 1:1", "D1", "1/2", True), + (games.create_stripped_down_poker_efg(), "Alice has King", "Bet", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice has King", "Fold", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice has Queen", "Bet", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice has Queen", "Fold", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob's response", "Call", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob's response", "Fold", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob's response", "Call", "1/2", True), + (games.create_stripped_down_poker_efg(), "Bob's response", "Fold", "1/2", True), + ], ) -def test_profile_indexing_by_infoset_and_action_labels_reference(game: gbt.Game, - infoset_label: str, - action_label: str, - prob: str | float, - rational_flag: bool): +def test_profile_indexing_by_infoset_and_action_labels_reference( + game: gbt.Game, infoset_label: str, action_label: str, prob: str | float, rational_flag: bool +): """Here we use the infoset label and action label, with some examples where the action label alone throws a ValueError (checked in a separate test) """ @@ -237,26 +244,57 @@ def test_profile_indexing_by_infoset_and_action_labels_reference(game: gbt.Game, @pytest.mark.parametrize( "game,player_label,infoset_label,action_label,prob,rational_flag", - [(games.create_mixed_behav_game_efg(), "Player 1", "Infoset 1:1", "U1", 0.5, False), - (games.create_mixed_behav_game_efg(), "Player 1", "Infoset 1:1", "D1", 0.5, False), - (games.create_mixed_behav_game_efg(), "Player 1", "Infoset 1:1", "U1", "1/2", True), - (games.create_mixed_behav_game_efg(), "Player 1", "Infoset 1:1", "D1", "1/2", True), - (games.create_stripped_down_poker_efg(), "Alice", "Alice has King", "Bet", 0.5, False), - (games.create_stripped_down_poker_efg(), "Alice", "Alice has King", "Fold", 0.5, False), - (games.create_stripped_down_poker_efg(), "Alice", "Alice has Queen", "Bet", 0.5, False), - (games.create_stripped_down_poker_efg(), "Alice", "Alice has Queen", "Fold", 0.5, False), - (games.create_stripped_down_poker_efg(), "Bob", "Bob's response", "Call", 0.5, False), - (games.create_stripped_down_poker_efg(), "Bob", "Bob's response", "Fold", 0.5, False), - (games.create_stripped_down_poker_efg(), "Bob", "Bob's response", "Call", "1/2", True), - (games.create_stripped_down_poker_efg(), "Bob", "Bob's response", "Fold", "1/2", True), - ] + [ + ( + games.read_from_file("mixed_behavior_game.efg"), + "Player 1", + "Infoset 1:1", + "U1", + 0.5, + False, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + "Player 1", + "Infoset 1:1", + "D1", + 0.5, + False, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + "Player 1", + "Infoset 1:1", + "U1", + "1/2", + True, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + "Player 1", + "Infoset 1:1", + "D1", + "1/2", + True, + ), + (games.create_stripped_down_poker_efg(), "Alice", "Alice has King", "Bet", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice", "Alice has King", "Fold", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice", "Alice has Queen", "Bet", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice", "Alice has Queen", "Fold", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob", "Bob's response", "Call", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob", "Bob's response", "Fold", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob", "Bob's response", "Call", "1/2", True), + (games.create_stripped_down_poker_efg(), "Bob", "Bob's response", "Fold", "1/2", True), + ], ) -def test_profile_indexing_by_player_infoset_action_labels_reference(game: gbt.Game, - player_label: str, - infoset_label: str, - action_label: str, - prob: str | float, - rational_flag: bool): +def test_profile_indexing_by_player_infoset_action_labels_reference( + game: gbt.Game, + player_label: str, + infoset_label: str, + action_label: str, + prob: str | float, + rational_flag: bool, +): """Here we use the infoset label and action label, with some examples where the action label alone throws a ValueError (checked in a separate test) """ @@ -267,41 +305,52 @@ def test_profile_indexing_by_player_infoset_action_labels_reference(game: gbt.Ga @pytest.mark.parametrize( "game,infoset_label,action_label,rational_flag", - [(games.create_mixed_behav_game_efg(), "1:1", "U2", True), # U2 is at a different iset - (games.create_mixed_behav_game_efg(), "1:1", "U2", False), - (games.create_mixed_behav_game_efg(), "1:1", "U4", True), # U4 isn't in the game - (games.create_mixed_behav_game_efg(), "1:1", "U4", False), - (games.create_stripped_down_poker_efg(), "Alice has King", "MEET", True), - (games.create_stripped_down_poker_efg(), "Alice has King", "MEET", False), - ] + [ + ( + games.read_from_file("mixed_behavior_game.efg"), + "1:1", + "U2", + True, + ), # U2 is at a different iset + (games.read_from_file("mixed_behavior_game.efg"), "1:1", "U2", False), + ( + games.read_from_file("mixed_behavior_game.efg"), + "1:1", + "U4", + True, + ), # U4 isn't in the game + (games.read_from_file("mixed_behavior_game.efg"), "1:1", "U4", False), + (games.create_stripped_down_poker_efg(), "Alice has King", "MEET", True), + (games.create_stripped_down_poker_efg(), "Alice has King", "MEET", False), + ], ) -def test_profile_indexing_by_invalid_infoset_or_action_label(game: gbt.Game, infoset_label: str, - action_label: str, - rational_flag: bool): +def test_profile_indexing_by_invalid_infoset_or_action_label( + game: gbt.Game, infoset_label: str, action_label: str, rational_flag: bool +): with pytest.raises(KeyError): game.mixed_behavior_profile(rational=rational_flag)[infoset_label][action_label] @pytest.mark.parametrize( "game,player_idx,infoset_idx,probs,rational_flag", - [(games.create_mixed_behav_game_efg(), 0, 0, [0.5, 0.5], False), - (games.create_mixed_behav_game_efg(), 1, 0, [0.5, 0.5], False), - (games.create_mixed_behav_game_efg(), 2, 0, [0.5, 0.5], False), - (games.create_mixed_behav_game_efg(), 0, 0, ["1/2", "1/2"], True), - (games.create_mixed_behav_game_efg(), 1, 0, ["1/2", "1/2"], True), - (games.create_mixed_behav_game_efg(), 2, 0, ["1/2", "1/2"], True), - (games.create_stripped_down_poker_efg(), 0, 0, [0.5, 0.5], False), - (games.create_stripped_down_poker_efg(), 0, 1, [0.5, 0.5], False), - (games.create_stripped_down_poker_efg(), 1, 0, [0.5, 0.5], False), - (games.create_stripped_down_poker_efg(), 0, 0, ["1/2", "1/2"], True), - (games.create_stripped_down_poker_efg(), 0, 1, ["1/2", "1/2"], True), - (games.create_stripped_down_poker_efg(), 1, 0, ["1/2", "1/2"], True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, [0.5, 0.5], False), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, [0.5, 0.5], False), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, [0.5, 0.5], False), + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, ["1/2", "1/2"], True), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, ["1/2", "1/2"], True), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, ["1/2", "1/2"], True), + (games.create_stripped_down_poker_efg(), 0, 0, [0.5, 0.5], False), + (games.create_stripped_down_poker_efg(), 0, 1, [0.5, 0.5], False), + (games.create_stripped_down_poker_efg(), 1, 0, [0.5, 0.5], False), + (games.create_stripped_down_poker_efg(), 0, 0, ["1/2", "1/2"], True), + (games.create_stripped_down_poker_efg(), 0, 1, ["1/2", "1/2"], True), + (games.create_stripped_down_poker_efg(), 1, 0, ["1/2", "1/2"], True), + ], ) -def test_profile_indexing_by_player_and_infoset_idx_reference(game: gbt.Game, - player_idx: int, - infoset_idx: int, - probs: list, rational_flag: bool): +def test_profile_indexing_by_player_and_infoset_idx_reference( + game: gbt.Game, player_idx: int, infoset_idx: int, probs: list, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) infoset = game.players[player_idx].infosets[infoset_idx] probs = [gbt.Rational(prob) for prob in probs] if rational_flag else probs @@ -310,23 +359,24 @@ def test_profile_indexing_by_player_and_infoset_idx_reference(game: gbt.Game, @pytest.mark.parametrize( "game,player_idx,infoset_label,probs,rational_flag", - [(games.create_mixed_behav_game_efg(), 0, "Infoset 1:1", [0.5, 0.5], False), - (games.create_mixed_behav_game_efg(), 1, "Infoset 2:1", [0.5, 0.5], False), - (games.create_mixed_behav_game_efg(), 2, "Infoset 3:1", [0.5, 0.5], False), - (games.create_mixed_behav_game_efg(), 0, "Infoset 1:1", ["1/2", "1/2"], True), - (games.create_mixed_behav_game_efg(), 1, "Infoset 2:1", ["1/2", "1/2"], True), - (games.create_mixed_behav_game_efg(), 2, "Infoset 3:1", ["1/2", "1/2"], True), - (games.create_stripped_down_poker_efg(), 0, "Alice has King", [0.5, 0.5], False), - (games.create_stripped_down_poker_efg(), 0, "Alice has Queen", [0.5, 0.5], False), - (games.create_stripped_down_poker_efg(), 1, "Bob's response", [0.5, 0.5], False), - (games.create_stripped_down_poker_efg(), 0, "Alice has King", ["1/2", "1/2"], True), - (games.create_stripped_down_poker_efg(), 0, "Alice has Queen", ["1/2", "1/2"], True), - (games.create_stripped_down_poker_efg(), 1, "Bob's response", ["1/2", "1/2"], True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), 0, "Infoset 1:1", [0.5, 0.5], False), + (games.read_from_file("mixed_behavior_game.efg"), 1, "Infoset 2:1", [0.5, 0.5], False), + (games.read_from_file("mixed_behavior_game.efg"), 2, "Infoset 3:1", [0.5, 0.5], False), + (games.read_from_file("mixed_behavior_game.efg"), 0, "Infoset 1:1", ["1/2", "1/2"], True), + (games.read_from_file("mixed_behavior_game.efg"), 1, "Infoset 2:1", ["1/2", "1/2"], True), + (games.read_from_file("mixed_behavior_game.efg"), 2, "Infoset 3:1", ["1/2", "1/2"], True), + (games.create_stripped_down_poker_efg(), 0, "Alice has King", [0.5, 0.5], False), + (games.create_stripped_down_poker_efg(), 0, "Alice has Queen", [0.5, 0.5], False), + (games.create_stripped_down_poker_efg(), 1, "Bob's response", [0.5, 0.5], False), + (games.create_stripped_down_poker_efg(), 0, "Alice has King", ["1/2", "1/2"], True), + (games.create_stripped_down_poker_efg(), 0, "Alice has Queen", ["1/2", "1/2"], True), + (games.create_stripped_down_poker_efg(), 1, "Bob's response", ["1/2", "1/2"], True), + ], ) -def test_profile_indexing_by_player_idx_infoset_label_reference(game: gbt.Game, player_idx: int, - infoset_label: str, probs: list, - rational_flag: bool): +def test_profile_indexing_by_player_idx_infoset_label_reference( + game: gbt.Game, player_idx: int, infoset_label: str, probs: list, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) player = game.players[player_idx] probs = [gbt.Rational(prob) for prob in probs] if rational_flag else probs @@ -336,16 +386,21 @@ def test_profile_indexing_by_player_idx_infoset_label_reference(game: gbt.Game, @pytest.mark.parametrize( "game,player_label,infoset_label,rational_flag", - [(games.create_mixed_behav_game_efg(), "Player 1", "1:1", True), # correct: "Infoset 1:1" - (games.create_mixed_behav_game_efg(), "Player 1", "1:1", False), - (games.create_stripped_down_poker_efg(), "Player 1", "(2,1)", True), # wrong player - (games.create_stripped_down_poker_efg(), "Player 1", "(2,1)", False), - ] + [ + ( + games.read_from_file("mixed_behavior_game.efg"), + "Player 1", + "1:1", + True, + ), # correct: "Infoset 1:1" + (games.read_from_file("mixed_behavior_game.efg"), "Player 1", "1:1", False), + (games.create_stripped_down_poker_efg(), "Player 1", "(2,1)", True), # wrong player + (games.create_stripped_down_poker_efg(), "Player 1", "(2,1)", False), + ], ) -def test_profile_indexing_by_player_and_invalid_infoset_label(game: gbt.Game, - player_label: str, - infoset_label: str, - rational_flag: bool): +def test_profile_indexing_by_player_and_invalid_infoset_label( + game: gbt.Game, player_label: str, infoset_label: str, rational_flag: bool +): """Test that we get a KeyError and that "player" appears in the error message""" with pytest.raises(KeyError, match="player"): game.mixed_behavior_profile(rational=rational_flag)[player_label][infoset_label] @@ -353,16 +408,16 @@ def test_profile_indexing_by_player_and_invalid_infoset_label(game: gbt.Game, @pytest.mark.parametrize( "game,player_label,action_label,rational_flag", - [(games.create_mixed_behav_game_efg(), "Player 1", "U2", True), - (games.create_mixed_behav_game_efg(), "Player 1", "U2", False), - (games.create_stripped_down_poker_efg(), "Player 1", "MEET", True), - (games.create_stripped_down_poker_efg(), "Player 1", "MEET", False), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), "Player 1", "U2", True), + (games.read_from_file("mixed_behavior_game.efg"), "Player 1", "U2", False), + (games.create_stripped_down_poker_efg(), "Player 1", "MEET", True), + (games.create_stripped_down_poker_efg(), "Player 1", "MEET", False), + ], ) -def test_profile_indexing_by_player_and_invalid_action_label(game: gbt.Game, - player_label: str, - action_label: str, - rational_flag: bool): +def test_profile_indexing_by_player_and_invalid_action_label( + game: gbt.Game, player_label: str, action_label: str, rational_flag: bool +): """Test that we get a KeyError and that "player" appears in the error message""" with pytest.raises(KeyError, match="player"): game.mixed_behavior_profile(rational=rational_flag)[player_label][action_label] @@ -370,21 +425,22 @@ def test_profile_indexing_by_player_and_invalid_action_label(game: gbt.Game, @pytest.mark.parametrize( "game,player_idx,behav_data,rational_flag", - [(games.create_mixed_behav_game_efg(), 0, [[0.5, 0.5]], False), - (games.create_mixed_behav_game_efg(), 1, [[0.5, 0.5]], False), - (games.create_mixed_behav_game_efg(), 2, [[0.5, 0.5]], False), - (games.create_mixed_behav_game_efg(), 0, [["1/2", "1/2"]], True), - (games.create_mixed_behav_game_efg(), 1, [["1/2", "1/2"]], True), - (games.create_mixed_behav_game_efg(), 2, [["1/2", "1/2"]], True), - (games.create_stripped_down_poker_efg(), 0, [[0.5, 0.5], [0.5, 0.5]], False), - (games.create_stripped_down_poker_efg(), 1, [[0.5, 0.5]], False), - (games.create_stripped_down_poker_efg(), 0, [["1/2", "1/2"], ["1/2", "1/2"]], True), - (games.create_stripped_down_poker_efg(), 1, [["1/2", "1/2"]], True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), 0, [[0.5, 0.5]], False), + (games.read_from_file("mixed_behavior_game.efg"), 1, [[0.5, 0.5]], False), + (games.read_from_file("mixed_behavior_game.efg"), 2, [[0.5, 0.5]], False), + (games.read_from_file("mixed_behavior_game.efg"), 0, [["1/2", "1/2"]], True), + (games.read_from_file("mixed_behavior_game.efg"), 1, [["1/2", "1/2"]], True), + (games.read_from_file("mixed_behavior_game.efg"), 2, [["1/2", "1/2"]], True), + (games.create_stripped_down_poker_efg(), 0, [[0.5, 0.5], [0.5, 0.5]], False), + (games.create_stripped_down_poker_efg(), 1, [[0.5, 0.5]], False), + (games.create_stripped_down_poker_efg(), 0, [["1/2", "1/2"], ["1/2", "1/2"]], True), + (games.create_stripped_down_poker_efg(), 1, [["1/2", "1/2"]], True), + ], ) -def test_profile_indexing_by_player_idx_reference(game: gbt.Game, player_idx: int, - behav_data: list, - rational_flag: bool): +def test_profile_indexing_by_player_idx_reference( + game: gbt.Game, player_idx: int, behav_data: list, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) player = game.players[player_idx] if rational_flag: @@ -394,21 +450,22 @@ def test_profile_indexing_by_player_idx_reference(game: gbt.Game, player_idx: in @pytest.mark.parametrize( "game,player_label,behav_data,rational_flag", - [(games.create_mixed_behav_game_efg(), "Player 1", [[0.5, 0.5]], False), - (games.create_mixed_behav_game_efg(), "Player 2", [[0.5, 0.5]], False), - (games.create_mixed_behav_game_efg(), "Player 3", [[0.5, 0.5]], False), - (games.create_mixed_behav_game_efg(), "Player 1", [["1/2", "1/2"]], True), - (games.create_mixed_behav_game_efg(), "Player 2", [["1/2", "1/2"]], True), - (games.create_mixed_behav_game_efg(), "Player 3", [["1/2", "1/2"]], True), - (games.create_stripped_down_poker_efg(), "Alice", [[0.5, 0.5], [0.5, 0.5]], False), - (games.create_stripped_down_poker_efg(), "Bob", [[0.5, 0.5]], False), - (games.create_stripped_down_poker_efg(), "Alice", [["1/2", "1/2"], ["1/2", "1/2"]], - True), - (games.create_stripped_down_poker_efg(), "Bob", [["1/2", "1/2"]], True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), "Player 1", [[0.5, 0.5]], False), + (games.read_from_file("mixed_behavior_game.efg"), "Player 2", [[0.5, 0.5]], False), + (games.read_from_file("mixed_behavior_game.efg"), "Player 3", [[0.5, 0.5]], False), + (games.read_from_file("mixed_behavior_game.efg"), "Player 1", [["1/2", "1/2"]], True), + (games.read_from_file("mixed_behavior_game.efg"), "Player 2", [["1/2", "1/2"]], True), + (games.read_from_file("mixed_behavior_game.efg"), "Player 3", [["1/2", "1/2"]], True), + (games.create_stripped_down_poker_efg(), "Alice", [[0.5, 0.5], [0.5, 0.5]], False), + (games.create_stripped_down_poker_efg(), "Bob", [[0.5, 0.5]], False), + (games.create_stripped_down_poker_efg(), "Alice", [["1/2", "1/2"], ["1/2", "1/2"]], True), + (games.create_stripped_down_poker_efg(), "Bob", [["1/2", "1/2"]], True), + ], ) -def test_profile_indexing_by_player_label_reference(game: gbt.Game, player_label: str, - behav_data: list, rational_flag: bool): +def test_profile_indexing_by_player_label_reference( + game: gbt.Game, player_label: str, behav_data: list, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) if rational_flag: behav_data = [[gbt.Rational(prob) for prob in probs] for probs in behav_data] @@ -417,34 +474,36 @@ def test_profile_indexing_by_player_label_reference(game: gbt.Game, player_label @pytest.mark.parametrize( "game,action_idx,prob,rational_flag", - [(games.create_mixed_behav_game_efg(), 0, 0.72, False), - (games.create_mixed_behav_game_efg(), 1, 0.28, False), - (games.create_mixed_behav_game_efg(), 2, 0.42, False), - (games.create_mixed_behav_game_efg(), 3, 0.58, False), - (games.create_mixed_behav_game_efg(), 4, 0.02, False), - (games.create_mixed_behav_game_efg(), 5, 0.98, False), - (games.create_mixed_behav_game_efg(), 0, "2/9", True), - (games.create_mixed_behav_game_efg(), 1, "7/9", True), - (games.create_mixed_behav_game_efg(), 2, "4/13", True), - (games.create_mixed_behav_game_efg(), 3, "9/13", True), - (games.create_mixed_behav_game_efg(), 4, "1/98", True), - (games.create_mixed_behav_game_efg(), 5, "97/98", True), - (games.create_stripped_down_poker_efg(), 0, 0.1, False), - (games.create_stripped_down_poker_efg(), 1, 0.2, False), - (games.create_stripped_down_poker_efg(), 2, 0.3, False), - (games.create_stripped_down_poker_efg(), 3, 0.4, False), - (games.create_stripped_down_poker_efg(), 4, 0.5, False), - (games.create_stripped_down_poker_efg(), 5, 0.6, False), - (games.create_stripped_down_poker_efg(), 0, "1/10", True), - (games.create_stripped_down_poker_efg(), 1, "2/10", True), - (games.create_stripped_down_poker_efg(), 2, "3/10", True), - (games.create_stripped_down_poker_efg(), 3, "4/10", True), - (games.create_stripped_down_poker_efg(), 4, "5/10", True), - (games.create_stripped_down_poker_efg(), 5, "6/10", True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), 0, 0.72, False), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0.28, False), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0.42, False), + (games.read_from_file("mixed_behavior_game.efg"), 3, 0.58, False), + (games.read_from_file("mixed_behavior_game.efg"), 4, 0.02, False), + (games.read_from_file("mixed_behavior_game.efg"), 5, 0.98, False), + (games.read_from_file("mixed_behavior_game.efg"), 0, "2/9", True), + (games.read_from_file("mixed_behavior_game.efg"), 1, "7/9", True), + (games.read_from_file("mixed_behavior_game.efg"), 2, "4/13", True), + (games.read_from_file("mixed_behavior_game.efg"), 3, "9/13", True), + (games.read_from_file("mixed_behavior_game.efg"), 4, "1/98", True), + (games.read_from_file("mixed_behavior_game.efg"), 5, "97/98", True), + (games.create_stripped_down_poker_efg(), 0, 0.1, False), + (games.create_stripped_down_poker_efg(), 1, 0.2, False), + (games.create_stripped_down_poker_efg(), 2, 0.3, False), + (games.create_stripped_down_poker_efg(), 3, 0.4, False), + (games.create_stripped_down_poker_efg(), 4, 0.5, False), + (games.create_stripped_down_poker_efg(), 5, 0.6, False), + (games.create_stripped_down_poker_efg(), 0, "1/10", True), + (games.create_stripped_down_poker_efg(), 1, "2/10", True), + (games.create_stripped_down_poker_efg(), 2, "3/10", True), + (games.create_stripped_down_poker_efg(), 3, "4/10", True), + (games.create_stripped_down_poker_efg(), 4, "5/10", True), + (games.create_stripped_down_poker_efg(), 5, "6/10", True), + ], ) -def test_set_probabilities_action(game: gbt.Game, action_idx: int, prob: str | float, - rational_flag: bool): +def test_set_probabilities_action( + game: gbt.Game, action_idx: int, prob: str | float, rational_flag: bool +): """Test to set probabilities of actions by action index""" profile = game.mixed_behavior_profile(rational=rational_flag) prob = gbt.Rational(prob) if rational_flag else prob @@ -455,24 +514,26 @@ def test_set_probabilities_action(game: gbt.Game, action_idx: int, prob: str | f @pytest.mark.parametrize( "game,label,prob,rational_flag", - [(games.create_mixed_behav_game_efg(), "U1", 0.72, False), - (games.create_mixed_behav_game_efg(), "D1", 0.28, False), - (games.create_mixed_behav_game_efg(), "U2", 0.42, False), - (games.create_mixed_behav_game_efg(), "D2", 0.58, False), - (games.create_mixed_behav_game_efg(), "U3", 0.02, False), - (games.create_mixed_behav_game_efg(), "D3", 0.98, False), - (games.create_mixed_behav_game_efg(), "U1", "2/9", True), - (games.create_mixed_behav_game_efg(), "D1", "7/9", True), - (games.create_mixed_behav_game_efg(), "U2", "4/13", True), - (games.create_mixed_behav_game_efg(), "D2", "9/13", True), - (games.create_mixed_behav_game_efg(), "U3", "1/98", True), - (games.create_mixed_behav_game_efg(), "D3", "97/98", True), - (games.create_stripped_down_poker_efg(), "Call", 0.3, False), - (games.create_stripped_down_poker_efg(), "Call", "3/10", True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), "U1", 0.72, False), + (games.read_from_file("mixed_behavior_game.efg"), "D1", 0.28, False), + (games.read_from_file("mixed_behavior_game.efg"), "U2", 0.42, False), + (games.read_from_file("mixed_behavior_game.efg"), "D2", 0.58, False), + (games.read_from_file("mixed_behavior_game.efg"), "U3", 0.02, False), + (games.read_from_file("mixed_behavior_game.efg"), "D3", 0.98, False), + (games.read_from_file("mixed_behavior_game.efg"), "U1", "2/9", True), + (games.read_from_file("mixed_behavior_game.efg"), "D1", "7/9", True), + (games.read_from_file("mixed_behavior_game.efg"), "U2", "4/13", True), + (games.read_from_file("mixed_behavior_game.efg"), "D2", "9/13", True), + (games.read_from_file("mixed_behavior_game.efg"), "U3", "1/98", True), + (games.read_from_file("mixed_behavior_game.efg"), "D3", "97/98", True), + (games.create_stripped_down_poker_efg(), "Call", 0.3, False), + (games.create_stripped_down_poker_efg(), "Call", "3/10", True), + ], ) -def test_set_probabilities_action_by_label(game: gbt.Game, label: str, - prob: str | float, rational_flag: bool): +def test_set_probabilities_action_by_label( + game: gbt.Game, label: str, prob: str | float, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) prob = gbt.Rational(prob) if rational_flag else prob profile[label] = prob @@ -481,22 +542,24 @@ def test_set_probabilities_action_by_label(game: gbt.Game, label: str, @pytest.mark.parametrize( "game,player_idx,infoset_idx,probs,rational_flag", - [(games.create_mixed_behav_game_efg(), 0, 0, [0.72, 0.28], False), - (games.create_mixed_behav_game_efg(), 1, 0, [0.42, 0.58], False), - (games.create_mixed_behav_game_efg(), 2, 0, [0.02, 0.98], False), - (games.create_mixed_behav_game_efg(), 0, 0, ["7/9", "2/9"], True), - (games.create_mixed_behav_game_efg(), 1, 0, ["4/13", "9/13"], True), - (games.create_mixed_behav_game_efg(), 2, 0, ["1/98", "97/98"], True), - (games.create_stripped_down_poker_efg(), 0, 0, [0.1, 0.9], False), - (games.create_stripped_down_poker_efg(), 0, 1, [0.2, 0.8], False), - (games.create_stripped_down_poker_efg(), 1, 0, [0.3, 0.7], False), - (games.create_stripped_down_poker_efg(), 0, 0, ["1/10", "9/10"], True), - (games.create_stripped_down_poker_efg(), 0, 1, ["2/10", "8/10"], True), - (games.create_stripped_down_poker_efg(), 1, 0, ["3/10", "7/10"], True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, [0.72, 0.28], False), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, [0.42, 0.58], False), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, [0.02, 0.98], False), + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, ["7/9", "2/9"], True), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, ["4/13", "9/13"], True), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, ["1/98", "97/98"], True), + (games.create_stripped_down_poker_efg(), 0, 0, [0.1, 0.9], False), + (games.create_stripped_down_poker_efg(), 0, 1, [0.2, 0.8], False), + (games.create_stripped_down_poker_efg(), 1, 0, [0.3, 0.7], False), + (games.create_stripped_down_poker_efg(), 0, 0, ["1/10", "9/10"], True), + (games.create_stripped_down_poker_efg(), 0, 1, ["2/10", "8/10"], True), + (games.create_stripped_down_poker_efg(), 1, 0, ["3/10", "7/10"], True), + ], ) -def test_set_probabilities_infoset(game: gbt.Game, player_idx: int, infoset_idx: int, probs: list, - rational_flag: bool): +def test_set_probabilities_infoset( + game: gbt.Game, player_idx: int, infoset_idx: int, probs: list, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) if rational_flag: probs = [gbt.Rational(p) for p in probs] @@ -507,22 +570,24 @@ def test_set_probabilities_infoset(game: gbt.Game, player_idx: int, infoset_idx: @pytest.mark.parametrize( "game,infoset_label,probs,rational_flag", - [(games.create_mixed_behav_game_efg(), "Infoset 1:1", [0.72, 0.28], False), - (games.create_mixed_behav_game_efg(), "Infoset 2:1", [0.42, 0.58], False), - (games.create_mixed_behav_game_efg(), "Infoset 3:1", [0.02, 0.98], False), - (games.create_mixed_behav_game_efg(), "Infoset 1:1", ["7/9", "2/9"], True), - (games.create_mixed_behav_game_efg(), "Infoset 2:1", ["4/13", "9/13"], True), - (games.create_mixed_behav_game_efg(), "Infoset 3:1", ["1/98", "97/98"], True), - (games.create_stripped_down_poker_efg(), "Alice has King", [0.1, 0.9], False), - (games.create_stripped_down_poker_efg(), "Alice has Queen", [0.2, 0.8], False), - (games.create_stripped_down_poker_efg(), "Bob's response", [0.3, 0.7], False), - (games.create_stripped_down_poker_efg(), "Alice has King", ["1/10", "9/10"], True), - (games.create_stripped_down_poker_efg(), "Alice has Queen", ["2/10", "8/10"], True), - (games.create_stripped_down_poker_efg(), "Bob's response", ["3/10", "7/10"], True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 1:1", [0.72, 0.28], False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 2:1", [0.42, 0.58], False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 3:1", [0.02, 0.98], False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 1:1", ["7/9", "2/9"], True), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 2:1", ["4/13", "9/13"], True), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 3:1", ["1/98", "97/98"], True), + (games.create_stripped_down_poker_efg(), "Alice has King", [0.1, 0.9], False), + (games.create_stripped_down_poker_efg(), "Alice has Queen", [0.2, 0.8], False), + (games.create_stripped_down_poker_efg(), "Bob's response", [0.3, 0.7], False), + (games.create_stripped_down_poker_efg(), "Alice has King", ["1/10", "9/10"], True), + (games.create_stripped_down_poker_efg(), "Alice has Queen", ["2/10", "8/10"], True), + (games.create_stripped_down_poker_efg(), "Bob's response", ["3/10", "7/10"], True), + ], ) -def test_set_probabilities_infoset_by_label(game: gbt.Game, infoset_label: str, probs: list, - rational_flag: bool): +def test_set_probabilities_infoset_by_label( + game: gbt.Game, infoset_label: str, probs: list, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) if rational_flag: probs = [gbt.Rational(p) for p in probs] @@ -532,20 +597,22 @@ def test_set_probabilities_infoset_by_label(game: gbt.Game, infoset_label: str, @pytest.mark.parametrize( "game,player_idx,behav_data,rational_flag", - [(games.create_mixed_behav_game_efg(), 0, [[0.72, 0.28]], False), - (games.create_mixed_behav_game_efg(), 1, [[0.42, 0.58]], False), - (games.create_mixed_behav_game_efg(), 2, [[0.02, 0.98]], False), - (games.create_mixed_behav_game_efg(), 0, [["7/9", "2/9"]], True), - (games.create_mixed_behav_game_efg(), 1, [["4/13", "9/13"]], True), - (games.create_mixed_behav_game_efg(), 2, [["1/98", "97/98"]], True), - (games.create_stripped_down_poker_efg(), 0, [[0.1, 0.9], [0.5, 0.5]], False), - (games.create_stripped_down_poker_efg(), 1, [[0.6, 0.4]], False), - (games.create_stripped_down_poker_efg(), 0, [["1/3", "2/3"], ["1/2", "1/2"]], True), - (games.create_stripped_down_poker_efg(), 1, [["2/3", "1/3"]], True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), 0, [[0.72, 0.28]], False), + (games.read_from_file("mixed_behavior_game.efg"), 1, [[0.42, 0.58]], False), + (games.read_from_file("mixed_behavior_game.efg"), 2, [[0.02, 0.98]], False), + (games.read_from_file("mixed_behavior_game.efg"), 0, [["7/9", "2/9"]], True), + (games.read_from_file("mixed_behavior_game.efg"), 1, [["4/13", "9/13"]], True), + (games.read_from_file("mixed_behavior_game.efg"), 2, [["1/98", "97/98"]], True), + (games.create_stripped_down_poker_efg(), 0, [[0.1, 0.9], [0.5, 0.5]], False), + (games.create_stripped_down_poker_efg(), 1, [[0.6, 0.4]], False), + (games.create_stripped_down_poker_efg(), 0, [["1/3", "2/3"], ["1/2", "1/2"]], True), + (games.create_stripped_down_poker_efg(), 1, [["2/3", "1/3"]], True), + ], ) -def test_set_probabilities_player(game: gbt.Game, player_idx: int, behav_data: list, - rational_flag: bool): +def test_set_probabilities_player( + game: gbt.Game, player_idx: int, behav_data: list, rational_flag: bool +): player = game.players[player_idx] profile = game.mixed_behavior_profile(rational=rational_flag) if rational_flag: @@ -556,21 +623,22 @@ def test_set_probabilities_player(game: gbt.Game, player_idx: int, behav_data: l @pytest.mark.parametrize( "game,player_label,behav_data,rational_flag", - [(games.create_mixed_behav_game_efg(), "Player 1", [[0.72, 0.28]], False), - (games.create_mixed_behav_game_efg(), "Player 2", [[0.42, 0.58]], False), - (games.create_mixed_behav_game_efg(), "Player 3", [[0.02, 0.98]], False), - (games.create_mixed_behav_game_efg(), "Player 1", [["7/9", "2/9"]], True), - (games.create_mixed_behav_game_efg(), "Player 2", [["4/13", "9/13"]], True), - (games.create_mixed_behav_game_efg(), "Player 3", [["1/98", "97/98"]], True), - (games.create_stripped_down_poker_efg(), "Alice", [[0.1, 0.9], [0.5, 0.5]], False), - (games.create_stripped_down_poker_efg(), "Bob", [[0.6, 0.4]], False), - (games.create_stripped_down_poker_efg(), "Alice", [["1/3", "2/3"], ["1/2", "1/2"]], - True), - (games.create_stripped_down_poker_efg(), "Bob", [["2/3", "1/3"]], True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), "Player 1", [[0.72, 0.28]], False), + (games.read_from_file("mixed_behavior_game.efg"), "Player 2", [[0.42, 0.58]], False), + (games.read_from_file("mixed_behavior_game.efg"), "Player 3", [[0.02, 0.98]], False), + (games.read_from_file("mixed_behavior_game.efg"), "Player 1", [["7/9", "2/9"]], True), + (games.read_from_file("mixed_behavior_game.efg"), "Player 2", [["4/13", "9/13"]], True), + (games.read_from_file("mixed_behavior_game.efg"), "Player 3", [["1/98", "97/98"]], True), + (games.create_stripped_down_poker_efg(), "Alice", [[0.1, 0.9], [0.5, 0.5]], False), + (games.create_stripped_down_poker_efg(), "Bob", [[0.6, 0.4]], False), + (games.create_stripped_down_poker_efg(), "Alice", [["1/3", "2/3"], ["1/2", "1/2"]], True), + (games.create_stripped_down_poker_efg(), "Bob", [["2/3", "1/3"]], True), + ], ) -def test_set_probabilities_player_by_label(game: gbt.Game, player_label: str, behav_data: list, - rational_flag: bool): +def test_set_probabilities_player_by_label( + game: gbt.Game, player_label: str, behav_data: list, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) if rational_flag: behav_data = [[gbt.Rational(prob) for prob in probs] for probs in behav_data] @@ -580,85 +648,90 @@ def test_set_probabilities_player_by_label(game: gbt.Game, player_label: str, be @pytest.mark.parametrize( "game,node_idx,realiz_prob,rational_flag", - [(games.create_mixed_behav_game_efg(), 0, "1", True), - (games.create_mixed_behav_game_efg(), 1, "1/2", True), - (games.create_mixed_behav_game_efg(), 2, "1/4", True), - (games.create_mixed_behav_game_efg(), 3, "1/8", True), - (games.create_mixed_behav_game_efg(), 4, "1/8", True), - (games.create_mixed_behav_game_efg(), 5, "1/4", True), - (games.create_mixed_behav_game_efg(), 6, "1/8", True), - (games.create_mixed_behav_game_efg(), 7, "1/8", True), - (games.create_mixed_behav_game_efg(), 8, "1/2", True), - (games.create_mixed_behav_game_efg(), 9, "1/4", True), - (games.create_mixed_behav_game_efg(), 10, "1/8", True), - (games.create_mixed_behav_game_efg(), 11, "1/8", True), - (games.create_mixed_behav_game_efg(), 12, "1/4", True), - (games.create_mixed_behav_game_efg(), 13, "1/8", True), - (games.create_mixed_behav_game_efg(), 14, "1/8", True), - (games.create_mixed_behav_game_efg(), 0, 1.0, False), - (games.create_mixed_behav_game_efg(), 1, 0.5, False), - (games.create_mixed_behav_game_efg(), 2, 0.25, False), - (games.create_mixed_behav_game_efg(), 3, 0.125, False), - (games.create_mixed_behav_game_efg(), 4, 0.125, False), - (games.create_mixed_behav_game_efg(), 5, 0.25, False), - (games.create_mixed_behav_game_efg(), 6, 0.125, False), - (games.create_mixed_behav_game_efg(), 7, 0.125, False), - (games.create_mixed_behav_game_efg(), 8, 0.5, False), - (games.create_mixed_behav_game_efg(), 9, 0.25, False), - (games.create_mixed_behav_game_efg(), 10, 0.125, False), - (games.create_mixed_behav_game_efg(), 11, 0.125, False), - (games.create_mixed_behav_game_efg(), 12, 0.25, False), - (games.create_mixed_behav_game_efg(), 13, 0.125, False), - (games.create_mixed_behav_game_efg(), 14, 0.125, False), - (games.create_stripped_down_poker_efg(), 0, "1", True), - (games.create_stripped_down_poker_efg(), 1, "1/2", True), - (games.create_stripped_down_poker_efg(), 2, "1/4", True), - (games.create_stripped_down_poker_efg(), 3, "1/8", True), - (games.create_stripped_down_poker_efg(), 4, "1/8", True), - (games.create_stripped_down_poker_efg(), 5, "1/4", True), - (games.create_stripped_down_poker_efg(), 6, "1/2", True), - (games.create_stripped_down_poker_efg(), 7, "1/4", True), - (games.create_stripped_down_poker_efg(), 8, "1/8", True), - (games.create_stripped_down_poker_efg(), 9, "1/8", True), - (games.create_stripped_down_poker_efg(), 10, "1/4", True), - (games.create_stripped_down_poker_efg(), 0, 1.0, False), - (games.create_stripped_down_poker_efg(), 1, 0.5, False), - (games.create_stripped_down_poker_efg(), 2, 0.25, False), - (games.create_stripped_down_poker_efg(), 3, 0.125, False), - (games.create_stripped_down_poker_efg(), 4, 0.125, False), - (games.create_stripped_down_poker_efg(), 5, 0.25, False), - (games.create_stripped_down_poker_efg(), 6, 0.5, False), - (games.create_stripped_down_poker_efg(), 7, 0.25, False), - (games.create_stripped_down_poker_efg(), 8, 0.125, False), - (games.create_stripped_down_poker_efg(), 9, 0.125, False), - (games.create_stripped_down_poker_efg(), 10, 0.25, False)] + [ + (games.read_from_file("mixed_behavior_game.efg"), 0, "1", True), + (games.read_from_file("mixed_behavior_game.efg"), 1, "1/2", True), + (games.read_from_file("mixed_behavior_game.efg"), 2, "1/4", True), + (games.read_from_file("mixed_behavior_game.efg"), 3, "1/8", True), + (games.read_from_file("mixed_behavior_game.efg"), 4, "1/8", True), + (games.read_from_file("mixed_behavior_game.efg"), 5, "1/4", True), + (games.read_from_file("mixed_behavior_game.efg"), 6, "1/8", True), + (games.read_from_file("mixed_behavior_game.efg"), 7, "1/8", True), + (games.read_from_file("mixed_behavior_game.efg"), 8, "1/2", True), + (games.read_from_file("mixed_behavior_game.efg"), 9, "1/4", True), + (games.read_from_file("mixed_behavior_game.efg"), 10, "1/8", True), + (games.read_from_file("mixed_behavior_game.efg"), 11, "1/8", True), + (games.read_from_file("mixed_behavior_game.efg"), 12, "1/4", True), + (games.read_from_file("mixed_behavior_game.efg"), 13, "1/8", True), + (games.read_from_file("mixed_behavior_game.efg"), 14, "1/8", True), + (games.read_from_file("mixed_behavior_game.efg"), 0, 1.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0.25, False), + (games.read_from_file("mixed_behavior_game.efg"), 3, 0.125, False), + (games.read_from_file("mixed_behavior_game.efg"), 4, 0.125, False), + (games.read_from_file("mixed_behavior_game.efg"), 5, 0.25, False), + (games.read_from_file("mixed_behavior_game.efg"), 6, 0.125, False), + (games.read_from_file("mixed_behavior_game.efg"), 7, 0.125, False), + (games.read_from_file("mixed_behavior_game.efg"), 8, 0.5, False), + (games.read_from_file("mixed_behavior_game.efg"), 9, 0.25, False), + (games.read_from_file("mixed_behavior_game.efg"), 10, 0.125, False), + (games.read_from_file("mixed_behavior_game.efg"), 11, 0.125, False), + (games.read_from_file("mixed_behavior_game.efg"), 12, 0.25, False), + (games.read_from_file("mixed_behavior_game.efg"), 13, 0.125, False), + (games.read_from_file("mixed_behavior_game.efg"), 14, 0.125, False), + (games.create_stripped_down_poker_efg(), 0, "1", True), + (games.create_stripped_down_poker_efg(), 1, "1/2", True), + (games.create_stripped_down_poker_efg(), 2, "1/4", True), + (games.create_stripped_down_poker_efg(), 3, "1/8", True), + (games.create_stripped_down_poker_efg(), 4, "1/8", True), + (games.create_stripped_down_poker_efg(), 5, "1/4", True), + (games.create_stripped_down_poker_efg(), 6, "1/2", True), + (games.create_stripped_down_poker_efg(), 7, "1/4", True), + (games.create_stripped_down_poker_efg(), 8, "1/8", True), + (games.create_stripped_down_poker_efg(), 9, "1/8", True), + (games.create_stripped_down_poker_efg(), 10, "1/4", True), + (games.create_stripped_down_poker_efg(), 0, 1.0, False), + (games.create_stripped_down_poker_efg(), 1, 0.5, False), + (games.create_stripped_down_poker_efg(), 2, 0.25, False), + (games.create_stripped_down_poker_efg(), 3, 0.125, False), + (games.create_stripped_down_poker_efg(), 4, 0.125, False), + (games.create_stripped_down_poker_efg(), 5, 0.25, False), + (games.create_stripped_down_poker_efg(), 6, 0.5, False), + (games.create_stripped_down_poker_efg(), 7, 0.25, False), + (games.create_stripped_down_poker_efg(), 8, 0.125, False), + (games.create_stripped_down_poker_efg(), 9, 0.125, False), + (games.create_stripped_down_poker_efg(), 10, 0.25, False), + ], ) -def test_realiz_prob_nodes_reference(game: gbt.Game, node_idx: int, - realiz_prob: str | float, rational_flag: bool): +def test_realiz_prob_nodes_reference( + game: gbt.Game, node_idx: int, realiz_prob: str | float, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) - realiz_prob = (gbt.Rational(realiz_prob) if rational_flag else realiz_prob) + realiz_prob = gbt.Rational(realiz_prob) if rational_flag else realiz_prob node = list(game.nodes)[node_idx] assert profile.realiz_prob(node) == realiz_prob @pytest.mark.parametrize( "game,player_idx,infoset_idx,prob,rational_flag", - [(games.create_mixed_behav_game_efg(), 0, 0, 1.0, False), - (games.create_mixed_behav_game_efg(), 1, 0, 1.0, False), - (games.create_mixed_behav_game_efg(), 2, 0, 1.0, False), - (games.create_mixed_behav_game_efg(), 0, 0, "1", True), - (games.create_mixed_behav_game_efg(), 1, 0, "1", True), - (games.create_mixed_behav_game_efg(), 2, 0, "1", True), - (games.create_stripped_down_poker_efg(), 0, 0, 0.5, False), - (games.create_stripped_down_poker_efg(), 0, 1, 0.5, False), - (games.create_stripped_down_poker_efg(), 1, 0, 0.5, False), - (games.create_stripped_down_poker_efg(), 0, 0, "1/2", True), - (games.create_stripped_down_poker_efg(), 0, 1, "1/2", True), - (games.create_stripped_down_poker_efg(), 1, 0, "1/2", True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, 1.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, 1.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, 1.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, "1", True), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, "1", True), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, "1", True), + (games.create_stripped_down_poker_efg(), 0, 0, 0.5, False), + (games.create_stripped_down_poker_efg(), 0, 1, 0.5, False), + (games.create_stripped_down_poker_efg(), 1, 0, 0.5, False), + (games.create_stripped_down_poker_efg(), 0, 0, "1/2", True), + (games.create_stripped_down_poker_efg(), 0, 1, "1/2", True), + (games.create_stripped_down_poker_efg(), 1, 0, "1/2", True), + ], ) -def test_infoset_prob_reference(game: gbt.Game, player_idx: int, infoset_idx: int, - prob: str | float, rational_flag: bool): +def test_infoset_prob_reference( + game: gbt.Game, player_idx: int, infoset_idx: int, prob: str | float, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) ip = profile.infoset_prob(game.players[player_idx].infosets[infoset_idx]) assert ip == (gbt.Rational(prob) if rational_flag else prob) @@ -666,44 +739,48 @@ def test_infoset_prob_reference(game: gbt.Game, player_idx: int, infoset_idx: in @pytest.mark.parametrize( "game,label,prob,rational_flag,", - [(games.create_mixed_behav_game_efg(), "Infoset 1:1", 1.0, False), - (games.create_mixed_behav_game_efg(), "Infoset 2:1", 1.0, False), - (games.create_mixed_behav_game_efg(), "Infoset 3:1", 1.0, False), - (games.create_mixed_behav_game_efg(), "Infoset 1:1", "1", True), - (games.create_mixed_behav_game_efg(), "Infoset 2:1", "1", True), - (games.create_mixed_behav_game_efg(), "Infoset 3:1", "1", True), - (games.create_stripped_down_poker_efg(), "Alice has King", 0.5, False), - (games.create_stripped_down_poker_efg(), "Alice has Queen", 0.5, False), - (games.create_stripped_down_poker_efg(), "Bob's response", 0.5, False), - (games.create_stripped_down_poker_efg(), "Alice has King", "1/2", True), - (games.create_stripped_down_poker_efg(), "Alice has Queen", "1/2", True), - (games.create_stripped_down_poker_efg(), "Bob's response", "1/2", True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 1:1", 1.0, False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 2:1", 1.0, False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 3:1", 1.0, False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 1:1", "1", True), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 2:1", "1", True), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 3:1", "1", True), + (games.create_stripped_down_poker_efg(), "Alice has King", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice has Queen", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob's response", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice has King", "1/2", True), + (games.create_stripped_down_poker_efg(), "Alice has Queen", "1/2", True), + (games.create_stripped_down_poker_efg(), "Bob's response", "1/2", True), + ], ) -def test_infoset_prob_by_label_reference(game: gbt.Game, label: str, - prob: str | float, rational_flag: bool): +def test_infoset_prob_by_label_reference( + game: gbt.Game, label: str, prob: str | float, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) assert profile.infoset_prob(label) == (gbt.Rational(prob) if rational_flag else prob) @pytest.mark.parametrize( "game,player_idx,infoset_idx,payoff,rational_flag", - [(games.create_mixed_behav_game_efg(), 0, 0, 3.0, False), - (games.create_mixed_behav_game_efg(), 1, 0, 3.0, False), - (games.create_mixed_behav_game_efg(), 2, 0, 3.25, False), - (games.create_mixed_behav_game_efg(), 0, 0, "3", True), - (games.create_mixed_behav_game_efg(), 1, 0, "3", True), - (games.create_mixed_behav_game_efg(), 2, 0, "13/4", True), - (games.create_stripped_down_poker_efg(), 0, 0, 0.25, False), - (games.create_stripped_down_poker_efg(), 0, 1, -0.75, False), - (games.create_stripped_down_poker_efg(), 1, 0, -0.5, False), - (games.create_stripped_down_poker_efg(), 0, 0, "1/4", True), - (games.create_stripped_down_poker_efg(), 0, 1, "-3/4", True), - (games.create_stripped_down_poker_efg(), 1, 0, "-1/2", True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, 3.25, False), + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, "3", True), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, "3", True), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, "13/4", True), + (games.create_stripped_down_poker_efg(), 0, 0, 0.25, False), + (games.create_stripped_down_poker_efg(), 0, 1, -0.75, False), + (games.create_stripped_down_poker_efg(), 1, 0, -0.5, False), + (games.create_stripped_down_poker_efg(), 0, 0, "1/4", True), + (games.create_stripped_down_poker_efg(), 0, 1, "-3/4", True), + (games.create_stripped_down_poker_efg(), 1, 0, "-1/2", True), + ], ) -def test_infoset_payoff_reference(game: gbt.Game, player_idx: int, infoset_idx: int, - payoff: str | float, rational_flag: bool): +def test_infoset_payoff_reference( + game: gbt.Game, player_idx: int, infoset_idx: int, payoff: str | float, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) iv = profile.infoset_value(game.players[player_idx].infosets[infoset_idx]) assert iv == (gbt.Rational(payoff) if rational_flag else payoff) @@ -711,58 +788,66 @@ def test_infoset_payoff_reference(game: gbt.Game, player_idx: int, infoset_idx: @pytest.mark.parametrize( "game,label,payoff,rational_flag", - [(games.create_mixed_behav_game_efg(), "Infoset 1:1", 3.0, False), - (games.create_mixed_behav_game_efg(), "Infoset 2:1", 3.0, False), - (games.create_mixed_behav_game_efg(), "Infoset 3:1", 3.25, False), - (games.create_mixed_behav_game_efg(), "Infoset 1:1", "3", True), - (games.create_mixed_behav_game_efg(), "Infoset 2:1", "3", True), - (games.create_mixed_behav_game_efg(), "Infoset 3:1", "13/4", True), - (games.create_stripped_down_poker_efg(), "Alice has King", 0.25, False), - (games.create_stripped_down_poker_efg(), "Alice has Queen", -0.75, False), - (games.create_stripped_down_poker_efg(), "Bob's response", -0.5, False), - (games.create_stripped_down_poker_efg(), "Alice has King", "1/4", True), - (games.create_stripped_down_poker_efg(), "Alice has Queen", "-3/4", True), - (games.create_stripped_down_poker_efg(), "Bob's response", "-1/2", True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 1:1", 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 2:1", 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 3:1", 3.25, False), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 1:1", "3", True), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 2:1", "3", True), + (games.read_from_file("mixed_behavior_game.efg"), "Infoset 3:1", "13/4", True), + (games.create_stripped_down_poker_efg(), "Alice has King", 0.25, False), + (games.create_stripped_down_poker_efg(), "Alice has Queen", -0.75, False), + (games.create_stripped_down_poker_efg(), "Bob's response", -0.5, False), + (games.create_stripped_down_poker_efg(), "Alice has King", "1/4", True), + (games.create_stripped_down_poker_efg(), "Alice has Queen", "-3/4", True), + (games.create_stripped_down_poker_efg(), "Bob's response", "-1/2", True), + ], ) -def test_infoset_payoff_by_label_reference(game: gbt.Game, label: str, - payoff: str | float, rational_flag: bool): +def test_infoset_payoff_by_label_reference( + game: gbt.Game, label: str, payoff: str | float, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) assert profile.infoset_value(label) == (gbt.Rational(payoff) if rational_flag else payoff) @pytest.mark.parametrize( "game,player_idx,infoset_idx,action_idx,payoff,rational_flag", - [(games.create_mixed_behav_game_efg(), 0, 0, 0, 3.0, False), - (games.create_mixed_behav_game_efg(), 0, 0, 1, 3.0, False), - (games.create_mixed_behav_game_efg(), 1, 0, 0, 3.0, False), - (games.create_mixed_behav_game_efg(), 1, 0, 1, 3.0, False), - (games.create_mixed_behav_game_efg(), 2, 0, 0, 3.5, False), - (games.create_mixed_behav_game_efg(), 2, 0, 1, 3.0, False), - (games.create_mixed_behav_game_efg(), 2, 0, 1, 3.0, False), - (games.create_mixed_behav_game_efg(), 0, 0, 0, "3/1", True), - (games.create_mixed_behav_game_efg(), 0, 0, 1, "3/1", True), - (games.create_mixed_behav_game_efg(), 1, 0, 0, "3/1", True), - (games.create_mixed_behav_game_efg(), 1, 0, 1, "3/1", True), - (games.create_mixed_behav_game_efg(), 2, 0, 0, "7/2", True), - (games.create_mixed_behav_game_efg(), 2, 0, 1, "3/1", True), - (games.create_stripped_down_poker_efg(), 0, 0, 0, 1.5, False), - (games.create_stripped_down_poker_efg(), 0, 0, 1, -1, False), - (games.create_stripped_down_poker_efg(), 0, 1, 0, -0.5, False), - (games.create_stripped_down_poker_efg(), 0, 1, 1, -1, False), - (games.create_stripped_down_poker_efg(), 1, 0, 0, 0, False), - (games.create_stripped_down_poker_efg(), 1, 0, 1, -1, False), - (games.create_stripped_down_poker_efg(), 0, 0, 0, "3/2", True), - (games.create_stripped_down_poker_efg(), 0, 0, 1, -1, True), - (games.create_stripped_down_poker_efg(), 0, 1, 0, "-1/2", True), - (games.create_stripped_down_poker_efg(), 0, 1, 1, -1, True), - (games.create_stripped_down_poker_efg(), 1, 0, 0, 0, True), - (games.create_stripped_down_poker_efg(), 1, 0, 1, -1, True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, 0, 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, 1, 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, 0, 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, 1, 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, 0, 3.5, False), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, 1, 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, 1, 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, 0, "3/1", True), + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, 1, "3/1", True), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, 0, "3/1", True), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, 1, "3/1", True), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, 0, "7/2", True), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, 1, "3/1", True), + (games.create_stripped_down_poker_efg(), 0, 0, 0, 1.5, False), + (games.create_stripped_down_poker_efg(), 0, 0, 1, -1, False), + (games.create_stripped_down_poker_efg(), 0, 1, 0, -0.5, False), + (games.create_stripped_down_poker_efg(), 0, 1, 1, -1, False), + (games.create_stripped_down_poker_efg(), 1, 0, 0, 0, False), + (games.create_stripped_down_poker_efg(), 1, 0, 1, -1, False), + (games.create_stripped_down_poker_efg(), 0, 0, 0, "3/2", True), + (games.create_stripped_down_poker_efg(), 0, 0, 1, -1, True), + (games.create_stripped_down_poker_efg(), 0, 1, 0, "-1/2", True), + (games.create_stripped_down_poker_efg(), 0, 1, 1, -1, True), + (games.create_stripped_down_poker_efg(), 1, 0, 0, 0, True), + (games.create_stripped_down_poker_efg(), 1, 0, 1, -1, True), + ], ) -def test_action_payoff_reference(game: gbt.Game, player_idx: int, infoset_idx: int, - action_idx: int, payoff: str | float, - rational_flag: bool): +def test_action_payoff_reference( + game: gbt.Game, + player_idx: int, + infoset_idx: int, + action_idx: int, + payoff: str | float, + rational_flag: bool, +): profile = game.mixed_behavior_profile(rational=rational_flag) av = profile.action_value(game.players[player_idx].infosets[infoset_idx].actions[action_idx]) assert av == (gbt.Rational(payoff) if rational_flag else payoff) @@ -770,82 +855,83 @@ def test_action_payoff_reference(game: gbt.Game, player_idx: int, infoset_idx: i @pytest.mark.parametrize( "game,label,payoff,rational_flag", - [(games.create_mixed_behav_game_efg(), "U1", 3.0, False), - (games.create_mixed_behav_game_efg(), "D1", 3.0, False), - (games.create_mixed_behav_game_efg(), "U2", 3.0, False), - (games.create_mixed_behav_game_efg(), "D2", 3.0, False), - (games.create_mixed_behav_game_efg(), "U3", 3.5, False), - (games.create_mixed_behav_game_efg(), "D3", 3.0, False), - (games.create_mixed_behav_game_efg(), "U1", "3", True), - (games.create_mixed_behav_game_efg(), "D1", "3", True), - (games.create_mixed_behav_game_efg(), "U2", "3", True), - (games.create_mixed_behav_game_efg(), "D2", "3", True), - (games.create_mixed_behav_game_efg(), "U3", "7/2", True), - (games.create_mixed_behav_game_efg(), "D3", "3", True), - (games.create_stripped_down_poker_efg(), "Call", 0, False), - (games.create_stripped_down_poker_efg(), "Call", "0", True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), "U1", 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), "D1", 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), "U2", 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), "D2", 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), "U3", 3.5, False), + (games.read_from_file("mixed_behavior_game.efg"), "D3", 3.0, False), + (games.read_from_file("mixed_behavior_game.efg"), "U1", "3", True), + (games.read_from_file("mixed_behavior_game.efg"), "D1", "3", True), + (games.read_from_file("mixed_behavior_game.efg"), "U2", "3", True), + (games.read_from_file("mixed_behavior_game.efg"), "D2", "3", True), + (games.read_from_file("mixed_behavior_game.efg"), "U3", "7/2", True), + (games.read_from_file("mixed_behavior_game.efg"), "D3", "3", True), + (games.create_stripped_down_poker_efg(), "Call", 0, False), + (games.create_stripped_down_poker_efg(), "Call", "0", True), + ], ) -def test_action_value_by_label_reference(game: gbt.Game, label: str, - payoff: str | float, rational_flag: bool): +def test_action_value_by_label_reference( + game: gbt.Game, label: str, payoff: str | float, rational_flag: bool +): profile = game.mixed_behavior_profile(rational=rational_flag) assert profile.action_value(label) == (gbt.Rational(payoff) if rational_flag else payoff) @pytest.mark.parametrize( "game,rational_flag", - [(games.create_mixed_behav_game_efg(), False), - (games.create_mixed_behav_game_efg(), True), - (games.create_stripped_down_poker_efg(), False), - (games.create_stripped_down_poker_efg(), True), - (games.create_kuhn_poker_efg(), False), - (games.create_kuhn_poker_efg(), True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), False), + (games.read_from_file("mixed_behavior_game.efg"), True), + (games.create_stripped_down_poker_efg(), False), + (games.create_stripped_down_poker_efg(), True), + (games.create_kuhn_poker_efg(), False), + (games.create_kuhn_poker_efg(), True), + ], ) def test_action_regret_consistency(game: gbt.Game, rational_flag: bool): profile = game.mixed_behavior_profile(rational=rational_flag) for player in game.players: for infoset in player.infosets: for action in infoset.actions: - assert ( - profile.action_regret(action) == - max(profile.action_value(a) for a in infoset.actions) - - profile.action_value(action) - ) + assert profile.action_regret(action) == max( + profile.action_value(a) for a in infoset.actions + ) - profile.action_value(action) @pytest.mark.parametrize( "game,rational_flag", - [(games.create_mixed_behav_game_efg(), False), - (games.create_mixed_behav_game_efg(), True), - (games.create_stripped_down_poker_efg(), False), - (games.create_stripped_down_poker_efg(), True), - (games.create_kuhn_poker_efg(), False), - (games.create_kuhn_poker_efg(), True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), False), + (games.read_from_file("mixed_behavior_game.efg"), True), + (games.create_stripped_down_poker_efg(), False), + (games.create_stripped_down_poker_efg(), True), + (games.create_kuhn_poker_efg(), False), + (games.create_kuhn_poker_efg(), True), + ], ) def test_infoset_regret_consistency(game: gbt.Game, rational_flag: bool): profile = game.mixed_behavior_profile(rational=rational_flag) for player in game.players: for infoset in player.infosets: - assert ( - profile.infoset_regret(infoset) == - max(profile.action_value(a) for a in infoset.actions) - - profile.infoset_value(infoset) - ) + assert profile.infoset_regret(infoset) == max( + profile.action_value(a) for a in infoset.actions + ) - profile.infoset_value(infoset) @pytest.mark.parametrize( "game,rational_flag", - [(games.create_mixed_behav_game_efg(), False), - (games.create_mixed_behav_game_efg(), True), - (games.create_stripped_down_poker_efg(), False), - (games.create_stripped_down_poker_efg(), True), - (games.create_kuhn_poker_efg(), False), - (games.create_kuhn_poker_efg(), True), - (games.read_from_file("3_player.efg"), False), - (games.read_from_file("3_player.efg"), True) - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), False), + (games.read_from_file("mixed_behavior_game.efg"), True), + (games.create_stripped_down_poker_efg(), False), + (games.create_stripped_down_poker_efg(), True), + (games.create_kuhn_poker_efg(), False), + (games.create_kuhn_poker_efg(), True), + (games.read_from_file("3_player.efg"), False), + (games.read_from_file("3_player.efg"), True), + ], ) def test_max_regret_consistency(game: gbt.Game, rational_flag: bool): profile = game.mixed_behavior_profile(rational=rational_flag) @@ -854,71 +940,223 @@ def test_max_regret_consistency(game: gbt.Game, rational_flag: bool): @pytest.mark.parametrize( "game,rational_flag", - [(games.create_mixed_behav_game_efg(), False), - (games.create_mixed_behav_game_efg(), True), - (games.create_stripped_down_poker_efg(), False), - (games.create_stripped_down_poker_efg(), True), - (games.create_kuhn_poker_efg(), False), - (games.create_kuhn_poker_efg(), True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), False), + (games.read_from_file("mixed_behavior_game.efg"), True), + (games.create_stripped_down_poker_efg(), False), + (games.create_stripped_down_poker_efg(), True), + (games.create_kuhn_poker_efg(), False), + (games.create_kuhn_poker_efg(), True), + ], ) def test_agent_max_regret_consistency(game: gbt.Game, rational_flag: bool): profile = game.mixed_behavior_profile(rational=rational_flag) - assert ( - profile.agent_max_regret() == - max([profile.infoset_regret(infoset) for infoset in game.infosets]) + assert profile.agent_max_regret() == max( + [profile.infoset_regret(infoset) for infoset in game.infosets] ) @pytest.mark.parametrize( "game,player_idx,infoset_idx,action_idx,action_probs,rational_flag,tol,value", [ - # uniform - (games.create_mixed_behav_game_efg(), 0, 0, 0, None, False, TOL, 0), - (games.create_mixed_behav_game_efg(), 0, 0, 1, None, False, TOL, 0), - (games.create_mixed_behav_game_efg(), 1, 0, 0, None, False, TOL, 0), - (games.create_mixed_behav_game_efg(), 1, 0, 1, None, False, TOL, 0), - (games.create_mixed_behav_game_efg(), 2, 0, 0, None, False, TOL, 0), - (games.create_mixed_behav_game_efg(), 2, 0, 1, None, False, TOL, 0.5), # 3.5 - 3 - # U1 U2 U3 - (games.create_mixed_behav_game_efg(), 0, 0, 0, [1, 0, 1, 0, 1, 0], False, TOL, 0), - (games.create_mixed_behav_game_efg(), 0, 0, 0, [1, 0, 1, 0, 1, 0], True, ZERO, 0), - (games.create_mixed_behav_game_efg(), 0, 0, 1, [1, 0, 1, 0, 1, 0], False, TOL, 9), - (games.create_mixed_behav_game_efg(), 0, 0, 1, [1, 0, 1, 0, 1, 0], True, ZERO, 9), - (games.create_mixed_behav_game_efg(), 1, 0, 0, [1, 0, 1, 0, 1, 0], False, TOL, 0), - (games.create_mixed_behav_game_efg(), 1, 0, 0, [1, 0, 1, 0, 1, 0], True, ZERO, 0), - (games.create_mixed_behav_game_efg(), 1, 0, 1, [1, 0, 1, 0, 1, 0], False, TOL, 8), - (games.create_mixed_behav_game_efg(), 1, 0, 1, [1, 0, 1, 0, 1, 0], True, ZERO, 8), - # Mixed Nash equilibrium - (games.create_mixed_behav_game_efg(), 0, 0, 0, ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], - True, ZERO, 0), - (games.create_mixed_behav_game_efg(), 0, 0, 1, ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], - True, ZERO, 0), - (games.create_mixed_behav_game_efg(), 1, 0, 0, ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], - True, ZERO, 0), - (games.create_mixed_behav_game_efg(), 1, 0, 1, ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], - True, ZERO, 0), - (games.create_mixed_behav_game_efg(), 2, 0, 0, ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], - True, ZERO, 0), - (games.create_mixed_behav_game_efg(), 2, 0, 1, ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], - True, ZERO, 0), - # uniform - (games.create_stripped_down_poker_efg(), 0, 0, 0, None, False, TOL, 0), - (games.create_stripped_down_poker_efg(), 0, 0, 1, None, False, TOL, 2.5), # 1.5 - (-1) - (games.create_stripped_down_poker_efg(), 0, 1, 0, None, False, TOL, 0), - (games.create_stripped_down_poker_efg(), 0, 1, 1, None, False, TOL, 0.5), # -0.5 - (-1) - (games.create_stripped_down_poker_efg(), 1, 0, 0, None, False, TOL, 0), - (games.create_stripped_down_poker_efg(), 1, 0, 1, None, False, TOL, 1), # -0 - (-1) - # mixed Nash equilibrium - (games.create_stripped_down_poker_efg(), 0, 0, 0, ["1", "0", "1/3", "2/3", "2/3", "1/3"], - True, ZERO, 0), - (games.create_stripped_down_poker_efg(), 0, 0, 1, ["1", "0", "1/3", "2/3", "2/3", "1/3"], - True, ZERO, "8/3"), # (2/3*2 + 1/3*1) - (-1) - ] + # uniform + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, 0, None, False, TOL, 0), + (games.read_from_file("mixed_behavior_game.efg"), 0, 0, 1, None, False, TOL, 0), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, 0, None, False, TOL, 0), + (games.read_from_file("mixed_behavior_game.efg"), 1, 0, 1, None, False, TOL, 0), + (games.read_from_file("mixed_behavior_game.efg"), 2, 0, 0, None, False, TOL, 0), + ( + games.read_from_file("mixed_behavior_game.efg"), + 2, + 0, + 1, + None, + False, + TOL, + 0.5, + ), # 3.5 - 3 + # U1 U2 U3 + ( + games.read_from_file("mixed_behavior_game.efg"), + 0, + 0, + 0, + [1, 0, 1, 0, 1, 0], + False, + TOL, + 0, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + 0, + 0, + 0, + [1, 0, 1, 0, 1, 0], + True, + ZERO, + 0, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + 0, + 0, + 1, + [1, 0, 1, 0, 1, 0], + False, + TOL, + 9, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + 0, + 0, + 1, + [1, 0, 1, 0, 1, 0], + True, + ZERO, + 9, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + 1, + 0, + 0, + [1, 0, 1, 0, 1, 0], + False, + TOL, + 0, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + 1, + 0, + 0, + [1, 0, 1, 0, 1, 0], + True, + ZERO, + 0, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + 1, + 0, + 1, + [1, 0, 1, 0, 1, 0], + False, + TOL, + 8, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + 1, + 0, + 1, + [1, 0, 1, 0, 1, 0], + True, + ZERO, + 8, + ), + # Mixed Nash equilibrium + ( + games.read_from_file("mixed_behavior_game.efg"), + 0, + 0, + 0, + ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], + True, + ZERO, + 0, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + 0, + 0, + 1, + ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], + True, + ZERO, + 0, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + 1, + 0, + 0, + ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], + True, + ZERO, + 0, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + 1, + 0, + 1, + ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], + True, + ZERO, + 0, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + 2, + 0, + 0, + ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], + True, + ZERO, + 0, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + 2, + 0, + 1, + ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], + True, + ZERO, + 0, + ), + # uniform + (games.create_stripped_down_poker_efg(), 0, 0, 0, None, False, TOL, 0), + (games.create_stripped_down_poker_efg(), 0, 0, 1, None, False, TOL, 2.5), # 1.5 - (-1) + (games.create_stripped_down_poker_efg(), 0, 1, 0, None, False, TOL, 0), + (games.create_stripped_down_poker_efg(), 0, 1, 1, None, False, TOL, 0.5), # -0.5 - (-1) + (games.create_stripped_down_poker_efg(), 1, 0, 0, None, False, TOL, 0), + (games.create_stripped_down_poker_efg(), 1, 0, 1, None, False, TOL, 1), # -0 - (-1) + # mixed Nash equilibrium + ( + games.create_stripped_down_poker_efg(), + 0, + 0, + 0, + ["1", "0", "1/3", "2/3", "2/3", "1/3"], + True, + ZERO, + 0, + ), + ( + games.create_stripped_down_poker_efg(), + 0, + 0, + 1, + ["1", "0", "1/3", "2/3", "2/3", "1/3"], + True, + ZERO, + "8/3", + ), # (2/3*2 + 1/3*1) - (-1) + ], ) -def test_action_regret_reference(game: gbt.Game, player_idx: int, infoset_idx: int, - action_idx: int, action_probs: None | list, rational_flag: bool, - tol: gbt.Rational | float, value: str | float): +def test_action_regret_reference( + game: gbt.Game, + player_idx: int, + infoset_idx: int, + action_idx: int, + action_probs: None | list, + rational_flag: bool, + tol: gbt.Rational | float, + value: str | float, +): action = game.players[player_idx].infosets[infoset_idx].actions[action_idx] profile = game.mixed_behavior_profile(rational=rational_flag) if action_probs: @@ -930,11 +1168,12 @@ def test_action_regret_reference(game: gbt.Game, player_idx: int, infoset_idx: i @pytest.mark.parametrize( "game,rational_flag", - [(games.create_mixed_behav_game_efg(), False), - (games.create_mixed_behav_game_efg(), True), - (games.create_stripped_down_poker_efg(), False), - (games.create_stripped_down_poker_efg(), True), - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), False), + (games.read_from_file("mixed_behavior_game.efg"), True), + (games.create_stripped_down_poker_efg(), False), + (games.create_stripped_down_poker_efg(), True), + ], ) def test_martingale_property_of_node_value(game: gbt.Game, rational_flag: bool): """Loops over all nodes and for non-chance, non-terminal nodes, this checks that the node @@ -955,10 +1194,12 @@ def test_martingale_property_of_node_value(game: gbt.Game, rational_flag: bool): @pytest.mark.parametrize( "game,rational_flag", - [(games.create_mixed_behav_game_efg(), False), - (games.create_mixed_behav_game_efg(), True), - (games.create_stripped_down_poker_efg(), False), - (games.create_stripped_down_poker_efg(), True)] + [ + (games.read_from_file("mixed_behavior_game.efg"), False), + (games.read_from_file("mixed_behavior_game.efg"), True), + (games.create_stripped_down_poker_efg(), False), + (games.create_stripped_down_poker_efg(), True), + ], ) def test_node_value_consistency(game: gbt.Game, rational_flag: bool): """Test that the profile's node value at the root for each player matches the profile's payoff @@ -971,114 +1212,281 @@ def test_node_value_consistency(game: gbt.Game, rational_flag: bool): @pytest.mark.parametrize( "game,action_probs,rational_flag,expected_value", [ - # uniform (non-Nash): - (games.create_mixed_behav_game_efg(), None, True, "1/16"), - (games.create_mixed_behav_game_efg(), None, False, 0.0625), - # four pure Nash equilibria: - (games.create_mixed_behav_game_efg(), [1.0, 0.0, 1.0, 0.0, 1.0, 0.0], False, 0), # U1 U2 U3 - (games.create_mixed_behav_game_efg(), ["1", "0", "1", "0", "1", "0"], True, 0), - (games.create_mixed_behav_game_efg(), ["1", "0", "0", "1", "0", "1"], True, 0), # U1 D2 D3 - (games.create_mixed_behav_game_efg(), [1.0, 0.0, 0.0, 1.0, 0, 1.0], False, 0), - (games.create_mixed_behav_game_efg(), ["0", "1", "1", "0", "0", "1"], True, 0), # D1 U2 D3 - (games.create_mixed_behav_game_efg(), [0.0, 1.0, 1.0, 0.0, 0, 1.0], False, 0), - (games.create_mixed_behav_game_efg(), ["0", "1", "0", "1", "1", "0"], True, 0), # D1 D2 U3 - (games.create_mixed_behav_game_efg(), [0.0, 1.0, 0.0, 1.0, 1.0, 0], False, 0), - # mixed Nash equilibrium (only rational tested): - (games.create_mixed_behav_game_efg(), ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], True, 0), - # non-Nash pure profile: D1 D2 D3 - (games.create_mixed_behav_game_efg(), [0.0, 1.0, 0.0, 1.0, 0.0, 1.0], False, 29.0), - (games.create_mixed_behav_game_efg(), ["0", "1", "0", "1", "0", "1"], True, "29"), - # uniform (non-Nash): - (games.create_stripped_down_poker_efg(), None, True, "15/8"), - (games.create_stripped_down_poker_efg(), None, False, 1.875), - # mixed Nash equilibrium (only rational tested): - (games.create_stripped_down_poker_efg(), ["1", "0", "1/3", "2/3", "2/3", "1/3"], True, 0), - # non-Nash pure profile: - # Raise at 1:1, Raise at 1:2, Meet at 2:1 - (games.create_stripped_down_poker_efg(), ["1", "0", "1", "0", "1", "0"], True, 1), - (games.create_stripped_down_poker_efg(), [1.0, 0.0, 1.0, 0.0, 1.0, 0.0], False, 1.0), - ] + # uniform (non-Nash): + (games.read_from_file("mixed_behavior_game.efg"), None, True, "1/16"), + (games.read_from_file("mixed_behavior_game.efg"), None, False, 0.0625), + # four pure Nash equilibria: + ( + games.read_from_file("mixed_behavior_game.efg"), + [1.0, 0.0, 1.0, 0.0, 1.0, 0.0], + False, + 0, + ), # U1 U2 U3 + (games.read_from_file("mixed_behavior_game.efg"), ["1", "0", "1", "0", "1", "0"], True, 0), + ( + games.read_from_file("mixed_behavior_game.efg"), + ["1", "0", "0", "1", "0", "1"], + True, + 0, + ), # U1 D2 D3 + (games.read_from_file("mixed_behavior_game.efg"), [1.0, 0.0, 0.0, 1.0, 0, 1.0], False, 0), + ( + games.read_from_file("mixed_behavior_game.efg"), + ["0", "1", "1", "0", "0", "1"], + True, + 0, + ), # D1 U2 D3 + (games.read_from_file("mixed_behavior_game.efg"), [0.0, 1.0, 1.0, 0.0, 0, 1.0], False, 0), + ( + games.read_from_file("mixed_behavior_game.efg"), + ["0", "1", "0", "1", "1", "0"], + True, + 0, + ), # D1 D2 U3 + (games.read_from_file("mixed_behavior_game.efg"), [0.0, 1.0, 0.0, 1.0, 1.0, 0], False, 0), + # mixed Nash equilibrium (only rational tested): + ( + games.read_from_file("mixed_behavior_game.efg"), + ["2/5", "3/5", "1/2", "1/2", "1/3", "2/3"], + True, + 0, + ), + # non-Nash pure profile: D1 D2 D3 + ( + games.read_from_file("mixed_behavior_game.efg"), + [0.0, 1.0, 0.0, 1.0, 0.0, 1.0], + False, + 29.0, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + ["0", "1", "0", "1", "0", "1"], + True, + "29", + ), + # uniform (non-Nash): + (games.create_stripped_down_poker_efg(), None, True, "15/8"), + (games.create_stripped_down_poker_efg(), None, False, 1.875), + # mixed Nash equilibrium (only rational tested): + (games.create_stripped_down_poker_efg(), ["1", "0", "1/3", "2/3", "2/3", "1/3"], True, 0), + # non-Nash pure profile: + # Raise at 1:1, Raise at 1:2, Meet at 2:1 + (games.create_stripped_down_poker_efg(), ["1", "0", "1", "0", "1", "0"], True, 1), + (games.create_stripped_down_poker_efg(), [1.0, 0.0, 1.0, 0.0, 1.0, 0.0], False, 1.0), + ], ) -def test_agent_liap_value_reference(game: gbt.Game, action_probs: None | list, - rational_flag: bool, expected_value: str | float): +def test_agent_liap_value_reference( + game: gbt.Game, action_probs: None | list, rational_flag: bool, expected_value: str | float +): """Tests agent_liap_value under profile given by action_probs (which will be uniform if action_probs is None) """ profile = game.mixed_behavior_profile(rational=rational_flag) if action_probs: _set_action_probs(profile, action_probs, rational_flag) - assert ( - profile.agent_liap_value() == (gbt.Rational(expected_value) - if rational_flag else expected_value) + assert profile.agent_liap_value() == ( + gbt.Rational(expected_value) if rational_flag else expected_value ) @pytest.mark.parametrize( "game,action_probs,rational_flag,max_regret,agent_max_regret,liap_value,agent_liap_value", [ - # uniform (non-Nash): - (games.create_mixed_behav_game_efg(), None, True, "1/4", "1/4", "1/16", "1/16"), - (games.create_mixed_behav_game_efg(), None, False, 0.25, 0.25, 0.0625, 0.0625), - # Myerson fig 4.2 - (games.read_from_file("myerson_fig_4_2.efg"), [0, 1, 0, 1, 1, 0], True, 1, 0, 1, 0), - ] + # uniform (non-Nash): + ( + games.read_from_file("mixed_behavior_game.efg"), + None, + True, + "1/4", + "1/4", + "1/16", + "1/16", + ), + (games.read_from_file("mixed_behavior_game.efg"), None, False, 0.25, 0.25, 0.0625, 0.0625), + # Myerson fig 4.2 + (games.read_from_file("myerson_fig_4_2.efg"), [0, 1, 0, 1, 1, 0], True, 1, 0, 1, 0), + ], ) -def test_agent_max_regret_versus_non_agent(game: gbt.Game, action_probs: None | list, - rational_flag: bool, - max_regret: str | float, - agent_max_regret: str | float, - agent_liap_value: str | float, - liap_value: str | float, - ): +def test_agent_max_regret_versus_non_agent( + game: gbt.Game, + action_probs: None | list, + rational_flag: bool, + max_regret: str | float, + agent_max_regret: str | float, + agent_liap_value: str | float, + liap_value: str | float, +): profile = game.mixed_behavior_profile(rational=rational_flag) if action_probs: _set_action_probs(profile, action_probs, rational_flag) - assert (profile.max_regret() == (gbt.Rational(max_regret) if rational_flag else max_regret)) - assert ( - profile.agent_max_regret() == (gbt.Rational(agent_max_regret) - if rational_flag else agent_max_regret) + assert profile.max_regret() == (gbt.Rational(max_regret) if rational_flag else max_regret) + assert profile.agent_max_regret() == ( + gbt.Rational(agent_max_regret) if rational_flag else agent_max_regret ) - assert (profile.liap_value() == (gbt.Rational(liap_value) if rational_flag else liap_value)) - assert ( - profile.agent_liap_value() == (gbt.Rational(agent_liap_value) - if rational_flag else agent_liap_value) + assert profile.liap_value() == (gbt.Rational(liap_value) if rational_flag else liap_value) + assert profile.agent_liap_value() == ( + gbt.Rational(agent_liap_value) if rational_flag else agent_liap_value ) @pytest.mark.parametrize( "game,tol,probs,infoset_idx,member_idx,value,rational_flag", - [(games.create_mixed_behav_game_efg(), TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 0, 0, 1.0, False), - (games.create_mixed_behav_game_efg(), TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 1, 0, 0.8, False), - (games.create_mixed_behav_game_efg(), TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 1, 1, 0.2, False), - (games.create_mixed_behav_game_efg(), TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 2, 0, 0.32, False), - (games.create_mixed_behav_game_efg(), TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 2, 1, 0.48, False), - (games.create_mixed_behav_game_efg(), ZERO, ["4/5", "1/5", "2/5", "3/5", "0", "1"], 0, 0, "1", - True), - (games.create_mixed_behav_game_efg(), ZERO, ["4/5", "1/5", "2/5", "3/5", "0", "1"], 1, 0, - "4/5", True), - (games.create_mixed_behav_game_efg(), ZERO, ["4/5", "1/5", "2/5", "3/5", "0", "1"], 1, 1, - "1/5", True), - (games.create_mixed_behav_game_efg(), ZERO, ["4/5", "1/5", "2/5", "3/5", "0", "1"], 2, 0, - "8/25", True), - (games.create_mixed_behav_game_efg(), ZERO, ["4/5", "1/5", "2/5", "3/5", "0", "1"], 2, 1, - "12/25", True), - (games.create_stripped_down_poker_efg(), ZERO, ["4/5", "1/5", "2/5", "3/5", "0", "1"], - 0, 0, "1", True), - (games.create_stripped_down_poker_efg(), ZERO, ["4/5", "1/5", "2/5", "3/5", "0", "1"], - 1, 0, "1", True), - (games.create_stripped_down_poker_efg(), ZERO, ["4/5", "1/5", "2/5", "3/5", "0", "1"], - 2, 0, "2/3", True), - (games.create_stripped_down_poker_efg(), ZERO, ["4/5", "1/5", "2/5", "3/5", "0", "1"], - 2, 1, "1/3", True), - (games.create_stripped_down_poker_efg(), ZERO, ["1", "0", "2/5", "3/5", "0", "1"], - 2, 0, "5/7", True), - (games.create_stripped_down_poker_efg(), ZERO, ["1", "0", "2/5", "3/5", "0", "1"], - 2, 1, "2/7", True), - ] - ) -def test_node_belief_reference(game: gbt.Game, tol: gbt.Rational | float, - probs: list, infoset_idx: int, member_idx: int, - value: str | float, rational_flag: bool): + [ + ( + games.read_from_file("mixed_behavior_game.efg"), + TOL, + [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], + 0, + 0, + 1.0, + False, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + TOL, + [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], + 1, + 0, + 0.8, + False, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + TOL, + [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], + 1, + 1, + 0.2, + False, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + TOL, + [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], + 2, + 0, + 0.32, + False, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + TOL, + [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], + 2, + 1, + 0.48, + False, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + ZERO, + ["4/5", "1/5", "2/5", "3/5", "0", "1"], + 0, + 0, + "1", + True, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + ZERO, + ["4/5", "1/5", "2/5", "3/5", "0", "1"], + 1, + 0, + "4/5", + True, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + ZERO, + ["4/5", "1/5", "2/5", "3/5", "0", "1"], + 1, + 1, + "1/5", + True, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + ZERO, + ["4/5", "1/5", "2/5", "3/5", "0", "1"], + 2, + 0, + "8/25", + True, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + ZERO, + ["4/5", "1/5", "2/5", "3/5", "0", "1"], + 2, + 1, + "12/25", + True, + ), + ( + games.create_stripped_down_poker_efg(), + ZERO, + ["4/5", "1/5", "2/5", "3/5", "0", "1"], + 0, + 0, + "1", + True, + ), + ( + games.create_stripped_down_poker_efg(), + ZERO, + ["4/5", "1/5", "2/5", "3/5", "0", "1"], + 1, + 0, + "1", + True, + ), + ( + games.create_stripped_down_poker_efg(), + ZERO, + ["4/5", "1/5", "2/5", "3/5", "0", "1"], + 2, + 0, + "2/3", + True, + ), + ( + games.create_stripped_down_poker_efg(), + ZERO, + ["4/5", "1/5", "2/5", "3/5", "0", "1"], + 2, + 1, + "1/3", + True, + ), + ( + games.create_stripped_down_poker_efg(), + ZERO, + ["1", "0", "2/5", "3/5", "0", "1"], + 2, + 0, + "5/7", + True, + ), + ( + games.create_stripped_down_poker_efg(), + ZERO, + ["1", "0", "2/5", "3/5", "0", "1"], + 2, + 1, + "2/7", + True, + ), + ], +) +def test_node_belief_reference( + game: gbt.Game, + tol: gbt.Rational | float, + probs: list, + infoset_idx: int, + member_idx: int, + value: str | float, + rational_flag: bool, +): profile = game.mixed_behavior_profile(rational=rational_flag) _set_action_probs(profile, probs, rational_flag) node = game.infosets[infoset_idx].members[member_idx] @@ -1088,9 +1496,10 @@ def test_node_belief_reference(game: gbt.Game, tol: gbt.Rational | float, @pytest.mark.parametrize( "game,rational_flag", - [(games.create_stripped_down_poker_efg(), True), - (games.create_stripped_down_poker_efg(), False), - ] + [ + (games.create_stripped_down_poker_efg(), True), + (games.create_stripped_down_poker_efg(), False), + ], ) def test_payoff_value_error_with_chance_player(game: gbt.Game, rational_flag: bool): """Ensure a value error is thrown when we call payoff for a chance player""" @@ -1101,9 +1510,10 @@ def test_payoff_value_error_with_chance_player(game: gbt.Game, rational_flag: bo @pytest.mark.parametrize( "game,rational_flag", - [(games.create_stripped_down_poker_efg(), True), - (games.create_stripped_down_poker_efg(), False), - ] + [ + (games.create_stripped_down_poker_efg(), True), + (games.create_stripped_down_poker_efg(), False), + ], ) def test_infoset_value_error_with_chance_player_infoset(game: gbt.Game, rational_flag: bool): """Ensure a value error is raised when we call action value for a chance action""" @@ -1114,9 +1524,10 @@ def test_infoset_value_error_with_chance_player_infoset(game: gbt.Game, rational @pytest.mark.parametrize( "game,rational_flag", - [(games.create_stripped_down_poker_efg(), True), - (games.create_stripped_down_poker_efg(), False), - ] + [ + (games.create_stripped_down_poker_efg(), True), + (games.create_stripped_down_poker_efg(), False), + ], ) def test_action_value_error_with_chance_player_action(game: gbt.Game, rational_flag: bool): """Ensure a value error is raised when we call action value for a chance action""" @@ -1125,9 +1536,14 @@ def test_action_value_error_with_chance_player_action(game: gbt.Game, rational_f game.mixed_behavior_profile(rational=rational_flag).action_value(chance_action) -def _get_answers_one_order(game: gbt.Game, action_probs_1st: tuple, action_probs_2nd: tuple, - rational_flag: bool, func_to_test: typing.Callable, - object_to_test_on: typing.Any): +def _get_answers_one_order( + game: gbt.Game, + action_probs_1st: tuple, + action_probs_2nd: tuple, + rational_flag: bool, + func_to_test: typing.Callable, + object_to_test_on: typing.Any, +): """helper function for the 'profile_order' caching tests""" ret = dict() profile = game.mixed_behavior_profile(rational=rational_flag) @@ -1138,14 +1554,27 @@ def _get_answers_one_order(game: gbt.Game, action_probs_1st: tuple, action_probs return ret -def _get_and_check_answers(game: gbt.Game, action_probs1: tuple, action_probs2: tuple, - rational_flag: bool, func_to_test: typing.Callable, - objects_to_test_on: typing.Collection): +def _get_and_check_answers( + game: gbt.Game, + action_probs1: tuple, + action_probs2: tuple, + rational_flag: bool, + func_to_test: typing.Callable, + objects_to_test_on: typing.Collection, +): """helper function for the 'profile_order' caching tests""" - order1_answers = {o: _get_answers_one_order(game, action_probs1, action_probs2, rational_flag, - func_to_test, o) for o in objects_to_test_on} - order2_answers = {o: _get_answers_one_order(game, action_probs2, action_probs1, rational_flag, - func_to_test, o) for o in objects_to_test_on} + order1_answers = { + o: _get_answers_one_order( + game, action_probs1, action_probs2, rational_flag, func_to_test, o + ) + for o in objects_to_test_on + } + order2_answers = { + o: _get_answers_one_order( + game, action_probs2, action_probs1, rational_flag, func_to_test, o + ) + for o in objects_to_test_on + } assert order1_answers == order2_answers @@ -1162,177 +1591,481 @@ def _get_and_check_answers(game: gbt.Game, action_probs1: tuple, action_probs2: @pytest.mark.parametrize( "game,action_probs1,action_probs2,rational_flag,func_to_test,objects_to_test", [ - ###################################################################################### - # belief (at nodes) - (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda x, y: x.belief(y), lambda x: x.nodes), - (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.belief(y), lambda x: x.nodes), - (games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda x, y: x.belief(y), lambda x: x.nodes), - (games.create_stripped_down_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.belief(y), lambda x: x.nodes), - ###################################################################################### - # realiz_prob (at nodes) - (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda x, y: x.realiz_prob(y), lambda x: x.nodes), - (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.realiz_prob(y), lambda x: x.nodes), - (games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda x, y: x.realiz_prob(y), lambda x: x.nodes), - (games.create_stripped_down_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.realiz_prob(y), lambda x: x.nodes), - ###################################################################################### - # infoset_prob - (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda x, y: x.infoset_prob(y), lambda x: x.infosets), - (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.infoset_prob(y), lambda x: x.infosets), - (games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda x, y: x.infoset_prob(y), lambda x: x.infosets), - (games.create_stripped_down_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.infoset_prob(y), lambda x: x.infosets), - ###################################################################################### - # infoset_value - (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda x, y: x.infoset_value(y), lambda x: x.infosets), - (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.infoset_value(y), lambda x: x.infosets), - (games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda x, y: x.infoset_value(y), lambda x: x.infosets), - (games.create_stripped_down_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.infoset_value(y), lambda x: x.infosets), - ###################################################################################### - # action_value - (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda x, y: x.action_value(y), lambda x: x.actions), - (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.action_value(y), lambda x: x.actions), - (games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda x, y: x.action_value(y), lambda x: x.actions), - (games.create_stripped_down_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.action_value(y), lambda x: x.actions), - ###################################################################################### - # regret (for actions) - (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda x, y: x.action_regret(y), lambda x: x.actions), - (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.action_regret(y), lambda x: x.actions), - (games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda x, y: x.action_regret(y), lambda x: x.actions), - (games.create_stripped_down_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.action_regret(y), lambda x: x.actions), - ###################################################################################### - # node_value - (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda x, y: x.node_value(player=y[0], node=y[1]), - lambda x: list(product(x.players, x.nodes))), - (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.node_value(player=y[0], node=y[1]), - lambda x: list(product(x.players, x.nodes))), - (games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda x, y: x.node_value(player=y[0], node=y[1]), - lambda x: list(product(x.players, x.nodes))), - (games.create_stripped_down_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.node_value(player=y[0], node=y[1]), - lambda x: list(product(x.players, x.nodes))), - ###################################################################################### - # agent_liap_value (of profile, hence [1] for objects_to_test, - # any singleton collection would do) - (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda x, y: x.agent_liap_value(), lambda x: [1]), - (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.agent_liap_value(), lambda x: [1]), - (games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda x, y: x.agent_liap_value(), lambda x: [1]), - (games.create_stripped_down_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.agent_liap_value(), lambda x: [1]), - ###################################################################################### - # liap_value (of profile, hence [1] for objects_to_test, - # any singleton collection would do) - (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda x, y: x.liap_value(), lambda x: [1]), - (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.liap_value(), lambda x: [1]), - (games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda x, y: x.liap_value(), lambda x: [1]), - (games.create_stripped_down_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.liap_value(), lambda x: [1]), - ###################################################################################### - # agent_max_regret (of profile, hence [1] for objects_to_test, - # any singleton collection would do) - (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda x, y: x.agent_max_regret(), lambda x: [1]), - (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.agent_max_regret(), lambda x: [1]), - (games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda x, y: x.agent_max_regret(), lambda x: [1]), - (games.create_stripped_down_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.agent_max_regret(), lambda x: [1]), - ###################################################################################### - # max_regret (of profile, hence [1] for objects_to_test, - # any singleton collection would do) - (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda x, y: x.max_regret(), lambda x: [1]), - (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.max_regret(), lambda x: [1]), - (games.create_stripped_down_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda x, y: x.max_regret(), lambda x: [1]), - (games.create_stripped_down_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.max_regret(), lambda x: [1]), - ] + ###################################################################################### + # belief (at nodes) + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda x, y: x.belief(y), + lambda x: x.nodes, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.belief(y), + lambda x: x.nodes, + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda x, y: x.belief(y), + lambda x: x.nodes, + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.belief(y), + lambda x: x.nodes, + ), + ###################################################################################### + # realiz_prob (at nodes) + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda x, y: x.realiz_prob(y), + lambda x: x.nodes, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.realiz_prob(y), + lambda x: x.nodes, + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda x, y: x.realiz_prob(y), + lambda x: x.nodes, + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.realiz_prob(y), + lambda x: x.nodes, + ), + ###################################################################################### + # infoset_prob + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda x, y: x.infoset_prob(y), + lambda x: x.infosets, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.infoset_prob(y), + lambda x: x.infosets, + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda x, y: x.infoset_prob(y), + lambda x: x.infosets, + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.infoset_prob(y), + lambda x: x.infosets, + ), + ###################################################################################### + # infoset_value + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda x, y: x.infoset_value(y), + lambda x: x.infosets, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.infoset_value(y), + lambda x: x.infosets, + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda x, y: x.infoset_value(y), + lambda x: x.infosets, + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.infoset_value(y), + lambda x: x.infosets, + ), + ###################################################################################### + # action_value + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda x, y: x.action_value(y), + lambda x: x.actions, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.action_value(y), + lambda x: x.actions, + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda x, y: x.action_value(y), + lambda x: x.actions, + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.action_value(y), + lambda x: x.actions, + ), + ###################################################################################### + # regret (for actions) + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda x, y: x.action_regret(y), + lambda x: x.actions, + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.action_regret(y), + lambda x: x.actions, + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda x, y: x.action_regret(y), + lambda x: x.actions, + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.action_regret(y), + lambda x: x.actions, + ), + ###################################################################################### + # node_value + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda x, y: x.node_value(player=y[0], node=y[1]), + lambda x: list(product(x.players, x.nodes)), + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.node_value(player=y[0], node=y[1]), + lambda x: list(product(x.players, x.nodes)), + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda x, y: x.node_value(player=y[0], node=y[1]), + lambda x: list(product(x.players, x.nodes)), + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.node_value(player=y[0], node=y[1]), + lambda x: list(product(x.players, x.nodes)), + ), + ###################################################################################### + # agent_liap_value (of profile, hence [1] for objects_to_test, + # any singleton collection would do) + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda x, y: x.agent_liap_value(), + lambda x: [1], + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.agent_liap_value(), + lambda x: [1], + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda x, y: x.agent_liap_value(), + lambda x: [1], + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.agent_liap_value(), + lambda x: [1], + ), + ###################################################################################### + # liap_value (of profile, hence [1] for objects_to_test, + # any singleton collection would do) + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda x, y: x.liap_value(), + lambda x: [1], + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.liap_value(), + lambda x: [1], + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda x, y: x.liap_value(), + lambda x: [1], + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.liap_value(), + lambda x: [1], + ), + ###################################################################################### + # agent_max_regret (of profile, hence [1] for objects_to_test, + # any singleton collection would do) + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda x, y: x.agent_max_regret(), + lambda x: [1], + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.agent_max_regret(), + lambda x: [1], + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda x, y: x.agent_max_regret(), + lambda x: [1], + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.agent_max_regret(), + lambda x: [1], + ), + ###################################################################################### + # max_regret (of profile, hence [1] for objects_to_test, + # any singleton collection would do) + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_doub, + PROBS_2A_doub, + False, + lambda x, y: x.max_regret(), + lambda x: [1], + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.max_regret(), + lambda x: [1], + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1B_doub, + PROBS_2B_doub, + False, + lambda x, y: x.max_regret(), + lambda x: [1], + ), + ( + games.create_stripped_down_poker_efg(), + PROBS_1A_rat, + PROBS_2A_rat, + True, + lambda x, y: x.max_regret(), + lambda x: [1], + ), + ], ) -def test_profile_order_consistency(game: gbt.Game, - action_probs1: tuple, - action_probs2: tuple, rational_flag: bool, - func_to_test: typing.Callable, - objects_to_test: typing.Callable): - _get_and_check_answers(game, action_probs1, action_probs2, rational_flag, func_to_test, - objects_to_test(game)) +def test_profile_order_consistency( + game: gbt.Game, + action_probs1: tuple, + action_probs2: tuple, + rational_flag: bool, + func_to_test: typing.Callable, + objects_to_test: typing.Callable, +): + _get_and_check_answers( + game, action_probs1, action_probs2, rational_flag, func_to_test, objects_to_test(game) + ) @pytest.mark.parametrize( "game,rational_flag,data", - [(games.create_mixed_behav_game_efg(), True, [[[0, 1]], [[0, 1]], [[1, 0]]]), - (games.create_mixed_behav_game_efg(), True, [[["1/5", "4/5"]], [["1/4", "3/4"]], [[1, 0]]]), - (games.create_stripped_down_poker_efg(), True, [[[1/5, 4/5], [3/5, 2/5]], [[1/4, 3/4]]]), - (games.create_mixed_behav_game_efg(), False, [[[0, 1]], [[1, 0]], [[1, 0]]]), - (games.create_mixed_behav_game_efg(), False, [[[1/5, 4/5]], [[1/4, 3/4]], [[1, 0]]]), - (games.create_stripped_down_poker_efg(), False, [[[1/5, 4/5], [3/5, 2/5]], [[1/4, 3/4]]]) - ] + [ + (games.read_from_file("mixed_behavior_game.efg"), True, [[[0, 1]], [[0, 1]], [[1, 0]]]), + ( + games.read_from_file("mixed_behavior_game.efg"), + True, + [[["1/5", "4/5"]], [["1/4", "3/4"]], [[1, 0]]], + ), + ( + games.create_stripped_down_poker_efg(), + True, + [[[1 / 5, 4 / 5], [3 / 5, 2 / 5]], [[1 / 4, 3 / 4]]], + ), + (games.read_from_file("mixed_behavior_game.efg"), False, [[[0, 1]], [[1, 0]], [[1, 0]]]), + ( + games.read_from_file("mixed_behavior_game.efg"), + False, + [[[1 / 5, 4 / 5]], [[1 / 4, 3 / 4]], [[1, 0]]], + ), + ( + games.create_stripped_down_poker_efg(), + False, + [[[1 / 5, 4 / 5], [3 / 5, 2 / 5]], [[1 / 4, 3 / 4]]], + ), + ], ) def test_specific_profile(game: gbt.Game, rational_flag: bool, data: list): """Test that the mixed behavior profile is initialized from a specific distribution for each player over his actions. """ profile = game.mixed_behavior_profile(rational=rational_flag, data=data) - for (action, prob) in zip(game.actions, [k for i in data for j in i for k in j], strict=True): + for action, prob in zip(game.actions, [k for i in data for j in i for k in j], strict=True): assert profile[action] == (gbt.Rational(prob) if rational_flag else prob) @pytest.mark.parametrize( "game,rational_flag,data", - [(games.create_mixed_behav_game_efg(), True, - [[[0, 1, 0]], [[1, 0]], [["1/2", "1/2"]]]), - (games.create_mixed_behav_game_efg(), True, - [[[0, 1]], [[1, 0]], [[1, 0]], [[0, 1]]]), - (games.create_stripped_down_poker_efg(), True, - [[["1/5", "4/5"], ["3/5", "2/5"]], [["1/4", "3/4"], ["1/4", "3/4"]]]), - (games.create_el_farol_bar_game_efg(), True, - [[4/9, 5/9], [0], [1/2, 1/2], [11/12, 1/12], [1/2, 1/2]]), - (games.create_el_farol_bar_game_efg(), True, - [[1/2, 1/2]]), - (games.create_mixed_behav_game_efg(), False, - [[[0, 1, 0]], [[1, 0]], [[1, 0]]]), - (games.create_mixed_behav_game_efg(), False, - [[[0, 1]], [[1, 0]], [[1, 0]], [[0, 1]]]), - (games.create_stripped_down_poker_efg(), False, - [[[1/5, 4/5], [3/5, 2/5]], [[1/4, 3/4], [1/4, 3/4]]]), - (games.create_el_farol_bar_game_efg(), False, - [[4/9, 5/9], [0], [1/2, 1/2], [11/12, 1/12], [1/2, 1/2]]), - (games.create_el_farol_bar_game_efg(), False, - [[1/2, 1/2]]) - ] + [ + ( + games.read_from_file("mixed_behavior_game.efg"), + True, + [[[0, 1, 0]], [[1, 0]], [["1/2", "1/2"]]], + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + True, + [[[0, 1]], [[1, 0]], [[1, 0]], [[0, 1]]], + ), + ( + games.create_stripped_down_poker_efg(), + True, + [[["1/5", "4/5"], ["3/5", "2/5"]], [["1/4", "3/4"], ["1/4", "3/4"]]], + ), + ( + games.create_el_farol_bar_game_efg(), + True, + [[4 / 9, 5 / 9], [0], [1 / 2, 1 / 2], [11 / 12, 1 / 12], [1 / 2, 1 / 2]], + ), + (games.create_el_farol_bar_game_efg(), True, [[1 / 2, 1 / 2]]), + ( + games.read_from_file("mixed_behavior_game.efg"), + False, + [[[0, 1, 0]], [[1, 0]], [[1, 0]]], + ), + ( + games.read_from_file("mixed_behavior_game.efg"), + False, + [[[0, 1]], [[1, 0]], [[1, 0]], [[0, 1]]], + ), + ( + games.create_stripped_down_poker_efg(), + False, + [[[1 / 5, 4 / 5], [3 / 5, 2 / 5]], [[1 / 4, 3 / 4], [1 / 4, 3 / 4]]], + ), + ( + games.create_el_farol_bar_game_efg(), + False, + [[4 / 9, 5 / 9], [0], [1 / 2, 1 / 2], [11 / 12, 1 / 12], [1 / 2, 1 / 2]], + ), + (games.create_el_farol_bar_game_efg(), False, [[1 / 2, 1 / 2]]), + ], ) def test_profile_data_error(game: gbt.Game, rational_flag: bool, data: list): """Test to ensure a pygambit.ValueError is raised when the data do not @@ -1345,11 +2078,18 @@ def test_profile_data_error(game: gbt.Game, rational_flag: bool, data: list): @pytest.mark.parametrize( "game,rational_flag,data", - [(games.read_from_file("coordination_4x4_payoff.nfg"), True, - [["1/5", "2/5", 0, "2/5"], ["1/4", "3/8", "1/4", "3/8"]]), - (games.read_from_file("coordination_4x4_payoff.nfg"), False, - [[1/5, 2/5, 0/5, 2/5], [1/4, 3/8, 1/4, 3/8]]), - ] + [ + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + True, + [["1/5", "2/5", 0, "2/5"], ["1/4", "3/8", "1/4", "3/8"]], + ), + ( + games.read_from_file("coordination_4x4_payoff.nfg"), + False, + [[1 / 5, 2 / 5, 0 / 5, 2 / 5], [1 / 4, 3 / 8, 1 / 4, 3 / 8]], + ), + ], ) def test_tree_representation_error(game: gbt.Game, rational_flag: bool, data: list): """Test to ensure a pygambit.UndefinedOperationError is raised when the game diff --git a/tests/test_games/mixed_behavior_game.efg b/tests/test_games/mixed_behavior_game.efg index d6de35662..ff93b5732 100644 --- a/tests/test_games/mixed_behavior_game.efg +++ b/tests/test_games/mixed_behavior_game.efg @@ -1,5 +1,10 @@ EFG 2 R "Test Extensive Form Game" { "Player 1" "Player 2" "Player 3" } -"" +" +Three-player extensive form game: binary tree with 3 infomation sets, one per player, +with 1, 2, and 4 nodes respectively. + +Since no information is revealed this is directly equivalent to a simultaneous move game. +" p "" 1 1 "Infoset 1:1" { "U1" "D1" } 0 p "" 2 1 "Infoset 2:1" { "U2" "D2" } 0 diff --git a/tests/test_mixed.py b/tests/test_mixed.py index 731f6f35f..f82535cb2 100644 --- a/tests/test_mixed.py +++ b/tests/test_mixed.py @@ -358,12 +358,12 @@ def test_profile_indexing_by_strategy_label_reference( [ ############################################################################ # mixed behav efg - (games.create_mixed_behav_game_efg(), P1, [0.5, 0.5], False), - (games.create_mixed_behav_game_efg(), P2, [0.5, 0.5], False), - (games.create_mixed_behav_game_efg(), P3, [0.5, 0.5], False), - (games.create_mixed_behav_game_efg(), P1, ["1/2", "1/2"], True), - (games.create_mixed_behav_game_efg(), P2, ["1/2", "1/2"], True), - (games.create_mixed_behav_game_efg(), P3, ["1/2", "1/2"], True), + (games.read_from_file("mixed_behavior_game.efg"), P1, [0.5, 0.5], False), + (games.read_from_file("mixed_behavior_game.efg"), P2, [0.5, 0.5], False), + (games.read_from_file("mixed_behavior_game.efg"), P3, [0.5, 0.5], False), + (games.read_from_file("mixed_behavior_game.efg"), P1, ["1/2", "1/2"], True), + (games.read_from_file("mixed_behavior_game.efg"), P2, ["1/2", "1/2"], True), + (games.read_from_file("mixed_behavior_game.efg"), P3, ["1/2", "1/2"], True), ############################################################################ # stripped-down poker efg (games.create_stripped_down_poker_efg(), "Alice", [0.25, 0.25, 0.25, 0.25], False), @@ -493,12 +493,12 @@ def test_profile_indexing_by_player_label_reference( ), (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], ["1/2", "1/2"]], "Bob", 1), ######################################################################### - (games.create_mixed_behav_game_efg(), False, None, P1, 3.0), - (games.create_mixed_behav_game_efg(), False, None, P2, 3.0), - (games.create_mixed_behav_game_efg(), False, None, P3, 3.25), - (games.create_mixed_behav_game_efg(), True, None, P1, 3), - (games.create_mixed_behav_game_efg(), True, None, P2, 3), - (games.create_mixed_behav_game_efg(), True, None, P3, "13/4"), + (games.read_from_file("mixed_behavior_game.efg"), False, None, P1, 3.0), + (games.read_from_file("mixed_behavior_game.efg"), False, None, P2, 3.0), + (games.read_from_file("mixed_behavior_game.efg"), False, None, P3, 3.25), + (games.read_from_file("mixed_behavior_game.efg"), True, None, P1, 3), + (games.read_from_file("mixed_behavior_game.efg"), True, None, P2, 3), + (games.read_from_file("mixed_behavior_game.efg"), True, None, P3, "13/4"), ], ) def test_payoff_by_label_reference( @@ -542,8 +542,8 @@ def test_strategy_value_by_label_reference( @pytest.mark.parametrize( "game,rational_flag", [ - (games.create_mixed_behav_game_efg(), False), - (games.create_mixed_behav_game_efg(), True), + (games.read_from_file("mixed_behavior_game.efg"), False), + (games.read_from_file("mixed_behavior_game.efg"), True), (games.create_centipede_game_with_chance_efg(), False), (games.create_centipede_game_with_chance_efg(), True), ], diff --git a/tests/test_nash.py b/tests/test_nash.py index 52343490e..f47801d61 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -44,7 +44,7 @@ (games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq(), []), # 3-player game ( - games.create_mixed_behav_game_efg(), + games.read_from_file("mixed_behavior_game.efg"), [ [[1, 0], [1, 0], [1, 0]], [[0, 1], [0, 1], [1, 0]], @@ -101,7 +101,7 @@ def test_enumpure_strategy(game: gbt.Game, pure_strategy_prof_data: list): (games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq(), []), # 3-player game ( - games.create_mixed_behav_game_efg(), + games.read_from_file("mixed_behavior_game.efg"), [ [[[1, 0]], [[1, 0]], [[1, 0]]], [[[1, 0]], [[0, 1]], [[0, 1]]], @@ -239,7 +239,7 @@ def test_enummixed_rational(game: gbt.Game, mixed_strategy_prof_data: list): ), # 3-player game # ( - # games.create_mixed_behav_game_efg(), + # games.read_from_file("mixed_behavior_game.efg"), # [ # [[["1/2", "1/2"]], [["2/5", "3/5"]], [["1/4", "3/4"]]], # [[["2/5", "3/5"]], [["1/2", "1/2"]], [["1/3", "2/3"]]], @@ -335,7 +335,7 @@ def test_enumpoly_ordered_behavior( [ # 3-player game ( - games.create_mixed_behav_game_efg(), + games.read_from_file("mixed_behavior_game.efg"), [ [[["2/5", "3/5"]], [["1/2", "1/2"]], [["1/3", "2/3"]]], [[["1/2", "1/2"]], [["2/5", "3/5"]], [["1/4", "3/4"]]], From 0e5aabdd742caa11505e417711c8e1f625695252 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 19:04:53 +0000 Subject: [PATCH 23/44] create_2x2_zero_sum_efg uses create_efg_corresponding_to_bimatrix_game --- tests/games.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/tests/games.py b/tests/games.py index da398bf0a..f37caade2 100644 --- a/tests/games.py +++ b/tests/games.py @@ -44,26 +44,17 @@ def create_efg_corresponding_to_bimatrix_game( def create_2x2_zero_sum_efg(missing_term_outcome: bool = False) -> gbt.Game: """ - TODO: use create_efg_corresponding_to_bimatrix_game - EFG corresponding to 2x2 zero-sum game (I,-I). - If missing_term_outcome, the terminal node after "T" then "r" does not have an outcome. + If missing_term_outcome, the terminal node after action 0 then 1 does not have an outcome. """ - g = gbt.Game.new_tree( - players=["Alice", "Bob"], title="2x2 matrix games (I,-I)") - g.append_move(g.root, "Alice", ["T", "B"]) - g.append_move(g.root.children, "Bob", ["l", "r"]) - - alice_win = g.add_outcome([1, -1], label="Alice win") - draw = g.add_outcome([0, 0], label="Draw") - - g.set_outcome(g.root.children["T"].children["l"], alice_win) - g.set_outcome(g.root.children["B"].children["r"], alice_win) - g.set_outcome(g.root.children["B"].children["l"], draw) - - if not missing_term_outcome: - g.set_outcome(g.root.children["T"].children["r"], draw) - + title = "EFG for 2x2 zero-sum game (I,-I)" + if missing_term_outcome: + title += " with missing terminal outcome" + A = np.eye(2) + B = -A + g = create_efg_corresponding_to_bimatrix_game(A, B, title) + if missing_term_outcome: + g.delete_outcome(g.root.children[0].children[1].outcome) return g From 9548e67ccbaec387c1d022273b3738aa917880c4 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 19:08:11 +0000 Subject: [PATCH 24/44] removed create_selten_horse_game_efg from games.py --- tests/games.py | 10 ---------- tests/test_io.py | 2 +- tests/test_mixed.py | 8 ++++---- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/tests/games.py b/tests/games.py index f37caade2..bb980da87 100644 --- a/tests/games.py +++ b/tests/games.py @@ -421,16 +421,6 @@ def create_el_farol_bar_game_efg() -> gbt.Game: return read_from_file("el_farol_bar.efg") -def create_selten_horse_game_efg() -> gbt.Game: - """ - Returns - ------- - Game - 5-player Selten's Horse Game - """ - return read_from_file("e01.efg") - - def create_EFG_for_nxn_bimatrix_coordination_game(n: int) -> gbt.Game: A = np.eye(n, dtype=int) B = A diff --git a/tests/test_io.py b/tests/test_io.py index c0bf876ec..3016c224b 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -112,7 +112,7 @@ def test_write_latex(): def test_read_write_efg(): - efg_game = games.create_selten_horse_game_efg() + efg_game = games.read_from_file("e01.efg") serialized_efg_game = efg_game.to_efg() deserialized_efg_game = gbt.read_efg(io.BytesIO(serialized_efg_game.encode())) double_serialized_efg_game = deserialized_efg_game.to_efg() diff --git a/tests/test_mixed.py b/tests/test_mixed.py index f82535cb2..25941127b 100644 --- a/tests/test_mixed.py +++ b/tests/test_mixed.py @@ -1359,9 +1359,9 @@ def test_player_regret_max_regret_consistency( False, ), ################################################################################# - # Selten's horse game + # Selten's horse ( - games.create_selten_horse_game_efg(), + games.read_from_file("e01.efg"), [["4/9", "5/9"], ["1/11", "10/11"], ["8/9", "1/9"]], [["4/9", "5/9"], ["10/11", "1/11"], ["8/9", "1/9"]], gbt.Rational("4/9"), @@ -1459,13 +1459,13 @@ def test_linearity_payoff_property( ################################################################################# # Selten's horse ( - games.create_selten_horse_game_efg(), + games.read_from_file("e01.efg"), [["4/9", "5/9"], ["6/11", "5/11"], ["4/7", "3/7"]], ZERO, True, ), ( - games.create_selten_horse_game_efg(), + games.read_from_file("e01.efg"), [[4 / 9, 5 / 9], [6 / 11, 5 / 11], [4 / 7, 3 / 7]], TOL, False, From cdb6201032233106020d8ff22ced67a34eabb2be Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 19:13:47 +0000 Subject: [PATCH 25/44] removed create_el_farol_bar_game_efg and create_centipede_game_with_chance_efg from games.py --- tests/games.py | 20 ------------- tests/test_behav.py | 8 ++--- tests/test_mixed.py | 72 ++++++++++++++++++++++----------------------- 3 files changed, 40 insertions(+), 60 deletions(-) diff --git a/tests/games.py b/tests/games.py index bb980da87..e01e9f335 100644 --- a/tests/games.py +++ b/tests/games.py @@ -401,26 +401,6 @@ def create_one_shot_trust_efg(unique_NE_variant: bool = False) -> gbt.Game: return g -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_efg() -> gbt.Game: - """ - Returns - ------- - Game - 5-player El Farol Bar Game - """ - return read_from_file("el_farol_bar.efg") - - def create_EFG_for_nxn_bimatrix_coordination_game(n: int) -> gbt.Game: A = np.eye(n, dtype=int) B = A diff --git a/tests/test_behav.py b/tests/test_behav.py index 6097d342e..62ec7bbf3 100644 --- a/tests/test_behav.py +++ b/tests/test_behav.py @@ -2039,11 +2039,11 @@ def test_specific_profile(game: gbt.Game, rational_flag: bool, data: list): [[["1/5", "4/5"], ["3/5", "2/5"]], [["1/4", "3/4"], ["1/4", "3/4"]]], ), ( - games.create_el_farol_bar_game_efg(), + games.read_from_file("el_farol_bar.efg"), True, [[4 / 9, 5 / 9], [0], [1 / 2, 1 / 2], [11 / 12, 1 / 12], [1 / 2, 1 / 2]], ), - (games.create_el_farol_bar_game_efg(), True, [[1 / 2, 1 / 2]]), + (games.read_from_file("el_farol_bar.efg"), True, [[1 / 2, 1 / 2]]), ( games.read_from_file("mixed_behavior_game.efg"), False, @@ -2060,11 +2060,11 @@ def test_specific_profile(game: gbt.Game, rational_flag: bool, data: list): [[[1 / 5, 4 / 5], [3 / 5, 2 / 5]], [[1 / 4, 3 / 4], [1 / 4, 3 / 4]]], ), ( - games.create_el_farol_bar_game_efg(), + games.read_from_file("el_farol_bar.efg"), False, [[4 / 9, 5 / 9], [0], [1 / 2, 1 / 2], [11 / 12, 1 / 12], [1 / 2, 1 / 2]], ), - (games.create_el_farol_bar_game_efg(), False, [[1 / 2, 1 / 2]]), + (games.read_from_file("el_farol_bar.efg"), False, [[1 / 2, 1 / 2]]), ], ) def test_profile_data_error(game: gbt.Game, rational_flag: bool, data: list): diff --git a/tests/test_mixed.py b/tests/test_mixed.py index 25941127b..287ee9eac 100644 --- a/tests/test_mixed.py +++ b/tests/test_mixed.py @@ -46,10 +46,10 @@ def _set_action_probs(profile: gbt.MixedStrategyProfile, probs: list, rational_f ), ############################################################################### # centipede with chance efg - (games.create_centipede_game_with_chance_efg(), [[0, 0, 0, 0], [1, 0, 0, 0]], True), - (games.create_centipede_game_with_chance_efg(), [[1, 0, 0, 0], [0, 0, 0, 0]], True), - (games.create_centipede_game_with_chance_efg(), [[0, 0, 0, 0], [1, 0, 0, 0]], False), - (games.create_centipede_game_with_chance_efg(), [[1, 0, 0, 0], [0, 0, 0, 0]], False), + (games.read_from_file("cent3.efg"), [[0, 0, 0, 0], [1, 0, 0, 0]], True), + (games.read_from_file("cent3.efg"), [[1, 0, 0, 0], [0, 0, 0, 0]], True), + (games.read_from_file("cent3.efg"), [[0, 0, 0, 0], [1, 0, 0, 0]], False), + (games.read_from_file("cent3.efg"), [[1, 0, 0, 0], [0, 0, 0, 0]], False), ], ) def test_normalize_zero_value_error(game, profile_data, rational_flag): @@ -83,10 +83,10 @@ def test_normalize_zero_value_error(game, profile_data, rational_flag): ), ############################################################################### # centipede with chance efg - (games.create_centipede_game_with_chance_efg(), [[-1, 0, 0, 0], [1, 0, 0, 0]], True), - (games.create_centipede_game_with_chance_efg(), [[1, 0, 0, 0], [-1, 0, 0, 0]], True), - (games.create_centipede_game_with_chance_efg(), [[-1, 0, 0, 0], [1, 0, 0, 0]], False), - (games.create_centipede_game_with_chance_efg(), [[1, 0, 0, 0], [-1, 0, 0, 0]], False), + (games.read_from_file("cent3.efg"), [[-1, 0, 0, 0], [1, 0, 0, 0]], True), + (games.read_from_file("cent3.efg"), [[1, 0, 0, 0], [-1, 0, 0, 0]], True), + (games.read_from_file("cent3.efg"), [[-1, 0, 0, 0], [1, 0, 0, 0]], False), + (games.read_from_file("cent3.efg"), [[1, 0, 0, 0], [-1, 0, 0, 0]], False), ], ) def test_normalize_neg_entry_value_error(game, profile_data, rational_flag): @@ -115,13 +115,13 @@ def test_normalize_neg_entry_value_error(game, profile_data, rational_flag): ############################################################################### # centipede with chance efg ( - games.create_centipede_game_with_chance_efg(), + games.read_from_file("cent3.efg"), [[1, 2, 3, 14], [1, 1, 1, 1]], [["1/20", "2/20", "3/20", "14/20"], ["1/4", "1/4", "1/4", "1/4"]], True, ), ( - games.create_centipede_game_with_chance_efg(), + games.read_from_file("cent3.efg"), [[1.0, 2.0, 3.0, 14.0], [1, 1, 1, 1]], [[1 / 20, 2 / 20, 3 / 20, 14 / 20], [0.25, 0.25, 0.25, 0.25]], False, @@ -544,8 +544,8 @@ def test_strategy_value_by_label_reference( [ (games.read_from_file("mixed_behavior_game.efg"), False), (games.read_from_file("mixed_behavior_game.efg"), True), - (games.create_centipede_game_with_chance_efg(), False), - (games.create_centipede_game_with_chance_efg(), True), + (games.read_from_file("cent3.efg"), False), + (games.read_from_file("cent3.efg"), True), ], ) def test_as_behavior_roundtrip(game: gbt.Game, rational_flag: bool): @@ -774,14 +774,14 @@ def test_strategy_value_reference( ############################################################################## # El Farol bar game efg ( - games.create_el_farol_bar_game_efg(), + games.read_from_file("el_farol_bar.efg"), [["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"]], 0, ZERO, True, ), ( - games.create_el_farol_bar_game_efg(), + games.read_from_file("el_farol_bar.efg"), [[1, 0], [1, 0], [0, 1], [0, 1], [0, 1]], 0, ZERO, @@ -972,14 +972,14 @@ def test_liap_value_reference( ############################################################################## # El Farol bar game efg ( - games.create_el_farol_bar_game_efg(), + games.read_from_file("el_farol_bar.efg"), [["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"], ["1/2", "1/2"]], [0] * 5, ZERO, True, ), ( - games.create_el_farol_bar_game_efg(), + games.read_from_file("el_farol_bar.efg"), [[1, 0], [1, 0], [0, 1], [0, 1], [0, 1]], [0] * 5, ZERO, @@ -1104,12 +1104,12 @@ def test_player_regret_max_regret_reference( (games.read_from_file("2x2_bimatrix_all_zero_payoffs.nfg"), True), ################################################################################# # El Farol bar game efg - (games.create_el_farol_bar_game_efg(), False), - (games.create_el_farol_bar_game_efg(), True), + (games.read_from_file("el_farol_bar.efg"), False), + (games.read_from_file("el_farol_bar.efg"), True), ################################################################################# # Centipede with chance efg - (games.create_centipede_game_with_chance_efg(), False), - (games.create_centipede_game_with_chance_efg(), True), + (games.read_from_file("cent3.efg"), False), + (games.read_from_file("cent3.efg"), True), ################################################################################# # 2x2x2 nfg (games.read_from_file("2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), False), @@ -1146,13 +1146,13 @@ def test_strategy_regret_consistency(game: gbt.Game, rational_flag: bool): ################################################################################# # Centipede with chance efg ( - games.create_centipede_game_with_chance_efg(), + games.read_from_file("cent3.efg"), [["1/3", "1/3", "1/3", "0/1"], ["1/10", "3/5", "3/10", 0]], ZERO, True, ), ( - games.create_centipede_game_with_chance_efg(), + games.read_from_file("cent3.efg"), [[1 / 3, 1 / 3, 1 / 3, 0], [0.10, 3 / 5, 0.3, 0]], TOL, False, @@ -1160,13 +1160,13 @@ def test_strategy_regret_consistency(game: gbt.Game, rational_flag: bool): ################################################################################# # El Farol bar game efg ( - games.create_el_farol_bar_game_efg(), + games.read_from_file("el_farol_bar.efg"), [[1, 0], ["1/2", "1/2"], ["1/3", "2/3"], ["1/5", "4/5"], ["1/8", "7/8"]], ZERO, True, ), ( - games.create_el_farol_bar_game_efg(), + games.read_from_file("el_farol_bar.efg"), [[1, 0], [1 / 2, 1 / 2], [1 / 3, 2 / 3], [1 / 5, 4 / 5], [1 / 8, 7 / 8]], TOL, False, @@ -1239,13 +1239,13 @@ def test_liap_value_consistency( ################################################################################# # Centipede with chance efg ( - games.create_centipede_game_with_chance_efg(), + games.read_from_file("cent3.efg"), [["1/3", "1/3", "1/3", "0/1"], ["1/10", "3/5", "3/10", 0]], ZERO, True, ), ( - games.create_centipede_game_with_chance_efg(), + games.read_from_file("cent3.efg"), [[1 / 3, 1 / 3, 1 / 3, 0], [0.10, 3 / 5, 0.3, 0]], TOL, False, @@ -1253,13 +1253,13 @@ def test_liap_value_consistency( ################################################################################# # El Farol bar game efg ( - games.create_el_farol_bar_game_efg(), + games.read_from_file("el_farol_bar.efg"), [[1, 0], ["1/2", "1/2"], ["1/3", "2/3"], ["1/5", "4/5"], ["1/8", "7/8"]], ZERO, True, ), ( - games.create_el_farol_bar_game_efg(), + games.read_from_file("el_farol_bar.efg"), [[1, 0], [1 / 2, 1 / 2], [1 / 3, 2 / 3], [1 / 5, 4 / 5], [1 / 8, 7 / 8]], TOL, False, @@ -1343,7 +1343,7 @@ def test_player_regret_max_regret_consistency( ################################################################################# # Centipede game with chance ( - games.create_centipede_game_with_chance_efg(), + games.read_from_file("cent3.efg"), [["1/3", "1/3", "1/3", "0/1"], ["1/10", "3/5", "3/10", "0/1"]], [["1/3", "1/3", "1/3", "0/1"], ["1/5", "2/5", "1/5", "1/5"]], gbt.Rational("1/12"), @@ -1351,7 +1351,7 @@ def test_player_regret_max_regret_consistency( True, ), ( - games.create_centipede_game_with_chance_efg(), + games.read_from_file("cent3.efg"), [[1 / 3, 1 / 3, 1 / 3, 0 / 1], [1 / 10, 3 / 5, 3 / 10, 0 / 1]], [[1 / 3, 1 / 3, 1 / 3, 0 / 1], [1 / 5, 2 / 5, 1 / 5, 1 / 5]], 1 / 12, @@ -1371,7 +1371,7 @@ def test_player_regret_max_regret_consistency( ################################################################################# # El Farol bar game ( - games.create_el_farol_bar_game_efg(), + games.read_from_file("el_farol_bar.efg"), [["4/9", "5/9"], ["1/3", "2/3"], ["1/2", "1/2"], ["11/12", "1/12"], ["1/2", "1/2"]], [["4/9", "5/9"], ["1/3", "2/3"], ["1/2", "1/2"], ["1/12", "11/12"], ["1/2", "1/2"]], gbt.Rational("1/2"), @@ -1445,13 +1445,13 @@ def test_linearity_payoff_property( ################################################################################# # Centipede game with chance ( - games.create_centipede_game_with_chance_efg(), + games.read_from_file("cent3.efg"), [["1/5", "2/5", "1/5", "1/5"], ["1/10", "3/5", "3/10", "0/1"]], ZERO, True, ), ( - games.create_centipede_game_with_chance_efg(), + games.read_from_file("cent3.efg"), [[1 / 3, 1 / 3, 1 / 3, 0 / 1], [1 / 10, 3 / 5, 3 / 10, 0 / 1]], TOL, False, @@ -1473,7 +1473,7 @@ def test_linearity_payoff_property( ################################################################################# # El Farol bar game ( - games.create_el_farol_bar_game_efg(), + games.read_from_file("el_farol_bar.efg"), [["4/9", "5/9"], ["1/3", "2/3"], ["0/1", "1/1"], ["11/12", "1/12"], ["1/3", "2/3"]], ZERO, True, @@ -1523,7 +1523,7 @@ def test_payoff_and_strategy_value_consistency( ################################################################################# # centipede game with chance ( - games.create_centipede_game_with_chance_efg(), + games.read_from_file("cent3.efg"), [["1/3", "1/3", "1/3", "0"], ["1/10", "3/5", "3/10", "0"]], [["1/3", "1/3", "1/3", "0"], ["1/10", "3/5", "3/10", "0"]], "82943/62500", @@ -1531,7 +1531,7 @@ def test_payoff_and_strategy_value_consistency( ZERO, ), ( - games.create_centipede_game_with_chance_efg(), + games.read_from_file("cent3.efg"), [[1 / 3, 1 / 3, 1 / 3, 0], [1 / 10, 3 / 5, 3 / 10, 0]], [[1 / 3, 1 / 3, 1 / 3, 0], [1 / 10, 3 / 5, 3 / 10, 0]], 82943 / 62500, From 2394420ecc300fa7ccac1b8c99914e0252a219cd Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 14 Jan 2026 19:15:30 +0000 Subject: [PATCH 26/44] comment in create_one_shot_trust_efg, which could be removed in due course --- tests/games.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/games.py b/tests/games.py index e01e9f335..450282a52 100644 --- a/tests/games.py +++ b/tests/games.py @@ -373,6 +373,8 @@ def kuhn_poker_lcp_first_mixed_strategy_prof(): def create_one_shot_trust_efg(unique_NE_variant: bool = False) -> gbt.Game: """ + TODO: this could be replaced with two .efg files + One-shot trust game, after Kreps (1990) The unique_NE_variant makes Trust a dominant strategy, replacing the From 60fe1c477ac490cf1367838b3f61c3a151d39fd9 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Tue, 27 Jan 2026 09:59:53 +0000 Subject: [PATCH 27/44] enumpure strategy/agent tests using test_nash_{stategy,agent}_solver respectively --- tests/test_nash.py | 399 ++++++++++++++++++++++++++++++--------------- 1 file changed, 266 insertions(+), 133 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 7cd092127..b66317ddc 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -21,138 +21,6 @@ TOL = 1e-13 # tolerance for floating point assertions -@pytest.mark.nash -@pytest.mark.nash_enumpure_strategy -@pytest.mark.parametrize( - "game,pure_strategy_prof_data", - [ - # Zero-sum games - ( - games.read_from_file("two_player_perfect_info_win_lose.efg"), - [ - [[0, 0, 1, 0], [1, 0, 0]], - [[0, 0, 1, 0], [0, 1, 0]], - [[0, 0, 1, 0], [0, 0, 1]], - ] - ), - (games.create_stripped_down_poker_efg(), []), - # Non-zero-sum 2-player games - (games.create_one_shot_trust_efg(), [[[0, 1], [0, 1]]]), - ( - games.create_EFG_for_nxn_bimatrix_coordination_game(3), - [ - [[1, 0, 0], [1, 0, 0]], - [[0, 1, 0], [0, 1, 0]], - [[0, 0, 1], [0, 0, 1]], - ], - ), - (games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq(), []), - # 3-player game - ( - games.read_from_file("mixed_behavior_game.efg"), - [ - [[1, 0], [1, 0], [1, 0]], - [[0, 1], [0, 1], [1, 0]], - [[0, 1], [1, 0], [0, 1]], - [[1, 0], [0, 1], [0, 1]], - ], - ), - ] -) -def test_enumpure_strategy(game: gbt.Game, pure_strategy_prof_data: list): - """Test calls of enumeration of pure strategy equilibria - - Tests max regret being zero (internal consistency) and compares the computed sequence of - pure strategy equilibria to a previosuly computed sequence (regression test) - """ - result = gbt.nash.enumpure_solve(game) - assert len(result.equilibria) == len(pure_strategy_prof_data) - for eq, exp in zip(result.equilibria, pure_strategy_prof_data, strict=True): - assert eq.max_regret() == 0 - expected = game.mixed_strategy_profile(rational=True, data=exp) - assert eq == expected - - -@pytest.mark.nash -@pytest.mark.nash_enumpure_agent -@pytest.mark.parametrize( - "game,pure_behav_prof_data", - [ - ############################################################# - # Examples where Nash pure behaviors and agent-form pure equillibrium behaviors coincide - ############################################################# - # Zero-sum games - ( - games.read_from_file("two_player_perfect_info_win_lose.efg"), - [ - [[[1, 0], [1, 0]], [[0, 1], [1, 0]]], - [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], - [[[0, 1], [1, 0]], [[1, 0], [0, 1]]], - [[[0, 1], [1, 0]], [[0, 1], [1, 0]]], - [[[0, 1], [1, 0]], [[0, 1], [0, 1]]] - ] - ), - (games.create_stripped_down_poker_efg(), []), - # Non-zero-sum 2-player games - (games.create_one_shot_trust_efg(), [[[[0, 1]], [[0, 1]]]]), - ( - games.create_EFG_for_nxn_bimatrix_coordination_game(3), - [ - [[[1, 0, 0]], [[1, 0, 0]]], - [[[0, 1, 0]], [[0, 1, 0]]], - [[[0, 0, 1]], [[0, 0, 1]]], - ], - ), - (games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq(), []), - # 3-player game - ( - games.read_from_file("mixed_behavior_game.efg"), - [ - [[[1, 0]], [[1, 0]], [[1, 0]]], - [[[1, 0]], [[0, 1]], [[0, 1]]], - [[[0, 1]], [[1, 0]], [[0, 1]]], - [[[0, 1]], [[0, 1]], [[1, 0]]], - ], - ), - ############################################################# - # Examples where the are agent-form pure equillibrium behaviors that are not Nash eq - ############################################################# - ( - games.read_from_file("myerson_fig_4_2.efg"), - [ - [[[1, 0], [0, 1]], [[0, 1]]], - [[[0, 1], [0, 1]], [[1, 0]]] - ] - ), - ] -) -def test_enumpure_agent(game: gbt.Game, pure_behav_prof_data: list): - """Test calls of enumeration of pure agent (behavior) equilibria - - Tests agent max regret being zero (internal consistency) and compares the computed - sequence of pure agent equilibria to a previosuly computed sequence (regression test) - - This should include all Nash equilibria in pure behaviors, but may include further - profiles that are not Nash equilibria - - """ - result = gbt.nash.enumpure_agent_solve(game) - assert len(result.equilibria) == len(pure_behav_prof_data) - for eq, exp in zip(result.equilibria, pure_behav_prof_data, strict=True): - assert eq.agent_max_regret() == 0 - expected = game.mixed_behavior_profile(rational=True, data=exp) - assert eq == expected - - -def test_enummixed_double(): - """Test calls of enumeration of mixed strategy equilibria for 2-player games, floating-point. - """ - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.enummixed_solve(game, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - def d(*probs) -> tuple: """Helper function to let us write d() to be suggestive of "probability distribution on simplex" ("Delta") @@ -170,7 +38,123 @@ class EquilibriumTestCase: prob_tol: float | gbt.Rational = Q(0) +NASH_ENUMPURE_CASES = [ + # Zero-sum games + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, + "two_player_perfect_info_win_lose.efg"), + solver=functools.partial(gbt.nash.enumpure_solve), + expected=[ + [d(0, 0, 1, 0), d(1, 0, 0)], + [d(0, 0, 1, 0), d(0, 1, 0)], + [d(0, 0, 1, 0), d(0, 0, 1)], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=games.create_stripped_down_poker_efg, + solver=functools.partial(gbt.nash.enumpure_solve), + expected=[], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test2_TODO", + ), + # Non-zero-sum 2-player games + pytest.param( + EquilibriumTestCase( + factory=games.create_one_shot_trust_efg, + solver=functools.partial(gbt.nash.enumpure_solve), + expected=[ + [d(0, 1), d(0, 1)] + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test3", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_one_shot_trust_efg, unique_NE_variant=True), + solver=functools.partial(gbt.nash.enumpure_solve), + expected=[ + [d(1, 0), d(0, 1)] + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test3b", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), + solver=functools.partial(gbt.nash.enumpure_solve), + expected=[ + [d(1, 0, 0), d(1, 0, 0)], + [d(0, 1, 0), d(0, 1, 0)], + [d(0, 0, 1), d(0, 0, 1)], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test4", + ), + pytest.param( + EquilibriumTestCase( + factory=games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq, + solver=functools.partial(gbt.nash.enumpure_solve), + expected=[], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test4", + ), + # 3-player game + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "mixed_behavior_game.efg"), + solver=functools.partial(gbt.nash.enumpure_solve), + expected=[ + [d(1, 0), d(1, 0), d(1, 0)], + [d(0, 1), d(0, 1), d(1, 0)], + [d(0, 1), d(1, 0), d(0, 1)], + [d(1, 0), d(0, 1), d(0, 1)], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test5", + ), + # 2x2x2 strategic form game based on local max cut -- 2 pure + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, + "2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + solver=functools.partial(gbt.nash.enumpure_solve), + expected=[ + [d(1, 0), d(0, 1), d(1, 0)], + [d(0, 1), d(1, 0), d(0, 1)], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test6", + ), +] + + NASH_ENUMMIXED_RATIONAL_CASES = [ + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, + "two_player_perfect_info_win_lose.efg"), + solver=functools.partial(gbt.nash.enummixed_solve, rational=True), + expected=[ + [d(0, 0, 1, 0), d(1, 0, 0)], + [d(0, 0, 1, 0), d(0, 1, 0)], + [d(0, 0, 1, 0), d(0, 0, 1)], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test1_TODO", + ), pytest.param( EquilibriumTestCase( factory=games.create_stripped_down_poker_efg, @@ -225,10 +209,22 @@ class EquilibriumTestCase: ) ] +# def test_enummixed_double(): + # """Test calls of enumeration of mixed strategy equilibria for 2-player games, floating-point. + # """ + # game = games.read_from_file("stripped_down_poker.efg") + # result = gbt.nash.enummixed_solve(game, rational=False) + # assert len(result.equilibria) == 1 + # # For floating-point results are not exact, so we skip testing exact values for now + + +CASES = [] +CASES += NASH_ENUMPURE_CASES +CASES += NASH_ENUMMIXED_RATIONAL_CASES @pytest.mark.nash @pytest.mark.parametrize( - "test_case", NASH_ENUMMIXED_RATIONAL_CASES, ids=lambda c: c.label + "test_case", CASES, ids=lambda c: c.label ) def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: """Test calls of Nash solvers. @@ -252,6 +248,143 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: assert abs(eq[strategy] - expected[strategy]) <= test_case.prob_tol +NASH_ENUMPURE_AGENT_CASES = [ + # ############################################################# + # Examples where Nash pure behaviors and agent-form pure equillibrium behaviors coincide + # ############################################################# + # Zero-sum games + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, + "two_player_perfect_info_win_lose.efg"), + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[ + [[d(1, 0), d(1, 0)], [d(0, 1), d(1, 0)]], + [[d(0, 1), d(1, 0)], [d(1, 0), d(1, 0)]], + [[d(0, 1), d(1, 0)], [d(1, 0), d(0, 1)]], + [[d(0, 1), d(1, 0)], [d(0, 1), d(1, 0)]], + [[d(0, 1), d(1, 0)], [d(0, 1), d(0, 1)]] + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=games.create_stripped_down_poker_efg, + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test2_TODO", + ), + # Non-zero-sum 2-player games + pytest.param( + EquilibriumTestCase( + factory=games.create_one_shot_trust_efg, + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[ + [[d(0, 1)], [d(0, 1)]], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test3_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_one_shot_trust_efg, unique_NE_variant=True), + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[ + [[d(1, 0)], [d(0, 1)]], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test3b", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[ + [[d(1, 0, 0)], [d(1, 0, 0)]], + [[d(0, 1, 0)], [d(0, 1, 0)]], + [[d(0, 0, 1)], [d(0, 0, 1)]], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test4", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq), + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test4", + ), + # 3-player games + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "mixed_behavior_game.efg"), + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[ + [[d(1, 0)], [d(1, 0)], [d(1, 0)]], + [[d(1, 0)], [d(0, 1)], [d(0, 1)]], + [[d(0, 1)], [d(1, 0)], [d(0, 1)]], + [[d(0, 1)], [d(0, 1)], [d(1, 0)]], + ] + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test5", + ), + ############################################################# + # Examples where the are agent-form pure equillibrium behaviors that are not Nash eq + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "myerson_fig_4_2.efg"), + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[ + [[d(1, 0), d(0, 1)], [d(0, 1)]], + [[d(0, 1), d(0, 1)], [d(1, 0)]] + ] + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test6", + ), +] + + +@pytest.mark.nash +@pytest.mark.parametrize( + "test_case", NASH_ENUMPURE_AGENT_CASES, ids=lambda c: c.label +) +def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: + """Test calls of Nash solvers in EFGs using "agent" versions. + + Subtests: + - Agent max regret no more than `test_case.regret_tol` + - Agent max regret no more than max regret (+ `test_case.regret_tol`) + - Equilibria are output in the expected order. Equilibria are deemed to match if the maximum + difference in probabilities is no more than `test_case.prob_tol` + """ + game = test_case.factory() + result = test_case.solver(game) + with subtests.test("number of equilibria found"): + assert len(result.equilibria) == len(test_case.expected) + for (i, (eq, exp)) in enumerate(zip(result.equilibria, test_case.expected, strict=True)): + with subtests.test(eq=i, check="agent_max_regret"): + assert eq.agent_max_regret() <= test_case.regret_tol + with subtests.test(eq=i, check="max_regret"): + assert eq.agent_max_regret() <= eq.max_regret() + test_case.regret_tol + with subtests.test(eq=i, check="strategy_profile"): + expected = game.mixed_behavior_profile(rational=True, data=exp) + for player in game.players: + for action in player.actions: + assert abs(eq[action] - expected[action]) <= test_case.prob_tol + + @pytest.mark.nash @pytest.mark.nash_enumpoly_behavior @pytest.mark.parametrize( From 0ce85381b322861fa3a4aaa839b90a74e950152d Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 28 Jan 2026 11:57:19 +0000 Subject: [PATCH 28/44] enumpoly tests using test_nash_{strategy,agent}_solver respectively --- tests/test_nash.py | 694 ++++++++++++++++++++++++++++----------------- 1 file changed, 438 insertions(+), 256 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index b66317ddc..726dde926 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -31,6 +31,7 @@ def d(*probs) -> tuple: @dataclasses.dataclass class EquilibriumTestCase: """Summarising the data relevant for a test fixture of a call to an equilibrium solver.""" + factory: typing.Callable[[], gbt.Game] solver: typing.Callable[[gbt.Game], gbt.nash.NashComputationResult] expected: list @@ -38,12 +39,13 @@ class EquilibriumTestCase: prob_tol: float | gbt.Rational = Q(0) -NASH_ENUMPURE_CASES = [ +ENUMPURE_CASES = [ # Zero-sum games pytest.param( EquilibriumTestCase( - factory=functools.partial(games.read_from_file, - "two_player_perfect_info_win_lose.efg"), + factory=functools.partial( + games.read_from_file, "two_player_perfect_info_win_lose.efg" + ), solver=functools.partial(gbt.nash.enumpure_solve), expected=[ [d(0, 0, 1, 0), d(1, 0, 0)], @@ -68,9 +70,7 @@ class EquilibriumTestCase: EquilibriumTestCase( factory=games.create_one_shot_trust_efg, solver=functools.partial(gbt.nash.enumpure_solve), - expected=[ - [d(0, 1), d(0, 1)] - ], + expected=[[d(0, 1), d(0, 1)]], ), marks=pytest.mark.nash_enumpure_strategy, id="test3", @@ -79,9 +79,7 @@ class EquilibriumTestCase: EquilibriumTestCase( factory=functools.partial(games.create_one_shot_trust_efg, unique_NE_variant=True), solver=functools.partial(gbt.nash.enumpure_solve), - expected=[ - [d(1, 0), d(0, 1)] - ], + expected=[[d(1, 0), d(0, 1)]], ), marks=pytest.mark.nash_enumpure_strategy, id="test3b", @@ -123,11 +121,12 @@ class EquilibriumTestCase: marks=pytest.mark.nash_enumpure_strategy, id="test5", ), - # 2x2x2 strategic form game based on local max cut -- 2 pure + # 2x2x2 strategic form game based on local max cut -- 2 pure pytest.param( EquilibriumTestCase( - factory=functools.partial(games.read_from_file, - "2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"), + factory=functools.partial( + games.read_from_file, "2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg" + ), solver=functools.partial(gbt.nash.enumpure_solve), expected=[ [d(1, 0), d(0, 1), d(1, 0)], @@ -140,11 +139,12 @@ class EquilibriumTestCase: ] -NASH_ENUMMIXED_RATIONAL_CASES = [ +ENUMMIXED_RATIONAL_CASES = [ pytest.param( EquilibriumTestCase( - factory=functools.partial(games.read_from_file, - "two_player_perfect_info_win_lose.efg"), + factory=functools.partial( + games.read_from_file, "two_player_perfect_info_win_lose.efg" + ), solver=functools.partial(gbt.nash.enummixed_solve, rational=True), expected=[ [d(0, 0, 1, 0), d(1, 0, 0)], @@ -190,7 +190,7 @@ class EquilibriumTestCase: [d(0, 1, 0), d(0, 1, 0)], [d(0, Q("1/2"), Q("1/2")), d(0, Q("1/2"), Q("1/2"))], [d(0, 0, 1), d(0, 0, 1)], - ] + ], ), marks=pytest.mark.nash_enummixed_strategy, id="test3", @@ -200,32 +200,33 @@ class EquilibriumTestCase: factory=games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq, solver=functools.partial(gbt.nash.enummixed_solve, rational=True), expected=[ - [d(Q("1/30"), Q("1/6"), Q("3/10"), Q("3/10"), Q("1/6"), Q("1/30")), - d(Q("1/6"), Q("1/30"), Q("3/10"), Q("3/10"), Q("1/30"), Q("1/6"))], - ] + [ + d(Q("1/30"), Q("1/6"), Q("3/10"), Q("3/10"), Q("1/6"), Q("1/30")), + d(Q("1/6"), Q("1/30"), Q("3/10"), Q("3/10"), Q("1/30"), Q("1/6")), + ], + ], ), marks=pytest.mark.nash_enummixed_strategy, id="test4", - ) + ), ] # def test_enummixed_double(): - # """Test calls of enumeration of mixed strategy equilibria for 2-player games, floating-point. - # """ - # game = games.read_from_file("stripped_down_poker.efg") - # result = gbt.nash.enummixed_solve(game, rational=False) - # assert len(result.equilibria) == 1 - # # For floating-point results are not exact, so we skip testing exact values for now +# """Test calls of enumeration of mixed strategy equilibria for 2-player games, floating-point. +# """ +# game = games.read_from_file("stripped_down_poker.efg") +# result = gbt.nash.enummixed_solve(game, rational=False) +# assert len(result.equilibria) == 1 +# # For floating-point results are not exact, so we skip testing exact values for now CASES = [] -CASES += NASH_ENUMPURE_CASES -CASES += NASH_ENUMMIXED_RATIONAL_CASES +CASES += ENUMPURE_CASES +CASES += ENUMMIXED_RATIONAL_CASES + @pytest.mark.nash -@pytest.mark.parametrize( - "test_case", CASES, ids=lambda c: c.label -) +@pytest.mark.parametrize("test_case", CASES, ids=lambda c: c.label) def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: """Test calls of Nash solvers. @@ -238,7 +239,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: result = test_case.solver(game) with subtests.test("number of equilibria found"): assert len(result.equilibria) == len(test_case.expected) - for (i, (eq, exp)) in enumerate(zip(result.equilibria, test_case.expected, strict=True)): + for i, (eq, exp) in enumerate(zip(result.equilibria, test_case.expected, strict=True)): with subtests.test(eq=i, check="max_regret"): assert eq.max_regret() <= test_case.regret_tol with subtests.test(eq=i, check="strategy_profile"): @@ -248,22 +249,23 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: assert abs(eq[strategy] - expected[strategy]) <= test_case.prob_tol -NASH_ENUMPURE_AGENT_CASES = [ +ENUMPURE_AGENT_CASES = [ # ############################################################# # Examples where Nash pure behaviors and agent-form pure equillibrium behaviors coincide # ############################################################# # Zero-sum games pytest.param( EquilibriumTestCase( - factory=functools.partial(games.read_from_file, - "two_player_perfect_info_win_lose.efg"), + factory=functools.partial( + games.read_from_file, "two_player_perfect_info_win_lose.efg" + ), solver=functools.partial(gbt.nash.enumpure_agent_solve), expected=[ [[d(1, 0), d(1, 0)], [d(0, 1), d(1, 0)]], [[d(0, 1), d(1, 0)], [d(1, 0), d(1, 0)]], [[d(0, 1), d(1, 0)], [d(1, 0), d(0, 1)]], [[d(0, 1), d(1, 0)], [d(0, 1), d(1, 0)]], - [[d(0, 1), d(1, 0)], [d(0, 1), d(0, 1)]] + [[d(0, 1), d(1, 0)], [d(0, 1), d(0, 1)]], ], ), marks=pytest.mark.nash_enumpure_strategy, @@ -271,7 +273,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: ), pytest.param( EquilibriumTestCase( - factory=games.create_stripped_down_poker_efg, + factory=games.create_stripped_down_poker_efg, solver=functools.partial(gbt.nash.enumpure_agent_solve), expected=[], ), @@ -281,7 +283,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: # Non-zero-sum 2-player games pytest.param( EquilibriumTestCase( - factory=games.create_one_shot_trust_efg, + factory=games.create_one_shot_trust_efg, solver=functools.partial(gbt.nash.enumpure_agent_solve), expected=[ [[d(0, 1)], [d(0, 1)]], @@ -300,7 +302,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: ), marks=pytest.mark.nash_enumpure_strategy, id="test3b", - ), + ), pytest.param( EquilibriumTestCase( factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), @@ -313,17 +315,18 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: ), marks=pytest.mark.nash_enumpure_strategy, id="test4", - ), + ), pytest.param( EquilibriumTestCase( factory=functools.partial( - games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq), + games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq + ), solver=functools.partial(gbt.nash.enumpure_agent_solve), expected=[], ), marks=pytest.mark.nash_enumpure_strategy, id="test4", - ), + ), # 3-player games pytest.param( EquilibriumTestCase( @@ -334,57 +337,25 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: [[d(1, 0)], [d(0, 1)], [d(0, 1)]], [[d(0, 1)], [d(1, 0)], [d(0, 1)]], [[d(0, 1)], [d(0, 1)], [d(1, 0)]], - ] + ], ), marks=pytest.mark.nash_enumpure_strategy, id="test5", - ), + ), ############################################################# # Examples where the are agent-form pure equillibrium behaviors that are not Nash eq pytest.param( EquilibriumTestCase( factory=functools.partial(games.read_from_file, "myerson_fig_4_2.efg"), solver=functools.partial(gbt.nash.enumpure_agent_solve), - expected=[ - [[d(1, 0), d(0, 1)], [d(0, 1)]], - [[d(0, 1), d(0, 1)], [d(1, 0)]] - ] + expected=[[[d(1, 0), d(0, 1)], [d(0, 1)]], [[d(0, 1), d(0, 1)], [d(1, 0)]]], ), marks=pytest.mark.nash_enumpure_strategy, id="test6", - ), + ), ] -@pytest.mark.nash -@pytest.mark.parametrize( - "test_case", NASH_ENUMPURE_AGENT_CASES, ids=lambda c: c.label -) -def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: - """Test calls of Nash solvers in EFGs using "agent" versions. - - Subtests: - - Agent max regret no more than `test_case.regret_tol` - - Agent max regret no more than max regret (+ `test_case.regret_tol`) - - Equilibria are output in the expected order. Equilibria are deemed to match if the maximum - difference in probabilities is no more than `test_case.prob_tol` - """ - game = test_case.factory() - result = test_case.solver(game) - with subtests.test("number of equilibria found"): - assert len(result.equilibria) == len(test_case.expected) - for (i, (eq, exp)) in enumerate(zip(result.equilibria, test_case.expected, strict=True)): - with subtests.test(eq=i, check="agent_max_regret"): - assert eq.agent_max_regret() <= test_case.regret_tol - with subtests.test(eq=i, check="max_regret"): - assert eq.agent_max_regret() <= eq.max_regret() + test_case.regret_tol - with subtests.test(eq=i, check="strategy_profile"): - expected = game.mixed_behavior_profile(rational=True, data=exp) - for player in game.players: - for action in player.actions: - assert abs(eq[action] - expected[action]) <= test_case.prob_tol - - @pytest.mark.nash @pytest.mark.nash_enumpoly_behavior @pytest.mark.parametrize( @@ -392,9 +363,9 @@ def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: [ # 2-player zero-sum games ( - games.create_stripped_down_poker_efg(), - [[[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]]], - None, + games.create_stripped_down_poker_efg(), + [[[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]]], + None, ), # 2-player non-zero-sum games pytest.param( @@ -403,31 +374,31 @@ def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: # second entry assumes we extend to Nash using only pure behaviors # currently we get [[0, 1]], [[0, 0]]] as a second eq None, - marks=pytest.mark.xfail(reason="Problem with enumpoly, as per issue #660") + marks=pytest.mark.xfail(reason="Problem with enumpoly, as per issue #660"), ), pytest.param( games.create_one_shot_trust_efg(unique_NE_variant=True), [[[[1, 0]], [[0, 1]]]], # currently we get [[0, 1]], [[0, 0]]] as a second eq None, - marks=pytest.mark.xfail(reason="Problem with enumpoly, as per issue #660") + marks=pytest.mark.xfail(reason="Problem with enumpoly, as per issue #660"), ), ( - games.create_EFG_for_nxn_bimatrix_coordination_game(3), - [ - [[["1/3", "1/3", "1/3"]], [["1/3", "1/3", "1/3"]]], - [[["1/2", "1/2", 0]], [["1/2", "1/2", 0]]], - [[["1/2", 0, "1/2"]], [["1/2", 0, "1/2"]]], - [[[1, 0, 0]], [[1, 0, 0]]], - [[[0, "1/2", "1/2"]], [[0, "1/2", "1/2"]]], - [[[0, 1, 0]], [[0, 1, 0]]], - [[[0, 0, 1]], [[0, 0, 1]]], - ], - None, + games.create_EFG_for_nxn_bimatrix_coordination_game(3), + [ + [[["1/3", "1/3", "1/3"]], [["1/3", "1/3", "1/3"]]], + [[["1/2", "1/2", 0]], [["1/2", "1/2", 0]]], + [[["1/2", 0, "1/2"]], [["1/2", 0, "1/2"]]], + [[[1, 0, 0]], [[1, 0, 0]]], + [[[0, "1/2", "1/2"]], [[0, "1/2", "1/2"]]], + [[[0, 1, 0]], [[0, 1, 0]]], + [[[0, 0, 1]], [[0, 0, 1]]], + ], + None, ), ( - games.create_EFG_for_nxn_bimatrix_coordination_game(4), - [[[["1/4", "1/4", "1/4", "1/4"]], [["1/4", "1/4", "1/4", "1/4"]]]], - 1, + games.create_EFG_for_nxn_bimatrix_coordination_game(4), + [[[["1/4", "1/4", "1/4", "1/4"]], [["1/4", "1/4", "1/4", "1/4"]]]], + 1, ), # 3-player game # ( @@ -444,16 +415,16 @@ def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: games.read_from_file("3_player.efg"), [ [[[1, 0], [1, 0]], [[1, 0], ["1/2", "1/2"]], [[1, 0], [0, 1]]], - [[[1, 0], [1, 0]], [[1, 0], [0, 1]], - [[1, 0], ["1/3", "2/3"]]]], + [[[1, 0], [1, 0]], [[1, 0], [0, 1]], [[1, 0], ["1/3", "2/3"]]], + ], 2, ), ( games.read_from_file("3_player_with_nonterm_outcomes.efg"), [ [[[1, 0], [1, 0]], [[1, 0], ["1/2", "1/2"]], [[1, 0], [0, 1]]], - [[[1, 0], [1, 0]], [[1, 0], [0, 1]], - [[1, 0], ["1/3", "2/3"]]]], + [[[1, 0], [1, 0]], [[1, 0], [0, 1]], [[1, 0], ["1/3", "2/3"]]], + ], 2, ), ############################################################################## @@ -471,23 +442,25 @@ def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: ############################################################################## ############################################################################## ( - games.read_from_file("chance_in_middle.efg"), - [[[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], - ], # [[[1, 0], [1, 0], [1, 0], [0, 0], [0, 0]], [[0, 1], [1, 0]]], - # [[[0, 1], [0, 0], [0, 0], [1, 0], [1, 0]], [[1, 0], [0, 1]]], - 1, # subsequent eqs have undefined infosets; include after #issue 660 + games.read_from_file("chance_in_middle.efg"), + [ + [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], + ], # [[[1, 0], [1, 0], [1, 0], [0, 0], [0, 0]], [[0, 1], [1, 0]]], + # [[[0, 1], [0, 0], [0, 0], [1, 0], [1, 0]], [[1, 0], [0, 1]]], + 1, # subsequent eqs have undefined infosets; include after #issue 660 ), ( - games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg"), - [[[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], - ], # [[[1, 0], [1, 0], [1, 0], [0, 0], [0, 0]], [[0, 1], [1, 0]]], - # [[[0, 1], [0, 0], [0, 0], [1, 0], [1, 0]], [[1, 0], [0, 1]]], - 1, + games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg"), + [ + [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], + ], # [[[1, 0], [1, 0], [1, 0], [0, 0], [0, 0]], [[0, 1], [1, 0]]], + # [[[0, 1], [0, 0], [0, 0], [1, 0], [1, 0]], [[1, 0], [0, 1]]], + 1, ), ], ) def test_enumpoly_ordered_behavior( - game: gbt.Game, mixed_behav_prof_data: list, stop_after: None | int + game: gbt.Game, mixed_behav_prof_data: list, stop_after: None | int ): """Test calls of enumpoly for mixed behavior equilibria, using max_regret and agent_max_regret (internal consistency); and @@ -495,11 +468,6 @@ def test_enumpoly_ordered_behavior( This set will be the full set of all computed equilibria if stop_after is None, else the first stop_after-many equilibria. - This is the "ordered" version where we test for the outputs coming in a specific - order; there is also an "unordered" version. The game 2x2x2.nfg, for example, - has a point at which the Jacobian is singular. As a result, the order in which it - returns the two totally-mixed equilbria is system-dependent due, essentially, - to inherent numerical instability near that point. """ if stop_after: result = gbt.nash.enumpoly_solve( @@ -520,6 +488,234 @@ def test_enumpoly_ordered_behavior( assert abs(eq[p][i][a] - expected[p][i][a]) <= TOL +ENUMPOLY_AGENT_CASES = [ + # ############################################################# + # Examples where Nash pure behaviors and agent-form pure equillibrium behaviors coincide + # ############################################################# + # 2-player zero-sum games + pytest.param( + EquilibriumTestCase( + factory=games.create_stripped_down_poker_efg, + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), + expected=[ + [[d(1, 0), d("1/3", "2/3")], [d("2/3", "1/3")]], + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_enumpoly_behavior, + id="test1_TODO", + ), + # 2-player non-zero-sum games + pytest.param( + EquilibriumTestCase( + factory=games.create_one_shot_trust_efg, + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), + expected=[ + [[d(0, 1)], [d("1/2", "1/2")]], + [[d(0, 1)], [d(0, 1)]], + # second entry assumes we extend to Nash using only pure behaviors + # currently we get [[0, 1]], [[0, 0]]] as a second eq + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=[ + pytest.mark.nash_enumpoly_behavior, + pytest.mark.xfail(reason="Problem with enumpoly, as per issue #660"), + ], + id="test2_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_one_shot_trust_efg, unique_NE_variant=True), + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), + expected=[ + [[[d(1, 0)], [d(0, 1)]]], + # currently we get [d(0, 1)], [d(0, 0)]] as a second eq + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=[ + pytest.mark.nash_enumpoly_behavior, + pytest.mark.xfail(reason="Problem with enumpoly, as per issue #660"), + ], + id="test3_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2_player_non_zero_sum.efg"), + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=1), + expected=[ + [[d("1/3", "2/3")], [d("1/2", "1/2")]], + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_enumpoly_behavior, + id="test8_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "2_player_non_zero_sum_missing_term_outcome.efg" + ), + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=1), + expected=[ + [[d("1/3", "2/3")], [d("1/2", "1/2")]], + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_enumpoly_behavior, + id="test9_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "chance_in_middle.efg"), + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=1), + expected=[ + [ + [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")], + ], + # [[[1, 0], [1, 0], [1, 0], [0, 0], [0, 0]], [[0, 1], [1, 0]]], + # [[[0, 1], [0, 0], [0, 0], [1, 0], [1, 0]], [[1, 0], [0, 1]]], + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_enumpoly_behavior, + id="test9_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "chance_in_middle_with_nonterm_outcomes.efg" + ), + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=1), + expected=[ + [ + [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")], + ], + # [[[1, 0], [1, 0], [1, 0], [0, 0], [0, 0]], [[0, 1], [1, 0]]], + # [[[0, 1], [0, 0], [0, 0], [1, 0], [1, 0]], [[1, 0], [0, 1]]], + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_enumpoly_behavior, + id="test9_TODO", + ), + # 3-player games + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), + expected=[ + [[d("1/3", "1/3", "1/3")], [d("1/3", "1/3", "1/3")]], + [[d("1/2", "1/2", 0)], [d("1/2", "1/2", 0)]], + [[d("1/2", 0, "1/2")], [d("1/2", 0, "1/2")]], + [[d(1, 0, 0)], [d(1, 0, 0)]], + [[d(0, "1/2", "1/2")], [d(0, "1/2", "1/2")]], + [[d(0, 1, 0)], [d(0, 1, 0)]], + [[d(0, 0, 1)], [d(0, 0, 1)]], + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_enumpoly_behavior, + id="test4_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "3_player.efg"), + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=2), + expected=[ + [[d(1, 0), d(1, 0)], [d(1, 0), d("1/2", "1/2")], [d(1, 0), d(0, 1)]], + [[d(1, 0), d(1, 0)], [d(1, 0), d(0, 1)], [d(1, 0), d("1/3", "2/3")]], + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_enumpoly_behavior, + id="test6_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "3_player_with_nonterm_outcomes.efg"), + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=2), + expected=[ + [[d(1, 0), d(1, 0)], [d(1, 0), d("1/2", "1/2")], [d(1, 0), d(0, 1)]], + [[d(1, 0), d(1, 0)], [d(1, 0), d(0, 1)], [d(1, 0), d("1/3", "2/3")]], + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_enumpoly_behavior, + id="test7_TODO", + ), + # 4-player game + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=4), + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=1), + expected=[ + [[d("1/4", "1/4", "1/4", "1/4")], [d("1/4", "1/4", "1/4", "1/4")]], + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_enumpoly_behavior, + id="test5_TODO", + ), +] +# ############################################################################## +# 3-player game +# ( +# games.read_from_file("mixed_behavior_game.efg"), +# [ +# [[["1/2", "1/2"]], [["2/5", "3/5"]], [["1/4", "3/4"]]], +# [[["2/5", "3/5"]], [["1/2", "1/2"]], [["1/3", "2/3"]]], +# ], +# 2, # 9 in total found by enumpoly (see unordered test) +# ), +# ############################################################################## + +AGENT_CASES = [] +AGENT_CASES += ENUMPURE_AGENT_CASES +AGENT_CASES += ENUMPOLY_AGENT_CASES + + +@pytest.mark.nash +@pytest.mark.parametrize("test_case", AGENT_CASES, ids=lambda c: c.label) +def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: + """Test calls of Nash solvers in EFGs using "agent" versions. + + Subtests: + - Agent max regret no more than `test_case.regret_tol` + - Agent max regret no more than max regret (+ `test_case.regret_tol`) + - Equilibria are output in the expected order. Equilibria are deemed to match if the maximum + difference in probabilities is no more than `test_case.prob_tol` + """ + game = test_case.factory() + result = test_case.solver(game) + with subtests.test("number of equilibria found"): + assert len(result.equilibria) == len(test_case.expected) + for i, (eq, exp) in enumerate(zip(result.equilibria, test_case.expected, strict=True)): + with subtests.test(eq=i, check="agent_max_regret"): + assert eq.agent_max_regret() <= test_case.regret_tol + with subtests.test(eq=i, check="max_regret"): + assert eq.agent_max_regret() <= eq.max_regret() + test_case.regret_tol + with subtests.test(eq=i, check="strategy_profile"): + expected = game.mixed_behavior_profile(rational=True, data=exp) + for player in game.players: + for action in player.actions: + assert abs(eq[action] - expected[action]) <= test_case.prob_tol + + @pytest.mark.nash @pytest.mark.nash_enumpoly_behavior @pytest.mark.parametrize( @@ -527,24 +723,29 @@ def test_enumpoly_ordered_behavior( [ # 3-player game ( - games.read_from_file("mixed_behavior_game.efg"), - [ - [[["2/5", "3/5"]], [["1/2", "1/2"]], [["1/3", "2/3"]]], - [[["1/2", "1/2"]], [["2/5", "3/5"]], [["1/4", "3/4"]]], - [[["1/2", "1/2"]], [["1/2", "1/2"]], [[1, 0]]], - [[["1/3", "2/3"]], [[1, 0]], [["1/4", "3/4"]]], - [[[1, 0]], [[1, 0]], [[1, 0]]], - [[[1, 0]], [[0, 1]], [[0, 1]]], - [[[0, 1]], [["1/4", "3/4"]], [["1/3", "2/3"]]], - [[[0, 1]], [[1, 0]], [[0, 1]]], - [[[0, 1]], [[0, 1]], [[1, 0]]], - ], - 9, + games.read_from_file("mixed_behavior_game.efg"), + [ + [[["2/5", "3/5"]], [["1/2", "1/2"]], [["1/3", "2/3"]]], + [[["1/2", "1/2"]], [["2/5", "3/5"]], [["1/4", "3/4"]]], + [[["1/2", "1/2"]], [["1/2", "1/2"]], [[1, 0]]], + [[["1/3", "2/3"]], [[1, 0]], [["1/4", "3/4"]]], + [[[1, 0]], [[1, 0]], [[1, 0]]], + [[[1, 0]], [[0, 1]], [[0, 1]]], + [[[0, 1]], [["1/4", "3/4"]], [["1/3", "2/3"]]], + [[[0, 1]], [[1, 0]], [[0, 1]]], + [[[0, 1]], [[0, 1]], [[1, 0]]], + ], + 9, ), ], ) +# This is the "ordered" version where we test for the outputs coming in a specific +# order; there is also an "unordered" version. The game 2x2x2.nfg, for example, +# has a point at which the Jacobian is singular. As a result, the order in which it +# returns the two totally-mixed equilbria is system-dependent due, essentially, +# to inherent numerical instability near that point. def test_enumpoly_unordered_behavior( - game: gbt.Game, mixed_behav_prof_data: list, stop_after: None | int + game: gbt.Game, mixed_behav_prof_data: list, stop_after: None | int ): """Test calls of enumpoly for mixed behavior equilibria, using max_regret and agent_max_regret (internal consistency); and @@ -605,60 +806,59 @@ def test_lcp_strategy_double(): "game,mixed_strategy_prof_data,stop_after", [ # Zero-sum games + (games.create_2x2_zero_sum_efg(), [[["1/2", "1/2"], ["1/2", "1/2"]]], None), ( - games.create_2x2_zero_sum_efg(), - [[["1/2", "1/2"], ["1/2", "1/2"]]], - None - ), - ( - games.create_2x2_zero_sum_efg(missing_term_outcome=True), - [[["1/2", "1/2"], ["1/2", "1/2"]]], - None + games.create_2x2_zero_sum_efg(missing_term_outcome=True), + [[["1/2", "1/2"], ["1/2", "1/2"]]], + None, ), (games.create_stripped_down_poker_efg(), [[["1/3", "2/3", 0, 0], ["2/3", "1/3"]]], None), ( - games.create_stripped_down_poker_efg(nonterm_outcomes=True), - [[["1/3", "2/3", 0, 0], ["2/3", "1/3"]]], - None + games.create_stripped_down_poker_efg(nonterm_outcomes=True), + [[["1/3", "2/3", 0, 0], ["2/3", "1/3"]]], + None, ), (games.create_kuhn_poker_efg(), [games.kuhn_poker_lcp_first_mixed_strategy_prof()], 1), ( - games.create_kuhn_poker_efg(nonterm_outcomes=True), - [games.kuhn_poker_lcp_first_mixed_strategy_prof()], - 1 + games.create_kuhn_poker_efg(nonterm_outcomes=True), + [games.kuhn_poker_lcp_first_mixed_strategy_prof()], + 1, ), # Non-zero-sum games (games.create_one_shot_trust_efg(), [[[0, 1], ["1/2", "1/2"]]], None), ( - games.create_EFG_for_nxn_bimatrix_coordination_game(3), - [ - [[1, 0, 0], [1, 0, 0]], - [["1/2", "1/2", 0], ["1/2", "1/2", 0]], - [[0, 1, 0], [0, 1, 0]], - [[0, "1/2", "1/2"], [0, "1/2", "1/2"]], - [["1/3", "1/3", "1/3"], ["1/3", "1/3", "1/3"]], - [["1/2", 0, "1/2"], ["1/2", 0, "1/2"]], - [[0, 0, 1], [0, 0, 1]], - ], - None, + games.create_EFG_for_nxn_bimatrix_coordination_game(3), + [ + [[1, 0, 0], [1, 0, 0]], + [["1/2", "1/2", 0], ["1/2", "1/2", 0]], + [[0, 1, 0], [0, 1, 0]], + [[0, "1/2", "1/2"], [0, "1/2", "1/2"]], + [["1/3", "1/3", "1/3"], ["1/3", "1/3", "1/3"]], + [["1/2", 0, "1/2"], ["1/2", 0, "1/2"]], + [[0, 0, 1], [0, 0, 1]], + ], + None, ), ( - games.create_EFG_for_nxn_bimatrix_coordination_game(4), - [[[1, 0, 0, 0], [1, 0, 0, 0]]], - 1, + games.create_EFG_for_nxn_bimatrix_coordination_game(4), + [[[1, 0, 0, 0], [1, 0, 0, 0]]], + 1, ), ( - games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq(), + games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq(), + [ [ - [["1/30", "1/6", "3/10", "3/10", "1/6", "1/30"], - ["1/6", "1/30", "3/10", "3/10", "1/30", "1/6"]], + ["1/30", "1/6", "3/10", "3/10", "1/6", "1/30"], + ["1/6", "1/30", "3/10", "3/10", "1/30", "1/6"], ], - None + ], + None, ), - ] + ], ) -def test_lcp_strategy_rational(game: gbt.Game, mixed_strategy_prof_data: list, - stop_after: None | int): +def test_lcp_strategy_rational( + game: gbt.Game, mixed_strategy_prof_data: list, stop_after: None | int +): """Test calls of LCP for mixed strategy equilibria, rational precision using max_regret (internal consistency); and comparison to a sequence of previously computed equilibria using this function (regression test). @@ -695,16 +895,12 @@ def test_lcp_behavior_double(): "game,mixed_behav_prof_data", [ # Zero-sum games (also tested with lp solve) - ( - games.create_2x2_zero_sum_efg(), - [[["1/2", "1/2"]], [["1/2", "1/2"]]] - ), + (games.create_2x2_zero_sum_efg(), [[["1/2", "1/2"]], [["1/2", "1/2"]]]), ( games.create_2x2_zero_sum_efg(missing_term_outcome=True), [[["1/2", "1/2"]], [["1/2", "1/2"]]], ), - (games.create_matching_pennies_efg(), - [[["1/2", "1/2"]], [["1/2", "1/2"]]]), + (games.create_matching_pennies_efg(), [[["1/2", "1/2"]], [["1/2", "1/2"]]]), ( games.create_matching_pennies_efg(with_neutral_outcome=True), [[["1/2", "1/2"]], [["1/2", "1/2"]]], @@ -715,18 +911,18 @@ def test_lcp_behavior_double(): [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]], ), ( - games.create_kuhn_poker_efg(), + games.create_kuhn_poker_efg(), + [ [ - [ - ["2/3", "1/3"], - [1, 0], - [1, 0], - ["1/3", "2/3"], - [0, 1], - ["1/2", "1/2"], - ], - [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], + ["2/3", "1/3"], + [1, 0], + [1, 0], + ["1/3", "2/3"], + [0, 1], + ["1/2", "1/2"], ], + [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], + ], ), ( games.create_kuhn_poker_efg(nonterm_outcomes=True), @@ -745,12 +941,12 @@ def test_lcp_behavior_double(): # In the next test case: # 1/2-1/2 for l/r is determined by MixedBehaviorProfile.UndefinedToCentroid() ( - games.read_from_file("perfect_info_with_chance.efg"), - [[[0, 1]], [[0, 1], [0, 1]]], + games.read_from_file("perfect_info_with_chance.efg"), + [[[0, 1]], [[0, 1], [0, 1]]], ), ( - games.read_from_file("two_player_perfect_info_win_lose.efg"), - [[[0, 1], [1, 0]], [[0, 1], ["1/2", "1/2"]]], + games.read_from_file("two_player_perfect_info_win_lose.efg"), + [[[0, 1], [1, 0]], [[0, 1], ["1/2", "1/2"]]], ), ( games.read_from_file("two_player_perfect_info_win_lose_with_nonterm_outcomes.efg"), @@ -761,7 +957,7 @@ def test_lcp_behavior_double(): [ [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], [["2/3", "1/3"], ["1/3", "2/3"], ["1/3", "2/3"]], - ] + ], ), ( games.read_from_file("2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg"), @@ -771,56 +967,46 @@ def test_lcp_behavior_double(): ], ), ( - games.read_from_file("large_payoff_game.efg"), - [ - [[1, 0], [1, 0]], - [[0, 1], ["9999999999999999999/10000000000000000000", - "1/10000000000000000000"]], - ], + games.read_from_file("large_payoff_game.efg"), + [ + [[1, 0], [1, 0]], + [[0, 1], ["9999999999999999999/10000000000000000000", "1/10000000000000000000"]], + ], ), ( games.read_from_file("chance_in_middle.efg"), - [ - [["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], - [[1, 0], ["6/11", "5/11"]] - ] + [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], ), ( games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg"), - [ - [["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], - [[1, 0], ["6/11", "5/11"]] - ], + [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], ), # Non-zero-sum games ( - games.read_from_file("reduction_both_players_payoff_ties_GTE_survey.efg"), - [[[0, 0, 1, 0], [1, 0]], [[0, 1], [0, 1], [0, 1], [0, 1]]], + games.read_from_file("reduction_both_players_payoff_ties_GTE_survey.efg"), + [[[0, 0, 1, 0], [1, 0]], [[0, 1], [0, 1], [0, 1], [0, 1]]], ), ( - games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq(), - [ - [["1/30", "1/6", "3/10", "3/10", "1/6", "1/30"]], - [["1/6", "1/30", "3/10", "3/10", "1/30", "1/6"]], - ], + games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq(), + [ + [["1/30", "1/6", "3/10", "3/10", "1/6", "1/30"]], + [["1/6", "1/30", "3/10", "3/10", "1/30", "1/6"]], + ], ), (games.create_EFG_for_nxn_bimatrix_coordination_game(3), [[[0, 0, 1]], [[0, 0, 1]]]), ( - games.create_EFG_for_nxn_bimatrix_coordination_game(4), - [[[0, 0, 0, 1]], [[0, 0, 0, 1]]], + games.create_EFG_for_nxn_bimatrix_coordination_game(4), + [[[0, 0, 0, 1]], [[0, 0, 0, 1]]], ), ( games.read_from_file("entry_accommodation.efg"), - [[["2/3", "1/3"], [1, 0], [1, 0]], [["2/3", "1/3"]]] + [[["2/3", "1/3"], [1, 0], [1, 0]], [["2/3", "1/3"]]], ), ( games.read_from_file("entry_accommodation_with_nonterm_outcomes.efg"), [[["2/3", "1/3"], [1, 0], [1, 0]], [["2/3", "1/3"]]], ), - ( - games.read_from_file("2_player_non_zero_sum.efg"), - [[["1/3", "2/3"]], [["1/2", "1/2"]]] - ), + (games.read_from_file("2_player_non_zero_sum.efg"), [[["1/3", "2/3"]], [["1/2", "1/2"]]]), ( games.read_from_file("2_player_non_zero_sum_missing_term_outcome.efg"), [[["1/3", "2/3"]], [["1/2", "1/2"]]], @@ -856,22 +1042,22 @@ def test_lp_strategy_double(): "game,mixed_strategy_prof_data", [ ( - games.create_2x2_zero_sum_efg(), - [["1/2", "1/2"], ["1/2", "1/2"]], + games.create_2x2_zero_sum_efg(), + [["1/2", "1/2"], ["1/2", "1/2"]], ), ( - games.create_2x2_zero_sum_efg(missing_term_outcome=True), - [["1/2", "1/2"], ["1/2", "1/2"]], + games.create_2x2_zero_sum_efg(missing_term_outcome=True), + [["1/2", "1/2"], ["1/2", "1/2"]], ), (games.create_stripped_down_poker_efg(), [["1/3", "2/3", 0, 0], ["2/3", "1/3"]]), ( - games.create_stripped_down_poker_efg(nonterm_outcomes=True), - [["1/3", "2/3", 0, 0], ["2/3", "1/3"]] + games.create_stripped_down_poker_efg(nonterm_outcomes=True), + [["1/3", "2/3", 0, 0], ["2/3", "1/3"]], ), (games.create_kuhn_poker_efg(), games.kuhn_poker_lp_mixed_strategy_prof()), ( - games.create_kuhn_poker_efg(nonterm_outcomes=True), - games.kuhn_poker_lp_mixed_strategy_prof() + games.create_kuhn_poker_efg(nonterm_outcomes=True), + games.kuhn_poker_lp_mixed_strategy_prof(), ), ], ) @@ -908,7 +1094,7 @@ def test_lp_behavior_double(): ), ( games.create_2x2_zero_sum_efg(missing_term_outcome=False), - [[["1/2", "1/2"]], [["1/2", "1/2"]]] + [[["1/2", "1/2"]], [["1/2", "1/2"]]], ), ( games.create_2x2_zero_sum_efg(missing_term_outcome=True), @@ -916,7 +1102,8 @@ def test_lp_behavior_double(): ), ( games.create_matching_pennies_efg(with_neutral_outcome=False), - [[["1/2", "1/2"]], [["1/2", "1/2"]]]), + [[["1/2", "1/2"]], [["1/2", "1/2"]]], + ), ( games.create_matching_pennies_efg(with_neutral_outcome=True), [[["1/2", "1/2"]], [["1/2", "1/2"]]], @@ -930,11 +1117,11 @@ def test_lp_behavior_double(): [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]], ), ( - games.create_kuhn_poker_efg(), - [ - [[1, 0], [1, 0], [1, 0], ["2/3", "1/3"], [1, 0], [0, 1]], - [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], - ], + games.create_kuhn_poker_efg(), + [ + [[1, 0], [1, 0], [1, 0], ["2/3", "1/3"], [1, 0], [0, 1]], + [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], + ], ), ( games.create_kuhn_poker_efg(nonterm_outcomes=True), @@ -951,22 +1138,22 @@ def test_lp_behavior_double(): ], ), ( - games.read_from_file("zerosum_efg_from_sequence_form_STOC94_paper.efg"), - [ - [[0, 1], ["2/3", "1/3"], ["1/3", "2/3"]], - [["5/6", "1/6"], ["5/9", "4/9"]], - ], + games.read_from_file("zerosum_efg_from_sequence_form_STOC94_paper.efg"), + [ + [[0, 1], ["2/3", "1/3"], ["1/3", "2/3"]], + [["5/6", "1/6"], ["5/9", "4/9"]], + ], ), ( - games.read_from_file("perfect_info_with_chance.efg"), - [[[0, 1]], [[1, 0], [1, 0]]], + games.read_from_file("perfect_info_with_chance.efg"), + [[[0, 1]], [[1, 0], [1, 0]]], ), ( games.read_from_file("2_player_chance.efg"), [ [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], [["2/3", "1/3"], ["2/3", "1/3"], ["1/3", "2/3"]], - ] + ], ), ( games.read_from_file("2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg"), @@ -976,26 +1163,19 @@ def test_lp_behavior_double(): ], ), ( - games.read_from_file("large_payoff_game.efg"), - [ - [[1, 0], [1, 0]], - [[0, 1], ["9999999999999999999/10000000000000000000", - "1/10000000000000000000"]], - ], + games.read_from_file("large_payoff_game.efg"), + [ + [[1, 0], [1, 0]], + [[0, 1], ["9999999999999999999/10000000000000000000", "1/10000000000000000000"]], + ], ), ( games.read_from_file("chance_in_middle.efg"), - [ - [["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], - [[1, 0], ["6/11", "5/11"]] - ], + [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], ), ( games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg"), - [ - [["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], - [[1, 0], ["6/11", "5/11"]] - ], + [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], ), ], ) @@ -1086,8 +1266,9 @@ def test_logit_solve_branch_error_with_invalid_max_accel(): def test_logit_solve_branch(): game = games.read_from_file("const_sum_game.nfg") - assert len(gbt.qre.logit_solve_branch( - game=game, maxregret=0.2, first_step=0.2, max_accel=1)) > 0 + assert ( + len(gbt.qre.logit_solve_branch(game=game, maxregret=0.2, first_step=0.2, max_accel=1)) > 0 + ) def test_logit_solve_lambda_error_with_invalid_first_step(): @@ -1108,5 +1289,6 @@ def test_logit_solve_lambda_error_with_invalid_max_accel(): def test_logit_solve_lambda(): game = games.read_from_file("const_sum_game.nfg") - assert len(gbt.qre.logit_solve_lambda( - game=game, lam=[1, 2, 3], first_step=0.2, max_accel=1)) > 0 + assert ( + len(gbt.qre.logit_solve_lambda(game=game, lam=[1, 2, 3], first_step=0.2, max_accel=1)) > 0 + ) From 6d07a37653823c3b4341d8c3adda13ecb08dae81 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Thu, 29 Jan 2026 11:19:56 +0000 Subject: [PATCH 29/44] xfail tests for issue 660 now separates from passing tests --- tests/test_nash.py | 97 ++++++++++++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 726dde926..50706c075 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -506,43 +506,6 @@ def test_enumpoly_ordered_behavior( marks=pytest.mark.nash_enumpoly_behavior, id="test1_TODO", ), - # 2-player non-zero-sum games - pytest.param( - EquilibriumTestCase( - factory=games.create_one_shot_trust_efg, - solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), - expected=[ - [[d(0, 1)], [d("1/2", "1/2")]], - [[d(0, 1)], [d(0, 1)]], - # second entry assumes we extend to Nash using only pure behaviors - # currently we get [[0, 1]], [[0, 0]]] as a second eq - ], - regret_tol=TOL, - prob_tol=TOL, - ), - marks=[ - pytest.mark.nash_enumpoly_behavior, - pytest.mark.xfail(reason="Problem with enumpoly, as per issue #660"), - ], - id="test2_TODO", - ), - pytest.param( - EquilibriumTestCase( - factory=functools.partial(games.create_one_shot_trust_efg, unique_NE_variant=True), - solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), - expected=[ - [[[d(1, 0)], [d(0, 1)]]], - # currently we get [d(0, 1)], [d(0, 0)]] as a second eq - ], - regret_tol=TOL, - prob_tol=TOL, - ), - marks=[ - pytest.mark.nash_enumpoly_behavior, - pytest.mark.xfail(reason="Problem with enumpoly, as per issue #660"), - ], - id="test3_TODO", - ), pytest.param( EquilibriumTestCase( factory=functools.partial(games.read_from_file, "2_player_non_zero_sum.efg"), @@ -716,6 +679,66 @@ def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: assert abs(eq[action] - expected[action]) <= test_case.prob_tol +ENUMPOLY_ISSUE_660_CASES = [ + # 2-player non-zero-sum games + pytest.param( + EquilibriumTestCase( + factory=games.create_one_shot_trust_efg, + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), + expected=[ + [[d(0, 1)], [d("1/2", "1/2")]], + [[d(0, 1)], [d(0, 1)]], + # second entry assumes we extend to Nash using only pure behaviors + # currently we get [[0, 1]], [[0, 0]]] as a second eq + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=[ + pytest.mark.nash_enumpoly_behavior, + pytest.mark.xfail(reason="Problem with enumpoly, as per issue #660"), + ], + id="enumpoly_one_shot_trust_issue_660", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_one_shot_trust_efg, unique_NE_variant=True), + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), + expected=[ + [[[d(1, 0)], [d(0, 1)]]], + # currently we get [d(0, 1)], [d(0, 0)]] as a second eq + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=[ + pytest.mark.nash_enumpoly_behavior, + pytest.mark.xfail(reason="Problem with enumpoly, as per issue #660"), + ], + id="enumpoly_one_shot_trust_unique_NE_issue_660", + ), +] + + +@pytest.mark.nash +@pytest.mark.parametrize("test_case", ENUMPOLY_ISSUE_660_CASES, ids=lambda c: c.label) +def test_nash_agent_solver_no_subtests_only_profile(test_case: EquilibriumTestCase) -> None: + """Test calls of Nash solvers in EFGs using "agent" versions. + + Checks for expected number of equilibria, and that the equilibria are output + in the expected order. Equilibria are deemed to match if the maximum + difference in probabilities is no more than `test_case.prob_tol` + """ + game = test_case.factory() + result = test_case.solver(game) + assert len(result.equilibria) == len(test_case.expected) + for eq, exp in zip(result.equilibria, test_case.expected, strict=True): + expected = game.mixed_behavior_profile(rational=True, data=exp) + for player in game.players: + for action in player.actions: + assert abs(eq[action] - expected[action]) <= test_case.prob_tol + + @pytest.mark.nash @pytest.mark.nash_enumpoly_behavior @pytest.mark.parametrize( From 4e20c6b98c3f55b3dbe6e3cbe5869ddc0793aff7 Mon Sep 17 00:00:00 2001 From: rahulsavani Date: Thu, 5 Feb 2026 10:16:08 +0000 Subject: [PATCH 30/44] test_enumpoly_ordered_behavior removed --- tests/test_nash.py | 132 --------------------------------------------- 1 file changed, 132 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 50706c075..1f6ffc736 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -356,138 +356,6 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: ] -@pytest.mark.nash -@pytest.mark.nash_enumpoly_behavior -@pytest.mark.parametrize( - "game,mixed_behav_prof_data,stop_after", - [ - # 2-player zero-sum games - ( - games.create_stripped_down_poker_efg(), - [[[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]]], - None, - ), - # 2-player non-zero-sum games - pytest.param( - games.create_one_shot_trust_efg(), - [[[[0, 1]], [["1/2", "1/2"]]], [[[0, 1]], [[0, 1]]]], - # second entry assumes we extend to Nash using only pure behaviors - # currently we get [[0, 1]], [[0, 0]]] as a second eq - None, - marks=pytest.mark.xfail(reason="Problem with enumpoly, as per issue #660"), - ), - pytest.param( - games.create_one_shot_trust_efg(unique_NE_variant=True), - [[[[1, 0]], [[0, 1]]]], # currently we get [[0, 1]], [[0, 0]]] as a second eq - None, - marks=pytest.mark.xfail(reason="Problem with enumpoly, as per issue #660"), - ), - ( - games.create_EFG_for_nxn_bimatrix_coordination_game(3), - [ - [[["1/3", "1/3", "1/3"]], [["1/3", "1/3", "1/3"]]], - [[["1/2", "1/2", 0]], [["1/2", "1/2", 0]]], - [[["1/2", 0, "1/2"]], [["1/2", 0, "1/2"]]], - [[[1, 0, 0]], [[1, 0, 0]]], - [[[0, "1/2", "1/2"]], [[0, "1/2", "1/2"]]], - [[[0, 1, 0]], [[0, 1, 0]]], - [[[0, 0, 1]], [[0, 0, 1]]], - ], - None, - ), - ( - games.create_EFG_for_nxn_bimatrix_coordination_game(4), - [[[["1/4", "1/4", "1/4", "1/4"]], [["1/4", "1/4", "1/4", "1/4"]]]], - 1, - ), - # 3-player game - # ( - # games.read_from_file("mixed_behavior_game.efg"), - # [ - # [[["1/2", "1/2"]], [["2/5", "3/5"]], [["1/4", "3/4"]]], - # [[["2/5", "3/5"]], [["1/2", "1/2"]], [["1/3", "2/3"]]], - # ], - # 2, # 9 in total found by enumpoly (see unordered test) - # ), - ############################################################################## - ############################################################################## - ( - games.read_from_file("3_player.efg"), - [ - [[[1, 0], [1, 0]], [[1, 0], ["1/2", "1/2"]], [[1, 0], [0, 1]]], - [[[1, 0], [1, 0]], [[1, 0], [0, 1]], [[1, 0], ["1/3", "2/3"]]], - ], - 2, - ), - ( - games.read_from_file("3_player_with_nonterm_outcomes.efg"), - [ - [[[1, 0], [1, 0]], [[1, 0], ["1/2", "1/2"]], [[1, 0], [0, 1]]], - [[[1, 0], [1, 0]], [[1, 0], [0, 1]], [[1, 0], ["1/3", "2/3"]]], - ], - 2, - ), - ############################################################################## - ############################################################################## - ( - games.read_from_file("2_player_non_zero_sum.efg"), - [[[["1/3", "2/3"]], [["1/2", "1/2"]]]], - 1, - ), - ( - games.read_from_file("2_player_non_zero_sum_missing_term_outcome.efg"), - [[[["1/3", "2/3"]], [["1/2", "1/2"]]]], - 1, - ), - ############################################################################## - ############################################################################## - ( - games.read_from_file("chance_in_middle.efg"), - [ - [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], - ], # [[[1, 0], [1, 0], [1, 0], [0, 0], [0, 0]], [[0, 1], [1, 0]]], - # [[[0, 1], [0, 0], [0, 0], [1, 0], [1, 0]], [[1, 0], [0, 1]]], - 1, # subsequent eqs have undefined infosets; include after #issue 660 - ), - ( - games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg"), - [ - [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], - ], # [[[1, 0], [1, 0], [1, 0], [0, 0], [0, 0]], [[0, 1], [1, 0]]], - # [[[0, 1], [0, 0], [0, 0], [1, 0], [1, 0]], [[1, 0], [0, 1]]], - 1, - ), - ], -) -def test_enumpoly_ordered_behavior( - game: gbt.Game, mixed_behav_prof_data: list, stop_after: None | int -): - """Test calls of enumpoly for mixed behavior equilibria, - using max_regret and agent_max_regret (internal consistency); and - comparison to a set of previously computed equilibria with this function (regression test). - This set will be the full set of all computed equilibria if stop_after is None, - else the first stop_after-many equilibria. - - """ - if stop_after: - result = gbt.nash.enumpoly_solve( - game, use_strategic=False, stop_after=stop_after, maxregret=0.00001 - ) - assert len(result.equilibria) == stop_after - else: - # compute all - result = gbt.nash.enumpoly_solve(game, use_strategic=False) - assert len(result.equilibria) == len(mixed_behav_prof_data) - for eq, exp in zip(result.equilibria, mixed_behav_prof_data, strict=True): - assert abs(eq.max_regret()) <= TOL - assert abs(eq.agent_max_regret()) <= TOL - expected = game.mixed_behavior_profile(rational=True, data=exp) - for p in game.players: - for i in p.infosets: - for a in i.actions: - assert abs(eq[p][i][a] - expected[p][i][a]) <= TOL - - ENUMPOLY_AGENT_CASES = [ # ############################################################# # Examples where Nash pure behaviors and agent-form pure equillibrium behaviors coincide From 8b84d0f344228e8d5767d7b3b3e66e7992ea73fe Mon Sep 17 00:00:00 2001 From: rahulsavani Date: Thu, 5 Feb 2026 13:31:31 +0000 Subject: [PATCH 31/44] test_enumpoly_unordered_behavior removed --- tests/test_nash.py | 98 ++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 55 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 1f6ffc736..d750ae29b 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -607,15 +607,12 @@ def test_nash_agent_solver_no_subtests_only_profile(test_case: EquilibriumTestCa assert abs(eq[action] - expected[action]) <= test_case.prob_tol -@pytest.mark.nash -@pytest.mark.nash_enumpoly_behavior -@pytest.mark.parametrize( - "game,mixed_behav_prof_data,stop_after", - [ - # 3-player game - ( - games.read_from_file("mixed_behavior_game.efg"), - [ +ENUMPOLY_AGENT_UNORDERED_CASES = [ + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "mixed_behavior_game.efg"), + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=9), + expected=[ [[["2/5", "3/5"]], [["1/2", "1/2"]], [["1/3", "2/3"]]], [[["1/2", "1/2"]], [["2/5", "3/5"]], [["1/4", "3/4"]]], [[["1/2", "1/2"]], [["1/2", "1/2"]], [[1, 0]]], @@ -626,61 +623,52 @@ def test_nash_agent_solver_no_subtests_only_profile(test_case: EquilibriumTestCa [[[0, 1]], [[1, 0]], [[0, 1]]], [[[0, 1]], [[0, 1]], [[1, 0]]], ], - 9, + regret_tol=TOL, + prob_tol=TOL, ), - ], -) -# This is the "ordered" version where we test for the outputs coming in a specific -# order; there is also an "unordered" version. The game 2x2x2.nfg, for example, -# has a point at which the Jacobian is singular. As a result, the order in which it -# returns the two totally-mixed equilbria is system-dependent due, essentially, -# to inherent numerical instability near that point. -def test_enumpoly_unordered_behavior( - game: gbt.Game, mixed_behav_prof_data: list, stop_after: None | int -): - """Test calls of enumpoly for mixed behavior equilibria, - using max_regret and agent_max_regret (internal consistency); and - comparison to a set of previously computed equilibria using this function (regression test). + marks=pytest.mark.nash_enumpoly_behavior, + id="test5_TODO", + ), +] - This set will be the full set of all computed equilibria if stop_after is None, - else the first stop_after-many equilibria. - This is the "unordered" version where we test for the outputs belong to a set - of expected output; there is also an "unordered" that expects the outputs in a specific order. +@pytest.mark.nash +@pytest.mark.parametrize("test_case", ENUMPOLY_AGENT_UNORDERED_CASES, ids=lambda c: c.label) +def test_nash_agent_solver_unordered(test_case: EquilibriumTestCase, subtests) -> None: + """Test calls of Nash solvers in EFGs using "agent" versions. - In this unordered version, once something from the expected set is found it is removed, - so we are checking for no duplicate outputs. + Subtests: + - Agent max regret no more than `test_case.regret_tol` + - Agent max regret no more than max regret (+ `test_case.regret_tol`) + - Equilibria that are output are distinct and all appear in the expected set + Equilibria are deemed to match if the maximum difference in probabilities is no more + than `test_case.prob_tol` """ - if stop_after: - result = gbt.nash.enumpoly_solve( - game, use_strategic=False, stop_after=stop_after, maxregret=0.00001 - ) - assert len(result.equilibria) == stop_after - else: - # compute all - result = gbt.nash.enumpoly_solve(game, use_strategic=False) - - assert len(result.equilibria) == len(mixed_behav_prof_data) - def are_the_same(game, found, candidate): for p in game.players: - for i in p.infosets: - for a in i.actions: - if not abs(found[p][i][a] - candidate[p][i][a]) <= TOL: - return False + for a in p.actions: + if not abs(found[a] - candidate[a]) <= TOL: + return False return True - for eq in result.equilibria: - assert abs(eq.max_regret()) <= TOL - assert abs(eq.agent_max_regret()) <= TOL - found = False - for exp in mixed_behav_prof_data[:]: - expected = game.mixed_behavior_profile(rational=True, data=exp) - if are_the_same(game, eq, expected): - mixed_behav_prof_data.remove(exp) - found = True - break - assert found + game = test_case.factory() + result = test_case.solver(game) + with subtests.test("number of equilibria found"): + assert len(result.equilibria) == len(test_case.expected) + for i, eq in enumerate(result.equilibria): + with subtests.test(eq=i, check="agent_max_regret"): + assert eq.agent_max_regret() <= test_case.regret_tol + with subtests.test(eq=i, check="max_regret"): + assert eq.agent_max_regret() <= eq.max_regret() + test_case.regret_tol + with subtests.test(eq=i, check="strategy_profile"): + found = False + for exp in test_case.expected[:]: + expected = game.mixed_behavior_profile(rational=True, data=exp) + if are_the_same(game, eq, expected): + test_case.expected.remove(exp) + found = True + break + assert found def test_lcp_strategy_double(): From dc66cce890730fcf9c31372143abbbc9429872c1 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 11 Feb 2026 10:30:37 +0000 Subject: [PATCH 32/44] test_nash_behavior_solver wip --- tests/test_nash.py | 279 +++++++++++++++++++++++++++------------------ 1 file changed, 165 insertions(+), 114 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index d750ae29b..38dd1de63 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -39,6 +39,10 @@ class EquilibriumTestCase: prob_tol: float | gbt.Rational = Q(0) +################################################################################################## +# NASH SOLVER IN MIXED STRATEGIES +################################################################################################## + ENUMPURE_CASES = [ # Zero-sum games pytest.param( @@ -248,6 +252,153 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: for strategy in player.strategies: assert abs(eq[strategy] - expected[strategy]) <= test_case.prob_tol +################################################################################################## +# NASH SOLVER IN MIXED BEHAVIORS +################################################################################################## + +# games.read_from_file("two_player_perfect_info_win_lose.efg"), +# [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], + +# games.read_from_file("two_player_perfect_info_win_lose_with_nonterm_outcomes.efg"), +# [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], + +# games.create_2x2_zero_sum_efg(missing_term_outcome=False), +# [[["1/2", "1/2"]], [["1/2", "1/2"]]], + +# games.create_2x2_zero_sum_efg(missing_term_outcome=True), +# [[["1/2", "1/2"]], [["1/2", "1/2"]]], + +# games.create_matching_pennies_efg(with_neutral_outcome=False), +# [[["1/2", "1/2"]], [["1/2", "1/2"]]], + +# games.create_matching_pennies_efg(with_neutral_outcome=True), +# [[["1/2", "1/2"]], [["1/2", "1/2"]]], + +# games.create_stripped_down_poker_efg(), +# [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]], + +# games.create_stripped_down_poker_efg(nonterm_outcomes=True), +# [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]], + +# games.create_kuhn_poker_efg(), +# [ + # [[1, 0], [1, 0], [1, 0], ["2/3", "1/3"], [1, 0], [0, 1]], + # [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], +# ], + +# games.create_kuhn_poker_efg(nonterm_outcomes=True), +# [ + # [ + # [1, 0], + # [1, 0], + # [1, 0], + # ["2/3", "1/3"], + # [1, 0], + # [0, 1], + # ], + # [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], +# ], + +# games.read_from_file("zerosum_efg_from_sequence_form_STOC94_paper.efg"), +# [ + # [[0, 1], ["2/3", "1/3"], ["1/3", "2/3"]], + # [["5/6", "1/6"], ["5/9", "4/9"]], +# ], + +# games.read_from_file("perfect_info_with_chance.efg"), +# [[[0, 1]], [[1, 0], [1, 0]]], + +# games.read_from_file("2_player_chance.efg"), +# [ + # [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], + # [["2/3", "1/3"], ["2/3", "1/3"], ["1/3", "2/3"]], +# ], + +# games.read_from_file("2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg"), +# [ + # [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], + # [["2/3", "1/3"], ["2/3", "1/3"], ["1/3", "2/3"]], +# ], + +# games.read_from_file("large_payoff_game.efg"), +# [ + # [[1, 0], [1, 0]], + # [[0, 1], ["9999999999999999999/10000000000000000000", "1/10000000000000000000"]], +# ], + +# games.read_from_file("chance_in_middle.efg"), +# [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], + +# games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg"), +# [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], + + +LP_BEHAVIOR_RATIONAL_CASES = [ + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "chance_in_middle_with_nonterm_outcomes.efg" + ), + solver=gbt.nash.lp_solve, + expected=[ + [ + [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")] + ] + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test1_TODO", + ), + ] + +CASES = [] +CASES += LP_BEHAVIOR_RATIONAL_CASES + + +@pytest.mark.nash +@pytest.mark.parametrize("test_case", CASES, ids=lambda c: c.label) +def test_nash_behavior_solver(test_case: EquilibriumTestCase, subtests) -> None: + """Test calls of Nash solvers in mixed behaviors + + Subtests: + - Max regret no more than `test_case.regret_tol` + - Agent max regret no more than max regret (+ `test_case.regret_tol`) + - Equilibria are output in the expected order. Equilibria are deemed to match if the maximum + difference in probabilities is no more than `test_case.prob_tol` + """ + game = test_case.factory() + result = test_case.solver(game) + with subtests.test("number of equilibria found"): + assert len(result.equilibria) == len(test_case.expected) + for i, (eq, exp) in enumerate(zip(result.equilibria, test_case.expected, strict=True)): + with subtests.test(eq=i, check="max_regret"): + assert eq.max_regret() <= test_case.regret_tol + with subtests.test(eq=i, check="max_regret"): + assert eq.agent_max_regret() <= eq.max_regret() + test_case.regret_tol + with subtests.test(eq=i, check="strategy_profile"): + expected = game.mixed_behavior_profile(rational=True, data=exp) + for player in game.players: + for action in player.actions: + assert abs(eq[action] - expected[action]) <= test_case.prob_tol + +# def test_lp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): + # """Test calls of LP for mixed behavior equilibria, rational precision, + # using max_regret and agent_max_regret (internal consistency); and + # comparison to a previously computed equilibrium using this function (regression test). + # """ + # result = gbt.nash.lp_solve(game, use_strategic=False, rational=True) + # assert len(result.equilibria) == 1 + # eq = result.equilibria[0] + # assert eq.max_regret() == 0 + # assert eq.agent_max_regret() == 0 + # expected = game.mixed_behavior_profile(rational=True, data=mixed_behav_prof_data) + # assert eq == expected + +################################################################################################## +# AGENTS NASH SOLVERS (IN MIXED BEHAVIORS +################################################################################################## + ENUMPURE_AGENT_CASES = [ # ############################################################# @@ -515,6 +666,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: # ), # ############################################################################## + AGENT_CASES = [] AGENT_CASES += ENUMPURE_AGENT_CASES AGENT_CASES += ENUMPOLY_AGENT_CASES @@ -547,6 +699,10 @@ def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: assert abs(eq[action] - expected[action]) <= test_case.prob_tol +################################################################################################## +# TEMP FOR ISSUE 660 +################################################################################################## + ENUMPOLY_ISSUE_660_CASES = [ # 2-player non-zero-sum games pytest.param( @@ -607,6 +763,10 @@ def test_nash_agent_solver_no_subtests_only_profile(test_case: EquilibriumTestCa assert abs(eq[action] - expected[action]) <= test_case.prob_tol +################################################################################################## +# AGENT UNORDERED +################################################################################################## + ENUMPOLY_AGENT_UNORDERED_CASES = [ pytest.param( EquilibriumTestCase( @@ -671,6 +831,11 @@ def are_the_same(game, found, candidate): assert found +################################################################################################## +# STILL TODO........ +################################################################################################## + + def test_lcp_strategy_double(): """Test calls of LCP for mixed strategy equilibria, floating-point.""" game = games.read_from_file("stripped_down_poker.efg") @@ -958,120 +1123,6 @@ def test_lp_behavior_double(): # For floating-point results are not exact, so we skip testing exact values for now -@pytest.mark.nash -@pytest.mark.nash_lp_behavior -@pytest.mark.parametrize( - "game,mixed_behav_prof_data", - [ - ( - games.read_from_file("two_player_perfect_info_win_lose.efg"), - [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], - ), - ( - games.read_from_file("two_player_perfect_info_win_lose_with_nonterm_outcomes.efg"), - [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], - ), - ( - games.create_2x2_zero_sum_efg(missing_term_outcome=False), - [[["1/2", "1/2"]], [["1/2", "1/2"]]], - ), - ( - games.create_2x2_zero_sum_efg(missing_term_outcome=True), - [[["1/2", "1/2"]], [["1/2", "1/2"]]], - ), - ( - games.create_matching_pennies_efg(with_neutral_outcome=False), - [[["1/2", "1/2"]], [["1/2", "1/2"]]], - ), - ( - games.create_matching_pennies_efg(with_neutral_outcome=True), - [[["1/2", "1/2"]], [["1/2", "1/2"]]], - ), - ( - games.create_stripped_down_poker_efg(), - [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]], - ), - ( - games.create_stripped_down_poker_efg(nonterm_outcomes=True), - [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]], - ), - ( - games.create_kuhn_poker_efg(), - [ - [[1, 0], [1, 0], [1, 0], ["2/3", "1/3"], [1, 0], [0, 1]], - [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], - ], - ), - ( - games.create_kuhn_poker_efg(nonterm_outcomes=True), - [ - [ - [1, 0], - [1, 0], - [1, 0], - ["2/3", "1/3"], - [1, 0], - [0, 1], - ], - [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], - ], - ), - ( - games.read_from_file("zerosum_efg_from_sequence_form_STOC94_paper.efg"), - [ - [[0, 1], ["2/3", "1/3"], ["1/3", "2/3"]], - [["5/6", "1/6"], ["5/9", "4/9"]], - ], - ), - ( - games.read_from_file("perfect_info_with_chance.efg"), - [[[0, 1]], [[1, 0], [1, 0]]], - ), - ( - games.read_from_file("2_player_chance.efg"), - [ - [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], - [["2/3", "1/3"], ["2/3", "1/3"], ["1/3", "2/3"]], - ], - ), - ( - games.read_from_file("2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg"), - [ - [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], - [["2/3", "1/3"], ["2/3", "1/3"], ["1/3", "2/3"]], - ], - ), - ( - games.read_from_file("large_payoff_game.efg"), - [ - [[1, 0], [1, 0]], - [[0, 1], ["9999999999999999999/10000000000000000000", "1/10000000000000000000"]], - ], - ), - ( - games.read_from_file("chance_in_middle.efg"), - [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], - ), - ( - games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg"), - [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], - ), - ], -) -def test_lp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): - """Test calls of LP for mixed behavior equilibria, rational precision, - using max_regret and agent_max_regret (internal consistency); and - comparison to a previously computed equilibrium using this function (regression test). - """ - result = gbt.nash.lp_solve(game, use_strategic=False, rational=True) - assert len(result.equilibria) == 1 - eq = result.equilibria[0] - assert eq.max_regret() == 0 - assert eq.agent_max_regret() == 0 - expected = game.mixed_behavior_profile(rational=True, data=mixed_behav_prof_data) - assert eq == expected - - def test_liap_strategy(): """Test calls of liap for mixed strategy equilibria.""" game = games.read_from_file("stripped_down_poker.efg") From e1dfe807a3ba54b211f1b1907517fdd3f3b72159 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 11 Feb 2026 11:57:58 +0000 Subject: [PATCH 33/44] test_nash_behavior_solver wip --- tests/test_nash.py | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 38dd1de63..85bf74797 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -326,14 +326,40 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: # [[0, 1], ["9999999999999999999/10000000000000000000", "1/10000000000000000000"]], # ], -# games.read_from_file("chance_in_middle.efg"), -# [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], - -# games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg"), -# [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], - LP_BEHAVIOR_RATIONAL_CASES = [ + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "large_payoff_game.efg" + ), + solver=gbt.nash.lp_solve, + expected=[ + [ + [d(1, 0), d(1, 0)], + [d(0, 1), d("9999999999999999999/10000000000000000000", "1/10000000000000000000")], + ] + ], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "chance_in_middle.efg" + ), + solver=gbt.nash.lp_solve, + expected=[ + [ + [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")] + ] + ], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), pytest.param( EquilibriumTestCase( factory=functools.partial( @@ -347,10 +373,10 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: ] ], ), - marks=pytest.mark.nash_enumpure_strategy, + marks=pytest.mark.nash_lp_behavior, id="test1_TODO", ), - ] +] CASES = [] CASES += LP_BEHAVIOR_RATIONAL_CASES From 1735cb5debb33227463722aa6cb32376d99932ce Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Thu, 12 Feb 2026 08:23:02 +0000 Subject: [PATCH 34/44] LP_STRATEGY_RATIONAL_CASES for test_nash_strategy_solver --- tests/games.py | 117 +++++------ tests/test_extensive.py | 4 +- tests/test_nash.py | 450 ++++++++++++++++++++++++++-------------- 3 files changed, 346 insertions(+), 225 deletions(-) diff --git a/tests/games.py b/tests/games.py index 450282a52..05c138049 100644 --- a/tests/games.py +++ b/tests/games.py @@ -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. @@ -42,19 +42,29 @@ def create_efg_corresponding_to_bimatrix_game( # Extensive-form games (efg) -def create_2x2_zero_sum_efg(missing_term_outcome: bool = False) -> gbt.Game: +def create_2x2_zero_sum_efg(variant: None | str = None) -> gbt.Game: """ EFG corresponding to 2x2 zero-sum game (I,-I). - If missing_term_outcome, the terminal node after action 0 then 1 does not have an outcome. + + If variant is: + - "missing term outcome", terminal node after action 0 then 1 does not have an outcome. + - "with neutral outcome", there is a (0,0) payoff outcomes at a non-terminal node. """ title = "EFG for 2x2 zero-sum game (I,-I)" - if missing_term_outcome: - title += " with missing terminal outcome" + + if variant: + title += " " + variant + A = np.eye(2) B = -A g = create_efg_corresponding_to_bimatrix_game(A, B, title) - if missing_term_outcome: + + if variant == "missing term outcome": g.delete_outcome(g.root.children[0].children[1].outcome) + elif variant == "with neutral outcome": + neutral = g.add_outcome([0, 0], label="neutral") + g.set_outcome(g.root.children[0], neutral) + return g @@ -91,8 +101,9 @@ def create_stripped_down_poker_efg(nonterm_outcomes: bool = False) -> gbt.Game: return read_from_file("stripped_down_poker.efg") g = gbt.Game.new_tree( - players=["Alice", "Bob"], title="Stripped-Down Poker: a simple game of one-card\ - poker from Reiley et al (2008)." + players=["Alice", "Bob"], + title="Stripped-Down Poker: a simple game of one-card\ + poker from Reiley et al (2008).", ) deals = ["King", "Queen"] g.append_move(g.root, g.players.chance, deals) @@ -107,21 +118,15 @@ def create_stripped_down_poker_efg(nonterm_outcomes: bool = False) -> gbt.Game: bob_calls_and_loses_outcome = g.add_outcome([4, -1], label="Bob Calls and Loses") for node in g.root.children: - g.append_move( - node, - player="Alice", - actions=["Bet", "Fold"] - ) + g.append_move(node, player="Alice", actions=["Bet", "Fold"]) g.set_outcome(node.children["Fold"], alice_folds_outcome) g.set_outcome(node.children["Bet"], alice_bets_outcome) - alice_bets_nodes = [g.root.children["King"].children["Bet"], - g.root.children["Queen"].children["Bet"]] - g.append_move( - alice_bets_nodes, - player="Bob", - actions=["Call", "Fold"] - ) + alice_bets_nodes = [ + g.root.children["King"].children["Bet"], + g.root.children["Queen"].children["Bet"], + ] + g.append_move(alice_bets_nodes, player="Bob", actions=["Call", "Fold"]) for node in alice_bets_nodes: g.set_outcome(node.children["Fold"], bob_folds_outcome) @@ -136,9 +141,7 @@ def _create_kuhn_poker_efg_without_outcomes(): """ Used in create_kuhn_poker_efg() """ - g = gbt.Game.new_tree( - players=["Alice", "Bob"], title="Three-card poker (J, Q, K), two-player" - ) + g = gbt.Game.new_tree(players=["Alice", "Bob"], title="Three-card poker (J, Q, K), two-player") cards = ["J", "Q", "K"] deals = ["JQ", "JK", "QJ", "QK", "KJ", "KQ"] @@ -147,25 +150,29 @@ def deals_by_infoset(player, card): return [d for d in deals if d[player_idx] == card] g.append_move(g.root, g.players.chance, deals) - g.set_chance_probs(g.root.infoset, [gbt.Rational(1, 6)]*6) + g.set_chance_probs(g.root.infoset, [gbt.Rational(1, 6)] * 6) for alice_card in cards: # Alice's first move term_nodes = [g.root.children[d] for d in deals_by_infoset("Alice", alice_card)] g.append_move(term_nodes, "Alice", ["Check", "Bet"]) for bob_card in cards: # Bob's move after Alice checks - term_nodes = [g.root.children[d].children["Check"] - for d in deals_by_infoset("Bob", bob_card)] + term_nodes = [ + g.root.children[d].children["Check"] for d in deals_by_infoset("Bob", bob_card) + ] g.append_move(term_nodes, "Bob", ["Check", "Bet"]) for alice_card in cards: # Alice's move if Bob's second action is bet - term_nodes = [g.root.children[d].children["Check"].children["Bet"] - for d in deals_by_infoset("Alice", alice_card)] + term_nodes = [ + g.root.children[d].children["Check"].children["Bet"] + for d in deals_by_infoset("Alice", alice_card) + ] g.append_move(term_nodes, "Alice", ["Fold", "Call"]) for bob_card in cards: # Bob's move after Alice bets initially - term_nodes = [g.root.children[d].children["Bet"] - for d in deals_by_infoset("Bob", bob_card)] + term_nodes = [ + g.root.children[d].children["Bet"] for d in deals_by_infoset("Bob", bob_card) + ] g.append_move(term_nodes, "Bob", ["Fold", "Call"]) return g @@ -189,7 +196,6 @@ def _create_kuhn_poker_efg_only_term_outcomes() -> gbt.Game: g = _create_kuhn_poker_efg_without_outcomes() def calculate_payoffs(term_node): - def get_path(node): path = [] while node.parent: @@ -231,10 +237,12 @@ def bet(player, payoffs, pot): return tuple(payoffs.values()) # create 4 possible outcomes just once - payoffs_to_outcomes = {(1, -1): g.add_outcome([1, -1], label="Alice wins 1"), - (2, -2): g.add_outcome([2, -2], label="Alice wins 2"), - (-1, 1): g.add_outcome([-1, 1], label="Bob wins 1"), - (-2, 2): g.add_outcome([-2, 2], label="Bob wins 2")} + payoffs_to_outcomes = { + (1, -1): g.add_outcome([1, -1], label="Alice wins 1"), + (2, -2): g.add_outcome([2, -2], label="Alice wins 2"), + (-1, 1): g.add_outcome([-1, 1], label="Bob wins 1"), + (-2, 2): g.add_outcome([-2, 2], label="Bob wins 2"), + } for term_node in [n for n in g.nodes if n.is_terminal]: outcome = payoffs_to_outcomes[calculate_payoffs(term_node)] @@ -280,7 +288,6 @@ def _create_kuhn_poker_efg_nonterm_outcomes() -> gbt.Game: outcomes_dict[tmp] = g.add_outcome(payoffs, label=tmp) def add_outcomes(term_node): - def get_path(node): path = [] while node.parent: @@ -388,9 +395,7 @@ def create_one_shot_trust_efg(unique_NE_variant: bool = False) -> gbt.Game: ) g.append_move(g.root, "Buyer", ["Trust", "Not trust"]) g.append_move(g.root.children[0], "Seller", ["Honor", "Abuse"]) - g.set_outcome( - g.root.children[0].children[0], g.add_outcome([1, 1], label="Trustworthy") - ) + g.set_outcome(g.root.children[0].children[0], g.add_outcome([1, 1], label="Trustworthy")) if unique_NE_variant: g.set_outcome( g.root.children[0].children[1], g.add_outcome(["1/2", 2], label="Untrustworthy") @@ -489,9 +494,7 @@ def __init__(self, params): self.m1 = params["m1"] def gbt_game(self): - g = gbt.Game.new_tree( - players=["1", "2"], title=f"Centipede Game with {self.N} rounds" - ) + g = gbt.Game.new_tree(players=["1", "2"], title=f"Centipede Game with {self.N} rounds") current_node = g.root current_player = "1" for t in range(self.N): @@ -510,7 +513,6 @@ def gbt_game(self): return g def reduced_strategies(self): - if self.N % 2 == 0: n_moves = [int(self.N / 2)] * 2 else: @@ -594,7 +596,6 @@ def __init__(self, n_players, params): self.n_players = n_players def get_n_infosets(self, level): - if self.n_players == 1: return {1: 2 ** (level - 1)} @@ -649,16 +650,13 @@ def gbt_game(self): def reduced_strategic_form(self): # special case for 1 player - dims = ( - (self.size_of_rsf[0], 1) if len(self.size_of_rsf) == 1 else self.size_of_rsf - ) + dims = (self.size_of_rsf[0], 1) if len(self.size_of_rsf) == 1 else self.size_of_rsf zeros = np.zeros(dims, dtype=int) return [zeros] * len(self.players) class BinEfgOnePlayerIR(BinaryTreeGames): - def __init__(self, params): super().__init__(n_players=1, params=params) @@ -667,26 +665,21 @@ def _redu_strats(self, player, level): return self._redu_strategies_level_1(player) else: tmp = self._redu_strats(1, level - 1) - tmp = [ - t[1:] for t in tmp - ] # remove first action (1 from 1st half; 2 from 2nd half) + tmp = [t[1:] for t in tmp] # remove first action (1 from 1st half; 2 from 2nd half) n_half = int(len(tmp) / 2) 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 - ) + n_stars = 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] ) class BinEfgTwoOrThreePlayers(BinaryTreeGames): - def _redu_strats(self, player, level): if level == 1: return self._redu_strategies_level_1(player) @@ -698,11 +691,9 @@ def _redu_strats(self, player, level): n_stars = tmp1[player] - tmp2[last_player] - 1 stars = "*" * n_stars return [ - "1" + t + stars - for t in self._redu_strats(player=last_player, level=level - 1) + "1" + t + stars for t in self._redu_strats(player=last_player, level=level - 1) ] + [ - "2" + stars + t - for t in self._redu_strats(player=last_player, level=level - 1) + "2" + stars + t for t in self._redu_strats(player=last_player, level=level - 1) ] elif player == 2: tmp = self._redu_strats(player=1, level=level - 1) diff --git a/tests/test_extensive.py b/tests/test_extensive.py index f97584844..1381994d3 100644 --- a/tests/test_extensive.py +++ b/tests/test_extensive.py @@ -162,7 +162,7 @@ def test_outcome_index_exception_label(): [np.array([[1, 0], [0, 1]]), np.array([[-1, 0], [0, -1]])] ), ( - games.create_2x2_zero_sum_efg(missing_term_outcome=True), + games.create_2x2_zero_sum_efg(variant="missing term outcome"), [["1", "2"], ["1", "2"]], [np.array([[1, 0], [0, 1]]), np.array([[-1, 0], [0, -1]])] ), @@ -434,7 +434,7 @@ def test_reduced_strategic_form( ), ( games.create_2x2_zero_sum_efg(), - games.create_2x2_zero_sum_efg(missing_term_outcome=True) + games.create_2x2_zero_sum_efg(variant="missing term outcome"), ), ( games.create_matching_pennies_efg(), diff --git a/tests/test_nash.py b/tests/test_nash.py index 85bf74797..6c64e17d2 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -156,7 +156,7 @@ class EquilibriumTestCase: [d(0, 0, 1, 0), d(0, 0, 1)], ], ), - marks=pytest.mark.nash_enumpure_strategy, + marks=pytest.mark.nash_enummixed_strategy, id="test1_TODO", ), pytest.param( @@ -215,18 +215,89 @@ class EquilibriumTestCase: ), ] -# def test_enummixed_double(): -# """Test calls of enumeration of mixed strategy equilibria for 2-player games, floating-point. -# """ -# game = games.read_from_file("stripped_down_poker.efg") -# result = gbt.nash.enummixed_solve(game, rational=False) -# assert len(result.equilibria) == 1 -# # For floating-point results are not exact, so we skip testing exact values for now + +def test_enummixed_double(): + """Test calls of enumeration of mixed strategy equilibria for 2-player games, + floating-point.""" + game = games.read_from_file("stripped_down_poker.efg") + result = gbt.nash.enummixed_solve(game, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + + +def test_lp_strategy_double(): + """Test calls of LP for mixed strategy equilibria, floating-point.""" + game = games.read_from_file("stripped_down_poker.efg") + result = gbt.nash.lp_solve(game, use_strategic=True, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now +LP_STRATEGY_RATIONAL_CASES = [ + pytest.param( + EquilibriumTestCase( + factory=games.create_2x2_zero_sum_efg, + solver=functools.partial(gbt.nash.lp_solve, rational=True, use_strategic=True), + expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], + ), + marks=pytest.mark.nash_lp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="missing term outcome" + ), + solver=functools.partial(gbt.nash.lp_solve, rational=True, use_strategic=True), + expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], + ), + marks=pytest.mark.nash_lp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_stripped_down_poker_efg, nonterm_outcomes=False + ), + solver=functools.partial(gbt.nash.lp_solve, rational=True, use_strategic=True), + expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], + ), + marks=pytest.mark.nash_lp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_stripped_down_poker_efg, nonterm_outcomes=True), + solver=functools.partial(gbt.nash.lp_solve, rational=True, use_strategic=True), + expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], + ), + marks=pytest.mark.nash_lp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=False), + solver=functools.partial(gbt.nash.lp_solve, rational=True, use_strategic=True), + expected=[games.kuhn_poker_lp_mixed_strategy_prof()], + ), + marks=pytest.mark.nash_lp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=True), + solver=functools.partial(gbt.nash.lp_solve, rational=True, use_strategic=True), + expected=[games.kuhn_poker_lp_mixed_strategy_prof()], + ), + marks=pytest.mark.nash_lp_strategy, + id="test1_TODO", + ), +] + CASES = [] CASES += ENUMPURE_CASES CASES += ENUMMIXED_RATIONAL_CASES +CASES += LP_STRATEGY_RATIONAL_CASES @pytest.mark.nash @@ -252,93 +323,119 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: for strategy in player.strategies: assert abs(eq[strategy] - expected[strategy]) <= test_case.prob_tol + ################################################################################################## # NASH SOLVER IN MIXED BEHAVIORS ################################################################################################## -# games.read_from_file("two_player_perfect_info_win_lose.efg"), -# [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], - -# games.read_from_file("two_player_perfect_info_win_lose_with_nonterm_outcomes.efg"), -# [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], - -# games.create_2x2_zero_sum_efg(missing_term_outcome=False), -# [[["1/2", "1/2"]], [["1/2", "1/2"]]], - -# games.create_2x2_zero_sum_efg(missing_term_outcome=True), -# [[["1/2", "1/2"]], [["1/2", "1/2"]]], - -# games.create_matching_pennies_efg(with_neutral_outcome=False), -# [[["1/2", "1/2"]], [["1/2", "1/2"]]], - -# games.create_matching_pennies_efg(with_neutral_outcome=True), -# [[["1/2", "1/2"]], [["1/2", "1/2"]]], - -# games.create_stripped_down_poker_efg(), -# [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]], - -# games.create_stripped_down_poker_efg(nonterm_outcomes=True), -# [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]], - -# games.create_kuhn_poker_efg(), -# [ - # [[1, 0], [1, 0], [1, 0], ["2/3", "1/3"], [1, 0], [0, 1]], - # [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], -# ], - -# games.create_kuhn_poker_efg(nonterm_outcomes=True), -# [ - # [ - # [1, 0], - # [1, 0], - # [1, 0], - # ["2/3", "1/3"], - # [1, 0], - # [0, 1], - # ], - # [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], -# ], - -# games.read_from_file("zerosum_efg_from_sequence_form_STOC94_paper.efg"), -# [ - # [[0, 1], ["2/3", "1/3"], ["1/3", "2/3"]], - # [["5/6", "1/6"], ["5/9", "4/9"]], -# ], - -# games.read_from_file("perfect_info_with_chance.efg"), -# [[[0, 1]], [[1, 0], [1, 0]]], - -# games.read_from_file("2_player_chance.efg"), -# [ - # [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], - # [["2/3", "1/3"], ["2/3", "1/3"], ["1/3", "2/3"]], -# ], - -# games.read_from_file("2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg"), -# [ - # [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], - # [["2/3", "1/3"], ["2/3", "1/3"], ["1/3", "2/3"]], -# ], - -# games.read_from_file("large_payoff_game.efg"), -# [ - # [[1, 0], [1, 0]], - # [[0, 1], ["9999999999999999999/10000000000000000000", "1/10000000000000000000"]], -# ], - LP_BEHAVIOR_RATIONAL_CASES = [ pytest.param( EquilibriumTestCase( factory=functools.partial( - games.read_from_file, "large_payoff_game.efg" + games.read_from_file, "two_player_perfect_info_win_lose.efg" + ), + solver=gbt.nash.lp_solve, + expected=[[[d(0, 1), d(1, 0)], [d(1, 0), d(1, 0)]]], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "two_player_perfect_info_win_lose_with_nonterm_outcomes.efg" + ), + solver=gbt.nash.lp_solve, + expected=[[[d(0, 1), d(1, 0)], [d(1, 0), d(1, 0)]]], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="missing term outcome" + ), + solver=gbt.nash.lp_solve, + expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="with neutral outcome" + ), + solver=gbt.nash.lp_solve, + expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_2x2_zero_sum_efg, variant=None), + solver=gbt.nash.lp_solve, + expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_stripped_down_poker_efg, nonterm_outcomes=False ), solver=gbt.nash.lp_solve, expected=[ - [ - [d(1, 0), d(1, 0)], - [d(0, 1), d("9999999999999999999/10000000000000000000", "1/10000000000000000000")], - ] + [ + [d(1, 0), d("1/3", "2/3")], + [d("2/3", "1/3")], + ] + ], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_stripped_down_poker_efg, nonterm_outcomes=True), + solver=gbt.nash.lp_solve, + expected=[ + [ + [d(1, 0), d("1/3", "2/3")], + [d("2/3", "1/3")], + ] + ], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=False), + solver=gbt.nash.lp_solve, + expected=[ + [ + [d(1, 0), d(1, 0), d(1, 0), d("2/3", "1/3"), d(1, 0), d(0, 1)], + [d(1, 0), d("2/3", "1/3"), d(0, 1), d(0, 1), d("2/3", "1/3"), d(1, 0)], + ] + ], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=True), + solver=gbt.nash.lp_solve, + expected=[ + [ + [d(1, 0), d(1, 0), d(1, 0), d("2/3", "1/3"), d(1, 0), d(0, 1)], + [d(1, 0), d("2/3", "1/3"), d(0, 1), d(0, 1), d("2/3", "1/3"), d(1, 0)], + ] ], ), marks=pytest.mark.nash_lp_behavior, @@ -347,13 +444,89 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: pytest.param( EquilibriumTestCase( factory=functools.partial( - games.read_from_file, "chance_in_middle.efg" + games.read_from_file, "zerosum_efg_from_sequence_form_STOC94_paper.efg" ), solver=gbt.nash.lp_solve, expected=[ [ - [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], - [d(1, 0), d("6/11", "5/11")] + [d(0, 1), d("2/3", "1/3"), d("1/3", "2/3")], + [d("5/6", "1/6"), d("5/9", "4/9")], + ] + ], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "perfect_info_with_chance.efg"), + solver=gbt.nash.lp_solve, + expected=[ + [ + [d(0, 1)], + [d(1, 0), d(1, 0)], + ] + ], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2_player_chance.efg"), + solver=gbt.nash.lp_solve, + expected=[ + [ + [d("1/3", 0, "2/3"), d("2/3", 0, "1/3")], + [d("2/3", "1/3"), d("2/3", "1/3"), d("1/3", "2/3")], + ] + ], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, + "2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg", + ), + solver=gbt.nash.lp_solve, + expected=[ + [ + [d("1/3", 0, "2/3"), d("2/3", 0, "1/3")], + [d("2/3", "1/3"), d("2/3", "1/3"), d("1/3", "2/3")], + ] + ], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "large_payoff_game.efg"), + solver=gbt.nash.lp_solve, + expected=[ + [ + [d(1, 0), d(1, 0)], + [ + d(0, 1), + d("9999999999999999999/10000000000000000000", "1/10000000000000000000"), + ], + ] + ], + ), + marks=pytest.mark.nash_lp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "chance_in_middle.efg"), + solver=gbt.nash.lp_solve, + expected=[ + [ + [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")], ] ], ), @@ -368,8 +541,8 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: solver=gbt.nash.lp_solve, expected=[ [ - [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], - [d(1, 0), d("6/11", "5/11")] + [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")], ] ], ), @@ -408,21 +581,30 @@ def test_nash_behavior_solver(test_case: EquilibriumTestCase, subtests) -> None: for action in player.actions: assert abs(eq[action] - expected[action]) <= test_case.prob_tol -# def test_lp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): - # """Test calls of LP for mixed behavior equilibria, rational precision, - # using max_regret and agent_max_regret (internal consistency); and - # comparison to a previously computed equilibrium using this function (regression test). - # """ - # result = gbt.nash.lp_solve(game, use_strategic=False, rational=True) - # assert len(result.equilibria) == 1 - # eq = result.equilibria[0] - # assert eq.max_regret() == 0 - # assert eq.agent_max_regret() == 0 - # expected = game.mixed_behavior_profile(rational=True, data=mixed_behav_prof_data) - # assert eq == expected + +############################################################ +# CREATE AUTO VARIANTS OF THE RATIONAL TESTS FOR DOUBLES? +############################################################ + + +def test_lp_behavior_double(): + """Test calls of LP for mixed behavior equilibria, floating-point.""" + game = games.read_from_file("stripped_down_poker.efg") + result = gbt.nash.lp_solve(game, use_strategic=False, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + + +def test_lcp_behavior_double(): + """Test calls of LCP for mixed behavior equilibria, floating-point.""" + game = games.read_from_file("stripped_down_poker.efg") + result = gbt.nash.lcp_solve(game, use_strategic=False, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + ################################################################################################## -# AGENTS NASH SOLVERS (IN MIXED BEHAVIORS +# AGENTS NASH SOLVERS (IN MIXED BEHAVIORS) ################################################################################################## @@ -830,6 +1012,7 @@ def test_nash_agent_solver_unordered(test_case: EquilibriumTestCase, subtests) - Equilibria are deemed to match if the maximum difference in probabilities is no more than `test_case.prob_tol` """ + def are_the_same(game, found, candidate): for p in game.players: for a in p.actions: @@ -878,7 +1061,7 @@ def test_lcp_strategy_double(): # Zero-sum games (games.create_2x2_zero_sum_efg(), [[["1/2", "1/2"], ["1/2", "1/2"]]], None), ( - games.create_2x2_zero_sum_efg(missing_term_outcome=True), + games.create_2x2_zero_sum_efg(variant="missing term outcome"), [[["1/2", "1/2"], ["1/2", "1/2"]]], None, ), @@ -951,14 +1134,6 @@ def test_lcp_strategy_rational( assert eq == expected -def test_lcp_behavior_double(): - """Test calls of LCP for mixed behavior equilibria, floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.lcp_solve(game, use_strategic=False, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - @pytest.mark.nash @pytest.mark.nash_lcp_behavior @pytest.mark.parametrize( @@ -967,7 +1142,7 @@ def test_lcp_behavior_double(): # Zero-sum games (also tested with lp solve) (games.create_2x2_zero_sum_efg(), [[["1/2", "1/2"]], [["1/2", "1/2"]]]), ( - games.create_2x2_zero_sum_efg(missing_term_outcome=True), + games.create_2x2_zero_sum_efg(variant="missing term outcome"), [[["1/2", "1/2"]], [["1/2", "1/2"]]], ), (games.create_matching_pennies_efg(), [[["1/2", "1/2"]], [["1/2", "1/2"]]]), @@ -1098,55 +1273,10 @@ def test_lcp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): assert eq == expected -def test_lp_strategy_double(): - """Test calls of LP for mixed strategy equilibria, floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.lp_solve(game, use_strategic=True, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - -@pytest.mark.nash -@pytest.mark.nash_lp_strategy -@pytest.mark.parametrize( - "game,mixed_strategy_prof_data", - [ - ( - games.create_2x2_zero_sum_efg(), - [["1/2", "1/2"], ["1/2", "1/2"]], - ), - ( - games.create_2x2_zero_sum_efg(missing_term_outcome=True), - [["1/2", "1/2"], ["1/2", "1/2"]], - ), - (games.create_stripped_down_poker_efg(), [["1/3", "2/3", 0, 0], ["2/3", "1/3"]]), - ( - games.create_stripped_down_poker_efg(nonterm_outcomes=True), - [["1/3", "2/3", 0, 0], ["2/3", "1/3"]], - ), - (games.create_kuhn_poker_efg(), games.kuhn_poker_lp_mixed_strategy_prof()), - ( - games.create_kuhn_poker_efg(nonterm_outcomes=True), - games.kuhn_poker_lp_mixed_strategy_prof(), - ), - ], -) -def test_lp_strategy_rational(game: gbt.Game, mixed_strategy_prof_data: list): - """Test calls of LP for mixed strategy equilibria, rational precision.""" - result = gbt.nash.lp_solve(game, use_strategic=True, rational=True) - assert len(result.equilibria) == 1 - eq = result.equilibria[0] - assert eq.max_regret() == 0 - expected = game.mixed_strategy_profile(rational=True, data=mixed_strategy_prof_data) - assert eq == expected - - -def test_lp_behavior_double(): - """Test calls of LP for mixed behavior equilibria, floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.lp_solve(game, use_strategic=False, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now +################################################################################################## +# The following methods are are tested below but not beyond that they run: +# liap, simpdiv, ipa, gnm, logit +################################################################################################## def test_liap_strategy(): From d54dbf199bc10210b65eebfc66e4edd87c3ddccc Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Thu, 12 Feb 2026 11:55:54 +0000 Subject: [PATCH 35/44] LCP_BEHAVIOR_RATIONAL_CASES for test_nash_behavior_solver --- tests/test_nash.py | 435 ++++++++++++++++++++++++++++++--------------- 1 file changed, 296 insertions(+), 139 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 6c64e17d2..87c41e8e3 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -551,8 +551,304 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: ), ] +################################################################################################# +################################################################################################# + +LCP_BEHAVIOR_RATIONAL_CASES = [ + # Zero-sum games (also tested with lp solve) + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant=None + ), + solver=gbt.nash.lcp_solve, + expected=[ + [ + [d("1/2", "1/2")], + [d("1/2", "1/2")] + ] + ] + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="with neutral outcome" + ), + solver=gbt.nash.lcp_solve, + expected=[ + [ + [d("1/2", "1/2")], + [d("1/2", "1/2")] + ] + ] + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="missing term outcome" + ), + solver=gbt.nash.lcp_solve, + expected=[ + [ + [d("1/2", "1/2")], + [d("1/2", "1/2")] + ] + ] + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_stripped_down_poker_efg, nonterm_outcomes=True + ), + solver=gbt.nash.lcp_solve, + expected=[ + [ + [d(1, 0), d("1/3", "2/3")], + [d("2/3", "1/3")] + ] + ] + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_kuhn_poker_efg, nonterm_outcomes=False + ), + solver=gbt.nash.lcp_solve, + expected=[ + [ + [d("2/3", "1/3"), d(1, 0), d(1, 0), d("1/3", "2/3"), d(0, 1), d("1/2", "1/2")], + [d(1, 0), d("2/3", "1/3"), d(0, 1), d(0, 1), d("2/3", "1/3"), d(1, 0)] + ] + ] + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_kuhn_poker_efg, nonterm_outcomes=True + ), + solver=gbt.nash.lcp_solve, + expected=[ + [ + [d("2/3", "1/3"), d(1, 0), d(1, 0), d("1/3", "2/3"), d(0, 1), d("1/2", "1/2")], + [d(1, 0), d("2/3", "1/3"), d(0, 1), d(0, 1), d("2/3", "1/3"), d(1, 0)] + ] + ] + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, + "perfect_info_with_chance.efg" + ), + solver=gbt.nash.lcp_solve, + expected=[[[d(0, 1)], + [d(0, 1), d(0, 1)]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, + "two_player_perfect_info_win_lose.efg" + ), + solver=gbt.nash.lcp_solve, + expected=[[[d(0, 1), d(1, 0)], + [d(0, 1), d("1/2", "1/2")]]], + # 1/2-1/2 for l/r is determined by MixedBehaviorProfile.UndefinedToCentroid() + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, + "two_player_perfect_info_win_lose_with_nonterm_outcomes.efg" + ), + solver=gbt.nash.lcp_solve, + expected=[[[d(0, 1), d(1, 0)], + [d(0, 1), d("1/2", "1/2")]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, + "2_player_chance.efg" + ), + solver=gbt.nash.lcp_solve, + expected=[[[d("1/3", 0, "2/3"), d("2/3", 0, "1/3")], + [d("2/3", "1/3"), d("1/3", "2/3"), d("1/3", "2/3")]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, + "2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg" + ), + solver=gbt.nash.lcp_solve, + expected=[[[d("1/3", 0, "2/3"), d("2/3", 0, "1/3")], + [d("2/3", "1/3"), d("1/3", "2/3"), d("1/3", "2/3")]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "large_payoff_game.efg" + ), + solver=gbt.nash.lcp_solve, + expected=[[[d(1, 0), d(1, 0)], [d(0, 1), + d("9999999999999999999/10000000000000000000", "1/10000000000000000000")]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "chance_in_middle.efg" + ), + solver=gbt.nash.lcp_solve, + expected=[[[d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "chance_in_middle_with_nonterm_outcomes.efg" + ), + solver=gbt.nash.lcp_solve, + expected=[[[d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + # Non-zero-sum games + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "reduction_both_players_payoff_ties_GTE_survey.efg" + ), + solver=gbt.nash.lcp_solve, + expected=[[[d(0, 0, 1, 0), d(1, 0)], + [d(0, 1), d(0, 1), d(0, 1), d(0, 1)]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq, + solver=gbt.nash.lcp_solve, + expected=[[[d("1/30", "1/6", "3/10", "3/10", "1/6", "1/30")], + [d("1/6", "1/30", "3/10", "3/10", "1/30", "1/6")]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_EFG_for_nxn_bimatrix_coordination_game, 3 + ), + solver=gbt.nash.lcp_solve, + expected=[[[d(0, 0, 1)], [d(0, 0, 1)]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_EFG_for_nxn_bimatrix_coordination_game, 4 + ), + solver=gbt.nash.lcp_solve, + expected=[[[d(0, 0, 0, 1)], [d(0, 0, 0, 1)]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "entry_accommodation.efg" + ), + solver=gbt.nash.lcp_solve, + expected=[[[d("2/3", "1/3"), d(1, 0), d(1, 0)], [d("2/3", "1/3")]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "entry_accommodation_with_nonterm_outcomes.efg" + ), + solver=gbt.nash.lcp_solve, + expected=[[[d("2/3", "1/3"), d(1, 0), d(1, 0)], [d("2/3", "1/3")]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "2_player_non_zero_sum.efg" + ), + solver=gbt.nash.lcp_solve, + expected=[[[d("1/3", "2/3")], [d("1/2", "1/2")]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "2_player_non_zero_sum_missing_term_outcome.efg" + ), + solver=gbt.nash.lcp_solve, + expected=[[[d("1/3", "2/3")], [d("1/2", "1/2")]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test1_TODO", + ), +] + + CASES = [] CASES += LP_BEHAVIOR_RATIONAL_CASES +CASES += LCP_BEHAVIOR_RATIONAL_CASES @pytest.mark.nash @@ -1134,145 +1430,6 @@ def test_lcp_strategy_rational( assert eq == expected -@pytest.mark.nash -@pytest.mark.nash_lcp_behavior -@pytest.mark.parametrize( - "game,mixed_behav_prof_data", - [ - # Zero-sum games (also tested with lp solve) - (games.create_2x2_zero_sum_efg(), [[["1/2", "1/2"]], [["1/2", "1/2"]]]), - ( - games.create_2x2_zero_sum_efg(variant="missing term outcome"), - [[["1/2", "1/2"]], [["1/2", "1/2"]]], - ), - (games.create_matching_pennies_efg(), [[["1/2", "1/2"]], [["1/2", "1/2"]]]), - ( - games.create_matching_pennies_efg(with_neutral_outcome=True), - [[["1/2", "1/2"]], [["1/2", "1/2"]]], - ), - (games.create_stripped_down_poker_efg(), [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]]), - ( - games.create_stripped_down_poker_efg(nonterm_outcomes=True), - [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]], - ), - ( - games.create_kuhn_poker_efg(), - [ - [ - ["2/3", "1/3"], - [1, 0], - [1, 0], - ["1/3", "2/3"], - [0, 1], - ["1/2", "1/2"], - ], - [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], - ], - ), - ( - games.create_kuhn_poker_efg(nonterm_outcomes=True), - [ - [ - ["2/3", "1/3"], - [1, 0], - [1, 0], - ["1/3", "2/3"], - [0, 1], - ["1/2", "1/2"], - ], - [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], - ], - ), - # In the next test case: - # 1/2-1/2 for l/r is determined by MixedBehaviorProfile.UndefinedToCentroid() - ( - games.read_from_file("perfect_info_with_chance.efg"), - [[[0, 1]], [[0, 1], [0, 1]]], - ), - ( - games.read_from_file("two_player_perfect_info_win_lose.efg"), - [[[0, 1], [1, 0]], [[0, 1], ["1/2", "1/2"]]], - ), - ( - games.read_from_file("two_player_perfect_info_win_lose_with_nonterm_outcomes.efg"), - [[[0, 1], [1, 0]], [[0, 1], ["1/2", "1/2"]]], - ), - ( - games.read_from_file("2_player_chance.efg"), - [ - [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], - [["2/3", "1/3"], ["1/3", "2/3"], ["1/3", "2/3"]], - ], - ), - ( - games.read_from_file("2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg"), - [ - [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], - [["2/3", "1/3"], ["1/3", "2/3"], ["1/3", "2/3"]], - ], - ), - ( - games.read_from_file("large_payoff_game.efg"), - [ - [[1, 0], [1, 0]], - [[0, 1], ["9999999999999999999/10000000000000000000", "1/10000000000000000000"]], - ], - ), - ( - games.read_from_file("chance_in_middle.efg"), - [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], - ), - ( - games.read_from_file("chance_in_middle_with_nonterm_outcomes.efg"), - [[["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], ["6/11", "5/11"]]], - ), - # Non-zero-sum games - ( - games.read_from_file("reduction_both_players_payoff_ties_GTE_survey.efg"), - [[[0, 0, 1, 0], [1, 0]], [[0, 1], [0, 1], [0, 1], [0, 1]]], - ), - ( - games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq(), - [ - [["1/30", "1/6", "3/10", "3/10", "1/6", "1/30"]], - [["1/6", "1/30", "3/10", "3/10", "1/30", "1/6"]], - ], - ), - (games.create_EFG_for_nxn_bimatrix_coordination_game(3), [[[0, 0, 1]], [[0, 0, 1]]]), - ( - games.create_EFG_for_nxn_bimatrix_coordination_game(4), - [[[0, 0, 0, 1]], [[0, 0, 0, 1]]], - ), - ( - games.read_from_file("entry_accommodation.efg"), - [[["2/3", "1/3"], [1, 0], [1, 0]], [["2/3", "1/3"]]], - ), - ( - games.read_from_file("entry_accommodation_with_nonterm_outcomes.efg"), - [[["2/3", "1/3"], [1, 0], [1, 0]], [["2/3", "1/3"]]], - ), - (games.read_from_file("2_player_non_zero_sum.efg"), [[["1/3", "2/3"]], [["1/2", "1/2"]]]), - ( - games.read_from_file("2_player_non_zero_sum_missing_term_outcome.efg"), - [[["1/3", "2/3"]], [["1/2", "1/2"]]], - ), - ], -) -def test_lcp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): - """Test calls of LCP for mixed behavior equilibria, rational precision. - - using max_regret and agent_max_regret (internal consistency); and - comparison to a previously computed equilibrium using this function (regression test). - """ - result = gbt.nash.lcp_solve(game, use_strategic=False, rational=True) - assert len(result.equilibria) == 1 - eq = result.equilibria[0] - assert eq.max_regret() == 0 - assert eq.agent_max_regret() == 0 - expected = game.mixed_behavior_profile(rational=True, data=mixed_behav_prof_data) - assert eq == expected - - ################################################################################################## # The following methods are are tested below but not beyond that they run: # liap, simpdiv, ipa, gnm, logit From 92a2599e57333aa53be5bde481ccc6d2f7f086c2 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Thu, 12 Feb 2026 11:58:17 +0000 Subject: [PATCH 36/44] removed create_matching_pennies_efg; using create_2x2_zero_sum_efg instead --- tests/games.py | 16 ---------------- tests/test_extensive.py | 4 ++-- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/tests/games.py b/tests/games.py index 05c138049..312854b43 100644 --- a/tests/games.py +++ b/tests/games.py @@ -68,22 +68,6 @@ def create_2x2_zero_sum_efg(variant: None | str = None) -> gbt.Game: 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. - """ - title = "Matching Pennies" - if with_neutral_outcome: - title += " with nonterminal neutral outcome" - A = np.array([[1, -1], [-1, 1]]) - B = -A - g = create_efg_corresponding_to_bimatrix_game(A, B, title) - if with_neutral_outcome: - neutral = g.add_outcome([0, 0], label="neutral") - g.set_outcome(g.root.children[0], neutral) - return g - - def create_stripped_down_poker_efg(nonterm_outcomes: bool = False) -> gbt.Game: """ Returns diff --git a/tests/test_extensive.py b/tests/test_extensive.py index 1381994d3..55d9b5e5d 100644 --- a/tests/test_extensive.py +++ b/tests/test_extensive.py @@ -437,8 +437,8 @@ def test_reduced_strategic_form( games.create_2x2_zero_sum_efg(variant="missing term outcome"), ), ( - games.create_matching_pennies_efg(), - games.create_matching_pennies_efg(with_neutral_outcome=True) + games.create_2x2_zero_sum_efg(), + games.create_2x2_zero_sum_efg(variant="with nonterm outcome"), ), ], ) From 7d741e54b3151e327c967d856043f3f32951742a Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Thu, 12 Feb 2026 12:16:55 +0000 Subject: [PATCH 37/44] LCP_STRATEGY_RATIONAL_CASES for test_nash_strategy_solver --- tests/test_nash.py | 236 +++++++++++++++++++++++++++------------------ 1 file changed, 142 insertions(+), 94 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 87c41e8e3..bf254958b 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -233,6 +233,14 @@ def test_lp_strategy_double(): # For floating-point results are not exact, so we skip testing exact values for now +def test_lcp_strategy_double(): + """Test calls of LCP for mixed strategy equilibria, floating-point.""" + game = games.read_from_file("stripped_down_poker.efg") + result = gbt.nash.lcp_solve(game, use_strategic=True, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + + LP_STRATEGY_RATIONAL_CASES = [ pytest.param( EquilibriumTestCase( @@ -294,10 +302,144 @@ def test_lp_strategy_double(): ), ] + +LCP_STRATEGY_RATIONAL_CASES = [ + # Zero-sum games + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_2x2_zero_sum_efg, + variant=None), + solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, + stop_after=None), + expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_2x2_zero_sum_efg, + variant="with neutral outcome"), + solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, + stop_after=None), + expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_2x2_zero_sum_efg, + variant="missing term outcome"), + solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, + stop_after=None), + expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_stripped_down_poker_efg, + nonterm_outcomes=False), + solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, + stop_after=None), + expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_stripped_down_poker_efg, + nonterm_outcomes=True), + solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, + stop_after=None), + expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=False), + solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, + stop_after=1), + expected=[games.kuhn_poker_lcp_first_mixed_strategy_prof()], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=True), + solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, + stop_after=1), + expected=[games.kuhn_poker_lcp_first_mixed_strategy_prof()], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test1_TODO", + ), + # Non-zero-sum games + pytest.param( + EquilibriumTestCase( + factory=games.create_one_shot_trust_efg, + solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, + stop_after=None), + expected=[[d(0, 1), + d("1/2", "1/2")]], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), + solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, + stop_after=None), + expected=[ + [d(1, 0, 0), d(1, 0, 0)], + [d("1/2", "1/2", 0), d("1/2", "1/2", 0)], + [d(0, 1, 0), d(0, 1, 0)], + [d(0, "1/2", "1/2"), d(0, "1/2", "1/2")], + [d("1/3", "1/3", "1/3"), d("1/3", "1/3", "1/3")], + [d("1/2", 0, "1/2"), d("1/2", 0, "1/2")], + [d(0, 0, 1), d(0, 0, 1)], + ] + ), + marks=pytest.mark.nash_lcp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=4), + solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, + stop_after=1), + expected=[[d(1, 0, 0, 0), + d(1, 0, 0, 0)]], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq, + solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, + stop_after=None), + expected=[[d("1/30", "1/6", "3/10", "3/10", "1/6", "1/30"), + d("1/6", "1/30", "3/10", "3/10", "1/30", "1/6")]], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test1_TODO", + ), +] + + CASES = [] CASES += ENUMPURE_CASES CASES += ENUMMIXED_RATIONAL_CASES CASES += LP_STRATEGY_RATIONAL_CASES +CASES += LCP_STRATEGY_RATIONAL_CASES @pytest.mark.nash @@ -1336,100 +1478,6 @@ def are_the_same(game, found, candidate): assert found -################################################################################################## -# STILL TODO........ -################################################################################################## - - -def test_lcp_strategy_double(): - """Test calls of LCP for mixed strategy equilibria, floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.lcp_solve(game, use_strategic=True, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - -@pytest.mark.nash -@pytest.mark.nash_lcp_strategy -@pytest.mark.parametrize( - "game,mixed_strategy_prof_data,stop_after", - [ - # Zero-sum games - (games.create_2x2_zero_sum_efg(), [[["1/2", "1/2"], ["1/2", "1/2"]]], None), - ( - games.create_2x2_zero_sum_efg(variant="missing term outcome"), - [[["1/2", "1/2"], ["1/2", "1/2"]]], - None, - ), - (games.create_stripped_down_poker_efg(), [[["1/3", "2/3", 0, 0], ["2/3", "1/3"]]], None), - ( - games.create_stripped_down_poker_efg(nonterm_outcomes=True), - [[["1/3", "2/3", 0, 0], ["2/3", "1/3"]]], - None, - ), - (games.create_kuhn_poker_efg(), [games.kuhn_poker_lcp_first_mixed_strategy_prof()], 1), - ( - games.create_kuhn_poker_efg(nonterm_outcomes=True), - [games.kuhn_poker_lcp_first_mixed_strategy_prof()], - 1, - ), - # Non-zero-sum games - (games.create_one_shot_trust_efg(), [[[0, 1], ["1/2", "1/2"]]], None), - ( - games.create_EFG_for_nxn_bimatrix_coordination_game(3), - [ - [[1, 0, 0], [1, 0, 0]], - [["1/2", "1/2", 0], ["1/2", "1/2", 0]], - [[0, 1, 0], [0, 1, 0]], - [[0, "1/2", "1/2"], [0, "1/2", "1/2"]], - [["1/3", "1/3", "1/3"], ["1/3", "1/3", "1/3"]], - [["1/2", 0, "1/2"], ["1/2", 0, "1/2"]], - [[0, 0, 1], [0, 0, 1]], - ], - None, - ), - ( - games.create_EFG_for_nxn_bimatrix_coordination_game(4), - [[[1, 0, 0, 0], [1, 0, 0, 0]]], - 1, - ), - ( - games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq(), - [ - [ - ["1/30", "1/6", "3/10", "3/10", "1/6", "1/30"], - ["1/6", "1/30", "3/10", "3/10", "1/30", "1/6"], - ], - ], - None, - ), - ], -) -def test_lcp_strategy_rational( - game: gbt.Game, mixed_strategy_prof_data: list, stop_after: None | int -): - """Test calls of LCP for mixed strategy equilibria, rational precision - using max_regret (internal consistency); and comparison to a sequence of previously - computed equilibria using this function (regression test). - - This sequence will correspond to the full set of all computed equilibria if stop_after - is None, else the first stop_after-many equilibria. - """ - result = gbt.nash.lcp_solve(game, use_strategic=True, rational=True, stop_after=stop_after) - - if stop_after: - result = gbt.nash.lcp_solve(game, use_strategic=True, stop_after=stop_after) - assert len(result.equilibria) == stop_after - else: - # compute all - result = gbt.nash.lcp_solve(game, use_strategic=True) - assert len(result.equilibria) == len(mixed_strategy_prof_data) - for eq, exp in zip(result.equilibria, mixed_strategy_prof_data, strict=True): - assert eq.max_regret() == 0 - expected = game.mixed_strategy_profile(rational=True, data=exp) - assert eq == expected - - ################################################################################################## # The following methods are are tested below but not beyond that they run: # liap, simpdiv, ipa, gnm, logit From 02fe6a5985e3c17d4a446138e5d7df3072ecf978 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Thu, 12 Feb 2026 13:48:47 +0000 Subject: [PATCH 38/44] minor reorder --- tests/test_nash.py | 54 ++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index bf254958b..90b29db6d 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -216,31 +216,6 @@ class EquilibriumTestCase: ] -def test_enummixed_double(): - """Test calls of enumeration of mixed strategy equilibria for 2-player games, - floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.enummixed_solve(game, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - -def test_lp_strategy_double(): - """Test calls of LP for mixed strategy equilibria, floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.lp_solve(game, use_strategic=True, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - -def test_lcp_strategy_double(): - """Test calls of LCP for mixed strategy equilibria, floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.lcp_solve(game, use_strategic=True, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - LP_STRATEGY_RATIONAL_CASES = [ pytest.param( EquilibriumTestCase( @@ -466,6 +441,31 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: assert abs(eq[strategy] - expected[strategy]) <= test_case.prob_tol +def test_enummixed_double(): + """Test calls of enumeration of mixed strategy equilibria for 2-player games, + floating-point.""" + game = games.read_from_file("stripped_down_poker.efg") + result = gbt.nash.enummixed_solve(game, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + + +def test_lp_strategy_double(): + """Test calls of LP for mixed strategy equilibria, floating-point.""" + game = games.read_from_file("stripped_down_poker.efg") + result = gbt.nash.lp_solve(game, use_strategic=True, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + + +def test_lcp_strategy_double(): + """Test calls of LCP for mixed strategy equilibria, floating-point.""" + game = games.read_from_file("stripped_down_poker.efg") + result = gbt.nash.lcp_solve(game, use_strategic=True, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + + ################################################################################################## # NASH SOLVER IN MIXED BEHAVIORS ################################################################################################## @@ -693,8 +693,6 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: ), ] -################################################################################################# -################################################################################################# LCP_BEHAVIOR_RATIONAL_CASES = [ # Zero-sum games (also tested with lp solve) @@ -1441,7 +1439,7 @@ def test_nash_agent_solver_no_subtests_only_profile(test_case: EquilibriumTestCa @pytest.mark.nash @pytest.mark.parametrize("test_case", ENUMPOLY_AGENT_UNORDERED_CASES, ids=lambda c: c.label) def test_nash_agent_solver_unordered(test_case: EquilibriumTestCase, subtests) -> None: - """Test calls of Nash solvers in EFGs using "agent" versions. + """Test calls of Nash solvers in EFGs using "agent" versions -- UNORDERED Subtests: - Agent max regret no more than `test_case.regret_tol` From b7a8cfca5286fbbcc2020f20bea16d74c9eebc78 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Fri, 13 Feb 2026 07:56:31 +0000 Subject: [PATCH 39/44] enumpoly now correctly under behavior test --- tests/test_nash.py | 642 ++++++++++++++++++++++----------------------- 1 file changed, 313 insertions(+), 329 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 90b29db6d..8cfb8b12e 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -282,10 +282,10 @@ class EquilibriumTestCase: # Zero-sum games pytest.param( EquilibriumTestCase( - factory=functools.partial(games.create_2x2_zero_sum_efg, - variant=None), - solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, - stop_after=None), + factory=functools.partial(games.create_2x2_zero_sum_efg, variant=None), + solver=functools.partial( + gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=None + ), expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], ), marks=pytest.mark.nash_lcp_strategy, @@ -293,10 +293,12 @@ class EquilibriumTestCase: ), pytest.param( EquilibriumTestCase( - factory=functools.partial(games.create_2x2_zero_sum_efg, - variant="with neutral outcome"), - solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, - stop_after=None), + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="with neutral outcome" + ), + solver=functools.partial( + gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=None + ), expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], ), marks=pytest.mark.nash_lcp_strategy, @@ -304,10 +306,12 @@ class EquilibriumTestCase: ), pytest.param( EquilibriumTestCase( - factory=functools.partial(games.create_2x2_zero_sum_efg, - variant="missing term outcome"), - solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, - stop_after=None), + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="missing term outcome" + ), + solver=functools.partial( + gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=None + ), expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], ), marks=pytest.mark.nash_lcp_strategy, @@ -315,10 +319,12 @@ class EquilibriumTestCase: ), pytest.param( EquilibriumTestCase( - factory=functools.partial(games.create_stripped_down_poker_efg, - nonterm_outcomes=False), - solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, - stop_after=None), + factory=functools.partial( + games.create_stripped_down_poker_efg, nonterm_outcomes=False + ), + solver=functools.partial( + gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=None + ), expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], ), marks=pytest.mark.nash_lcp_strategy, @@ -326,10 +332,10 @@ class EquilibriumTestCase: ), pytest.param( EquilibriumTestCase( - factory=functools.partial(games.create_stripped_down_poker_efg, - nonterm_outcomes=True), - solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, - stop_after=None), + factory=functools.partial(games.create_stripped_down_poker_efg, nonterm_outcomes=True), + solver=functools.partial( + gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=None + ), expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], ), marks=pytest.mark.nash_lcp_strategy, @@ -338,8 +344,9 @@ class EquilibriumTestCase: pytest.param( EquilibriumTestCase( factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=False), - solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, - stop_after=1), + solver=functools.partial( + gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=1 + ), expected=[games.kuhn_poker_lcp_first_mixed_strategy_prof()], ), marks=pytest.mark.nash_lcp_strategy, @@ -348,8 +355,9 @@ class EquilibriumTestCase: pytest.param( EquilibriumTestCase( factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=True), - solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, - stop_after=1), + solver=functools.partial( + gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=1 + ), expected=[games.kuhn_poker_lcp_first_mixed_strategy_prof()], ), marks=pytest.mark.nash_lcp_strategy, @@ -359,10 +367,10 @@ class EquilibriumTestCase: pytest.param( EquilibriumTestCase( factory=games.create_one_shot_trust_efg, - solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, - stop_after=None), - expected=[[d(0, 1), - d("1/2", "1/2")]], + solver=functools.partial( + gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=None + ), + expected=[[d(0, 1), d("1/2", "1/2")]], ), marks=pytest.mark.nash_lcp_strategy, id="test1_TODO", @@ -370,17 +378,18 @@ class EquilibriumTestCase: pytest.param( EquilibriumTestCase( factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), - solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, - stop_after=None), + solver=functools.partial( + gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=None + ), expected=[ - [d(1, 0, 0), d(1, 0, 0)], - [d("1/2", "1/2", 0), d("1/2", "1/2", 0)], - [d(0, 1, 0), d(0, 1, 0)], - [d(0, "1/2", "1/2"), d(0, "1/2", "1/2")], - [d("1/3", "1/3", "1/3"), d("1/3", "1/3", "1/3")], - [d("1/2", 0, "1/2"), d("1/2", 0, "1/2")], - [d(0, 0, 1), d(0, 0, 1)], - ] + [d(1, 0, 0), d(1, 0, 0)], + [d("1/2", "1/2", 0), d("1/2", "1/2", 0)], + [d(0, 1, 0), d(0, 1, 0)], + [d(0, "1/2", "1/2"), d(0, "1/2", "1/2")], + [d("1/3", "1/3", "1/3"), d("1/3", "1/3", "1/3")], + [d("1/2", 0, "1/2"), d("1/2", 0, "1/2")], + [d(0, 0, 1), d(0, 0, 1)], + ], ), marks=pytest.mark.nash_lcp_strategy, id="test1_TODO", @@ -388,10 +397,10 @@ class EquilibriumTestCase: pytest.param( EquilibriumTestCase( factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=4), - solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, - stop_after=1), - expected=[[d(1, 0, 0, 0), - d(1, 0, 0, 0)]], + solver=functools.partial( + gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=1 + ), + expected=[[d(1, 0, 0, 0), d(1, 0, 0, 0)]], ), marks=pytest.mark.nash_lcp_strategy, id="test1_TODO", @@ -399,10 +408,15 @@ class EquilibriumTestCase: pytest.param( EquilibriumTestCase( factory=games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq, - solver=functools.partial(gbt.nash.lcp_solve, rational=True, use_strategic=True, - stop_after=None), - expected=[[d("1/30", "1/6", "3/10", "3/10", "1/6", "1/30"), - d("1/6", "1/30", "3/10", "3/10", "1/30", "1/6")]], + solver=functools.partial( + gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=None + ), + expected=[ + [ + d("1/30", "1/6", "3/10", "3/10", "1/6", "1/30"), + d("1/6", "1/30", "3/10", "3/10", "1/30", "1/6"), + ] + ], ), marks=pytest.mark.nash_lcp_strategy, id="test1_TODO", @@ -698,16 +712,9 @@ def test_lcp_strategy_double(): # Zero-sum games (also tested with lp solve) pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.create_2x2_zero_sum_efg, variant=None - ), + factory=functools.partial(games.create_2x2_zero_sum_efg, variant=None), solver=gbt.nash.lcp_solve, - expected=[ - [ - [d("1/2", "1/2")], - [d("1/2", "1/2")] - ] - ] + expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", @@ -718,12 +725,7 @@ def test_lcp_strategy_double(): games.create_2x2_zero_sum_efg, variant="with neutral outcome" ), solver=gbt.nash.lcp_solve, - expected=[ - [ - [d("1/2", "1/2")], - [d("1/2", "1/2")] - ] - ] + expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", @@ -734,73 +736,53 @@ def test_lcp_strategy_double(): games.create_2x2_zero_sum_efg, variant="missing term outcome" ), solver=gbt.nash.lcp_solve, - expected=[ - [ - [d("1/2", "1/2")], - [d("1/2", "1/2")] - ] - ] + expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", ), pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.create_stripped_down_poker_efg, nonterm_outcomes=True - ), + factory=functools.partial(games.create_stripped_down_poker_efg, nonterm_outcomes=True), solver=gbt.nash.lcp_solve, - expected=[ - [ - [d(1, 0), d("1/3", "2/3")], - [d("2/3", "1/3")] - ] - ] + expected=[[[d(1, 0), d("1/3", "2/3")], [d("2/3", "1/3")]]], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", ), pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.create_kuhn_poker_efg, nonterm_outcomes=False - ), + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=False), solver=gbt.nash.lcp_solve, expected=[ [ - [d("2/3", "1/3"), d(1, 0), d(1, 0), d("1/3", "2/3"), d(0, 1), d("1/2", "1/2")], - [d(1, 0), d("2/3", "1/3"), d(0, 1), d(0, 1), d("2/3", "1/3"), d(1, 0)] + [d("2/3", "1/3"), d(1, 0), d(1, 0), d("1/3", "2/3"), d(0, 1), d("1/2", "1/2")], + [d(1, 0), d("2/3", "1/3"), d(0, 1), d(0, 1), d("2/3", "1/3"), d(1, 0)], ] - ] + ], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", ), pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.create_kuhn_poker_efg, nonterm_outcomes=True - ), + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=True), solver=gbt.nash.lcp_solve, expected=[ [ - [d("2/3", "1/3"), d(1, 0), d(1, 0), d("1/3", "2/3"), d(0, 1), d("1/2", "1/2")], - [d(1, 0), d("2/3", "1/3"), d(0, 1), d(0, 1), d("2/3", "1/3"), d(1, 0)] + [d("2/3", "1/3"), d(1, 0), d(1, 0), d("1/3", "2/3"), d(0, 1), d("1/2", "1/2")], + [d(1, 0), d("2/3", "1/3"), d(0, 1), d(0, 1), d("2/3", "1/3"), d(1, 0)], ] - ] + ], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", ), pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.read_from_file, - "perfect_info_with_chance.efg" - ), + factory=functools.partial(games.read_from_file, "perfect_info_with_chance.efg"), solver=gbt.nash.lcp_solve, - expected=[[[d(0, 1)], - [d(0, 1), d(0, 1)]]], + expected=[[[d(0, 1)], [d(0, 1), d(0, 1)]]], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", @@ -808,12 +790,10 @@ def test_lcp_strategy_double(): pytest.param( EquilibriumTestCase( factory=functools.partial( - games.read_from_file, - "two_player_perfect_info_win_lose.efg" + games.read_from_file, "two_player_perfect_info_win_lose.efg" ), solver=gbt.nash.lcp_solve, - expected=[[[d(0, 1), d(1, 0)], - [d(0, 1), d("1/2", "1/2")]]], + expected=[[[d(0, 1), d(1, 0)], [d(0, 1), d("1/2", "1/2")]]], # 1/2-1/2 for l/r is determined by MixedBehaviorProfile.UndefinedToCentroid() ), marks=pytest.mark.nash_lcp_behavior, @@ -822,25 +802,24 @@ def test_lcp_strategy_double(): pytest.param( EquilibriumTestCase( factory=functools.partial( - games.read_from_file, - "two_player_perfect_info_win_lose_with_nonterm_outcomes.efg" + games.read_from_file, "two_player_perfect_info_win_lose_with_nonterm_outcomes.efg" ), solver=gbt.nash.lcp_solve, - expected=[[[d(0, 1), d(1, 0)], - [d(0, 1), d("1/2", "1/2")]]], + expected=[[[d(0, 1), d(1, 0)], [d(0, 1), d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", ), pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.read_from_file, - "2_player_chance.efg" - ), + factory=functools.partial(games.read_from_file, "2_player_chance.efg"), solver=gbt.nash.lcp_solve, - expected=[[[d("1/3", 0, "2/3"), d("2/3", 0, "1/3")], - [d("2/3", "1/3"), d("1/3", "2/3"), d("1/3", "2/3")]]], + expected=[ + [ + [d("1/3", 0, "2/3"), d("2/3", 0, "1/3")], + [d("2/3", "1/3"), d("1/3", "2/3"), d("1/3", "2/3")], + ] + ], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", @@ -849,35 +828,46 @@ def test_lcp_strategy_double(): EquilibriumTestCase( factory=functools.partial( games.read_from_file, - "2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg" + "2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg", ), solver=gbt.nash.lcp_solve, - expected=[[[d("1/3", 0, "2/3"), d("2/3", 0, "1/3")], - [d("2/3", "1/3"), d("1/3", "2/3"), d("1/3", "2/3")]]], + expected=[ + [ + [d("1/3", 0, "2/3"), d("2/3", 0, "1/3")], + [d("2/3", "1/3"), d("1/3", "2/3"), d("1/3", "2/3")], + ] + ], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", ), pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.read_from_file, "large_payoff_game.efg" - ), + factory=functools.partial(games.read_from_file, "large_payoff_game.efg"), solver=gbt.nash.lcp_solve, - expected=[[[d(1, 0), d(1, 0)], [d(0, 1), - d("9999999999999999999/10000000000000000000", "1/10000000000000000000")]]], + expected=[ + [ + [d(1, 0), d(1, 0)], + [ + d(0, 1), + d("9999999999999999999/10000000000000000000", "1/10000000000000000000"), + ], + ] + ], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", ), pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.read_from_file, "chance_in_middle.efg" - ), + factory=functools.partial(games.read_from_file, "chance_in_middle.efg"), solver=gbt.nash.lcp_solve, - expected=[[[d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], - [d(1, 0), d("6/11", "5/11")]]], + expected=[ + [ + [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")], + ] + ], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", @@ -888,8 +878,12 @@ def test_lcp_strategy_double(): games.read_from_file, "chance_in_middle_with_nonterm_outcomes.efg" ), solver=gbt.nash.lcp_solve, - expected=[[[d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], - [d(1, 0), d("6/11", "5/11")]]], + expected=[ + [ + [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")], + ] + ], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", @@ -901,8 +895,7 @@ def test_lcp_strategy_double(): games.read_from_file, "reduction_both_players_payoff_ties_GTE_survey.efg" ), solver=gbt.nash.lcp_solve, - expected=[[[d(0, 0, 1, 0), d(1, 0)], - [d(0, 1), d(0, 1), d(0, 1), d(0, 1)]]], + expected=[[[d(0, 0, 1, 0), d(1, 0)], [d(0, 1), d(0, 1), d(0, 1), d(0, 1)]]], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", @@ -911,17 +904,19 @@ def test_lcp_strategy_double(): EquilibriumTestCase( factory=games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq, solver=gbt.nash.lcp_solve, - expected=[[[d("1/30", "1/6", "3/10", "3/10", "1/6", "1/30")], - [d("1/6", "1/30", "3/10", "3/10", "1/30", "1/6")]]], + expected=[ + [ + [d("1/30", "1/6", "3/10", "3/10", "1/6", "1/30")], + [d("1/6", "1/30", "3/10", "3/10", "1/30", "1/6")], + ] + ], ), marks=pytest.mark.nash_lcp_behavior, id="test1_TODO", ), pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.create_EFG_for_nxn_bimatrix_coordination_game, 3 - ), + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, 3), solver=gbt.nash.lcp_solve, expected=[[[d(0, 0, 1)], [d(0, 0, 1)]]], ), @@ -930,9 +925,7 @@ def test_lcp_strategy_double(): ), pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.create_EFG_for_nxn_bimatrix_coordination_game, 4 - ), + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, 4), solver=gbt.nash.lcp_solve, expected=[[[d(0, 0, 0, 1)], [d(0, 0, 0, 1)]]], ), @@ -941,9 +934,7 @@ def test_lcp_strategy_double(): ), pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.read_from_file, "entry_accommodation.efg" - ), + factory=functools.partial(games.read_from_file, "entry_accommodation.efg"), solver=gbt.nash.lcp_solve, expected=[[[d("2/3", "1/3"), d(1, 0), d(1, 0)], [d("2/3", "1/3")]]], ), @@ -963,9 +954,7 @@ def test_lcp_strategy_double(): ), pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.read_from_file, "2_player_non_zero_sum.efg" - ), + factory=functools.partial(games.read_from_file, "2_player_non_zero_sum.efg"), solver=gbt.nash.lcp_solve, expected=[[[d("1/3", "2/3")], [d("1/2", "1/2")]]], ), @@ -986,172 +975,7 @@ def test_lcp_strategy_double(): ] -CASES = [] -CASES += LP_BEHAVIOR_RATIONAL_CASES -CASES += LCP_BEHAVIOR_RATIONAL_CASES - - -@pytest.mark.nash -@pytest.mark.parametrize("test_case", CASES, ids=lambda c: c.label) -def test_nash_behavior_solver(test_case: EquilibriumTestCase, subtests) -> None: - """Test calls of Nash solvers in mixed behaviors - - Subtests: - - Max regret no more than `test_case.regret_tol` - - Agent max regret no more than max regret (+ `test_case.regret_tol`) - - Equilibria are output in the expected order. Equilibria are deemed to match if the maximum - difference in probabilities is no more than `test_case.prob_tol` - """ - game = test_case.factory() - result = test_case.solver(game) - with subtests.test("number of equilibria found"): - assert len(result.equilibria) == len(test_case.expected) - for i, (eq, exp) in enumerate(zip(result.equilibria, test_case.expected, strict=True)): - with subtests.test(eq=i, check="max_regret"): - assert eq.max_regret() <= test_case.regret_tol - with subtests.test(eq=i, check="max_regret"): - assert eq.agent_max_regret() <= eq.max_regret() + test_case.regret_tol - with subtests.test(eq=i, check="strategy_profile"): - expected = game.mixed_behavior_profile(rational=True, data=exp) - for player in game.players: - for action in player.actions: - assert abs(eq[action] - expected[action]) <= test_case.prob_tol - - -############################################################ -# CREATE AUTO VARIANTS OF THE RATIONAL TESTS FOR DOUBLES? -############################################################ - - -def test_lp_behavior_double(): - """Test calls of LP for mixed behavior equilibria, floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.lp_solve(game, use_strategic=False, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - -def test_lcp_behavior_double(): - """Test calls of LCP for mixed behavior equilibria, floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.lcp_solve(game, use_strategic=False, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - -################################################################################################## -# AGENTS NASH SOLVERS (IN MIXED BEHAVIORS) -################################################################################################## - - -ENUMPURE_AGENT_CASES = [ - # ############################################################# - # Examples where Nash pure behaviors and agent-form pure equillibrium behaviors coincide - # ############################################################# - # Zero-sum games - pytest.param( - EquilibriumTestCase( - factory=functools.partial( - games.read_from_file, "two_player_perfect_info_win_lose.efg" - ), - solver=functools.partial(gbt.nash.enumpure_agent_solve), - expected=[ - [[d(1, 0), d(1, 0)], [d(0, 1), d(1, 0)]], - [[d(0, 1), d(1, 0)], [d(1, 0), d(1, 0)]], - [[d(0, 1), d(1, 0)], [d(1, 0), d(0, 1)]], - [[d(0, 1), d(1, 0)], [d(0, 1), d(1, 0)]], - [[d(0, 1), d(1, 0)], [d(0, 1), d(0, 1)]], - ], - ), - marks=pytest.mark.nash_enumpure_strategy, - id="test1_TODO", - ), - pytest.param( - EquilibriumTestCase( - factory=games.create_stripped_down_poker_efg, - solver=functools.partial(gbt.nash.enumpure_agent_solve), - expected=[], - ), - marks=pytest.mark.nash_enumpure_strategy, - id="test2_TODO", - ), - # Non-zero-sum 2-player games - pytest.param( - EquilibriumTestCase( - factory=games.create_one_shot_trust_efg, - solver=functools.partial(gbt.nash.enumpure_agent_solve), - expected=[ - [[d(0, 1)], [d(0, 1)]], - ], - ), - marks=pytest.mark.nash_enumpure_strategy, - id="test3_TODO", - ), - pytest.param( - EquilibriumTestCase( - factory=functools.partial(games.create_one_shot_trust_efg, unique_NE_variant=True), - solver=functools.partial(gbt.nash.enumpure_agent_solve), - expected=[ - [[d(1, 0)], [d(0, 1)]], - ], - ), - marks=pytest.mark.nash_enumpure_strategy, - id="test3b", - ), - pytest.param( - EquilibriumTestCase( - factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), - solver=functools.partial(gbt.nash.enumpure_agent_solve), - expected=[ - [[d(1, 0, 0)], [d(1, 0, 0)]], - [[d(0, 1, 0)], [d(0, 1, 0)]], - [[d(0, 0, 1)], [d(0, 0, 1)]], - ], - ), - marks=pytest.mark.nash_enumpure_strategy, - id="test4", - ), - pytest.param( - EquilibriumTestCase( - factory=functools.partial( - games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq - ), - solver=functools.partial(gbt.nash.enumpure_agent_solve), - expected=[], - ), - marks=pytest.mark.nash_enumpure_strategy, - id="test4", - ), - # 3-player games - pytest.param( - EquilibriumTestCase( - factory=functools.partial(games.read_from_file, "mixed_behavior_game.efg"), - solver=functools.partial(gbt.nash.enumpure_agent_solve), - expected=[ - [[d(1, 0)], [d(1, 0)], [d(1, 0)]], - [[d(1, 0)], [d(0, 1)], [d(0, 1)]], - [[d(0, 1)], [d(1, 0)], [d(0, 1)]], - [[d(0, 1)], [d(0, 1)], [d(1, 0)]], - ], - ), - marks=pytest.mark.nash_enumpure_strategy, - id="test5", - ), - ############################################################# - # Examples where the are agent-form pure equillibrium behaviors that are not Nash eq - pytest.param( - EquilibriumTestCase( - factory=functools.partial(games.read_from_file, "myerson_fig_4_2.efg"), - solver=functools.partial(gbt.nash.enumpure_agent_solve), - expected=[[[d(1, 0), d(0, 1)], [d(0, 1)]], [[d(0, 1), d(0, 1)], [d(1, 0)]]], - ), - marks=pytest.mark.nash_enumpure_strategy, - id="test6", - ), -] - - -ENUMPOLY_AGENT_CASES = [ +ENUMPOLY_BEHAVIOR_CASES = [ # ############################################################# # Examples where Nash pure behaviors and agent-form pure equillibrium behaviors coincide # ############################################################# @@ -1310,19 +1134,19 @@ def test_lcp_behavior_double(): # ), # ############################################################################## - -AGENT_CASES = [] -AGENT_CASES += ENUMPURE_AGENT_CASES -AGENT_CASES += ENUMPOLY_AGENT_CASES +CASES = [] +CASES += LP_BEHAVIOR_RATIONAL_CASES +CASES += LCP_BEHAVIOR_RATIONAL_CASES +CASES += ENUMPOLY_BEHAVIOR_CASES @pytest.mark.nash -@pytest.mark.parametrize("test_case", AGENT_CASES, ids=lambda c: c.label) -def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: - """Test calls of Nash solvers in EFGs using "agent" versions. +@pytest.mark.parametrize("test_case", CASES, ids=lambda c: c.label) +def test_nash_behavior_solver(test_case: EquilibriumTestCase, subtests) -> None: + """Test calls of Nash solvers in EFGs in mixed behaviors Subtests: - - Agent max regret no more than `test_case.regret_tol` + - Max regret no more than `test_case.regret_tol` - Agent max regret no more than max regret (+ `test_case.regret_tol`) - Equilibria are output in the expected order. Equilibria are deemed to match if the maximum difference in probabilities is no more than `test_case.prob_tol` @@ -1332,8 +1156,8 @@ def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: with subtests.test("number of equilibria found"): assert len(result.equilibria) == len(test_case.expected) for i, (eq, exp) in enumerate(zip(result.equilibria, test_case.expected, strict=True)): - with subtests.test(eq=i, check="agent_max_regret"): - assert eq.agent_max_regret() <= test_case.regret_tol + with subtests.test(eq=i, check="max_regret"): + assert eq.max_regret() <= test_case.regret_tol with subtests.test(eq=i, check="max_regret"): assert eq.agent_max_regret() <= eq.max_regret() + test_case.regret_tol with subtests.test(eq=i, check="strategy_profile"): @@ -1343,8 +1167,29 @@ def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: assert abs(eq[action] - expected[action]) <= test_case.prob_tol +############################################################ +# CREATE AUTO VARIANTS OF THE RATIONAL TESTS FOR DOUBLES? +############################################################ + + +def test_lp_behavior_double(): + """Test calls of LP for mixed behavior equilibria, floating-point.""" + game = games.read_from_file("stripped_down_poker.efg") + result = gbt.nash.lp_solve(game, use_strategic=False, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + + +def test_lcp_behavior_double(): + """Test calls of LCP for mixed behavior equilibria, floating-point.""" + game = games.read_from_file("stripped_down_poker.efg") + result = gbt.nash.lcp_solve(game, use_strategic=False, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + + ################################################################################################## -# TEMP FOR ISSUE 660 +# BEHAVIOR SOLVER WITHOUT SUBTESTS -- TEMP FOR ISSUE 660 ################################################################################################## ENUMPOLY_ISSUE_660_CASES = [ @@ -1390,13 +1235,8 @@ def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: @pytest.mark.nash @pytest.mark.parametrize("test_case", ENUMPOLY_ISSUE_660_CASES, ids=lambda c: c.label) -def test_nash_agent_solver_no_subtests_only_profile(test_case: EquilibriumTestCase) -> None: - """Test calls of Nash solvers in EFGs using "agent" versions. - - Checks for expected number of equilibria, and that the equilibria are output - in the expected order. Equilibria are deemed to match if the maximum - difference in probabilities is no more than `test_case.prob_tol` - """ +def test_nash_behavior_solver_no_subtests_only_profile(test_case: EquilibriumTestCase) -> None: + """TEMP: to be included with test_nash_behavior_solver when 660 is resolved.""" game = test_case.factory() result = test_case.solver(game) assert len(result.equilibria) == len(test_case.expected) @@ -1408,10 +1248,10 @@ def test_nash_agent_solver_no_subtests_only_profile(test_case: EquilibriumTestCa ################################################################################################## -# AGENT UNORDERED +# BEHVAIOR SOLVER -- UNORDERED ################################################################################################## -ENUMPOLY_AGENT_UNORDERED_CASES = [ +ENUMPOLY_BEHAVIOR_UNORDERED_CASES = [ pytest.param( EquilibriumTestCase( factory=functools.partial(games.read_from_file, "mixed_behavior_game.efg"), @@ -1437,9 +1277,9 @@ def test_nash_agent_solver_no_subtests_only_profile(test_case: EquilibriumTestCa @pytest.mark.nash -@pytest.mark.parametrize("test_case", ENUMPOLY_AGENT_UNORDERED_CASES, ids=lambda c: c.label) -def test_nash_agent_solver_unordered(test_case: EquilibriumTestCase, subtests) -> None: - """Test calls of Nash solvers in EFGs using "agent" versions -- UNORDERED +@pytest.mark.parametrize("test_case", ENUMPOLY_BEHAVIOR_UNORDERED_CASES, ids=lambda c: c.label) +def test_nash_behavior_solver_unordered(test_case: EquilibriumTestCase, subtests) -> None: + """Test calls of Nash solvers in EFGs in mixed behaviors -- UNORDERED Subtests: - Agent max regret no more than `test_case.regret_tol` @@ -1462,7 +1302,7 @@ def are_the_same(game, found, candidate): assert len(result.equilibria) == len(test_case.expected) for i, eq in enumerate(result.equilibria): with subtests.test(eq=i, check="agent_max_regret"): - assert eq.agent_max_regret() <= test_case.regret_tol + assert eq.max_regret() <= test_case.regret_tol with subtests.test(eq=i, check="max_regret"): assert eq.agent_max_regret() <= eq.max_regret() + test_case.regret_tol with subtests.test(eq=i, check="strategy_profile"): @@ -1476,6 +1316,150 @@ def are_the_same(game, found, candidate): assert found +################################################################################################## +# AGENTS NASH SOLVERS (IN MIXED BEHAVIORS) +################################################################################################## + + +ENUMPURE_AGENT_CASES = [ + # ############################################################# + # Examples where Nash pure behaviors and agent-form pure equillibrium behaviors coincide + # ############################################################# + # Zero-sum games + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "two_player_perfect_info_win_lose.efg" + ), + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[ + [[d(1, 0), d(1, 0)], [d(0, 1), d(1, 0)]], + [[d(0, 1), d(1, 0)], [d(1, 0), d(1, 0)]], + [[d(0, 1), d(1, 0)], [d(1, 0), d(0, 1)]], + [[d(0, 1), d(1, 0)], [d(0, 1), d(1, 0)]], + [[d(0, 1), d(1, 0)], [d(0, 1), d(0, 1)]], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test1_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=games.create_stripped_down_poker_efg, + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test2_TODO", + ), + # Non-zero-sum 2-player games + pytest.param( + EquilibriumTestCase( + factory=games.create_one_shot_trust_efg, + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[ + [[d(0, 1)], [d(0, 1)]], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test3_TODO", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_one_shot_trust_efg, unique_NE_variant=True), + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[ + [[d(1, 0)], [d(0, 1)]], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test3b", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[ + [[d(1, 0, 0)], [d(1, 0, 0)]], + [[d(0, 1, 0)], [d(0, 1, 0)]], + [[d(0, 0, 1)], [d(0, 0, 1)]], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test4", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq + ), + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test4", + ), + # 3-player games + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "mixed_behavior_game.efg"), + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[ + [[d(1, 0)], [d(1, 0)], [d(1, 0)]], + [[d(1, 0)], [d(0, 1)], [d(0, 1)]], + [[d(0, 1)], [d(1, 0)], [d(0, 1)]], + [[d(0, 1)], [d(0, 1)], [d(1, 0)]], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test5", + ), + ############################################################# + # Examples where the are agent-form pure equillibrium behaviors that are not Nash eq + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "myerson_fig_4_2.efg"), + solver=functools.partial(gbt.nash.enumpure_agent_solve), + expected=[[[d(1, 0), d(0, 1)], [d(0, 1)]], [[d(0, 1), d(0, 1)], [d(1, 0)]]], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test6", + ), +] + + +AGENT_CASES = [] +AGENT_CASES += ENUMPURE_AGENT_CASES +# TO ADD: pygambit.nash.liap_agent_solve + + +@pytest.mark.nash +@pytest.mark.parametrize("test_case", AGENT_CASES, ids=lambda c: c.label) +def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: + """Test calls of Nash solvers in EFGs using "agent" versions. + + Subtests: + - Agent max regret no more than `test_case.regret_tol` + - Agent max regret no more than max regret (+ `test_case.regret_tol`) + - Equilibria are output in the expected order. Equilibria are deemed to match if the maximum + difference in probabilities is no more than `test_case.prob_tol` + """ + game = test_case.factory() + result = test_case.solver(game) + with subtests.test("number of equilibria found"): + assert len(result.equilibria) == len(test_case.expected) + for i, (eq, exp) in enumerate(zip(result.equilibria, test_case.expected, strict=True)): + with subtests.test(eq=i, check="agent_max_regret"): + assert eq.agent_max_regret() <= test_case.regret_tol + with subtests.test(eq=i, check="max_regret"): + assert eq.agent_max_regret() <= eq.max_regret() + test_case.regret_tol + with subtests.test(eq=i, check="strategy_profile"): + expected = game.mixed_behavior_profile(rational=True, data=exp) + for player in game.players: + for action in player.actions: + assert abs(eq[action] - expected[action]) <= test_case.prob_tol + + ################################################################################################## # The following methods are are tested below but not beyond that they run: # liap, simpdiv, ipa, gnm, logit From 01a0642299ee5c2a97ef494fba28e9b581a2534e Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Fri, 13 Feb 2026 11:03:51 +0000 Subject: [PATCH 40/44] test expected output in test_liap_agent --- tests/test_nash.py | 72 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 8cfb8b12e..f69aee508 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -19,6 +19,7 @@ from . import games TOL = 1e-13 # tolerance for floating point assertions +TOL_LARGE = 1e-3 # larger tolerance for floating point assertions def d(*probs) -> tuple: @@ -1427,12 +1428,41 @@ def are_the_same(game, found, candidate): ), ] +LIAP_AGENT_CASES = [ + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "two_player_perfect_info_win_lose.efg" + ), + solver=functools.partial(gbt.nash.liap_agent_solve), # Need to pass the start arg + expected=[[[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]]], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test1_TODO", + ), +] AGENT_CASES = [] AGENT_CASES += ENUMPURE_AGENT_CASES # TO ADD: pygambit.nash.liap_agent_solve +def test_liap_agent(): + """Test calls of agent liap for mixed behavior equilibria.""" + + game = games.read_from_file("stripped_down_poker.efg") + result = gbt.nash.liap_agent_solve(game.mixed_behavior_profile()) + assert len(result.equilibria) == 1 + eq = result.equilibria[0] + + exp = [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]] + exp = game.mixed_behavior_profile(exp, rational=True) + + for player in game.players: + for action in player.actions: + assert abs(eq[action] - exp[action]) <= TOL_LARGE + + @pytest.mark.nash @pytest.mark.parametrize("test_case", AGENT_CASES, ids=lambda c: c.label) def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: @@ -1461,23 +1491,23 @@ def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: ################################################################################################## -# The following methods are are tested below but not beyond that they run: -# liap, simpdiv, ipa, gnm, logit +# TODO: ################################################################################################## +def test_logit_solve_lambda(): + game = games.read_from_file("const_sum_game.nfg") + assert ( + len(gbt.qre.logit_solve_lambda(game=game, lam=[1, 2, 3], first_step=0.2, max_accel=1)) > 0 + ) + + def test_liap_strategy(): """Test calls of liap for mixed strategy equilibria.""" game = games.read_from_file("stripped_down_poker.efg") _ = gbt.nash.liap_solve(game.mixed_strategy_profile()) -def test_liap_agent(): - """Test calls of agent liap for mixed behavior equilibria.""" - game = games.read_from_file("stripped_down_poker.efg") - _ = gbt.nash.liap_agent_solve(game.mixed_behavior_profile()) - - def test_simpdiv_strategy(): """Test calls of simplicial subdivision for mixed strategy equilibria.""" game = games.read_from_file("stripped_down_poker.efg") @@ -1513,6 +1543,18 @@ def test_logit_behavior(): assert len(result.equilibria) == 1 +def test_logit_solve_branch(): + game = games.read_from_file("const_sum_game.nfg") + assert ( + len(gbt.qre.logit_solve_branch(game=game, maxregret=0.2, first_step=0.2, max_accel=1)) > 0 + ) + + +################################################################################################## +# The remaining tests check for raising errors +################################################################################################## + + def test_logit_solve_branch_error_with_invalid_maxregret(): game = games.read_from_file("const_sum_game.nfg") with pytest.raises(ValueError, match="must be positive"): @@ -1537,13 +1579,6 @@ def test_logit_solve_branch_error_with_invalid_max_accel(): gbt.qre.logit_solve_branch(game=game, max_accel=0.1) -def test_logit_solve_branch(): - game = games.read_from_file("const_sum_game.nfg") - assert ( - len(gbt.qre.logit_solve_branch(game=game, maxregret=0.2, first_step=0.2, max_accel=1)) > 0 - ) - - def test_logit_solve_lambda_error_with_invalid_first_step(): game = games.read_from_file("const_sum_game.nfg") with pytest.raises(ValueError, match="must be positive"): @@ -1558,10 +1593,3 @@ def test_logit_solve_lambda_error_with_invalid_max_accel(): gbt.qre.logit_solve_lambda(game=game, lam=[1, 2, 3], max_accel=0) with pytest.raises(ValueError, match="at least 1.0"): gbt.qre.logit_solve_lambda(game=game, lam=[1, 2, 3], max_accel=0.1) - - -def test_logit_solve_lambda(): - game = games.read_from_file("const_sum_game.nfg") - assert ( - len(gbt.qre.logit_solve_lambda(game=game, lam=[1, 2, 3], first_step=0.2, max_accel=1)) > 0 - ) From b29e42d5e56e624d3f09e95d8dee759c4a8289d6 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Fri, 13 Feb 2026 12:02:28 +0000 Subject: [PATCH 41/44] logit,ipa,gnm strategy tests using test_nash_strategy_solver --- pyproject.toml | 3 + tests/test_nash.py | 150 ++++++++++++++++++++++++++++++++------------- 2 files changed, 109 insertions(+), 44 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 71ea25bfc..360af6b24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,9 @@ markers = [ "nash_lcp_behavior: tests of lcp_solve in mixed behaviors", "nash_lp_strategy: tests of lp_solve in mixed strategies", "nash_lp_behavior: tests of lp_solve in mixed behaviors", + "nash_logit_strategy: tests of logit_solve in mixed strategies", + "nash_gnm_strategy: tests of gnm_solve in mixed strategies", + "nash_ipa_strategy: tests of lpa_solve in mixed strategies", "nash: all tests of Nash equilibrium solvers", "slow: all time-consuming tests", ] diff --git a/tests/test_nash.py b/tests/test_nash.py index f69aee508..dc6ad6447 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -20,6 +20,7 @@ TOL = 1e-13 # tolerance for floating point assertions TOL_LARGE = 1e-3 # larger tolerance for floating point assertions +TOL_HUGE = 1e-2 # huge tolerance for floating point assertions def d(*probs) -> tuple: @@ -425,11 +426,61 @@ class EquilibriumTestCase: ] +LOGIT_STRATEGY_CASES = [ + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "stripped_down_poker.efg"), + solver=functools.partial(gbt.nash.logit_solve, use_strategic=True), + expected=[[d("0.334", "0.667", 0, 0), d("0.667", "0.3324")]], + # expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], + # [[[0.3342446335455467, 0.6657553666093431, + # 1.2005988475699076e-296, 2.3913775890307135e-296], + # [0.6675673092925399, 0.33243269085235666]]] + prob_tol=TOL_HUGE, + regret_tol=TOL_LARGE, + ), + marks=pytest.mark.nash_logit_strategy, + id="test1_TODO", + ), +] + + +IPA_STRATEGY_CASES = [ + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "stripped_down_poker.efg"), + solver=gbt.nash.ipa_solve, + expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], + ), + marks=pytest.mark.nash_ipa_strategy, + id="test1_TODO", + ), +] + + +GNM_STRATEGY_CASES = [ + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "stripped_down_poker.efg"), + solver=gbt.nash.gnm_solve, + expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], + prob_tol=TOL_LARGE, + regret_tol=TOL_LARGE, + ), + marks=pytest.mark.nash_gnm_strategy, + id="test1_TODO", + ), +] + + CASES = [] CASES += ENUMPURE_CASES CASES += ENUMMIXED_RATIONAL_CASES CASES += LP_STRATEGY_RATIONAL_CASES CASES += LCP_STRATEGY_RATIONAL_CASES +CASES += LOGIT_STRATEGY_CASES +CASES += IPA_STRATEGY_CASES +CASES += GNM_STRATEGY_CASES @pytest.mark.nash @@ -1447,22 +1498,6 @@ def are_the_same(game, found, candidate): # TO ADD: pygambit.nash.liap_agent_solve -def test_liap_agent(): - """Test calls of agent liap for mixed behavior equilibria.""" - - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.liap_agent_solve(game.mixed_behavior_profile()) - assert len(result.equilibria) == 1 - eq = result.equilibria[0] - - exp = [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]] - exp = game.mixed_behavior_profile(exp, rational=True) - - for player in game.players: - for action in player.actions: - assert abs(eq[action] - exp[action]) <= TOL_LARGE - - @pytest.mark.nash @pytest.mark.parametrize("test_case", AGENT_CASES, ids=lambda c: c.label) def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: @@ -1495,11 +1530,37 @@ def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: ################################################################################################## -def test_logit_solve_lambda(): - game = games.read_from_file("const_sum_game.nfg") - assert ( - len(gbt.qre.logit_solve_lambda(game=game, lam=[1, 2, 3], first_step=0.2, max_accel=1)) > 0 - ) +def test_logit_behavior(): + """Test calls of logit for behavior equilibria.""" + game = games.read_from_file("stripped_down_poker.efg") + result = gbt.nash.logit_solve(game, use_strategic=False) + assert len(result.equilibria) == 1 + + # [[[[1.0, 0.0], [0.33333338649882943, 0.6666666135011706]], + # [[0.6666667065407631, 0.3333332934592369]]]] + + +################################################################################################## +# TODO: +# The below all take a start argument that depends on the game, which doesn't immediately +# work with our current implementation of EquilibriumTestClass +################################################################################################## + + +def test_liap_agent(): + """Test calls of agent liap for mixed behavior equilibria.""" + + game = games.read_from_file("stripped_down_poker.efg") + result = gbt.nash.liap_agent_solve(game.mixed_behavior_profile()) + assert len(result.equilibria) == 1 + eq = result.equilibria[0] + + exp = [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]] + exp = game.mixed_behavior_profile(exp, rational=True) + + for player in game.players: + for action in player.actions: + assert abs(eq[action] - exp[action]) <= TOL_LARGE def test_liap_strategy(): @@ -1507,6 +1568,11 @@ def test_liap_strategy(): game = games.read_from_file("stripped_down_poker.efg") _ = gbt.nash.liap_solve(game.mixed_strategy_profile()) + # NashComputationResult(method='liap', rational=False, use_strategic=True, equilibria=[], + # parameters={'start': [[0.25, 0.25, 0.25, 0.25], [0.5, 0.5]], + # 'maxregret': 0.0001, 'maxiter': 1000}) + # Nothing found! + def test_simpdiv_strategy(): """Test calls of simplicial subdivision for mixed strategy equilibria.""" @@ -1514,33 +1580,27 @@ def test_simpdiv_strategy(): result = gbt.nash.simpdiv_solve(game.mixed_strategy_profile(rational=True)) assert len(result.equilibria) == 1 - -def test_ipa_strategy(): - """Test calls of IPA for mixed strategy equilibria.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.ipa_solve(game) - assert len(result.equilibria) == 1 - - -def test_gnm_strategy(): - """Test calls of GNM for mixed strategy equilibria.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.gnm_solve(game) - assert len(result.equilibria) == 1 + # [[[Rational(174763, 524288), Rational(349525, 524288), Rational(0, 1), Rational(0, 1)], + # [Rational(699051, 1048576), Rational(349525, 1048576)]]] -def test_logit_strategy(): - """Test calls of logit for mixed strategy equilibria.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.logit_solve(game, use_strategic=True) - assert len(result.equilibria) == 1 +################################################################################################## +# QRE solvers +################################################################################################## -def test_logit_behavior(): - """Test calls of logit for behavior equilibria.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.logit_solve(game, use_strategic=False) - assert len(result.equilibria) == 1 +# Needs a new solver tester +def test_logit_solve_lambda(): + game = games.read_from_file("const_sum_game.nfg") + assert ( + len(gbt.qre.logit_solve_lambda(game=game, lam=[1, 2, 3], first_step=0.2, max_accel=1)) > 0 + ) + # [LogitQREMixedStrategyProfile(lam=1.000000,profile=[[0.6429793593274791, 0.3570206406725209], + # [0.588319024552166, 0.41168097544783405]]), + # LogitQREMixedStrategyProfile(lam=2.000000,profile=[[0.7726766071376159, 0.2273233928623842], + # [0.6117434791999494, 0.38825652080005063]]), + # LogitQREMixedStrategyProfile(lam=3.000000,profile=[[0.859536709259968, 0.14046329074003203], + # [0.6038157860344706, 0.39618421396552944]])] def test_logit_solve_branch(): @@ -1549,6 +1609,8 @@ def test_logit_solve_branch(): len(gbt.qre.logit_solve_branch(game=game, maxregret=0.2, first_step=0.2, max_accel=1)) > 0 ) + # [LogitQREMixedStrategyProfile(lam=0.000000,profile=[[0.5, 0.5], [0.5, 0.5]])] + ################################################################################################## # The remaining tests check for raising errors From 2444b5acdfc9ce16c0468ecc9145190b47fb0cc9 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Fri, 13 Feb 2026 12:26:01 +0000 Subject: [PATCH 42/44] lp,lcp,enummixed double cases (mirroring all rational cases) --- tests/test_nash.py | 349 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 326 insertions(+), 23 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index dc6ad6447..45f515a58 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -218,6 +218,85 @@ class EquilibriumTestCase: ] +ENUMMIXED_DOUBLE_CASES = [ + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "two_player_perfect_info_win_lose.efg" + ), + solver=functools.partial(gbt.nash.enummixed_solve, rational=False), + expected=[ + [d(0, 0, 1, 0), d(1, 0, 0)], + [d(0, 0, 1, 0), d(0, 1, 0)], + [d(0, 0, 1, 0), d(0, 0, 1)], + ], + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test_enumixed_double_1", + ), + pytest.param( + EquilibriumTestCase( + factory=games.create_stripped_down_poker_efg, + solver=functools.partial(gbt.nash.enummixed_solve, rational=False), + expected=[ + [d(Q("1/3"), Q("2/3"), 0, 0), d(Q("2/3"), Q("1/3"))], + ], + prob_tol=TOL, + regret_tol=TOL, + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test_enumixed_double_2", + ), + pytest.param( + EquilibriumTestCase( + factory=games.create_one_shot_trust_efg, + solver=functools.partial(gbt.nash.enummixed_solve, rational=False), + expected=[ + [d(0, 1), d(Q("1/2"), Q("1/2"))], + [d(0, 1), d(0, 1)], + ], + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test_enumixed_double_3", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), + solver=functools.partial(gbt.nash.enummixed_solve, rational=False), + expected=[ + [d(1, 0, 0), d(1, 0, 0)], + [d(Q("1/2"), Q("1/2"), 0), d(Q("1/2"), Q("1/2"), 0)], + [d(Q("1/3"), Q("1/3"), Q("1/3")), d(Q("1/3"), Q("1/3"), Q("1/3"))], + [d(Q("1/2"), 0, Q("1/2")), d(Q("1/2"), 0, Q("1/2"))], + [d(0, 1, 0), d(0, 1, 0)], + [d(0, Q("1/2"), Q("1/2")), d(0, Q("1/2"), Q("1/2"))], + [d(0, 0, 1), d(0, 0, 1)], + ], + prob_tol=TOL, + regret_tol=TOL, + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test_enumixed_double_4", + ), + pytest.param( + EquilibriumTestCase( + factory=games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq, + solver=functools.partial(gbt.nash.enummixed_solve, rational=False), + expected=[ + [ + d(Q("1/30"), Q("1/6"), Q("3/10"), Q("3/10"), Q("1/6"), Q("1/30")), + d(Q("1/6"), Q("1/30"), Q("3/10"), Q("3/10"), Q("1/30"), Q("1/6")), + ], + ], + prob_tol=TOL, + regret_tol=TOL, + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test_enumixed_double_5", + ), +] + + LP_STRATEGY_RATIONAL_CASES = [ pytest.param( EquilibriumTestCase( @@ -280,6 +359,76 @@ class EquilibriumTestCase: ] +LP_STRATEGY_DOUBLE_CASES = [ + pytest.param( + EquilibriumTestCase( + factory=games.create_2x2_zero_sum_efg, + solver=functools.partial(gbt.nash.lp_solve, rational=False, use_strategic=True), + expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], + ), + marks=pytest.mark.nash_lp_strategy, + id="test_lp_strategy_double_1", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="missing term outcome" + ), + solver=functools.partial(gbt.nash.lp_solve, rational=False, use_strategic=True), + expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], + ), + marks=pytest.mark.nash_lp_strategy, + id="test_lp_strategy_double_2", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_stripped_down_poker_efg, nonterm_outcomes=False + ), + solver=functools.partial(gbt.nash.lp_solve, rational=False, use_strategic=True), + expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lp_strategy, + id="test_lp_strategy_double_3", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_stripped_down_poker_efg, nonterm_outcomes=True), + solver=functools.partial(gbt.nash.lp_solve, rational=False, use_strategic=True), + expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lp_strategy, + id="test_lp_strategy_double_4", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=False), + solver=functools.partial(gbt.nash.lp_solve, rational=False, use_strategic=True), + expected=[games.kuhn_poker_lp_mixed_strategy_prof()], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lp_strategy, + id="test_lp_strategy_double_5", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=True), + solver=functools.partial(gbt.nash.lp_solve, rational=False, use_strategic=True), + expected=[games.kuhn_poker_lp_mixed_strategy_prof()], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lp_strategy, + id="test_lp_strategy_double_6", + ), +] + + LCP_STRATEGY_RATIONAL_CASES = [ # Zero-sum games pytest.param( @@ -291,7 +440,7 @@ class EquilibriumTestCase: expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], ), marks=pytest.mark.nash_lcp_strategy, - id="test1_TODO", + id="test_lcp_strategy_rational_1", ), pytest.param( EquilibriumTestCase( @@ -304,7 +453,7 @@ class EquilibriumTestCase: expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], ), marks=pytest.mark.nash_lcp_strategy, - id="test1_TODO", + id="test_lcp_strategy_rational_2", ), pytest.param( EquilibriumTestCase( @@ -317,7 +466,7 @@ class EquilibriumTestCase: expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], ), marks=pytest.mark.nash_lcp_strategy, - id="test1_TODO", + id="test_lcp_strategy_rational_3", ), pytest.param( EquilibriumTestCase( @@ -330,7 +479,7 @@ class EquilibriumTestCase: expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], ), marks=pytest.mark.nash_lcp_strategy, - id="test1_TODO", + id="test_lcp_strategy_rational_4", ), pytest.param( EquilibriumTestCase( @@ -341,7 +490,7 @@ class EquilibriumTestCase: expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], ), marks=pytest.mark.nash_lcp_strategy, - id="test1_TODO", + id="test_lcp_strategy_rational_5", ), pytest.param( EquilibriumTestCase( @@ -352,7 +501,7 @@ class EquilibriumTestCase: expected=[games.kuhn_poker_lcp_first_mixed_strategy_prof()], ), marks=pytest.mark.nash_lcp_strategy, - id="test1_TODO", + id="test_lcp_strategy_rational_6", ), pytest.param( EquilibriumTestCase( @@ -363,7 +512,7 @@ class EquilibriumTestCase: expected=[games.kuhn_poker_lcp_first_mixed_strategy_prof()], ), marks=pytest.mark.nash_lcp_strategy, - id="test1_TODO", + id="test_lcp_strategy_rational_7", ), # Non-zero-sum games pytest.param( @@ -375,7 +524,7 @@ class EquilibriumTestCase: expected=[[d(0, 1), d("1/2", "1/2")]], ), marks=pytest.mark.nash_lcp_strategy, - id="test1_TODO", + id="test_lcp_strategy_rational_8", ), pytest.param( EquilibriumTestCase( @@ -394,7 +543,7 @@ class EquilibriumTestCase: ], ), marks=pytest.mark.nash_lcp_strategy, - id="test1_TODO", + id="test_lcp_strategy_rational_9", ), pytest.param( EquilibriumTestCase( @@ -405,7 +554,7 @@ class EquilibriumTestCase: expected=[[d(1, 0, 0, 0), d(1, 0, 0, 0)]], ), marks=pytest.mark.nash_lcp_strategy, - id="test1_TODO", + id="test_lcp_strategy_rational_10", ), pytest.param( EquilibriumTestCase( @@ -421,7 +570,167 @@ class EquilibriumTestCase: ], ), marks=pytest.mark.nash_lcp_strategy, - id="test1_TODO", + id="test_lcp_strategy_rational_11", + ), +] + + +LCP_STRATEGY_DOUBLE_CASES = [ + # Zero-sum games + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_2x2_zero_sum_efg, variant=None), + solver=functools.partial( + gbt.nash.lcp_solve, rational=False, use_strategic=True, stop_after=None + ), + expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_strategy, + id="test_lcp_strategy_double_1", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="with neutral outcome" + ), + solver=functools.partial( + gbt.nash.lcp_solve, rational=False, use_strategic=True, stop_after=None + ), + expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_strategy, + id="test_lcp_strategy_double_2", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="missing term outcome" + ), + solver=functools.partial( + gbt.nash.lcp_solve, rational=False, use_strategic=True, stop_after=None + ), + expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_strategy, + id="test_lcp_strategy_double_3", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_stripped_down_poker_efg, nonterm_outcomes=False + ), + solver=functools.partial( + gbt.nash.lcp_solve, rational=False, use_strategic=True, stop_after=None + ), + expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test_lcp_strategy_double_4", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_stripped_down_poker_efg, nonterm_outcomes=True), + solver=functools.partial( + gbt.nash.lcp_solve, rational=False, use_strategic=True, stop_after=None + ), + expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test_lcp_strategy_double_5", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=False), + solver=functools.partial( + gbt.nash.lcp_solve, rational=False, use_strategic=True, stop_after=1 + ), + expected=[games.kuhn_poker_lcp_first_mixed_strategy_prof()], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_strategy, + id="test_lcp_strategy_double_6", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=True), + solver=functools.partial( + gbt.nash.lcp_solve, rational=False, use_strategic=True, stop_after=1 + ), + expected=[games.kuhn_poker_lcp_first_mixed_strategy_prof()], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_strategy, + id="test_lcp_strategy_double_7", + ), + # Non-zero-sum games + pytest.param( + EquilibriumTestCase( + factory=games.create_one_shot_trust_efg, + solver=functools.partial( + gbt.nash.lcp_solve, rational=False, use_strategic=True, stop_after=None + ), + expected=[[d(0, 1), d("1/2", "1/2")]], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test_lcp_strategy_double_8", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), + solver=functools.partial( + gbt.nash.lcp_solve, rational=False, use_strategic=True, stop_after=None + ), + expected=[ + [d(1, 0, 0), d(1, 0, 0)], + [d("1/2", "1/2", 0), d("1/2", "1/2", 0)], + [d(0, 1, 0), d(0, 1, 0)], + [d(0, "1/2", "1/2"), d(0, "1/2", "1/2")], + [d("1/3", "1/3", "1/3"), d("1/3", "1/3", "1/3")], + [d("1/2", 0, "1/2"), d("1/2", 0, "1/2")], + [d(0, 0, 1), d(0, 0, 1)], + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_strategy, + id="test_lcp_strategy_double_9", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=4), + solver=functools.partial( + gbt.nash.lcp_solve, rational=False, use_strategic=True, stop_after=1 + ), + expected=[[d(1, 0, 0, 0), d(1, 0, 0, 0)]], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test_lcp_strategy_double_10", + ), + pytest.param( + EquilibriumTestCase( + factory=games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq, + solver=functools.partial( + gbt.nash.lcp_solve, rational=False, use_strategic=True, stop_after=None + ), + expected=[ + [ + d("1/30", "1/6", "3/10", "3/10", "1/6", "1/30"), + d("1/6", "1/30", "3/10", "3/10", "1/30", "1/6"), + ] + ], + regret_tol=TOL, + prob_tol=TOL_LARGE, + ), + marks=pytest.mark.nash_lcp_strategy, + id="test_lcp_strategy_double_11", ), ] @@ -476,11 +785,14 @@ class EquilibriumTestCase: CASES = [] CASES += ENUMPURE_CASES CASES += ENUMMIXED_RATIONAL_CASES +CASES += ENUMMIXED_DOUBLE_CASES CASES += LP_STRATEGY_RATIONAL_CASES +CASES += LP_STRATEGY_DOUBLE_CASES CASES += LCP_STRATEGY_RATIONAL_CASES -CASES += LOGIT_STRATEGY_CASES -CASES += IPA_STRATEGY_CASES -CASES += GNM_STRATEGY_CASES +CASES += LCP_STRATEGY_DOUBLE_CASES +# CASES += LOGIT_STRATEGY_CASES +# CASES += IPA_STRATEGY_CASES +# CASES += GNM_STRATEGY_CASES @pytest.mark.nash @@ -507,15 +819,6 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: assert abs(eq[strategy] - expected[strategy]) <= test_case.prob_tol -def test_enummixed_double(): - """Test calls of enumeration of mixed strategy equilibria for 2-player games, - floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.enummixed_solve(game, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - def test_lp_strategy_double(): """Test calls of LP for mixed strategy equilibria, floating-point.""" game = games.read_from_file("stripped_down_poker.efg") From 1b18acff1eddb34a6eb8b82a2e8b17e987876ca7 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Fri, 13 Feb 2026 13:01:07 +0000 Subject: [PATCH 43/44] lp,lcp behvior double cases (mirroring all rational cases) --- tests/test_nash.py | 648 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 591 insertions(+), 57 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 45f515a58..c87ac6ab6 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -790,9 +790,9 @@ class EquilibriumTestCase: CASES += LP_STRATEGY_DOUBLE_CASES CASES += LCP_STRATEGY_RATIONAL_CASES CASES += LCP_STRATEGY_DOUBLE_CASES -# CASES += LOGIT_STRATEGY_CASES -# CASES += IPA_STRATEGY_CASES -# CASES += GNM_STRATEGY_CASES +CASES += LOGIT_STRATEGY_CASES +CASES += IPA_STRATEGY_CASES +CASES += GNM_STRATEGY_CASES @pytest.mark.nash @@ -819,22 +819,6 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: assert abs(eq[strategy] - expected[strategy]) <= test_case.prob_tol -def test_lp_strategy_double(): - """Test calls of LP for mixed strategy equilibria, floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.lp_solve(game, use_strategic=True, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - -def test_lcp_strategy_double(): - """Test calls of LCP for mixed strategy equilibria, floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.lcp_solve(game, use_strategic=True, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - ################################################################################################## # NASH SOLVER IN MIXED BEHAVIORS ################################################################################################## @@ -850,7 +834,7 @@ def test_lcp_strategy_double(): expected=[[[d(0, 1), d(1, 0)], [d(1, 0), d(1, 0)]]], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_1", ), pytest.param( EquilibriumTestCase( @@ -861,7 +845,7 @@ def test_lcp_strategy_double(): expected=[[[d(0, 1), d(1, 0)], [d(1, 0), d(1, 0)]]], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_2", ), pytest.param( EquilibriumTestCase( @@ -872,7 +856,7 @@ def test_lcp_strategy_double(): expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_3", ), pytest.param( EquilibriumTestCase( @@ -883,7 +867,7 @@ def test_lcp_strategy_double(): expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_4", ), pytest.param( EquilibriumTestCase( @@ -892,7 +876,7 @@ def test_lcp_strategy_double(): expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_5", ), pytest.param( EquilibriumTestCase( @@ -908,7 +892,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_6", ), pytest.param( EquilibriumTestCase( @@ -922,7 +906,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_7", ), pytest.param( EquilibriumTestCase( @@ -936,7 +920,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_8", ), pytest.param( EquilibriumTestCase( @@ -950,7 +934,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_9", ), pytest.param( EquilibriumTestCase( @@ -966,7 +950,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_10", ), pytest.param( EquilibriumTestCase( @@ -980,7 +964,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_11", ), pytest.param( EquilibriumTestCase( @@ -994,7 +978,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_12", ), pytest.param( EquilibriumTestCase( @@ -1011,7 +995,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_13", ), pytest.param( EquilibriumTestCase( @@ -1028,7 +1012,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_14", ), pytest.param( EquilibriumTestCase( @@ -1042,7 +1026,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_15", ), pytest.param( EquilibriumTestCase( @@ -1058,7 +1042,253 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lp_behavior, - id="test1_TODO", + id="test_lp_behavior_rational_16", + ), +] + + +LP_BEHAVIOR_DOUBLE_CASES = [ + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "two_player_perfect_info_win_lose.efg" + ), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[[[d(0, 1), d(1, 0)], [d(1, 0), d(1, 0)]]], + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_1", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "two_player_perfect_info_win_lose_with_nonterm_outcomes.efg" + ), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[[[d(0, 1), d(1, 0)], [d(1, 0), d(1, 0)]]], + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_2", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="missing term outcome" + ), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_3", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="with neutral outcome" + ), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_4", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_2x2_zero_sum_efg, variant=None), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_5", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_stripped_down_poker_efg, nonterm_outcomes=False + ), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[ + [ + [d(1, 0), d("1/3", "2/3")], + [d("2/3", "1/3")], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_6", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_stripped_down_poker_efg, nonterm_outcomes=True), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[ + [ + [d(1, 0), d("1/3", "2/3")], + [d("2/3", "1/3")], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_7", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=False), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[ + [ + [d("2/3", "1/3"), d(1, 0), d(1, 0), d("1/3", "2/3"), d(0, 1), d("1/2", "1/2")], + [d(1, 0), d("2/3", "1/3"), d(0, 1), d(0, 1), d("2/3", "1/3"), d(1, 0)], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_8", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=True), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[ + [ + [d("2/3", "1/3"), d(1, 0), d(1, 0), d("1/3", "2/3"), d(0, 1), d("1/2", "1/2")], + [d(1, 0), d("2/3", "1/3"), d(0, 1), d(0, 1), d("2/3", "1/3"), d(1, 0)], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_9", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "zerosum_efg_from_sequence_form_STOC94_paper.efg" + ), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[ + [ + [d(0, 1), d("2/3", "1/3"), d("1/3", "2/3")], + [d("5/6", "1/6"), d("5/9", "4/9")], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_10", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "perfect_info_with_chance.efg"), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[ + [ + [d(0, 1)], + [d(1, 0), d(1, 0)], + ] + ], + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_11", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2_player_chance.efg"), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[ + [ + [d("1/3", 0, "2/3"), d("2/3", 0, "1/3")], + [d("2/3", "1/3"), d("2/3", "1/3"), d("1/3", "2/3")], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_12", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, + "2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg", + ), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[ + [ + [d("1/3", 0, "2/3"), d("2/3", 0, "1/3")], + [d("2/3", "1/3"), d("2/3", "1/3"), d("1/3", "2/3")], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_13", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "large_payoff_game.efg"), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[ + [ + [d(1, 0), d(1, 0)], + [ + d(0, 1), + d("9999999999999999999/10000000000000000000", "1/10000000000000000000"), + ], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=[ + pytest.mark.nash_lp_behavior, + pytest.mark.xfail(reason="Problem with large payoffs when working in floats"), + ], + id="test_lp_behavior_double_14", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "chance_in_middle.efg"), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[ + [ + [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_15", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "chance_in_middle_with_nonterm_outcomes.efg" + ), + solver=functools.partial(gbt.nash.lp_solve, rational=False), + expected=[ + [ + [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lp_behavior, + id="test_lp_behavior_double_16", ), ] @@ -1072,7 +1302,7 @@ def test_lcp_strategy_double(): expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_1", ), pytest.param( EquilibriumTestCase( @@ -1083,7 +1313,7 @@ def test_lcp_strategy_double(): expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_2", ), pytest.param( EquilibriumTestCase( @@ -1094,7 +1324,7 @@ def test_lcp_strategy_double(): expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_3", ), pytest.param( EquilibriumTestCase( @@ -1103,7 +1333,7 @@ def test_lcp_strategy_double(): expected=[[[d(1, 0), d("1/3", "2/3")], [d("2/3", "1/3")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_4", ), pytest.param( EquilibriumTestCase( @@ -1117,7 +1347,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_5", ), pytest.param( EquilibriumTestCase( @@ -1131,7 +1361,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_6", ), pytest.param( EquilibriumTestCase( @@ -1140,7 +1370,7 @@ def test_lcp_strategy_double(): expected=[[[d(0, 1)], [d(0, 1), d(0, 1)]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_7", ), pytest.param( EquilibriumTestCase( @@ -1152,7 +1382,7 @@ def test_lcp_strategy_double(): # 1/2-1/2 for l/r is determined by MixedBehaviorProfile.UndefinedToCentroid() ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_8", ), pytest.param( EquilibriumTestCase( @@ -1163,7 +1393,7 @@ def test_lcp_strategy_double(): expected=[[[d(0, 1), d(1, 0)], [d(0, 1), d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_9", ), pytest.param( EquilibriumTestCase( @@ -1177,7 +1407,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_10", ), pytest.param( EquilibriumTestCase( @@ -1194,7 +1424,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_11", ), pytest.param( EquilibriumTestCase( @@ -1211,7 +1441,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_12", ), pytest.param( EquilibriumTestCase( @@ -1225,7 +1455,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_13", ), pytest.param( EquilibriumTestCase( @@ -1241,7 +1471,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_14", ), # Non-zero-sum games pytest.param( @@ -1253,7 +1483,7 @@ def test_lcp_strategy_double(): expected=[[[d(0, 0, 1, 0), d(1, 0)], [d(0, 1), d(0, 1), d(0, 1), d(0, 1)]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_15", ), pytest.param( EquilibriumTestCase( @@ -1267,7 +1497,7 @@ def test_lcp_strategy_double(): ], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_16", ), pytest.param( EquilibriumTestCase( @@ -1276,7 +1506,7 @@ def test_lcp_strategy_double(): expected=[[[d(0, 0, 1)], [d(0, 0, 1)]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_1", ), pytest.param( EquilibriumTestCase( @@ -1285,7 +1515,7 @@ def test_lcp_strategy_double(): expected=[[[d(0, 0, 0, 1)], [d(0, 0, 0, 1)]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_17", ), pytest.param( EquilibriumTestCase( @@ -1294,7 +1524,7 @@ def test_lcp_strategy_double(): expected=[[[d("2/3", "1/3"), d(1, 0), d(1, 0)], [d("2/3", "1/3")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_18", ), pytest.param( EquilibriumTestCase( @@ -1305,7 +1535,7 @@ def test_lcp_strategy_double(): expected=[[[d("2/3", "1/3"), d(1, 0), d(1, 0)], [d("2/3", "1/3")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_19", ), pytest.param( EquilibriumTestCase( @@ -1314,7 +1544,7 @@ def test_lcp_strategy_double(): expected=[[[d("1/3", "2/3")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_20", ), pytest.param( EquilibriumTestCase( @@ -1325,7 +1555,309 @@ def test_lcp_strategy_double(): expected=[[[d("1/3", "2/3")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test1_TODO", + id="test_lcp_behavior_rational_21", + ), +] + + +LCP_BEHAVIOR_DOUBLE_CASES = [ + # Zero-sum games (also tested with lp solve) + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_2x2_zero_sum_efg, variant=None), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_01", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="with neutral outcome" + ), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_02", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.create_2x2_zero_sum_efg, variant="missing term outcome" + ), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_03", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_stripped_down_poker_efg, nonterm_outcomes=True), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d(1, 0), d("1/3", "2/3")], [d("2/3", "1/3")]]], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_04", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=False), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[ + [ + [d("2/3", "1/3"), d(1, 0), d(1, 0), d("1/3", "2/3"), d(0, 1), d("1/2", "1/2")], + [d(1, 0), d("2/3", "1/3"), d(0, 1), d(0, 1), d("2/3", "1/3"), d(1, 0)], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_05", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_kuhn_poker_efg, nonterm_outcomes=True), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[ + [ + [d("2/3", "1/3"), d(1, 0), d(1, 0), d("1/3", "2/3"), d(0, 1), d("1/2", "1/2")], + [d(1, 0), d("2/3", "1/3"), d(0, 1), d(0, 1), d("2/3", "1/3"), d(1, 0)], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_06", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "perfect_info_with_chance.efg"), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d(0, 1)], [d(0, 1), d(0, 1)]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_07", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "two_player_perfect_info_win_lose.efg" + ), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d(0, 1), d(1, 0)], [d(0, 1), d("1/2", "1/2")]]], + # 1/2-1/2 for l/r is determined by MixedBehaviorProfile.UndefinedToCentroid() + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_08", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "two_player_perfect_info_win_lose_with_nonterm_outcomes.efg" + ), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d(0, 1), d(1, 0)], [d(0, 1), d("1/2", "1/2")]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_09", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2_player_chance.efg"), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[ + [ + [d("1/3", 0, "2/3"), d("2/3", 0, "1/3")], + [d("2/3", "1/3"), d("1/3", "2/3"), d("1/3", "2/3")], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_10", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, + "2_player_chance_nonterm_outcomes_and_missing_term_outcomes.efg", + ), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[ + [ + [d("1/3", 0, "2/3"), d("2/3", 0, "1/3")], + [d("2/3", "1/3"), d("1/3", "2/3"), d("1/3", "2/3")], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_11", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "large_payoff_game.efg"), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[ + [ + [d(1, 0), d(1, 0)], + [ + d(0, 1), + d("9999999999999999999/10000000000000000000", "1/10000000000000000000"), + ], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=[ + pytest.mark.nash_lcp_behavior, + pytest.mark.xfail(reason="Problem with large payoffs when working in floats"), + ], + id="test_lcp_behavior_double_12", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "chance_in_middle.efg"), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[ + [ + [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_13", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "chance_in_middle_with_nonterm_outcomes.efg" + ), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[ + [ + [d("3/11", "8/11"), d(1, 0), d(1, 0), d(1, 0), d(1, 0)], + [d(1, 0), d("6/11", "5/11")], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_14", + ), + # Non-zero-sum games + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "reduction_both_players_payoff_ties_GTE_survey.efg" + ), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d(0, 0, 1, 0), d(1, 0)], [d(0, 1), d(0, 1), d(0, 1), d(0, 1)]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_15", + ), + pytest.param( + EquilibriumTestCase( + factory=games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq, + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[ + [ + [d("1/30", "1/6", "3/10", "3/10", "1/6", "1/30")], + [d("1/6", "1/30", "3/10", "3/10", "1/30", "1/6")], + ] + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_16", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, 3), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d(0, 0, 1)], [d(0, 0, 1)]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_17", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, 4), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d(0, 0, 0, 1)], [d(0, 0, 0, 1)]]], + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_18", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "entry_accommodation.efg"), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d("2/3", "1/3"), d(1, 0), d(1, 0)], [d("2/3", "1/3")]]], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_19", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "entry_accommodation_with_nonterm_outcomes.efg" + ), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d("2/3", "1/3"), d(1, 0), d(1, 0)], [d("2/3", "1/3")]]], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_20", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2_player_non_zero_sum.efg"), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d("1/3", "2/3")], [d("1/2", "1/2")]]], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_21", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial( + games.read_from_file, "2_player_non_zero_sum_missing_term_outcome.efg" + ), + solver=functools.partial(gbt.nash.lcp_solve, rational=False), + expected=[[[d("1/3", "2/3")], [d("1/2", "1/2")]]], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_behavior, + id="test_lcp_behavior_double_22", ), ] @@ -1491,7 +2023,9 @@ def test_lcp_strategy_double(): CASES = [] CASES += LP_BEHAVIOR_RATIONAL_CASES +CASES += LP_BEHAVIOR_DOUBLE_CASES CASES += LCP_BEHAVIOR_RATIONAL_CASES +CASES += LCP_BEHAVIOR_DOUBLE_CASES CASES += ENUMPOLY_BEHAVIOR_CASES From aa761a567d125442507af1b4a4e2ea0e4658cff8 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Fri, 13 Feb 2026 14:19:51 +0000 Subject: [PATCH 44/44] logit_behavior tests + ids for all new tests --- pyproject.toml | 1 + tests/test_nash.py | 202 +++++++++++++++++++++------------------------ 2 files changed, 94 insertions(+), 109 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 360af6b24..82d234adc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,6 +84,7 @@ markers = [ "nash_lp_strategy: tests of lp_solve in mixed strategies", "nash_lp_behavior: tests of lp_solve in mixed behaviors", "nash_logit_strategy: tests of logit_solve in mixed strategies", + "nash_logit_behavior: tests of logit_solve in behavior strategies", "nash_gnm_strategy: tests of gnm_solve in mixed strategies", "nash_ipa_strategy: tests of lpa_solve in mixed strategies", "nash: all tests of Nash equilibrium solvers", diff --git a/tests/test_nash.py b/tests/test_nash.py index c87ac6ab6..adba7d916 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -42,7 +42,7 @@ class EquilibriumTestCase: ################################################################################################## -# NASH SOLVER IN MIXED STRATEGIES +# NASH SOLVER IN PURE/MIXED STRATEGIES (as opposed to pure/mixed behaviors) ################################################################################################## ENUMPURE_CASES = [ @@ -60,7 +60,7 @@ class EquilibriumTestCase: ], ), marks=pytest.mark.nash_enumpure_strategy, - id="test1_TODO", + id="test_enumpure_1", ), pytest.param( EquilibriumTestCase( @@ -69,7 +69,7 @@ class EquilibriumTestCase: expected=[], ), marks=pytest.mark.nash_enumpure_strategy, - id="test2_TODO", + id="test_enumpure_2", ), # Non-zero-sum 2-player games pytest.param( @@ -79,7 +79,7 @@ class EquilibriumTestCase: expected=[[d(0, 1), d(0, 1)]], ), marks=pytest.mark.nash_enumpure_strategy, - id="test3", + id="test_enumpure_3", ), pytest.param( EquilibriumTestCase( @@ -88,7 +88,7 @@ class EquilibriumTestCase: expected=[[d(1, 0), d(0, 1)]], ), marks=pytest.mark.nash_enumpure_strategy, - id="test3b", + id="test_enumpure_4", ), pytest.param( EquilibriumTestCase( @@ -101,7 +101,7 @@ class EquilibriumTestCase: ], ), marks=pytest.mark.nash_enumpure_strategy, - id="test4", + id="test_enumpure_5", ), pytest.param( EquilibriumTestCase( @@ -110,7 +110,7 @@ class EquilibriumTestCase: expected=[], ), marks=pytest.mark.nash_enumpure_strategy, - id="test4", + id="test_enumpure_6", ), # 3-player game pytest.param( @@ -125,7 +125,7 @@ class EquilibriumTestCase: ], ), marks=pytest.mark.nash_enumpure_strategy, - id="test5", + id="test_enumpure_7", ), # 2x2x2 strategic form game based on local max cut -- 2 pure pytest.param( @@ -140,7 +140,7 @@ class EquilibriumTestCase: ], ), marks=pytest.mark.nash_enumpure_strategy, - id="test6", + id="test_enumpure_8", ), ] @@ -159,7 +159,7 @@ class EquilibriumTestCase: ], ), marks=pytest.mark.nash_enummixed_strategy, - id="test1_TODO", + id="test_enumixed_rational_1", ), pytest.param( EquilibriumTestCase( @@ -170,7 +170,7 @@ class EquilibriumTestCase: ], ), marks=pytest.mark.nash_enummixed_strategy, - id="test1", + id="test_enumixed_rational_2", ), pytest.param( EquilibriumTestCase( @@ -182,7 +182,7 @@ class EquilibriumTestCase: ], ), marks=pytest.mark.nash_enummixed_strategy, - id="test2", + id="test_enumixed_rational_3", ), pytest.param( EquilibriumTestCase( @@ -199,7 +199,7 @@ class EquilibriumTestCase: ], ), marks=pytest.mark.nash_enummixed_strategy, - id="test3", + id="test_enumixed_rational_4", ), pytest.param( EquilibriumTestCase( @@ -213,7 +213,7 @@ class EquilibriumTestCase: ], ), marks=pytest.mark.nash_enummixed_strategy, - id="test4", + id="test_enumixed_rational_5", ), ] @@ -305,7 +305,7 @@ class EquilibriumTestCase: expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], ), marks=pytest.mark.nash_lp_strategy, - id="test1_TODO", + id="test_lp_strategy_rational_1", ), pytest.param( EquilibriumTestCase( @@ -316,7 +316,7 @@ class EquilibriumTestCase: expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], ), marks=pytest.mark.nash_lp_strategy, - id="test1_TODO", + id="test_lp_strategy_rational_2", ), pytest.param( EquilibriumTestCase( @@ -327,7 +327,7 @@ class EquilibriumTestCase: expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], ), marks=pytest.mark.nash_lp_strategy, - id="test1_TODO", + id="test_lp_strategy_rational_3", ), pytest.param( EquilibriumTestCase( @@ -336,7 +336,7 @@ class EquilibriumTestCase: expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], ), marks=pytest.mark.nash_lp_strategy, - id="test1_TODO", + id="test_lp_strategy_rational_4", ), pytest.param( EquilibriumTestCase( @@ -345,7 +345,7 @@ class EquilibriumTestCase: expected=[games.kuhn_poker_lp_mixed_strategy_prof()], ), marks=pytest.mark.nash_lp_strategy, - id="test1_TODO", + id="test_lp_strategy_rational_5", ), pytest.param( EquilibriumTestCase( @@ -354,7 +354,7 @@ class EquilibriumTestCase: expected=[games.kuhn_poker_lp_mixed_strategy_prof()], ), marks=pytest.mark.nash_lp_strategy, - id="test1_TODO", + id="test_lp_strategy_rational_6", ), ] @@ -440,7 +440,7 @@ class EquilibriumTestCase: expected=[[d("1/2", "1/2"), d("1/2", "1/2")]], ), marks=pytest.mark.nash_lcp_strategy, - id="test_lcp_strategy_rational_1", + id="test_lcp_strategy_rational_01", ), pytest.param( EquilibriumTestCase( @@ -588,7 +588,7 @@ class EquilibriumTestCase: prob_tol=TOL, ), marks=pytest.mark.nash_lcp_strategy, - id="test_lcp_strategy_double_1", + id="test_lcp_strategy_double_01", ), pytest.param( EquilibriumTestCase( @@ -749,7 +749,7 @@ class EquilibriumTestCase: regret_tol=TOL_LARGE, ), marks=pytest.mark.nash_logit_strategy, - id="test1_TODO", + id="test_logic_strategy_1", ), ] @@ -762,7 +762,7 @@ class EquilibriumTestCase: expected=[[d("1/3", "2/3", 0, 0), d("2/3", "1/3")]], ), marks=pytest.mark.nash_ipa_strategy, - id="test1_TODO", + id="test_ipa_1", ), ] @@ -777,7 +777,7 @@ class EquilibriumTestCase: regret_tol=TOL_LARGE, ), marks=pytest.mark.nash_gnm_strategy, - id="test1_TODO", + id="test_gnm_1", ), ] @@ -834,7 +834,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: expected=[[[d(0, 1), d(1, 0)], [d(1, 0), d(1, 0)]]], ), marks=pytest.mark.nash_lp_behavior, - id="test_lp_behavior_rational_1", + id="test_lp_behavior_rational_01", ), pytest.param( EquilibriumTestCase( @@ -1057,7 +1057,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: expected=[[[d(0, 1), d(1, 0)], [d(1, 0), d(1, 0)]]], ), marks=pytest.mark.nash_lp_behavior, - id="test_lp_behavior_double_1", + id="test_lp_behavior_double_01", ), pytest.param( EquilibriumTestCase( @@ -1302,7 +1302,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_rational_1", + id="test_lcp_behavior_rational_01", ), pytest.param( EquilibriumTestCase( @@ -1313,7 +1313,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: expected=[[[d("1/2", "1/2")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_rational_2", + id="test_lcp_behavior_rational_02", ), pytest.param( EquilibriumTestCase( @@ -1506,7 +1506,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: expected=[[[d(0, 0, 1)], [d(0, 0, 1)]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_rational_1", + id="test_lcp_behavior_rational_17", ), pytest.param( EquilibriumTestCase( @@ -1515,7 +1515,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: expected=[[[d(0, 0, 0, 1)], [d(0, 0, 0, 1)]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_rational_17", + id="test_lcp_behavior_rational_18", ), pytest.param( EquilibriumTestCase( @@ -1524,7 +1524,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: expected=[[[d("2/3", "1/3"), d(1, 0), d(1, 0)], [d("2/3", "1/3")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_rational_18", + id="test_lcp_behavior_rational_19", ), pytest.param( EquilibriumTestCase( @@ -1535,7 +1535,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: expected=[[[d("2/3", "1/3"), d(1, 0), d(1, 0)], [d("2/3", "1/3")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_rational_19", + id="test_lcp_behavior_rational_20", ), pytest.param( EquilibriumTestCase( @@ -1544,7 +1544,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: expected=[[[d("1/3", "2/3")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_rational_20", + id="test_lcp_behavior_rational_21", ), pytest.param( EquilibriumTestCase( @@ -1555,7 +1555,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: expected=[[[d("1/3", "2/3")], [d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_rational_21", + id="test_lcp_behavior_rational_22", ), ] @@ -1597,7 +1597,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: prob_tol=TOL, ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_double_03", + id="test_lcp_behavior_double_3", ), pytest.param( EquilibriumTestCase( @@ -1608,7 +1608,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: prob_tol=TOL, ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_double_04", + id="test_lcp_behavior_double_4", ), pytest.param( EquilibriumTestCase( @@ -1624,7 +1624,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: prob_tol=TOL, ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_double_05", + id="test_lcp_behavior_double_5", ), pytest.param( EquilibriumTestCase( @@ -1640,7 +1640,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: prob_tol=TOL, ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_double_06", + id="test_lcp_behavior_double_6", ), pytest.param( EquilibriumTestCase( @@ -1649,7 +1649,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: expected=[[[d(0, 1)], [d(0, 1), d(0, 1)]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_double_07", + id="test_lcp_behavior_double_7", ), pytest.param( EquilibriumTestCase( @@ -1661,7 +1661,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: # 1/2-1/2 for l/r is determined by MixedBehaviorProfile.UndefinedToCentroid() ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_double_08", + id="test_lcp_behavior_double_8", ), pytest.param( EquilibriumTestCase( @@ -1672,7 +1672,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: expected=[[[d(0, 1), d(1, 0)], [d(0, 1), d("1/2", "1/2")]]], ), marks=pytest.mark.nash_lcp_behavior, - id="test_lcp_behavior_double_09", + id="test_lcp_behavior_double_9", ), pytest.param( EquilibriumTestCase( @@ -1878,7 +1878,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: prob_tol=TOL, ), marks=pytest.mark.nash_enumpoly_behavior, - id="test1_TODO", + id="test_enumpoly_behavior_01", ), pytest.param( EquilibriumTestCase( @@ -1891,7 +1891,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: prob_tol=TOL, ), marks=pytest.mark.nash_enumpoly_behavior, - id="test8_TODO", + id="test_enumpoly_behavior_2", ), pytest.param( EquilibriumTestCase( @@ -1906,7 +1906,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: prob_tol=TOL, ), marks=pytest.mark.nash_enumpoly_behavior, - id="test9_TODO", + id="test_enumpoly_behavior_3", ), pytest.param( EquilibriumTestCase( @@ -1924,7 +1924,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: prob_tol=TOL, ), marks=pytest.mark.nash_enumpoly_behavior, - id="test9_TODO", + id="test_enumpoly_behavior_4", ), pytest.param( EquilibriumTestCase( @@ -1944,7 +1944,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: prob_tol=TOL, ), marks=pytest.mark.nash_enumpoly_behavior, - id="test9_TODO", + id="test_enumpoly_behavior_5", ), # 3-player games pytest.param( @@ -1964,7 +1964,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: prob_tol=TOL, ), marks=pytest.mark.nash_enumpoly_behavior, - id="test4_TODO", + id="test_enumpoly_behavior_6", ), pytest.param( EquilibriumTestCase( @@ -1978,7 +1978,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: prob_tol=TOL, ), marks=pytest.mark.nash_enumpoly_behavior, - id="test6_TODO", + id="test_enumpoly_behavior_7", ), pytest.param( EquilibriumTestCase( @@ -1992,7 +1992,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: prob_tol=TOL, ), marks=pytest.mark.nash_enumpoly_behavior, - id="test7_TODO", + id="test_enumpoly_behavior_8", ), # 4-player game pytest.param( @@ -2006,7 +2006,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: prob_tol=TOL, ), marks=pytest.mark.nash_enumpoly_behavior, - id="test5_TODO", + id="test_enumpoly_behavior_9", ), ] # ############################################################################## @@ -2021,12 +2021,31 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: # ), # ############################################################################## + +LOGIT_BEHAVIOR_CASES = [ + pytest.param( + EquilibriumTestCase( + factory=games.create_stripped_down_poker_efg, + solver=gbt.nash.logit_solve, + expected=[ + [[d(1, 0), d("1/3", "2/3")], [d("2/3", "1/3")]], + ], + regret_tol=TOL_LARGE, + prob_tol=TOL_LARGE, + ), + marks=pytest.mark.nash_logit_behavior, + id="test_logit_behavior_01", + ), +] + + CASES = [] CASES += LP_BEHAVIOR_RATIONAL_CASES CASES += LP_BEHAVIOR_DOUBLE_CASES CASES += LCP_BEHAVIOR_RATIONAL_CASES CASES += LCP_BEHAVIOR_DOUBLE_CASES CASES += ENUMPOLY_BEHAVIOR_CASES +CASES += LOGIT_BEHAVIOR_CASES @pytest.mark.nash @@ -2056,27 +2075,6 @@ def test_nash_behavior_solver(test_case: EquilibriumTestCase, subtests) -> None: assert abs(eq[action] - expected[action]) <= test_case.prob_tol -############################################################ -# CREATE AUTO VARIANTS OF THE RATIONAL TESTS FOR DOUBLES? -############################################################ - - -def test_lp_behavior_double(): - """Test calls of LP for mixed behavior equilibria, floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.lp_solve(game, use_strategic=False, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - -def test_lcp_behavior_double(): - """Test calls of LCP for mixed behavior equilibria, floating-point.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.lcp_solve(game, use_strategic=False, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - ################################################################################################## # BEHAVIOR SOLVER WITHOUT SUBTESTS -- TEMP FOR ISSUE 660 ################################################################################################## @@ -2160,7 +2158,7 @@ def test_nash_behavior_solver_no_subtests_only_profile(test_case: EquilibriumTes prob_tol=TOL, ), marks=pytest.mark.nash_enumpoly_behavior, - id="test5_TODO", + id="test_enumpoly_behavior_unordered_1", ), ] @@ -2230,7 +2228,7 @@ def are_the_same(game, found, candidate): ], ), marks=pytest.mark.nash_enumpure_strategy, - id="test1_TODO", + id="test_enumpure_agent_1", ), pytest.param( EquilibriumTestCase( @@ -2239,7 +2237,7 @@ def are_the_same(game, found, candidate): expected=[], ), marks=pytest.mark.nash_enumpure_strategy, - id="test2_TODO", + id="test_enumpure_agent_2", ), # Non-zero-sum 2-player games pytest.param( @@ -2251,7 +2249,7 @@ def are_the_same(game, found, candidate): ], ), marks=pytest.mark.nash_enumpure_strategy, - id="test3_TODO", + id="test_enumpure_agent_3", ), pytest.param( EquilibriumTestCase( @@ -2262,7 +2260,7 @@ def are_the_same(game, found, candidate): ], ), marks=pytest.mark.nash_enumpure_strategy, - id="test3b", + id="test_enumpure_agent_4", ), pytest.param( EquilibriumTestCase( @@ -2275,7 +2273,7 @@ def are_the_same(game, found, candidate): ], ), marks=pytest.mark.nash_enumpure_strategy, - id="test4", + id="test_enumpure_agent_5", ), pytest.param( EquilibriumTestCase( @@ -2286,7 +2284,7 @@ def are_the_same(game, found, candidate): expected=[], ), marks=pytest.mark.nash_enumpure_strategy, - id="test4", + id="test_enumpure_agent_6", ), # 3-player games pytest.param( @@ -2301,7 +2299,7 @@ def are_the_same(game, found, candidate): ], ), marks=pytest.mark.nash_enumpure_strategy, - id="test5", + id="test_enumpure_agent_7", ), ############################################################# # Examples where the are agent-form pure equillibrium behaviors that are not Nash eq @@ -2312,23 +2310,24 @@ def are_the_same(game, found, candidate): expected=[[[d(1, 0), d(0, 1)], [d(0, 1)]], [[d(0, 1), d(0, 1)], [d(1, 0)]]], ), marks=pytest.mark.nash_enumpure_strategy, - id="test6", + id="test_enumpure_agent_8", ), ] -LIAP_AGENT_CASES = [ - pytest.param( - EquilibriumTestCase( - factory=functools.partial( - games.read_from_file, "two_player_perfect_info_win_lose.efg" - ), - solver=functools.partial(gbt.nash.liap_agent_solve), # Need to pass the start arg - expected=[[[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]]], - ), - marks=pytest.mark.nash_enumpure_strategy, - id="test1_TODO", - ), -] + +# LIAP_AGENT_CASES = [ +# pytest.param( +# EquilibriumTestCase( +# factory=functools.partial( +# games.read_from_file, "two_player_perfect_info_win_lose.efg" +# ), +# solver=functools.partial(gbt.nash.liap_agent_solve), # Need to pass the start arg +# expected=[[[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]]], +# ), +# marks=pytest.mark.nash_enumpure_strategy, +# id="test_liap_agent_1", +# ), +# ] AGENT_CASES = [] AGENT_CASES += ENUMPURE_AGENT_CASES @@ -2362,21 +2361,6 @@ def test_nash_agent_solver(test_case: EquilibriumTestCase, subtests) -> None: assert abs(eq[action] - expected[action]) <= test_case.prob_tol -################################################################################################## -# TODO: -################################################################################################## - - -def test_logit_behavior(): - """Test calls of logit for behavior equilibria.""" - game = games.read_from_file("stripped_down_poker.efg") - result = gbt.nash.logit_solve(game, use_strategic=False) - assert len(result.equilibria) == 1 - - # [[[[1.0, 0.0], [0.33333338649882943, 0.6666666135011706]], - # [[0.6666667065407631, 0.3333332934592369]]]] - - ################################################################################################## # TODO: # The below all take a start argument that depends on the game, which doesn't immediately