From f6518ab19d9c70b03eb92bdcca52d1b6544de14d Mon Sep 17 00:00:00 2001 From: rahulsavani Date: Thu, 13 Nov 2025 09:02:56 +0000 Subject: [PATCH 1/2] new tests for enumpure_solve in pure strategies and behaviors --- pyproject.toml | 8 +-- tests/test_nash.py | 110 +++++++++++++++++++++++++++++++++++++++--- tests/test_players.py | 1 - 3 files changed, 107 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 860e21e9e..313d63418 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,12 +76,14 @@ max-line-length = 99 [tool.pytest.ini_options] addopts = "--strict-markers" markers = [ + "nash_enumpure_strategy: tests of enumpure_solve in pure strategies", + "nash_enumpure_agent: tests of enumpure_solve in pure behaviors", "nash_enummixed_strategy: tests of enummixed_solve in mixed strategies", - "nash_enumpoly_behavior: tests of enumpoly_solve in behavior strategies", + "nash_enumpoly_behavior: tests of enumpoly_solve in mixed behaviors", "nash_lcp_strategy: tests of lcp_solve in mixed strategies", - "nash_lcp_behavior: tests of lcp_solve in behavior strategies", + "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 behavior strategies", + "nash_lp_behavior: tests of lp_solve in mixed behaviors", "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 4d35c57d8..25db784c0 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -17,16 +17,110 @@ TOL = 1e-13 # tolerance for floating point assertions -def test_enumpure_strategy(): - """Test calls of enumeration of pure strategies.""" - game = games.read_from_file("poker.efg") - assert len(gbt.nash.enumpure_solve(game, use_strategic=True).equilibria) == 0 +@pytest.mark.nash +@pytest.mark.nash_enumpure_strategy +@pytest.mark.parametrize( + "game,pure_strategy_prof_data", + [ + # Zero-sum games + ( + games.create_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_myerson_2_card_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.create_mixed_behav_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, use_strategic=True) + assert len(result.equilibria) == len(pure_strategy_prof_data) + for eq, exp in zip(result.equilibria, pure_strategy_prof_data): + assert eq.max_regret() == 0 + expected = game.mixed_strategy_profile(rational=True, data=exp) + assert eq == expected -def test_enumpure_agent(): - """Test calls of enumeration of pure agent strategies.""" - game = games.read_from_file("poker.efg") - assert len(gbt.nash.enumpure_solve(game, use_strategic=False).equilibria) == 0 + +@pytest.mark.nash +@pytest.mark.nash_enumpure_agent +@pytest.mark.parametrize( + "game,pure_behav_prof_data", + [ + # Zero-sum games + ( + games.create_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_myerson_2_card_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.create_mixed_behav_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]]], + ], + ), + ] +) +def test_enumpure_agent(game: gbt.Game, pure_behav_prof_data: list): + """Test calls of enumeration of pure agent (behavior) equilibria + + Tests max regret being zero (internal consistency) and compares the computed sequence of + pure agent equilibria to a previosuly computed sequence (regression test) + """ + result = gbt.nash.enumpure_solve(game, use_strategic=False) + assert len(result.equilibria) == len(pure_behav_prof_data) + for eq, exp in zip(result.equilibria, pure_behav_prof_data): + assert eq.max_regret() == 0 + expected = game.mixed_behavior_profile(rational=True, data=exp) + assert eq == expected def test_enummixed_double(): diff --git a/tests/test_players.py b/tests/test_players.py index 5409d2c4e..84d690b10 100644 --- a/tests/test_players.py +++ b/tests/test_players.py @@ -39,7 +39,6 @@ def test_player_index_by_string(): def test_player_index_out_of_range(): game = gbt.Game.new_table([2, 2]) - print(f"Number of players: {len(game.players)}") assert len(game.players) == 2 with pytest.raises(IndexError): _ = game.players[2] From 60929ecae9f448541bbda4f837732e17db243e8b Mon Sep 17 00:00:00 2001 From: rahulsavani Date: Mon, 17 Nov 2025 20:44:33 +0000 Subject: [PATCH 2/2] example for enumpure_solve for an efg with a non-Nash agent form eq --- tests/test_games/agent_form_eq_example.efg | 14 ++++++++++++++ tests/test_nash.py | 19 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/test_games/agent_form_eq_example.efg diff --git a/tests/test_games/agent_form_eq_example.efg b/tests/test_games/agent_form_eq_example.efg new file mode 100644 index 000000000..b4e95f020 --- /dev/null +++ b/tests/test_games/agent_form_eq_example.efg @@ -0,0 +1,14 @@ +EFG 2 R "Untitled Extensive Game" { "Player 1" "Player 2" } +"Example from Figure 4.2 from Myerson's 1991 textbook" + +p "" 1 1 "" { "A1" "B1" } 0 +p "" 2 1 "" { "W2" "X2" } 0 +p "" 1 2 "" { "Y1" "Z1" } 0 +t "" 1 "" { 3, 0 } +t "" 2 "" { 0, 0 } +p "" 1 2 "" { "Y1" "Z1" } 0 +t "" 3 "" { 2, 3 } +t "" 4 "" { 4, 1 } +p "" 2 1 "" { "W2" "X2" } 0 +t "" 5 "" { 2, 3 } +t "" 6 "" { 3, 2 } diff --git a/tests/test_nash.py b/tests/test_nash.py index 25db784c0..2197764df 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -74,6 +74,9 @@ def test_enumpure_strategy(game: gbt.Game, pure_strategy_prof_data: list): @pytest.mark.parametrize( "game,pure_behav_prof_data", [ + ############################################################# + # Examples where Nash pure behaviors and agent-form pure equillibrium behaviors coincide + ############################################################# # Zero-sum games ( games.create_two_player_perfect_info_win_lose_efg(), @@ -107,6 +110,16 @@ def test_enumpure_strategy(game: gbt.Game, pure_strategy_prof_data: list): [[[0, 1]], [[0, 1]], [[1, 0]]], ], ), + ############################################################# + # Examples where the are agent-form pure equillibrium behaviors that are not Nash eq + ############################################################# + ( + games.read_from_file("agent_form_eq_example.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): @@ -114,11 +127,15 @@ def test_enumpure_agent(game: gbt.Game, pure_behav_prof_data: list): Tests 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_solve(game, use_strategic=False) assert len(result.equilibria) == len(pure_behav_prof_data) for eq, exp in zip(result.equilibria, pure_behav_prof_data): - assert eq.max_regret() == 0 + assert eq.max_regret() == 0 # This is true even for an agent form eq that is not Nash expected = game.mixed_behavior_profile(rational=True, data=exp) assert eq == expected