From 850327c86efa1e4785fb6668a75fd8e2cc078fd9 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Mon, 27 Apr 2026 15:41:14 +0100 Subject: [PATCH 01/10] Fix regression in action graph games --- ChangeLog | 7 +++++++ src/games/gameagg.cc | 1 + src/games/gamebagg.cc | 1 + 3 files changed, 9 insertions(+) diff --git a/ChangeLog b/ChangeLog index 4368c739ec..1767b272c5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,12 @@ # Changelog +## [16.7.0] - unreleased + +### Fixed +- Corrected a regression in action graph games that left the internal data structure not fully initialised, + leading to segmentation faults. + + ## [16.6.0] - 2026-03-24 ### Changed diff --git a/src/games/gameagg.cc b/src/games/gameagg.cc index 6341459c40..a702e8b011 100644 --- a/src/games/gameagg.cc +++ b/src/games/gameagg.cc @@ -186,6 +186,7 @@ GameAGGRep::GameAGGRep(std::shared_ptr p_aggPtr) : aggPtr(p_aggPtr) s->m_label = std::to_string(st++); }); } + IndexStrategies(); } Game GameAGGRep::Copy() const diff --git a/src/games/gamebagg.cc b/src/games/gamebagg.cc index dee69915a9..fea3bb695d 100644 --- a/src/games/gamebagg.cc +++ b/src/games/gamebagg.cc @@ -223,6 +223,7 @@ GameBAGGRep::GameBAGGRep(std::shared_ptr _baggPtr) }); } } + IndexStrategies(); } Game GameBAGGRep::Copy() const From 1bb0263fae66f0f20baf391b22344249411e995b Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Wed, 13 May 2026 20:14:45 +0100 Subject: [PATCH 02/10] tests for solving agg -- problem with max_regret (or lcp and enummixed solvers) --- tests/games.py | 2 ++ tests/test_nash.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/tests/games.py b/tests/games.py index 312854b43d..28870d1a77 100644 --- a/tests/games.py +++ b/tests/games.py @@ -14,6 +14,8 @@ def read_from_file(fn: str) -> gbt.Game: return gbt.read_efg(pathlib.Path("tests/test_games") / fn) elif fn.endswith(".nfg"): return gbt.read_nfg(pathlib.Path("tests/test_games") / fn) + elif fn.endswith(".agg"): + return gbt.read_agg(pathlib.Path("tests/test_games") / fn) else: raise ValueError(f"Unknown file extension in {fn}") diff --git a/tests/test_nash.py b/tests/test_nash.py index d0e664cd64..bf3f996583 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -159,6 +159,19 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_enumpure_strategy, id="test_enumpure_8", ), + # Action graph game + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2x2.agg"), + solver=functools.partial(gbt.nash.enumpure_solve), + expected=[ + [d(1, 0), d(1, 0)], + [d(0, 1), d(0, 1)], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test_enumpure_9", + ), ] @@ -232,6 +245,21 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_enummixed_strategy, id="test_enumixed_rational_5", ), + # Action graph game + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2x2.agg"), + solver=functools.partial(gbt.nash.enummixed_solve, rational=True), + expected=[ + [d(1, 0), d(1, 0)], + [d(0, 1), d(0, 1)], + [d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), + d("4186770418979088/4641467073735727", "454696654756639/4641467073735727")] + ], + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test_enummixed_rational_6", + ), ] @@ -589,6 +617,23 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_lcp_strategy, id="test_lcp_strategy_rational_11", ), + # Action graph game + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2x2.agg"), + solver=functools.partial( + gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=None + ), + expected=[ + [d(1, 0), d(1, 0)], + [d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), + d("4186770418979088/4641467073735727", "454696654756639/4641467073735727")], + [d(0, 1), d(0, 1)] + ], + ), + marks=pytest.mark.nash_lcp_strategy, + id="test_lcp_strategy_rational_12", + ), ] From 2a7ec1a8e0edcd9afe2b6d92cb40b0d38425aff0 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Thu, 14 May 2026 05:49:39 +0100 Subject: [PATCH 03/10] linting --- tests/test_nash.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index bf3f996583..50491ef399 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -253,8 +253,10 @@ class QREquilibriumTestCase: expected=[ [d(1, 0), d(1, 0)], [d(0, 1), d(0, 1)], - [d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), - d("4186770418979088/4641467073735727", "454696654756639/4641467073735727")] + [ + d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), + d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), + ], ], ), marks=pytest.mark.nash_enummixed_strategy, @@ -626,9 +628,11 @@ class QREquilibriumTestCase: ), expected=[ [d(1, 0), d(1, 0)], - [d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), - d("4186770418979088/4641467073735727", "454696654756639/4641467073735727")], - [d(0, 1), d(0, 1)] + [ + d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), + d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), + ], + [d(0, 1), d(0, 1)], ], ), marks=pytest.mark.nash_lcp_strategy, @@ -2203,16 +2207,13 @@ def test_nash_strategy_solver_w_start(test_case: EquilibriumTestCaseWithStart, s # 3-player perfect info game to test behavior two off equilibrium path pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.read_from_file, "3_player_PI_2_dev_off_eq_path.efg" - ), + factory=functools.partial(games.read_from_file, "3_player_PI_2_dev_off_eq_path.efg"), solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), expected=[ # candidate,10,10,1000,10000 [[d(1, 0)], [d(1, 0), d(1, 0, 0, 0)], [d(1, 0, 0, 0, 0)]], # candidate,01,00,0000,00000 - [[d(0, 1)], [d(1, 0), d(1, 0, 0, 0)], - [d(1, 0, 0, 0, 0)]], + [[d(0, 1)], [d(1, 0), d(1, 0, 0, 0)], [d(1, 0, 0, 0, 0)]], ], regret_tol=TOL, prob_tol=TOL, @@ -2222,9 +2223,7 @@ def test_nash_strategy_solver_w_start(test_case: EquilibriumTestCaseWithStart, s ), pytest.param( EquilibriumTestCase( - factory=functools.partial( - games.read_from_file, "3_player_PI_2_dev_off_eq_path.efg" - ), + factory=functools.partial(games.read_from_file, "3_player_PI_2_dev_off_eq_path.efg"), solver=functools.partial(gbt.nash.enumpoly_solve, stop_after=None), expected=[ [[d(1, 0)], [d(1, 0), d(1, 0, 0, 0)], [d(1, 0, 0, 0, 0)]], From 3dbe672c42464b768af33241744c943c744f6fec Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Thu, 14 May 2026 14:00:47 +0100 Subject: [PATCH 04/10] Implement outcomes in Python for action graph games. --- src/pygambit/gambit.pxd | 3 ++- src/pygambit/game.pxi | 4 ++-- src/pygambit/outcome.pxi | 14 ++++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 95ae672957..8a95315f22 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -249,7 +249,8 @@ cdef extern from "games/game.h": iterator begin() except + iterator end() except + - int IsTree() except + + bool IsTree() except + + bool IsAgg() except + string GetTitle() except + void SetTitle(string) except + diff --git a/src/pygambit/game.pxi b/src/pygambit/game.pxi index 7bee9a4470..011321fd49 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -858,8 +858,8 @@ class Game: self.game.deref().GetPlayer(pl+1).deref().GetStrategy(st+1) ) - if self.is_tree: - return TreeGameOutcome.wrap(self.game, psp) + if self.is_tree or self.game.deref().IsAgg(): + return DerivedGameOutcome.wrap(self.game, psp) else: outcome = Outcome.wrap(deref(deref(psp).deref()).GetOutcome()) if outcome.outcome != cython.cast(c_GameOutcome, NULL): diff --git a/src/pygambit/outcome.pxi b/src/pygambit/outcome.pxi index 06ff80a0e8..6f3e62f232 100644 --- a/src/pygambit/outcome.pxi +++ b/src/pygambit/outcome.pxi @@ -126,8 +126,10 @@ class Outcome: @cython.cclass -class TreeGameOutcome: - """Represents an outcome in a strategic game derived from an extensive game.""" +class DerivedGameOutcome: + """Represents an outcome in a strategic game derived from a game in another representation. + Such outcomes are one-to-one with the set of pure strategy profiles. + """ c_game = cython.declare(c_Game) psp = cython.declare(shared_ptr[c_PureStrategyProfile]) @@ -136,8 +138,8 @@ class TreeGameOutcome: @staticmethod @cython.cfunc - def wrap(game: c_Game, psp: shared_ptr[c_PureStrategyProfile]) -> TreeGameOutcome: - obj: TreeGameOutcome = TreeGameOutcome.__new__(TreeGameOutcome) + def wrap(game: c_Game, psp: shared_ptr[c_PureStrategyProfile]) -> DerivedGameOutcome: + obj: DerivedGameOutcome = DerivedGameOutcome.__new__(DerivedGameOutcome) obj.c_game = game obj.psp = psp return obj @@ -152,8 +154,8 @@ class TreeGameOutcome: def __eq__(self, other: typing.Any) -> bool: return ( - isinstance(other, TreeGameOutcome) and - deref(self.psp).deref() == deref(cython.cast(TreeGameOutcome, other).psp).deref() + isinstance(other, DerivedGameOutcome) and + deref(self.psp).deref() == deref(cython.cast(DerivedGameOutcome, other).psp).deref() ) def __getitem__(self, player: Player | str) -> Rational: From d77db64ad113c7e6ef4d821ceaf4bcdace290705 Mon Sep 17 00:00:00 2001 From: rahulsavani Date: Thu, 21 May 2026 23:12:59 +0300 Subject: [PATCH 05/10] small payoffs --- tests/test_nash.py | 48 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 50491ef399..192c183817 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -159,7 +159,7 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_enumpure_strategy, id="test_enumpure_8", ), - # Action graph game + # Action graph games pytest.param( EquilibriumTestCase( factory=functools.partial(games.read_from_file, "2x2.agg"), @@ -172,6 +172,18 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_enumpure_strategy, id="test_enumpure_9", ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2x2_small_payoffs.agg"), + solver=functools.partial(gbt.nash.enumpure_solve), + expected=[ + [d(1, 0), d(1, 0)], + [d(0, 1), d(0, 1)], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test_enumpure_10", + ), ] @@ -245,7 +257,7 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_enummixed_strategy, id="test_enumixed_rational_5", ), - # Action graph game + # Action graph games pytest.param( EquilibriumTestCase( factory=functools.partial(games.read_from_file, "2x2.agg"), @@ -262,6 +274,38 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_enummixed_strategy, id="test_enummixed_rational_6", ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2x2_rounded_payoffs.agg"), + solver=functools.partial(gbt.nash.enummixed_solve, rational=True), + expected=[ + [d(1, 0), d(1, 0)], + [d(0, 1), d(0, 1)], + [ + d("10/11", "1/11"), + d("10/11", "1/11"), + ], + ], + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test_enummixed_rational_7", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2x2_small_payoffs.agg"), + solver=functools.partial(gbt.nash.enummixed_solve, rational=True), + expected=[ + [d(1, 0), d(1, 0)], + [d(0, 1), d(0, 1)], + [ + d("1/2", "1/2"), + d("1/2", "1/2"), + ], + ], + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test_enummixed_rational_8", + ), ] From f94b4b61848e2b10c05029fdaaddadc1890b68f2 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Thu, 4 Jun 2026 06:19:44 +0100 Subject: [PATCH 06/10] double not rational tests for .agg --- tests/test_games/2x2_rounded_payoffs.agg | 49 +++++++ tests/test_games/2x2_small_payoffs.agg | 49 +++++++ tests/test_nash.py | 168 ++++++++++++----------- 3 files changed, 186 insertions(+), 80 deletions(-) create mode 100644 tests/test_games/2x2_rounded_payoffs.agg create mode 100644 tests/test_games/2x2_small_payoffs.agg diff --git a/tests/test_games/2x2_rounded_payoffs.agg b/tests/test_games/2x2_rounded_payoffs.agg new file mode 100644 index 0000000000..f86074818c --- /dev/null +++ b/tests/test_games/2x2_rounded_payoffs.agg @@ -0,0 +1,49 @@ +#AGG +# Generated by GAMUT v1.0.1 +# Random Symmetric Action Graph Game +# Game Parameter Values: +# Random seed: 1306765487422 +# Cmd Line: -players 2 -actions 2 -g RandomSymmetricAGG -output SpecialOutput -random_params -f 2x2.agg +# Players: 2 +# Actions: 2 2 +# players: 2 +# actions: [2] +# graph: RandomGraph +# graph_params: null +# Graph Params: +# { nodes: 2, edges: 4, sym_edges: false, reflex_ok: true } +# Players: 2 +# Actions: [ 2 2 ] + +#number of players: +2 +#number of action nodes: +2 +#number of func nodes: +0 + +#sizes of action sets: +2 2 + +#action sets: +0 1 +0 1 + + +#the action graph: +2 0 1 +2 1 0 + +#the types of func nodes: +#0: sum +#1: existence +#2: highest +#3: lowest + + +#the payoffs: +#now the payoff values: one row per action node. +#For each row: first, the type of the payoff format +#Then payoffs are given in lexicographical order of the input configurations +0 35 -4 +0 -10 95 diff --git a/tests/test_games/2x2_small_payoffs.agg b/tests/test_games/2x2_small_payoffs.agg new file mode 100644 index 0000000000..9895ea7310 --- /dev/null +++ b/tests/test_games/2x2_small_payoffs.agg @@ -0,0 +1,49 @@ +#AGG +# Generated by GAMUT v1.0.1 +# Random Symmetric Action Graph Game +# Game Parameter Values: +# Random seed: 1306765487422 +# Cmd Line: -players 2 -actions 2 -g RandomSymmetricAGG -output SpecialOutput -random_params -f 2x2.agg +# Players: 2 +# Actions: 2 2 +# players: 2 +# actions: [2] +# graph: RandomGraph +# graph_params: null +# Graph Params: +# { nodes: 2, edges: 4, sym_edges: false, reflex_ok: true } +# Players: 2 +# Actions: [ 2 2 ] + +#number of players: +2 +#number of action nodes: +2 +#number of func nodes: +0 + +#sizes of action sets: +2 2 + +#action sets: +0 1 +0 1 + + +#the action graph: +2 0 1 +2 1 0 + +#the types of func nodes: +#0: sum +#1: existence +#2: highest +#3: lowest + + +#the payoffs: +#now the payoff values: one row per action node. +#For each row: first, the type of the payoff format +#Then payoffs are given in lexicographical order of the input configurations +0 4 -1 +0 -2 5 diff --git a/tests/test_nash.py b/tests/test_nash.py index 192c183817..a4f06ed784 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -172,18 +172,18 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_enumpure_strategy, id="test_enumpure_9", ), - pytest.param( - EquilibriumTestCase( - factory=functools.partial(games.read_from_file, "2x2_small_payoffs.agg"), - solver=functools.partial(gbt.nash.enumpure_solve), - expected=[ - [d(1, 0), d(1, 0)], - [d(0, 1), d(0, 1)], - ], - ), - marks=pytest.mark.nash_enumpure_strategy, - id="test_enumpure_10", - ), + # pytest.param( + # EquilibriumTestCase( + # factory=functools.partial(games.read_from_file, "2x2_small_payoffs.agg"), + # solver=functools.partial(gbt.nash.enumpure_solve), + # expected=[ + # [d(1, 0), d(1, 0)], + # [d(0, 1), d(0, 1)], + # ], + # ), + # marks=pytest.mark.nash_enumpure_strategy, + # id="test_enumpure_10", + # ), ] @@ -257,55 +257,6 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_enummixed_strategy, id="test_enumixed_rational_5", ), - # Action graph games - pytest.param( - EquilibriumTestCase( - factory=functools.partial(games.read_from_file, "2x2.agg"), - solver=functools.partial(gbt.nash.enummixed_solve, rational=True), - expected=[ - [d(1, 0), d(1, 0)], - [d(0, 1), d(0, 1)], - [ - d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), - d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), - ], - ], - ), - marks=pytest.mark.nash_enummixed_strategy, - id="test_enummixed_rational_6", - ), - pytest.param( - EquilibriumTestCase( - factory=functools.partial(games.read_from_file, "2x2_rounded_payoffs.agg"), - solver=functools.partial(gbt.nash.enummixed_solve, rational=True), - expected=[ - [d(1, 0), d(1, 0)], - [d(0, 1), d(0, 1)], - [ - d("10/11", "1/11"), - d("10/11", "1/11"), - ], - ], - ), - marks=pytest.mark.nash_enummixed_strategy, - id="test_enummixed_rational_7", - ), - pytest.param( - EquilibriumTestCase( - factory=functools.partial(games.read_from_file, "2x2_small_payoffs.agg"), - solver=functools.partial(gbt.nash.enummixed_solve, rational=True), - expected=[ - [d(1, 0), d(1, 0)], - [d(0, 1), d(0, 1)], - [ - d("1/2", "1/2"), - d("1/2", "1/2"), - ], - ], - ), - marks=pytest.mark.nash_enummixed_strategy, - id="test_enummixed_rational_8", - ), ] @@ -385,6 +336,61 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_enummixed_strategy, id="test_enumixed_double_5", ), + # Action graph games + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2x2.agg"), + solver=functools.partial(gbt.nash.enummixed_solve, rational=False), + expected=[ + [d(1, 0), d(1, 0)], + [d(0, 1), d(0, 1)], + [ + d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), + d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), + ], + ], + prob_tol=TOL, + regret_tol=TOL, + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test_enummixed_rational_6", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2x2_rounded_payoffs.agg"), + solver=functools.partial(gbt.nash.enummixed_solve, rational=False), + expected=[ + [d(1, 0), d(1, 0)], + [d(0, 1), d(0, 1)], + [ + d("10/11", "1/11"), + d("10/11", "1/11"), + ], + ], + prob_tol=TOL, + regret_tol=TOL, + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test_enummixed_rational_7", + ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2x2_small_payoffs.agg"), + solver=functools.partial(gbt.nash.enummixed_solve, rational=False), + expected=[ + [d(1, 0), d(1, 0)], + [d(0, 1), d(0, 1)], + [ + d("1/2", "1/2"), + d("1/2", "1/2"), + ], + ], + prob_tol=TOL, + regret_tol=TOL, + ), + marks=pytest.mark.nash_enummixed_strategy, + id="test_enummixed_rational_8", + ), ] @@ -663,25 +669,6 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_lcp_strategy, id="test_lcp_strategy_rational_11", ), - # Action graph game - pytest.param( - EquilibriumTestCase( - factory=functools.partial(games.read_from_file, "2x2.agg"), - solver=functools.partial( - gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=None - ), - expected=[ - [d(1, 0), d(1, 0)], - [ - d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), - d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), - ], - [d(0, 1), d(0, 1)], - ], - ), - marks=pytest.mark.nash_lcp_strategy, - id="test_lcp_strategy_rational_12", - ), ] @@ -846,6 +833,27 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_lcp_strategy, id="test_lcp_strategy_double_11", ), + # Action graph game + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2x2.agg"), + solver=functools.partial( + gbt.nash.lcp_solve, rational=True, use_strategic=True, stop_after=None + ), + expected=[ + [d(1, 0), d(1, 0)], + [ + d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), + d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), + ], + [d(0, 1), d(0, 1)], + ], + regret_tol=TOL, + prob_tol=TOL, + ), + marks=pytest.mark.nash_lcp_strategy, + id="test_lcp_strategy_rational_12", + ), ] From 67ca25c5c030571f176b8c261bc75ffa543b56ad Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Sat, 6 Jun 2026 04:56:04 +0100 Subject: [PATCH 07/10] no crazy payoffs --- tests/test_games/2x2.agg | 20 +--------- tests/test_games/2x2_rounded_payoffs.agg | 49 ------------------------ tests/test_games/2x2_small_payoffs.agg | 15 -------- tests/test_nash.py | 28 +++----------- 4 files changed, 7 insertions(+), 105 deletions(-) delete mode 100644 tests/test_games/2x2_rounded_payoffs.agg diff --git a/tests/test_games/2x2.agg b/tests/test_games/2x2.agg index 3d1ef04d62..e29e5e8c98 100644 --- a/tests/test_games/2x2.agg +++ b/tests/test_games/2x2.agg @@ -1,20 +1,4 @@ #AGG -# Generated by GAMUT v1.0.1 -# Random Symmetric Action Graph Game -# Game Parameter Values: -# Random seed: 1306765487422 -# Cmd Line: -players 2 -actions 2 -g RandomSymmetricAGG -output SpecialOutput -random_params -f 2x2.agg -# Players: 2 -# Actions: 2 2 -# players: 2 -# actions: [2] -# graph: RandomGraph -# graph_params: null -# Graph Params: -# { nodes: 2, edges: 4, sym_edges: false, reflex_ok: true } -# Players: 2 -# Actions: [ 2 2 ] - #number of players: 2 #number of action nodes: @@ -45,5 +29,5 @@ #now the payoff values: one row per action node. #For each row: first, the type of the payoff format #Then payoffs are given in lexicographical order of the input configurations -0 35.622809717175556 -3.7188980070375948 -0 -10.180526107272556 95.1203958671928 +0 35 -4 +0 -10 95 diff --git a/tests/test_games/2x2_rounded_payoffs.agg b/tests/test_games/2x2_rounded_payoffs.agg deleted file mode 100644 index f86074818c..0000000000 --- a/tests/test_games/2x2_rounded_payoffs.agg +++ /dev/null @@ -1,49 +0,0 @@ -#AGG -# Generated by GAMUT v1.0.1 -# Random Symmetric Action Graph Game -# Game Parameter Values: -# Random seed: 1306765487422 -# Cmd Line: -players 2 -actions 2 -g RandomSymmetricAGG -output SpecialOutput -random_params -f 2x2.agg -# Players: 2 -# Actions: 2 2 -# players: 2 -# actions: [2] -# graph: RandomGraph -# graph_params: null -# Graph Params: -# { nodes: 2, edges: 4, sym_edges: false, reflex_ok: true } -# Players: 2 -# Actions: [ 2 2 ] - -#number of players: -2 -#number of action nodes: -2 -#number of func nodes: -0 - -#sizes of action sets: -2 2 - -#action sets: -0 1 -0 1 - - -#the action graph: -2 0 1 -2 1 0 - -#the types of func nodes: -#0: sum -#1: existence -#2: highest -#3: lowest - - -#the payoffs: -#now the payoff values: one row per action node. -#For each row: first, the type of the payoff format -#Then payoffs are given in lexicographical order of the input configurations -0 35 -4 -0 -10 95 diff --git a/tests/test_games/2x2_small_payoffs.agg b/tests/test_games/2x2_small_payoffs.agg index 9895ea7310..cdbd6fe602 100644 --- a/tests/test_games/2x2_small_payoffs.agg +++ b/tests/test_games/2x2_small_payoffs.agg @@ -1,19 +1,4 @@ #AGG -# Generated by GAMUT v1.0.1 -# Random Symmetric Action Graph Game -# Game Parameter Values: -# Random seed: 1306765487422 -# Cmd Line: -players 2 -actions 2 -g RandomSymmetricAGG -output SpecialOutput -random_params -f 2x2.agg -# Players: 2 -# Actions: 2 2 -# players: 2 -# actions: [2] -# graph: RandomGraph -# graph_params: null -# Graph Params: -# { nodes: 2, edges: 4, sym_edges: false, reflex_ok: true } -# Players: 2 -# Actions: [ 2 2 ] #number of players: 2 diff --git a/tests/test_nash.py b/tests/test_nash.py index a4f06ed784..9988a7c7be 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -341,24 +341,6 @@ class QREquilibriumTestCase: EquilibriumTestCase( factory=functools.partial(games.read_from_file, "2x2.agg"), solver=functools.partial(gbt.nash.enummixed_solve, rational=False), - expected=[ - [d(1, 0), d(1, 0)], - [d(0, 1), d(0, 1)], - [ - d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), - d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), - ], - ], - prob_tol=TOL, - regret_tol=TOL, - ), - marks=pytest.mark.nash_enummixed_strategy, - id="test_enummixed_rational_6", - ), - pytest.param( - EquilibriumTestCase( - factory=functools.partial(games.read_from_file, "2x2_rounded_payoffs.agg"), - solver=functools.partial(gbt.nash.enummixed_solve, rational=False), expected=[ [d(1, 0), d(1, 0)], [d(0, 1), d(0, 1)], @@ -371,7 +353,7 @@ class QREquilibriumTestCase: regret_tol=TOL, ), marks=pytest.mark.nash_enummixed_strategy, - id="test_enummixed_rational_7", + id="test_enummixed_double_7", ), pytest.param( EquilibriumTestCase( @@ -389,7 +371,7 @@ class QREquilibriumTestCase: regret_tol=TOL, ), marks=pytest.mark.nash_enummixed_strategy, - id="test_enummixed_rational_8", + id="test_enummixed_double_8", ), ] @@ -843,8 +825,8 @@ class QREquilibriumTestCase: expected=[ [d(1, 0), d(1, 0)], [ - d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), - d("4186770418979088/4641467073735727", "454696654756639/4641467073735727"), + d("10/11", "1/11"), + d("10/11", "1/11"), ], [d(0, 1), d(0, 1)], ], @@ -852,7 +834,7 @@ class QREquilibriumTestCase: prob_tol=TOL, ), marks=pytest.mark.nash_lcp_strategy, - id="test_lcp_strategy_rational_12", + id="test_lcp_strategy_double_12", ), ] From 375c61e963a44d35f4e4aa1febd0383815942bd2 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Mon, 8 Jun 2026 12:19:23 +0100 Subject: [PATCH 08/10] two BAGG tests --- tests/games.py | 2 + tests/test_games/Bayesian-Coffee-3-2-2-3.bagg | 211 ++++++++++++++++++ tests/test_nash.py | 86 +++++-- 3 files changed, 284 insertions(+), 15 deletions(-) create mode 100644 tests/test_games/Bayesian-Coffee-3-2-2-3.bagg diff --git a/tests/games.py b/tests/games.py index 28870d1a77..b3e1753f1b 100644 --- a/tests/games.py +++ b/tests/games.py @@ -16,6 +16,8 @@ def read_from_file(fn: str) -> gbt.Game: return gbt.read_nfg(pathlib.Path("tests/test_games") / fn) elif fn.endswith(".agg"): return gbt.read_agg(pathlib.Path("tests/test_games") / fn) + elif fn.endswith(".bagg"): + return gbt.read_bagg(pathlib.Path("tests/test_games") / fn) else: raise ValueError(f"Unknown file extension in {fn}") diff --git a/tests/test_games/Bayesian-Coffee-3-2-2-3.bagg b/tests/test_games/Bayesian-Coffee-3-2-2-3.bagg new file mode 100644 index 0000000000..5efd66b524 --- /dev/null +++ b/tests/test_games/Bayesian-Coffee-3-2-2-3.bagg @@ -0,0 +1,211 @@ +#BAGG +3 +13 +18 +2 2 2 +0.5 0.5 +0.5 0.5 +0.5 0.5 +7 7 +7 7 +7 7 +0 1 2 3 4 5 6 +0 7 8 9 10 11 12 +0 1 2 3 4 5 6 +0 7 8 9 10 11 12 +0 1 2 3 4 5 6 +0 7 8 9 10 11 12 +0 +3 13 19 25 +3 14 20 26 +3 15 21 27 +3 16 22 28 +3 17 23 29 +3 18 24 30 +3 13 19 25 +3 14 20 26 +3 15 21 27 +3 16 22 28 +3 17 23 29 +3 18 24 30 +2 1 7 +2 2 8 +2 3 9 +2 4 10 +2 5 11 +2 6 12 +3 14 16 17 +5 13 15 16 17 18 +3 14 17 18 +3 13 14 17 +5 13 14 15 16 18 +3 14 15 17 +2 15 18 +0 +2 13 16 +2 15 18 +0 +2 13 16 + +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 + +1 +1 +[ ] 89 + +1 +10 +[ 1 0 0 ] 94 +[ 3 0 0 ] 61 +[ 2 0 0 ] 35 +[ 1 2 0 ] 29 +[ 2 1 0 ] 58 +[ 1 1 0 ] 98 +[ 1 0 2 ] 38 +[ 1 1 1 ] 43 +[ 2 0 1 ] 35 +[ 1 0 1 ] 58 + +1 +6 +[ 3 0 0 ] 68 +[ 1 2 0 ] 65 +[ 2 1 0 ] 31 +[ 1 0 0 ] 11 +[ 1 1 0 ] 34 +[ 2 0 0 ] 58 + +1 +10 +[ 3 0 0 ] 96 +[ 1 2 0 ] 79 +[ 2 1 0 ] 86 +[ 1 0 2 ] 55 +[ 1 1 1 ] 79 +[ 2 0 1 ] 24 +[ 1 0 0 ] 37 +[ 1 0 1 ] 36 +[ 1 1 0 ] 99 +[ 2 0 0 ] 90 + +1 +10 +[ 1 0 0 ] 9 +[ 1 2 0 ] 38 +[ 1 1 0 ] 27 +[ 1 0 2 ] 50 +[ 1 1 1 ] 41 +[ 1 0 1 ] 96 +[ 3 0 0 ] 74 +[ 2 0 1 ] 82 +[ 2 1 0 ] 75 +[ 2 0 0 ] 99 + +1 +6 +[ 1 0 0 ] 55 +[ 1 2 0 ] 70 +[ 1 1 0 ] 46 +[ 3 0 0 ] 15 +[ 2 1 0 ] 60 +[ 2 0 0 ] 29 + +1 +10 +[ 1 0 0 ] 81 +[ 1 0 2 ] 23 +[ 1 0 1 ] 63 +[ 1 2 0 ] 54 +[ 1 1 1 ] 60 +[ 1 1 0 ] 0 +[ 3 0 0 ] 71 +[ 2 1 0 ] 14 +[ 2 0 1 ] 78 +[ 2 0 0 ] 37 + +1 +10 +[ 1 0 0 ] 3 +[ 3 0 0 ] 30 +[ 2 0 0 ] 7 +[ 1 2 0 ] 85 +[ 2 1 0 ] 35 +[ 1 1 0 ] 26 +[ 1 0 2 ] 41 +[ 1 1 1 ] 52 +[ 2 0 1 ] 79 +[ 1 0 1 ] 48 + +1 +6 +[ 3 0 0 ] 48 +[ 1 2 0 ] 58 +[ 2 1 0 ] 58 +[ 1 0 0 ] 79 +[ 1 1 0 ] 5 +[ 2 0 0 ] 3 + +1 +10 +[ 3 0 0 ] 53 +[ 1 2 0 ] 81 +[ 2 1 0 ] 1 +[ 1 0 2 ] 79 +[ 1 1 1 ] 34 +[ 2 0 1 ] 90 +[ 1 0 0 ] 21 +[ 1 0 1 ] 86 +[ 1 1 0 ] 38 +[ 2 0 0 ] 70 + +1 +10 +[ 3 0 0 ] 82 +[ 1 0 2 ] 72 +[ 2 0 1 ] 77 +[ 1 2 0 ] 16 +[ 1 1 1 ] 64 +[ 2 1 0 ] 33 +[ 1 0 0 ] 83 +[ 1 1 0 ] 75 +[ 1 0 1 ] 51 +[ 2 0 0 ] 16 + +1 +6 +[ 3 0 0 ] 43 +[ 1 2 0 ] 84 +[ 2 1 0 ] 0 +[ 1 0 0 ] 20 +[ 1 1 0 ] 87 +[ 2 0 0 ] 73 + +1 +10 +[ 3 0 0 ] 7 +[ 1 2 0 ] 46 +[ 2 1 0 ] 13 +[ 1 0 2 ] 46 +[ 1 1 1 ] 59 +[ 2 0 1 ] 26 +[ 1 0 0 ] 68 +[ 1 0 1 ] 67 +[ 1 1 0 ] 87 +[ 2 0 0 ] 78 diff --git a/tests/test_nash.py b/tests/test_nash.py index bf9170f886..b9d661a403 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -163,7 +163,7 @@ class QREquilibriumTestCase: pytest.param( EquilibriumTestCase( factory=functools.partial(games.read_from_file, "2x2.agg"), - solver=functools.partial(gbt.nash.enumpure_solve), + solver=gbt.nash.enumpure_solve, expected=[ [d(1, 0), d(1, 0)], [d(0, 1), d(0, 1)], @@ -172,18 +172,53 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_enumpure_strategy, id="test_enumpure_9", ), - # pytest.param( - # EquilibriumTestCase( - # factory=functools.partial(games.read_from_file, "2x2_small_payoffs.agg"), - # solver=functools.partial(gbt.nash.enumpure_solve), - # expected=[ - # [d(1, 0), d(1, 0)], - # [d(0, 1), d(0, 1)], - # ], - # ), - # marks=pytest.mark.nash_enumpure_strategy, - # id="test_enumpure_10", - # ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "2x2_small_payoffs.agg"), + solver=gbt.nash.enumpure_solve, + expected=[ + [d(1, 0), d(1, 0)], + [d(0, 1), d(0, 1)], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test_enumpure_10", + ), + # Bayesian Action graph games + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "Bayesian-Coffee-3-2-2-3.bagg"), + solver=gbt.nash.enumpure_solve, + expected=[ + [ + [0, 1, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + ], + [ + [1, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + ], + [ + [1, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + ], + ], + ), + marks=pytest.mark.nash_enumpure_strategy, + id="test_enumpure_11", + ), ] @@ -855,6 +890,26 @@ class QREquilibriumTestCase: marks=pytest.mark.nash_logit_strategy, id="test_logic_strategy_1", ), + pytest.param( + EquilibriumTestCase( + factory=functools.partial(games.read_from_file, "Bayesian-Coffee-3-2-2-3.bagg"), + solver=functools.partial(gbt.nash.logit_solve, use_strategic=True), + expected=[ + [ + [0.9124962637548039, 0.08750373624519617, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.9124962637547669, 0.08750373624523317, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.9124962637547208, 0.08750373624527921, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + ] + ], + prob_tol=TOL_HUGE, + regret_tol=TOL_LARGE, + ), + marks=pytest.mark.nash_logit_strategy, + id="test_logic_strategy_2", + ), ] @@ -2339,8 +2394,9 @@ def test_nash_strategy_solver_w_start(test_case: EquilibriumTestCaseWithStart, s ), pytest.param( EquilibriumTestCase( - factory=functools.partial(games.read_from_file, - "chance_root_5_moves_no_nonterm_player_nodes.efg"), + factory=functools.partial( + games.read_from_file, "chance_root_5_moves_no_nonterm_player_nodes.efg" + ), solver=gbt.nash.logit_solve, expected=[ [[]] # Zero-dimension edge case (two players) From 04b4a937f2aa99c67a8f1b0d9d7b37ff98231566 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Thu, 18 Jun 2026 12:17:33 +0100 Subject: [PATCH 09/10] logic -> logit typo in test id --- tests/test_nash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index b9d661a403..84c315b54e 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -908,7 +908,7 @@ class QREquilibriumTestCase: regret_tol=TOL_LARGE, ), marks=pytest.mark.nash_logit_strategy, - id="test_logic_strategy_2", + id="test_logit_strategy_2", ), ] From 3025d1192b12941f794adb330ec6d125dc64d418 Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Thu, 18 Jun 2026 12:27:49 +0100 Subject: [PATCH 10/10] reduce logit bagg tolerance --- tests/test_nash.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 84c315b54e..a3b171d0f2 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -893,7 +893,7 @@ class QREquilibriumTestCase: pytest.param( EquilibriumTestCase( factory=functools.partial(games.read_from_file, "Bayesian-Coffee-3-2-2-3.bagg"), - solver=functools.partial(gbt.nash.logit_solve, use_strategic=True), + solver=gbt.nash.logit_solve, expected=[ [ [0.9124962637548039, 0.08750373624519617, 0.0, 0.0, 0.0, 0.0, 0.0], @@ -904,7 +904,7 @@ class QREquilibriumTestCase: [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ] ], - prob_tol=TOL_HUGE, + prob_tol=TOL_LARGE, regret_tol=TOL_LARGE, ), marks=pytest.mark.nash_logit_strategy,