From 65f2e347f3d437bf0267a26725742992315c7d57 Mon Sep 17 00:00:00 2001 From: rahulsavani Date: Fri, 7 Nov 2025 19:14:11 +0000 Subject: [PATCH 1/2] Create a weaker test for enumpoly that ignores output order --- tests/test_nash.py | 93 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 93f94c039..56cfa4dc2 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -95,42 +95,118 @@ def test_enummixed_rational(): [[[["1/4", "1/4", "1/4", "1/4"]], [["1/4", "1/4", "1/4", "1/4"]]]], 1, ), + # 3-player game + # ( + # games.create_mixed_behav_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) + # ), + ], +) +def test_enumpoly_ordered_behavior_rational( + game: gbt.Game, mixed_behav_prof_data: list, stop_after: typing.Union[None, int] +): + """Test calls of enumpoly for mixed behavior equilibria, rational precision, + using max_regret (internal consistency); and comparison to a set of previously + computed equilibria using 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. + + This is the "ordered" version where we test for the outputs coming in a specific + order; this is also an "unordered" version that includes examples where the order + could not be relied on even for repeated runs on the same system when on Windows + (vis github actions) + + """ + 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): + assert abs(eq.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 + + +@pytest.mark.nash +@pytest.mark.nash_enumpoly_behavior +@pytest.mark.parametrize( + "game,mixed_behav_prof_data,stop_after", + [ # 3-player game ( games.create_mixed_behav_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"]]], + [[["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]]], ], - 2, # 9 in total found by enumpoly + 9, ), ], ) -def test_enumpoly_behavior_rational( +def test_enumpoly_unordered_behavior_rational( game: gbt.Game, mixed_behav_prof_data: list, stop_after: typing.Union[None, int] ): """Test calls of enumpoly for mixed behavior equilibria, rational precision, using max_regret (internal consistency); and comparison to a set of previously computed equilibria using 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. + + 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. + + In this unordered version, once something from the expected set is found it is removed, + so we are checking for no duplicate outputs. """ if stop_after: result = gbt.nash.enumpoly_solve( - game, use_strategic=False, stop_after=stop_after + 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): - assert abs(eq.max_regret()) <= TOL - expected = game.mixed_behavior_profile(rational=True, data=exp) + + def are_the_same(game, found, candidate): 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 + if not abs(found[p][i][a] - candidate[p][i][a]) <= TOL: + return False + return True + + for eq in result.equilibria: + assert abs(eq.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 def test_lcp_strategy_double(): @@ -261,7 +337,6 @@ def test_lp_behavior_double(): @pytest.mark.nash -@pytest.mark.slow @pytest.mark.nash_lp_behavior @pytest.mark.parametrize( "game,mixed_behav_prof_data", From 46db6ed5193c6e06663d5d2b54ee21a544ba297c Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Mon, 10 Nov 2025 09:25:35 +0000 Subject: [PATCH 2/2] Update test_nash.py --- tests/test_nash.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 56cfa4dc2..ae68a8543 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -106,20 +106,20 @@ def test_enummixed_rational(): # ), ], ) -def test_enumpoly_ordered_behavior_rational( +def test_enumpoly_ordered_behavior( game: gbt.Game, mixed_behav_prof_data: list, stop_after: typing.Union[None, int] ): - """Test calls of enumpoly for mixed behavior equilibria, rational precision, + """Test calls of enumpoly for mixed behavior equilibria, using max_regret (internal consistency); and comparison to a set of previously computed equilibria using 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. This is the "ordered" version where we test for the outputs coming in a specific - order; this is also an "unordered" version that includes examples where the order - could not be relied on even for repeated runs on the same system when on Windows - (vis github actions) - + 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( @@ -162,10 +162,10 @@ def test_enumpoly_ordered_behavior_rational( ), ], ) -def test_enumpoly_unordered_behavior_rational( +def test_enumpoly_unordered_behavior( game: gbt.Game, mixed_behav_prof_data: list, stop_after: typing.Union[None, int] ): - """Test calls of enumpoly for mixed behavior equilibria, rational precision, + """Test calls of enumpoly for mixed behavior equilibria, using max_regret (internal consistency); and comparison to a set of previously computed equilibria using this function (regression test).