From 2549e96007a3c4da83661cb9d91d5aafa44caea9 Mon Sep 17 00:00:00 2001 From: Amelie Kleber Date: Fri, 3 Jul 2026 00:06:52 +0200 Subject: [PATCH 1/7] Added 3 player max cut test for enumpoly --- pyproject.toml | 1 + ...g_from_local_max_cut_2_pure_1_mixed_eq.nfg | 1 + tests/test_nash.py | 22 +++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1b36d50efa..e2e4e55f26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,6 +102,7 @@ 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_strategy: tests of enumpoly_solve in mixed 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 mixed behaviors", 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 index f28f234f68..f1958f904b 100644 --- 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 @@ -6,6 +6,7 @@ NFG 1 R "2x2x2 game with 2 pure and 1 mixed equilibrium" - 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 +- In the mixed equilibrium all players mix strategies with equal probability (0.5, 0.5) - 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) diff --git a/tests/test_nash.py b/tests/test_nash.py index a3b171d0f2..93bd218590 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -411,6 +411,27 @@ class QREquilibriumTestCase: ] +ENUMPOLY_STRATEGY_CASES = [ + # 2x2x2 strategic form game based on local max cut -- 2 pure and 1 mixed + 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.enumpoly_solve, stop_after=None), + expected=[ + [d(1, 0), d(0, 1), d(1, 0)], + [d(0, 1), d(1, 0), d(0, 1)], + [d("1/2", "1/2"), d("1/2", "1/2"), d("1/2", "1/2")], + ], + prob_tol=TOL, + ), + marks=pytest.mark.nash_enumpoly_strategy, + id="test_enumpoly_strategy_1", + ), +] + + LP_STRATEGY_RATIONAL_CASES = [ pytest.param( EquilibriumTestCase( @@ -945,6 +966,7 @@ class QREquilibriumTestCase: CASES += ENUMPURE_CASES CASES += ENUMMIXED_RATIONAL_CASES CASES += ENUMMIXED_DOUBLE_CASES +CASES += ENUMPOLY_STRATEGY_CASES CASES += LP_STRATEGY_RATIONAL_CASES CASES += LP_STRATEGY_DOUBLE_CASES CASES += LCP_STRATEGY_RATIONAL_CASES From 10610b37f1c44e170db3b748493e669b025e847d Mon Sep 17 00:00:00 2001 From: Amelie Kleber Date: Fri, 3 Jul 2026 15:55:25 +0200 Subject: [PATCH 2/7] added strategic game tests for enumpoly: 3x3 coordination game, 3-player unique Nash (nau2004 sec4), 2x2x2 3 pure Nash (nau2004 sec5) --- tests/test_nash.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/test_nash.py b/tests/test_nash.py index 93bd218590..3d44f1be2e 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -425,10 +425,61 @@ class QREquilibriumTestCase: [d("1/2", "1/2"), d("1/2", "1/2"), d("1/2", "1/2")], ], prob_tol=TOL, + regret_tol=TOL, ), marks=pytest.mark.nash_enumpoly_strategy, id="test_enumpoly_strategy_1", ), + # coordination game with 3 pure and 4 mixed equilibria + 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, use_strategic=True), + 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)], + [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(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")], + ], + prob_tol=TOL, + regret_tol=TOL, + ), + marks=pytest.mark.nash_enumpoly_strategy, + id="test_enumpoly_strategy_2", + ), + # A three-player game with a unique Nash equilibrium in irrational mixed strategies (nau2004 sec4 catalog game) + pytest.param( + EquilibriumTestCase( + factory=functools.partial(gbt.catalog.load, "journals/ijgt/nau2004/sec4"), + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), + expected=[ + [d(0.6192325794725537, 0.3807674205274463), d(0.4798042226776053, 0.5201957773223946), d(0.3788253360656313, 0.6211746639343687)], + ], + prob_tol=TOL, + regret_tol=TOL, + ), + marks=pytest.mark.nash_enumpoly_strategy, + id="test_enumpoly_strategy_3", + ), + # A three-player 2x2x2 game with 3 pure, 2 incompletely mixed, and a continuum of completely mixed Nash equilibria (nau2004 sec5 catalog game) + pytest.param( + EquilibriumTestCase( + factory=functools.partial(gbt.catalog.load, "journals/ijgt/nau2004/sec5"), + solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), + expected=[ + [d(1, 0), d(0, 1), d(1, 0)], + [d(0, 1), d(1, 0), d(1, 0)], + [d(0, 1), d(0, 1), d(0, 1)], + ], + prob_tol=TOL, + regret_tol=TOL, + ), + marks=pytest.mark.nash_enumpoly_strategy, + id="test_enumpoly_strategy_4", + ), ] @@ -992,6 +1043,7 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: 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"): + print(eq) assert eq.max_regret() <= test_case.regret_tol with subtests.test(eq=i, check="strategy_profile"): expected = game.mixed_strategy_profile(rational=True, data=exp) From d22a093bb2d3cab04dd3c46e2134b3eb1b6c51bd Mon Sep 17 00:00:00 2001 From: Amelie Kleber Date: Fri, 3 Jul 2026 15:59:04 +0200 Subject: [PATCH 3/7] fix: clean up --- tests/test_nash.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 3d44f1be2e..ecec9b1a04 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -1043,7 +1043,6 @@ def test_nash_strategy_solver(test_case: EquilibriumTestCase, subtests) -> None: 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"): - print(eq) assert eq.max_regret() <= test_case.regret_tol with subtests.test(eq=i, check="strategy_profile"): expected = game.mixed_strategy_profile(rational=True, data=exp) From 8f8c04b1db650a1d90631985ddcc1ea1d062b0d0 Mon Sep 17 00:00:00 2001 From: Amelie Kleber Date: Fri, 3 Jul 2026 16:08:41 +0200 Subject: [PATCH 4/7] fix: clean up --- tests/test_nash.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index ecec9b1a04..5b3140afca 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -456,7 +456,9 @@ class QREquilibriumTestCase: factory=functools.partial(gbt.catalog.load, "journals/ijgt/nau2004/sec4"), solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), expected=[ - [d(0.6192325794725537, 0.3807674205274463), d(0.4798042226776053, 0.5201957773223946), d(0.3788253360656313, 0.6211746639343687)], + [d(0.6192325794725537, 0.3807674205274463), + d(0.4798042226776053, 0.5201957773223946), + d(0.3788253360656313, 0.6211746639343687)], ], prob_tol=TOL, regret_tol=TOL, From d405191ad91e92a00dbfaeae79a4ccfb190c3740 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:18:54 +0000 Subject: [PATCH 5/7] Initial plan From d3b5ac27c0163e7ebde262fc76fab97ecefd2df5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:22:06 +0000 Subject: [PATCH 6/7] Fix flake8 errors in tests/test_nash.py from PR #969 --- tests/test_nash.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 5b3140afca..0eb4c2a85d 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -450,15 +450,16 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_enumpoly_strategy, id="test_enumpoly_strategy_2", ), - # A three-player game with a unique Nash equilibrium in irrational mixed strategies (nau2004 sec4 catalog game) + # A three-player game with a unique Nash equilibrium in irrational mixed strategies + # (nau2004 sec4 catalog game) pytest.param( EquilibriumTestCase( factory=functools.partial(gbt.catalog.load, "journals/ijgt/nau2004/sec4"), solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), expected=[ - [d(0.6192325794725537, 0.3807674205274463), - d(0.4798042226776053, 0.5201957773223946), - d(0.3788253360656313, 0.6211746639343687)], + [d(0.6192325794725537, 0.3807674205274463), + d(0.4798042226776053, 0.5201957773223946), + d(0.3788253360656313, 0.6211746639343687)], ], prob_tol=TOL, regret_tol=TOL, @@ -466,7 +467,8 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_enumpoly_strategy, id="test_enumpoly_strategy_3", ), - # A three-player 2x2x2 game with 3 pure, 2 incompletely mixed, and a continuum of completely mixed Nash equilibria (nau2004 sec5 catalog game) + # A three-player 2x2x2 game with 3 pure, 2 incompletely mixed, and a continuum of + # completely mixed Nash equilibria (nau2004 sec5 catalog game) pytest.param( EquilibriumTestCase( factory=functools.partial(gbt.catalog.load, "journals/ijgt/nau2004/sec5"), From fa8e83239b2c7c11a7d9ed2d00fd3b9381951df3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:22:57 +0000 Subject: [PATCH 7/7] Fix inconsistent indentation in ENUMPOLY_STRATEGY_CASES expected lists --- tests/test_nash.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 0eb4c2a85d..6b105abd5c 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -436,13 +436,13 @@ class QREquilibriumTestCase: factory=functools.partial(games.create_EFG_for_nxn_bimatrix_coordination_game, n=3), solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None, use_strategic=True), 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)], - [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(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, 0, 0), d(1, 0, 0)], + [d(0, 1, 0), d(0, 1, 0)], + [d(0, 0, 1), d(0, 0, 1)], + [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(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")], ], prob_tol=TOL, regret_tol=TOL, @@ -474,9 +474,9 @@ class QREquilibriumTestCase: factory=functools.partial(gbt.catalog.load, "journals/ijgt/nau2004/sec5"), solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), expected=[ - [d(1, 0), d(0, 1), d(1, 0)], - [d(0, 1), d(1, 0), d(1, 0)], - [d(0, 1), d(0, 1), d(0, 1)], + [d(1, 0), d(0, 1), d(1, 0)], + [d(0, 1), d(1, 0), d(1, 0)], + [d(0, 1), d(0, 1), d(0, 1)], ], prob_tol=TOL, regret_tol=TOL,