From 34fad9227acc6cf8454176778ad764047e8417cd Mon Sep 17 00:00:00 2001 From: Rahul Savani Date: Fri, 6 Oct 2023 17:21:49 +0100 Subject: [PATCH 01/10] mixed behav profile: payoff, infoset_value, and action_value all throw value errors when called with the chance player --- ChangeLog | 2 ++ src/pygambit/behav.pxi | 21 ++++++++++++++++++--- src/pygambit/tests/test_behav.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index dabba37e1..787c83198 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,8 @@ - Empty or all-whitespace strings cannot be used to access members of games in pygambit. - Remaining compatibility code for wxWidgets 2.x removed from graphical interface. - Migrated to pytest for testing of pygambit. +- ValueErrors raised for mixed behavior profiles when payoff, action_value, or infoset_value are + called with the chance player. ## [16.1.0a3] - 2023-09-29 diff --git a/src/pygambit/behav.pxi b/src/pygambit/behav.pxi index fca206c3b..6f056a7f3 100644 --- a/src/pygambit/behav.pxi +++ b/src/pygambit/behav.pxi @@ -284,8 +284,13 @@ class MixedBehaviorProfile: If `player` is a `Player` from a different game. KeyError If `player` is a string and no player in the game has that label. + ValueError + If `player` resolves to the chance player """ - return self._payoff(self.game._resolve_player(player, 'payoff')) + resolved_player = self.game._resolve_player(player, 'payoff') + if resolved_player.is_chance: + raise ValueError("payoff() is not defined for the chance player") + return self._payoff(resolved_player) def infoset_value(self, infoset: typing.Union[Infoset, str]): """Returns the expected payoff to the player conditional on reaching an information set, @@ -303,8 +308,13 @@ class MixedBehaviorProfile: If `infoset` is an `Infoset` from a different game. KeyError If `infoset` is a string and no information set in the game has that label. + ValueError + If `infoset` resolves to an infoset that belongs to the chance player """ - return self._infoset_value(self.game._resolve_infoset(infoset, 'infoset_value')) + resolved_infoset = self.game._resolve_infoset(infoset, 'infoset_value') + if resolved_infoset.player.is_chance: + raise ValueError("infoset_value() is not defined for the chance player") + return self._infoset_value(resolved_infoset) def action_value(self, action: typing.Union[Action, str]): """Returns the expected payoff to the player of playing an action conditional on reaching its @@ -322,8 +332,13 @@ class MixedBehaviorProfile: If `action` is an `Action` from a different game. KeyError If `action` is a string and no action in the game has that label. + ValueError + If `action` resolves to an action that belongs to the chance player """ - return self._action_value(self.game._resolve_action(action, 'action_value')) + resolved_action = self.game._resolve_action(action, 'action_value') + if resolved_action.infoset.player.is_chance: + raise ValueError("action_value() is not defined for the chance player") + return self._action_value(resolved_action) def infoset_prob(self, infoset: typing.Union[Infoset, str]): """Returns the probability with which an information set is reached. diff --git a/src/pygambit/tests/test_behav.py b/src/pygambit/tests/test_behav.py index faff4520e..5f5697a7b 100644 --- a/src/pygambit/tests/test_behav.py +++ b/src/pygambit/tests/test_behav.py @@ -11,6 +11,12 @@ def setUp(self): self.profile_double = self.game.mixed_behavior_profile() self.profile_rational = self.game.mixed_behavior_profile(True) + self.game_with_chance = gbt.Game.read_game( + "test_games/complicated_extensive_game.efg" + ) + self.profile_double_with_chance = self.game_with_chance.mixed_behavior_profile() + self.profile_rational_with_chance = self.game_with_chance.mixed_behavior_profile(True) + def tearDown(self): del self.game del self.profile_double @@ -724,3 +730,29 @@ def test_infoset_belief(self): self.profile_rational.belief(self.game.infosets[0].members[0]) == gbt.Rational(1, 1) ) + + def test_payoff_with_chance_player(self): + """Test to ensure that payoff called with the chance player raises a ValueError""" + chance_player = self.game_with_chance.players.chance + self.assertRaises(ValueError, self.profile_double_with_chance.payoff, chance_player) + self.assertRaises(ValueError, self.profile_rational_with_chance.payoff, chance_player) + + def test_infoset_value_with_chance_player_infoset(self): + """Test to ensure that infoset_value called with an infoset of the chance player + raises a ValueError + """ + chance_infoset = self.game_with_chance.players.chance.infosets[0] + self.assertRaises(ValueError, self.profile_double_with_chance.infoset_value, + chance_infoset) + self.assertRaises(ValueError, self.profile_rational_with_chance.infoset_value, + chance_infoset) + + def test_action_value_with_chance_player_action(self): + """Test to ensure that action_value called with an action of the chance player + raises a ValueError + """ + chance_action = self.game_with_chance.players.chance.infosets[0].actions[0] + self.assertRaises(ValueError, self.profile_double_with_chance.action_value, + chance_action) + self.assertRaises(ValueError, self.profile_rational_with_chance.action_value, + chance_action) From 6691bd59d74cbf84357e58ba311fa7fc2ee1ce7b Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 12 Oct 2023 11:15:38 +0100 Subject: [PATCH 02/10] Clean up manifest for Python source distribution and test builds of source tarballs/distribution in CI. --- .github/workflows/python.yml | 10 +++++++--- .github/workflows/tools.yml | 22 ++++++++++++++++------ MANIFEST.in | 11 ++++++----- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 6cc2cc00a..8feb8181b 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -20,10 +20,14 @@ jobs: - name: Set up dependencies run: | python -m pip install --upgrade pip - pip install cython pytest wheel lxml numpy scipy - - name: Build extension + pip install setuptools cython pytest wheel lxml numpy scipy + - name: Build source distribution + run: + python setup.py sdist + - name: Build from source distribution run: | - python -m pip install -v . + cd dist + pip install -v pygambit*.tar.gz - name: Run tests run: | cd src/pygambit/tests diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 6283ab1f8..40fafbde1 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -22,12 +22,22 @@ jobs: wx3.1-headers \ wx-common \ libnotify-dev - - run: aclocal - - run: automake --add-missing - - run: autoconf - - run: ./configure - - run: make - - run: sudo make install + - name: Configure build with autotools + run: | + aclocal + automake --add-missing + autoconf + ./configure + - name: Make source tarball + run: make dist + - name: Build from source tarball + run: | + tar zxvf gambit*.tar.gz + rm gambit*.tar.gz + cd gambit-* + ./configure + make + sudo make install macos: runs-on: macos-latest diff --git a/MANIFEST.in b/MANIFEST.in index 5e4d61cbe..2ef7e783e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,12 +2,13 @@ recursive-include src/core *.cc *.h *.imp recursive-include src/games *.cc *.h *.imp recursive-include src/solvers *.c *.cc *.h *.imp include src/gambit.h -include src/tools/nfglp.cc -include src/tools/nfglp.h -include src/tools/efglp.cc -include src/tools/efglp.h +include src/tools/lp/nfglp.cc +include src/tools/lp/nfglp.h +include src/tools/lp/efglp.cc +include src/tools/lp/efglp.h include src/pygambit/*.pxd include src/pygambit/*.pyx include src/pygambit/*.pxi - +include src/pygambit/*.h +include src/README.rst From 5115f3ffdb9c54bb2fd85b508eb03d94c52ba500 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 12 Oct 2023 10:46:36 +0100 Subject: [PATCH 03/10] Migrate implementation of LP and enumpoly to src/solvers. This closes #350. --- MANIFEST.in | 4 - Makefile.am | 196 +++++++++--------- setup.py | 4 +- src/pygambit/gambit.pxd | 4 +- .../enumpoly/behavextend.cc | 0 src/{tools => solvers}/enumpoly/behavextend.h | 0 src/{tools => solvers}/enumpoly/efgensup.cc | 0 src/{tools => solvers}/enumpoly/efgensup.h | 0 src/{tools => solvers}/enumpoly/efgpoly.cc | 2 +- src/{tools => solvers}/enumpoly/gcomplex.cc | 0 src/{tools => solvers}/enumpoly/gcomplex.h | 0 src/{tools => solvers}/enumpoly/gnarray.h | 0 src/{tools => solvers}/enumpoly/gnarray.imp | 0 src/{tools => solvers}/enumpoly/gpartltr.cc | 0 src/{tools => solvers}/enumpoly/gpartltr.h | 0 src/{tools => solvers}/enumpoly/gpartltr.imp | 0 src/{tools => solvers}/enumpoly/gpoly.cc | 0 src/{tools => solvers}/enumpoly/gpoly.h | 0 src/{tools => solvers}/enumpoly/gpoly.imp | 0 src/{tools => solvers}/enumpoly/gpolylst.cc | 0 src/{tools => solvers}/enumpoly/gpolylst.h | 0 src/{tools => solvers}/enumpoly/gpolylst.imp | 0 src/{tools => solvers}/enumpoly/gsolver.cc | 0 src/{tools => solvers}/enumpoly/gsolver.h | 0 src/{tools => solvers}/enumpoly/gsolver.imp | 0 src/{tools => solvers}/enumpoly/gtree.h | 0 src/{tools => solvers}/enumpoly/gtree.imp | 0 src/{tools => solvers}/enumpoly/ideal.cc | 0 src/{tools => solvers}/enumpoly/ideal.h | 0 src/{tools => solvers}/enumpoly/ideal.imp | 0 src/{tools => solvers}/enumpoly/ineqsolv.cc | 0 src/{tools => solvers}/enumpoly/ineqsolv.h | 0 src/{tools => solvers}/enumpoly/ineqsolv.imp | 0 src/{tools => solvers}/enumpoly/interval.h | 0 src/{tools => solvers}/enumpoly/linrcomb.cc | 0 src/{tools => solvers}/enumpoly/linrcomb.h | 0 src/{tools => solvers}/enumpoly/linrcomb.imp | 0 src/{tools => solvers}/enumpoly/monomial.cc | 0 src/{tools => solvers}/enumpoly/monomial.h | 0 src/{tools => solvers}/enumpoly/monomial.imp | 0 src/{tools => solvers}/enumpoly/nfgcpoly.cc | 0 src/{tools => solvers}/enumpoly/nfgcpoly.h | 0 src/{tools => solvers}/enumpoly/nfgensup.cc | 0 src/{tools => solvers}/enumpoly/nfgensup.h | 0 src/{tools => solvers}/enumpoly/nfghs.cc | 0 src/{tools => solvers}/enumpoly/nfghs.h | 0 src/{tools => solvers}/enumpoly/nfgpoly.cc | 2 +- src/{tools => solvers}/enumpoly/odometer.cc | 0 src/{tools => solvers}/enumpoly/odometer.h | 0 src/{tools => solvers}/enumpoly/pelclass.cc | 0 src/{tools => solvers}/enumpoly/pelclass.h | 2 +- .../enumpoly/pelican}/pelclhpk.cc | 0 .../enumpoly/pelican}/pelclhpk.h | 0 .../enumpoly/pelican}/pelclqhl.cc | 0 .../enumpoly/pelican}/pelclqhl.h | 0 .../enumpoly/pelican}/pelclyal.cc | 0 .../enumpoly/pelican}/pelclyal.h | 0 .../enumpoly/pelican}/pelconv.cc | 0 .../enumpoly/pelican}/pelconv.h | 0 .../enumpoly/pelican}/peleval.cc | 0 .../enumpoly/pelican}/peleval.h | 0 .../enumpoly/pelican}/pelgennd.cc | 0 .../enumpoly/pelican}/pelgennd.h | 0 .../enumpoly/pelican}/pelgmatr.cc | 0 .../enumpoly/pelican}/pelgmatr.h | 0 .../enumpoly/pelican}/pelgntyp.h | 0 .../enumpoly/pelican}/pelhomot.cc | 0 .../enumpoly/pelican}/pelhomot.h | 0 .../enumpoly/pelican}/pelpred.cc | 0 .../enumpoly/pelican}/pelpred.h | 0 .../enumpoly/pelican}/pelprgen.cc | 0 .../enumpoly/pelican}/pelprgen.h | 0 .../enumpoly/pelican}/pelproc.cc | 0 .../enumpoly/pelican}/pelproc.h | 0 .../enumpoly/pelican}/pelpscon.h | 0 .../enumpoly/pelican}/pelpsys.cc | 0 .../enumpoly/pelican}/pelpsys.h | 0 .../enumpoly/pelican}/pelqhull.cc | 0 .../enumpoly/pelican}/pelqhull.h | 0 .../enumpoly/pelican}/pelsymbl.cc | 0 .../enumpoly/pelican}/pelsymbl.h | 0 .../enumpoly/pelican}/pelutils.cc | 0 .../enumpoly/pelican}/pelutils.h | 0 src/{tools => solvers}/enumpoly/poly.cc | 0 src/{tools => solvers}/enumpoly/poly.h | 0 src/{tools => solvers}/enumpoly/poly.imp | 0 src/{tools => solvers}/enumpoly/prepoly.cc | 0 src/{tools => solvers}/enumpoly/prepoly.h | 0 src/{tools => solvers}/enumpoly/quiksolv.cc | 0 src/{tools => solvers}/enumpoly/quiksolv.h | 2 +- src/{tools => solvers}/enumpoly/quiksolv.imp | 0 src/{tools => solvers}/enumpoly/rectangl.cc | 0 src/{tools => solvers}/enumpoly/rectangl.h | 0 src/{tools => solvers}/enumpoly/rectangl.imp | 0 src/{tools => solvers}/enumpoly/sfg.cc | 0 src/{tools => solvers}/enumpoly/sfg.h | 0 src/{tools => solvers}/enumpoly/sfstrat.cc | 0 src/{tools => solvers}/enumpoly/sfstrat.h | 0 src/{tools => solvers}/lp/efglp.cc | 0 src/{tools => solvers}/lp/efglp.h | 0 src/{tools => solvers}/lp/nfglp.cc | 0 src/{tools => solvers}/lp/nfglp.h | 0 src/tools/enumpoly/enumpoly.cc | 14 +- src/tools/lp/lp.cc | 4 +- 104 files changed, 114 insertions(+), 120 deletions(-) rename src/{tools => solvers}/enumpoly/behavextend.cc (100%) rename src/{tools => solvers}/enumpoly/behavextend.h (100%) rename src/{tools => solvers}/enumpoly/efgensup.cc (100%) rename src/{tools => solvers}/enumpoly/efgensup.h (100%) rename src/{tools => solvers}/enumpoly/efgpoly.cc (99%) rename src/{tools => solvers}/enumpoly/gcomplex.cc (100%) rename src/{tools => solvers}/enumpoly/gcomplex.h (100%) rename src/{tools => solvers}/enumpoly/gnarray.h (100%) rename src/{tools => solvers}/enumpoly/gnarray.imp (100%) rename src/{tools => solvers}/enumpoly/gpartltr.cc (100%) rename src/{tools => solvers}/enumpoly/gpartltr.h (100%) rename src/{tools => solvers}/enumpoly/gpartltr.imp (100%) rename src/{tools => solvers}/enumpoly/gpoly.cc (100%) rename src/{tools => solvers}/enumpoly/gpoly.h (100%) rename src/{tools => solvers}/enumpoly/gpoly.imp (100%) rename src/{tools => solvers}/enumpoly/gpolylst.cc (100%) rename src/{tools => solvers}/enumpoly/gpolylst.h (100%) rename src/{tools => solvers}/enumpoly/gpolylst.imp (100%) rename src/{tools => solvers}/enumpoly/gsolver.cc (100%) rename src/{tools => solvers}/enumpoly/gsolver.h (100%) rename src/{tools => solvers}/enumpoly/gsolver.imp (100%) rename src/{tools => solvers}/enumpoly/gtree.h (100%) rename src/{tools => solvers}/enumpoly/gtree.imp (100%) rename src/{tools => solvers}/enumpoly/ideal.cc (100%) rename src/{tools => solvers}/enumpoly/ideal.h (100%) rename src/{tools => solvers}/enumpoly/ideal.imp (100%) rename src/{tools => solvers}/enumpoly/ineqsolv.cc (100%) rename src/{tools => solvers}/enumpoly/ineqsolv.h (100%) rename src/{tools => solvers}/enumpoly/ineqsolv.imp (100%) rename src/{tools => solvers}/enumpoly/interval.h (100%) rename src/{tools => solvers}/enumpoly/linrcomb.cc (100%) rename src/{tools => solvers}/enumpoly/linrcomb.h (100%) rename src/{tools => solvers}/enumpoly/linrcomb.imp (100%) rename src/{tools => solvers}/enumpoly/monomial.cc (100%) rename src/{tools => solvers}/enumpoly/monomial.h (100%) rename src/{tools => solvers}/enumpoly/monomial.imp (100%) rename src/{tools => solvers}/enumpoly/nfgcpoly.cc (100%) rename src/{tools => solvers}/enumpoly/nfgcpoly.h (100%) rename src/{tools => solvers}/enumpoly/nfgensup.cc (100%) rename src/{tools => solvers}/enumpoly/nfgensup.h (100%) rename src/{tools => solvers}/enumpoly/nfghs.cc (100%) rename src/{tools => solvers}/enumpoly/nfghs.h (100%) rename src/{tools => solvers}/enumpoly/nfgpoly.cc (99%) rename src/{tools => solvers}/enumpoly/odometer.cc (100%) rename src/{tools => solvers}/enumpoly/odometer.h (100%) rename src/{tools => solvers}/enumpoly/pelclass.cc (100%) rename src/{tools => solvers}/enumpoly/pelclass.h (99%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelclhpk.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelclhpk.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelclqhl.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelclqhl.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelclyal.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelclyal.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelconv.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelconv.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/peleval.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/peleval.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelgennd.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelgennd.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelgmatr.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelgmatr.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelgntyp.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelhomot.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelhomot.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelpred.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelpred.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelprgen.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelprgen.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelproc.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelproc.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelpscon.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelpsys.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelpsys.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelqhull.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelqhull.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelsymbl.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelsymbl.h (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelutils.cc (100%) rename src/{tools/enumpoly => solvers/enumpoly/pelican}/pelutils.h (100%) rename src/{tools => solvers}/enumpoly/poly.cc (100%) rename src/{tools => solvers}/enumpoly/poly.h (100%) rename src/{tools => solvers}/enumpoly/poly.imp (100%) rename src/{tools => solvers}/enumpoly/prepoly.cc (100%) rename src/{tools => solvers}/enumpoly/prepoly.h (100%) rename src/{tools => solvers}/enumpoly/quiksolv.cc (100%) rename src/{tools => solvers}/enumpoly/quiksolv.h (99%) rename src/{tools => solvers}/enumpoly/quiksolv.imp (100%) rename src/{tools => solvers}/enumpoly/rectangl.cc (100%) rename src/{tools => solvers}/enumpoly/rectangl.h (100%) rename src/{tools => solvers}/enumpoly/rectangl.imp (100%) rename src/{tools => solvers}/enumpoly/sfg.cc (100%) rename src/{tools => solvers}/enumpoly/sfg.h (100%) rename src/{tools => solvers}/enumpoly/sfstrat.cc (100%) rename src/{tools => solvers}/enumpoly/sfstrat.h (100%) rename src/{tools => solvers}/lp/efglp.cc (100%) rename src/{tools => solvers}/lp/efglp.h (100%) rename src/{tools => solvers}/lp/nfglp.cc (100%) rename src/{tools => solvers}/lp/nfglp.h (100%) diff --git a/MANIFEST.in b/MANIFEST.in index 2ef7e783e..6383aa876 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,10 +2,6 @@ recursive-include src/core *.cc *.h *.imp recursive-include src/games *.cc *.h *.imp recursive-include src/solvers *.c *.cc *.h *.imp include src/gambit.h -include src/tools/lp/nfglp.cc -include src/tools/lp/nfglp.h -include src/tools/lp/efglp.cc -include src/tools/lp/efglp.h include src/pygambit/*.pxd include src/pygambit/*.pyx include src/pygambit/*.pxi diff --git a/Makefile.am b/Makefile.am index 6a3871756..6fc53ebb1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -474,100 +474,100 @@ gambit_enummixed_SOURCES = \ # sources from gpartltr to quiksolv were formerly in convenience lib libpoly. gambit_enumpoly_SOURCES = \ ${core_SOURCES} ${game_SOURCES} \ - src/tools/enumpoly/pelclhpk.cc \ - src/tools/enumpoly/pelclhpk.h \ - src/tools/enumpoly/pelclqhl.cc \ - src/tools/enumpoly/pelclqhl.h \ - src/tools/enumpoly/pelclyal.cc \ - src/tools/enumpoly/pelclyal.h \ - src/tools/enumpoly/pelconv.cc \ - src/tools/enumpoly/pelconv.h \ - src/tools/enumpoly/peleval.cc \ - src/tools/enumpoly/peleval.h \ - src/tools/enumpoly/pelgennd.cc \ - src/tools/enumpoly/pelgennd.h \ - src/tools/enumpoly/pelgmatr.cc \ - src/tools/enumpoly/pelgmatr.h \ - src/tools/enumpoly/pelgntyp.h \ - src/tools/enumpoly/pelhomot.cc \ - src/tools/enumpoly/pelhomot.h \ - src/tools/enumpoly/pelpred.cc \ - src/tools/enumpoly/pelpred.h \ - src/tools/enumpoly/pelprgen.cc \ - src/tools/enumpoly/pelprgen.h \ - src/tools/enumpoly/pelproc.cc \ - src/tools/enumpoly/pelproc.h \ - src/tools/enumpoly/pelpscon.h \ - src/tools/enumpoly/pelpsys.cc \ - src/tools/enumpoly/pelpsys.h \ - src/tools/enumpoly/pelqhull.cc \ - src/tools/enumpoly/pelqhull.h \ - src/tools/enumpoly/pelsymbl.cc \ - src/tools/enumpoly/pelsymbl.h \ - src/tools/enumpoly/pelutils.cc \ - src/tools/enumpoly/pelutils.h \ - src/tools/enumpoly/gpartltr.cc \ - src/tools/enumpoly/gpartltr.h \ - src/tools/enumpoly/gpartltr.imp \ - src/tools/enumpoly/gpoly.cc \ - src/tools/enumpoly/gpoly.h \ - src/tools/enumpoly/gpoly.imp \ - src/tools/enumpoly/gpolylst.cc \ - src/tools/enumpoly/gpolylst.h \ - src/tools/enumpoly/gpolylst.imp \ - src/tools/enumpoly/gsolver.cc \ - src/tools/enumpoly/gsolver.h \ - src/tools/enumpoly/gsolver.imp \ - src/tools/enumpoly/ideal.cc \ - src/tools/enumpoly/ideal.h \ - src/tools/enumpoly/ideal.imp \ - src/tools/enumpoly/ineqsolv.cc \ - src/tools/enumpoly/ineqsolv.h \ - src/tools/enumpoly/ineqsolv.imp \ - src/tools/enumpoly/interval.h \ - src/tools/enumpoly/monomial.cc \ - src/tools/enumpoly/monomial.h \ - src/tools/enumpoly/monomial.imp \ - src/tools/enumpoly/pelclass.cc \ - src/tools/enumpoly/pelclass.h \ - src/tools/enumpoly/poly.cc \ - src/tools/enumpoly/poly.h \ - src/tools/enumpoly/poly.imp \ - src/tools/enumpoly/prepoly.cc \ - src/tools/enumpoly/prepoly.h \ - src/tools/enumpoly/quiksolv.cc \ - src/tools/enumpoly/quiksolv.h \ - src/tools/enumpoly/quiksolv.imp \ - src/tools/enumpoly/rectangl.cc \ - src/tools/enumpoly/rectangl.h \ - src/tools/enumpoly/rectangl.imp \ - src/tools/enumpoly/behavextend.cc \ - src/tools/enumpoly/behavextend.h \ - src/tools/enumpoly/gcomplex.cc \ - src/tools/enumpoly/gcomplex.h \ - src/tools/enumpoly/gtree.h \ - src/tools/enumpoly/gtree.imp \ - src/tools/enumpoly/linrcomb.cc \ - src/tools/enumpoly/linrcomb.h \ - src/tools/enumpoly/linrcomb.imp \ - src/tools/enumpoly/efgensup.cc \ - src/tools/enumpoly/efgensup.h \ - src/tools/enumpoly/gnarray.h \ - src/tools/enumpoly/gnarray.imp \ - src/tools/enumpoly/sfg.cc \ - src/tools/enumpoly/sfg.h \ - src/tools/enumpoly/sfstrat.cc \ - src/tools/enumpoly/sfstrat.h \ - src/tools/enumpoly/nfgensup.cc \ - src/tools/enumpoly/nfgensup.h \ - src/tools/enumpoly/odometer.cc \ - src/tools/enumpoly/odometer.h \ - src/tools/enumpoly/nfgcpoly.cc \ - src/tools/enumpoly/nfgcpoly.h \ - src/tools/enumpoly/nfghs.cc \ - src/tools/enumpoly/nfghs.h \ - src/tools/enumpoly/efgpoly.cc \ - src/tools/enumpoly/nfgpoly.cc \ + src/solvers/enumpoly/pelican/pelclhpk.cc \ + src/solvers/enumpoly/pelican/pelclhpk.h \ + src/solvers/enumpoly/pelican/pelclqhl.cc \ + src/solvers/enumpoly/pelican/pelclqhl.h \ + src/solvers/enumpoly/pelican/pelclyal.cc \ + src/solvers/enumpoly/pelican/pelclyal.h \ + src/solvers/enumpoly/pelican/pelconv.cc \ + src/solvers/enumpoly/pelican/pelconv.h \ + src/solvers/enumpoly/pelican/peleval.cc \ + src/solvers/enumpoly/pelican/peleval.h \ + src/solvers/enumpoly/pelican/pelgennd.cc \ + src/solvers/enumpoly/pelican/pelgennd.h \ + src/solvers/enumpoly/pelican/pelgmatr.cc \ + src/solvers/enumpoly/pelican/pelgmatr.h \ + src/solvers/enumpoly/pelican/pelgntyp.h \ + src/solvers/enumpoly/pelican/pelhomot.cc \ + src/solvers/enumpoly/pelican/pelhomot.h \ + src/solvers/enumpoly/pelican/pelpred.cc \ + src/solvers/enumpoly/pelican/pelpred.h \ + src/solvers/enumpoly/pelican/pelprgen.cc \ + src/solvers/enumpoly/pelican/pelprgen.h \ + src/solvers/enumpoly/pelican/pelproc.cc \ + src/solvers/enumpoly/pelican/pelproc.h \ + src/solvers/enumpoly/pelican/pelpscon.h \ + src/solvers/enumpoly/pelican/pelpsys.cc \ + src/solvers/enumpoly/pelican/pelpsys.h \ + src/solvers/enumpoly/pelican/pelqhull.cc \ + src/solvers/enumpoly/pelican/pelqhull.h \ + src/solvers/enumpoly/pelican/pelsymbl.cc \ + src/solvers/enumpoly/pelican/pelsymbl.h \ + src/solvers/enumpoly/pelican/pelutils.cc \ + src/solvers/enumpoly/pelican/pelutils.h \ + src/solvers/enumpoly/gpartltr.cc \ + src/solvers/enumpoly/gpartltr.h \ + src/solvers/enumpoly/gpartltr.imp \ + src/solvers/enumpoly/gpoly.cc \ + src/solvers/enumpoly/gpoly.h \ + src/solvers/enumpoly/gpoly.imp \ + src/solvers/enumpoly/gpolylst.cc \ + src/solvers/enumpoly/gpolylst.h \ + src/solvers/enumpoly/gpolylst.imp \ + src/solvers/enumpoly/gsolver.cc \ + src/solvers/enumpoly/gsolver.h \ + src/solvers/enumpoly/gsolver.imp \ + src/solvers/enumpoly/ideal.cc \ + src/solvers/enumpoly/ideal.h \ + src/solvers/enumpoly/ideal.imp \ + src/solvers/enumpoly/ineqsolv.cc \ + src/solvers/enumpoly/ineqsolv.h \ + src/solvers/enumpoly/ineqsolv.imp \ + src/solvers/enumpoly/interval.h \ + src/solvers/enumpoly/monomial.cc \ + src/solvers/enumpoly/monomial.h \ + src/solvers/enumpoly/monomial.imp \ + src/solvers/enumpoly/pelclass.cc \ + src/solvers/enumpoly/pelclass.h \ + src/solvers/enumpoly/poly.cc \ + src/solvers/enumpoly/poly.h \ + src/solvers/enumpoly/poly.imp \ + src/solvers/enumpoly/prepoly.cc \ + src/solvers/enumpoly/prepoly.h \ + src/solvers/enumpoly/quiksolv.cc \ + src/solvers/enumpoly/quiksolv.h \ + src/solvers/enumpoly/quiksolv.imp \ + src/solvers/enumpoly/rectangl.cc \ + src/solvers/enumpoly/rectangl.h \ + src/solvers/enumpoly/rectangl.imp \ + src/solvers/enumpoly/behavextend.cc \ + src/solvers/enumpoly/behavextend.h \ + src/solvers/enumpoly/gcomplex.cc \ + src/solvers/enumpoly/gcomplex.h \ + src/solvers/enumpoly/gtree.h \ + src/solvers/enumpoly/gtree.imp \ + src/solvers/enumpoly/linrcomb.cc \ + src/solvers/enumpoly/linrcomb.h \ + src/solvers/enumpoly/linrcomb.imp \ + src/solvers/enumpoly/efgensup.cc \ + src/solvers/enumpoly/efgensup.h \ + src/solvers/enumpoly/gnarray.h \ + src/solvers/enumpoly/gnarray.imp \ + src/solvers/enumpoly/sfg.cc \ + src/solvers/enumpoly/sfg.h \ + src/solvers/enumpoly/sfstrat.cc \ + src/solvers/enumpoly/sfstrat.h \ + src/solvers/enumpoly/nfgensup.cc \ + src/solvers/enumpoly/nfgensup.h \ + src/solvers/enumpoly/odometer.cc \ + src/solvers/enumpoly/odometer.h \ + src/solvers/enumpoly/nfgcpoly.cc \ + src/solvers/enumpoly/nfgcpoly.h \ + src/solvers/enumpoly/nfghs.cc \ + src/solvers/enumpoly/nfghs.h \ + src/solvers/enumpoly/efgpoly.cc \ + src/solvers/enumpoly/nfgpoly.cc \ src/tools/enumpoly/enumpoly.cc gambit_enumpure_SOURCES = \ @@ -619,10 +619,10 @@ gambit_logit_SOURCES = \ gambit_lp_SOURCES = \ ${core_SOURCES} ${game_SOURCES} \ ${linalg_SOURCES} \ - src/tools/lp/efglp.cc \ - src/tools/lp/efglp.h \ - src/tools/lp/nfglp.cc \ - src/tools/lp/nfglp.h \ + src/solvers/lp/efglp.cc \ + src/solvers/lp/efglp.h \ + src/solvers/lp/nfglp.cc \ + src/solvers/lp/nfglp.h \ src/tools/lp/lp.cc gambit_simpdiv_SOURCES = \ diff --git a/setup.py b/setup.py index 1b455eaca..bf6ad658b 100644 --- a/setup.py +++ b/setup.py @@ -36,9 +36,7 @@ glob.glob("src/core/*.cc") + glob.glob("src/games/*.cc") + glob.glob("src/games/agg/*.cc") + - glob.glob("src/solvers/*/*.cc") + - ["src/tools/lp/nfglp.cc", - "src/tools/lp/efglp.cc"] + [fn for fn in glob.glob("src/solvers/*/*.cc") if "enumpoly" not in fn] ), 'include_dirs': ["src"], 'cflags': ( diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 5074c370e..d7dd2a274 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -386,11 +386,11 @@ cdef extern from "solvers/lcp/lcp.h": c_List[c_MixedBehaviorProfileDouble] LcpBehaviorSolveDouble(c_Game, int p_stopAfter, int p_maxDepth) except +RuntimeError c_List[c_MixedBehaviorProfileRational] LcpBehaviorSolveRational(c_Game, int p_stopAfter, int p_maxDepth) except +RuntimeError -cdef extern from "tools/lp/nfglp.h": +cdef extern from "solvers/lp/nfglp.h": c_List[c_MixedStrategyProfileDouble] LpStrategySolveDouble(c_Game) except +RuntimeError c_List[c_MixedStrategyProfileRational] LpStrategySolveRational(c_Game) except +RuntimeError -cdef extern from "tools/lp/efglp.h": +cdef extern from "solvers/lp/efglp.h": c_List[c_MixedBehaviorProfileDouble] LpBehaviorSolveDouble(c_Game) except +RuntimeError c_List[c_MixedBehaviorProfileRational] LpBehaviorSolveRational(c_Game) except +RuntimeError diff --git a/src/tools/enumpoly/behavextend.cc b/src/solvers/enumpoly/behavextend.cc similarity index 100% rename from src/tools/enumpoly/behavextend.cc rename to src/solvers/enumpoly/behavextend.cc diff --git a/src/tools/enumpoly/behavextend.h b/src/solvers/enumpoly/behavextend.h similarity index 100% rename from src/tools/enumpoly/behavextend.h rename to src/solvers/enumpoly/behavextend.h diff --git a/src/tools/enumpoly/efgensup.cc b/src/solvers/enumpoly/efgensup.cc similarity index 100% rename from src/tools/enumpoly/efgensup.cc rename to src/solvers/enumpoly/efgensup.cc diff --git a/src/tools/enumpoly/efgensup.h b/src/solvers/enumpoly/efgensup.h similarity index 100% rename from src/tools/enumpoly/efgensup.h rename to src/solvers/enumpoly/efgensup.h diff --git a/src/tools/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc similarity index 99% rename from src/tools/enumpoly/efgpoly.cc rename to src/solvers/enumpoly/efgpoly.cc index b43848987..6d4d63e86 100644 --- a/src/tools/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -384,7 +384,7 @@ void PrintSupport(std::ostream &p_stream, p_stream << std::endl; } -void SolveExtensive(const Game &p_game) +void EnumPolySolveExtensive(const Game &p_game) { List supports(PossibleNashSubsupports(BehaviorSupportProfile(p_game))); diff --git a/src/tools/enumpoly/gcomplex.cc b/src/solvers/enumpoly/gcomplex.cc similarity index 100% rename from src/tools/enumpoly/gcomplex.cc rename to src/solvers/enumpoly/gcomplex.cc diff --git a/src/tools/enumpoly/gcomplex.h b/src/solvers/enumpoly/gcomplex.h similarity index 100% rename from src/tools/enumpoly/gcomplex.h rename to src/solvers/enumpoly/gcomplex.h diff --git a/src/tools/enumpoly/gnarray.h b/src/solvers/enumpoly/gnarray.h similarity index 100% rename from src/tools/enumpoly/gnarray.h rename to src/solvers/enumpoly/gnarray.h diff --git a/src/tools/enumpoly/gnarray.imp b/src/solvers/enumpoly/gnarray.imp similarity index 100% rename from src/tools/enumpoly/gnarray.imp rename to src/solvers/enumpoly/gnarray.imp diff --git a/src/tools/enumpoly/gpartltr.cc b/src/solvers/enumpoly/gpartltr.cc similarity index 100% rename from src/tools/enumpoly/gpartltr.cc rename to src/solvers/enumpoly/gpartltr.cc diff --git a/src/tools/enumpoly/gpartltr.h b/src/solvers/enumpoly/gpartltr.h similarity index 100% rename from src/tools/enumpoly/gpartltr.h rename to src/solvers/enumpoly/gpartltr.h diff --git a/src/tools/enumpoly/gpartltr.imp b/src/solvers/enumpoly/gpartltr.imp similarity index 100% rename from src/tools/enumpoly/gpartltr.imp rename to src/solvers/enumpoly/gpartltr.imp diff --git a/src/tools/enumpoly/gpoly.cc b/src/solvers/enumpoly/gpoly.cc similarity index 100% rename from src/tools/enumpoly/gpoly.cc rename to src/solvers/enumpoly/gpoly.cc diff --git a/src/tools/enumpoly/gpoly.h b/src/solvers/enumpoly/gpoly.h similarity index 100% rename from src/tools/enumpoly/gpoly.h rename to src/solvers/enumpoly/gpoly.h diff --git a/src/tools/enumpoly/gpoly.imp b/src/solvers/enumpoly/gpoly.imp similarity index 100% rename from src/tools/enumpoly/gpoly.imp rename to src/solvers/enumpoly/gpoly.imp diff --git a/src/tools/enumpoly/gpolylst.cc b/src/solvers/enumpoly/gpolylst.cc similarity index 100% rename from src/tools/enumpoly/gpolylst.cc rename to src/solvers/enumpoly/gpolylst.cc diff --git a/src/tools/enumpoly/gpolylst.h b/src/solvers/enumpoly/gpolylst.h similarity index 100% rename from src/tools/enumpoly/gpolylst.h rename to src/solvers/enumpoly/gpolylst.h diff --git a/src/tools/enumpoly/gpolylst.imp b/src/solvers/enumpoly/gpolylst.imp similarity index 100% rename from src/tools/enumpoly/gpolylst.imp rename to src/solvers/enumpoly/gpolylst.imp diff --git a/src/tools/enumpoly/gsolver.cc b/src/solvers/enumpoly/gsolver.cc similarity index 100% rename from src/tools/enumpoly/gsolver.cc rename to src/solvers/enumpoly/gsolver.cc diff --git a/src/tools/enumpoly/gsolver.h b/src/solvers/enumpoly/gsolver.h similarity index 100% rename from src/tools/enumpoly/gsolver.h rename to src/solvers/enumpoly/gsolver.h diff --git a/src/tools/enumpoly/gsolver.imp b/src/solvers/enumpoly/gsolver.imp similarity index 100% rename from src/tools/enumpoly/gsolver.imp rename to src/solvers/enumpoly/gsolver.imp diff --git a/src/tools/enumpoly/gtree.h b/src/solvers/enumpoly/gtree.h similarity index 100% rename from src/tools/enumpoly/gtree.h rename to src/solvers/enumpoly/gtree.h diff --git a/src/tools/enumpoly/gtree.imp b/src/solvers/enumpoly/gtree.imp similarity index 100% rename from src/tools/enumpoly/gtree.imp rename to src/solvers/enumpoly/gtree.imp diff --git a/src/tools/enumpoly/ideal.cc b/src/solvers/enumpoly/ideal.cc similarity index 100% rename from src/tools/enumpoly/ideal.cc rename to src/solvers/enumpoly/ideal.cc diff --git a/src/tools/enumpoly/ideal.h b/src/solvers/enumpoly/ideal.h similarity index 100% rename from src/tools/enumpoly/ideal.h rename to src/solvers/enumpoly/ideal.h diff --git a/src/tools/enumpoly/ideal.imp b/src/solvers/enumpoly/ideal.imp similarity index 100% rename from src/tools/enumpoly/ideal.imp rename to src/solvers/enumpoly/ideal.imp diff --git a/src/tools/enumpoly/ineqsolv.cc b/src/solvers/enumpoly/ineqsolv.cc similarity index 100% rename from src/tools/enumpoly/ineqsolv.cc rename to src/solvers/enumpoly/ineqsolv.cc diff --git a/src/tools/enumpoly/ineqsolv.h b/src/solvers/enumpoly/ineqsolv.h similarity index 100% rename from src/tools/enumpoly/ineqsolv.h rename to src/solvers/enumpoly/ineqsolv.h diff --git a/src/tools/enumpoly/ineqsolv.imp b/src/solvers/enumpoly/ineqsolv.imp similarity index 100% rename from src/tools/enumpoly/ineqsolv.imp rename to src/solvers/enumpoly/ineqsolv.imp diff --git a/src/tools/enumpoly/interval.h b/src/solvers/enumpoly/interval.h similarity index 100% rename from src/tools/enumpoly/interval.h rename to src/solvers/enumpoly/interval.h diff --git a/src/tools/enumpoly/linrcomb.cc b/src/solvers/enumpoly/linrcomb.cc similarity index 100% rename from src/tools/enumpoly/linrcomb.cc rename to src/solvers/enumpoly/linrcomb.cc diff --git a/src/tools/enumpoly/linrcomb.h b/src/solvers/enumpoly/linrcomb.h similarity index 100% rename from src/tools/enumpoly/linrcomb.h rename to src/solvers/enumpoly/linrcomb.h diff --git a/src/tools/enumpoly/linrcomb.imp b/src/solvers/enumpoly/linrcomb.imp similarity index 100% rename from src/tools/enumpoly/linrcomb.imp rename to src/solvers/enumpoly/linrcomb.imp diff --git a/src/tools/enumpoly/monomial.cc b/src/solvers/enumpoly/monomial.cc similarity index 100% rename from src/tools/enumpoly/monomial.cc rename to src/solvers/enumpoly/monomial.cc diff --git a/src/tools/enumpoly/monomial.h b/src/solvers/enumpoly/monomial.h similarity index 100% rename from src/tools/enumpoly/monomial.h rename to src/solvers/enumpoly/monomial.h diff --git a/src/tools/enumpoly/monomial.imp b/src/solvers/enumpoly/monomial.imp similarity index 100% rename from src/tools/enumpoly/monomial.imp rename to src/solvers/enumpoly/monomial.imp diff --git a/src/tools/enumpoly/nfgcpoly.cc b/src/solvers/enumpoly/nfgcpoly.cc similarity index 100% rename from src/tools/enumpoly/nfgcpoly.cc rename to src/solvers/enumpoly/nfgcpoly.cc diff --git a/src/tools/enumpoly/nfgcpoly.h b/src/solvers/enumpoly/nfgcpoly.h similarity index 100% rename from src/tools/enumpoly/nfgcpoly.h rename to src/solvers/enumpoly/nfgcpoly.h diff --git a/src/tools/enumpoly/nfgensup.cc b/src/solvers/enumpoly/nfgensup.cc similarity index 100% rename from src/tools/enumpoly/nfgensup.cc rename to src/solvers/enumpoly/nfgensup.cc diff --git a/src/tools/enumpoly/nfgensup.h b/src/solvers/enumpoly/nfgensup.h similarity index 100% rename from src/tools/enumpoly/nfgensup.h rename to src/solvers/enumpoly/nfgensup.h diff --git a/src/tools/enumpoly/nfghs.cc b/src/solvers/enumpoly/nfghs.cc similarity index 100% rename from src/tools/enumpoly/nfghs.cc rename to src/solvers/enumpoly/nfghs.cc diff --git a/src/tools/enumpoly/nfghs.h b/src/solvers/enumpoly/nfghs.h similarity index 100% rename from src/tools/enumpoly/nfghs.h rename to src/solvers/enumpoly/nfghs.h diff --git a/src/tools/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc similarity index 99% rename from src/tools/enumpoly/nfgpoly.cc rename to src/solvers/enumpoly/nfgpoly.cc index d8af7a09d..4d89936e8 100644 --- a/src/tools/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -479,7 +479,7 @@ void PrintSupport(std::ostream &p_stream, p_stream << std::endl; } -void SolveStrategic(const Gambit::Game &p_nfg) +void EnumPolySolveStrategic(const Gambit::Game &p_nfg) { Gambit::List supports = PossibleNashSubsupports(p_nfg); diff --git a/src/tools/enumpoly/odometer.cc b/src/solvers/enumpoly/odometer.cc similarity index 100% rename from src/tools/enumpoly/odometer.cc rename to src/solvers/enumpoly/odometer.cc diff --git a/src/tools/enumpoly/odometer.h b/src/solvers/enumpoly/odometer.h similarity index 100% rename from src/tools/enumpoly/odometer.h rename to src/solvers/enumpoly/odometer.h diff --git a/src/tools/enumpoly/pelclass.cc b/src/solvers/enumpoly/pelclass.cc similarity index 100% rename from src/tools/enumpoly/pelclass.cc rename to src/solvers/enumpoly/pelclass.cc diff --git a/src/tools/enumpoly/pelclass.h b/src/solvers/enumpoly/pelclass.h similarity index 99% rename from src/tools/enumpoly/pelclass.h rename to src/solvers/enumpoly/pelclass.h index 17556111c..6d59fc8d1 100644 --- a/src/tools/enumpoly/pelclass.h +++ b/src/solvers/enumpoly/pelclass.h @@ -24,7 +24,7 @@ #include "gpolylst.h" /* extern "C" { */ -#include "pelprgen.h" +#include "pelican/pelprgen.h" /* } */ diff --git a/src/tools/enumpoly/pelclhpk.cc b/src/solvers/enumpoly/pelican/pelclhpk.cc similarity index 100% rename from src/tools/enumpoly/pelclhpk.cc rename to src/solvers/enumpoly/pelican/pelclhpk.cc diff --git a/src/tools/enumpoly/pelclhpk.h b/src/solvers/enumpoly/pelican/pelclhpk.h similarity index 100% rename from src/tools/enumpoly/pelclhpk.h rename to src/solvers/enumpoly/pelican/pelclhpk.h diff --git a/src/tools/enumpoly/pelclqhl.cc b/src/solvers/enumpoly/pelican/pelclqhl.cc similarity index 100% rename from src/tools/enumpoly/pelclqhl.cc rename to src/solvers/enumpoly/pelican/pelclqhl.cc diff --git a/src/tools/enumpoly/pelclqhl.h b/src/solvers/enumpoly/pelican/pelclqhl.h similarity index 100% rename from src/tools/enumpoly/pelclqhl.h rename to src/solvers/enumpoly/pelican/pelclqhl.h diff --git a/src/tools/enumpoly/pelclyal.cc b/src/solvers/enumpoly/pelican/pelclyal.cc similarity index 100% rename from src/tools/enumpoly/pelclyal.cc rename to src/solvers/enumpoly/pelican/pelclyal.cc diff --git a/src/tools/enumpoly/pelclyal.h b/src/solvers/enumpoly/pelican/pelclyal.h similarity index 100% rename from src/tools/enumpoly/pelclyal.h rename to src/solvers/enumpoly/pelican/pelclyal.h diff --git a/src/tools/enumpoly/pelconv.cc b/src/solvers/enumpoly/pelican/pelconv.cc similarity index 100% rename from src/tools/enumpoly/pelconv.cc rename to src/solvers/enumpoly/pelican/pelconv.cc diff --git a/src/tools/enumpoly/pelconv.h b/src/solvers/enumpoly/pelican/pelconv.h similarity index 100% rename from src/tools/enumpoly/pelconv.h rename to src/solvers/enumpoly/pelican/pelconv.h diff --git a/src/tools/enumpoly/peleval.cc b/src/solvers/enumpoly/pelican/peleval.cc similarity index 100% rename from src/tools/enumpoly/peleval.cc rename to src/solvers/enumpoly/pelican/peleval.cc diff --git a/src/tools/enumpoly/peleval.h b/src/solvers/enumpoly/pelican/peleval.h similarity index 100% rename from src/tools/enumpoly/peleval.h rename to src/solvers/enumpoly/pelican/peleval.h diff --git a/src/tools/enumpoly/pelgennd.cc b/src/solvers/enumpoly/pelican/pelgennd.cc similarity index 100% rename from src/tools/enumpoly/pelgennd.cc rename to src/solvers/enumpoly/pelican/pelgennd.cc diff --git a/src/tools/enumpoly/pelgennd.h b/src/solvers/enumpoly/pelican/pelgennd.h similarity index 100% rename from src/tools/enumpoly/pelgennd.h rename to src/solvers/enumpoly/pelican/pelgennd.h diff --git a/src/tools/enumpoly/pelgmatr.cc b/src/solvers/enumpoly/pelican/pelgmatr.cc similarity index 100% rename from src/tools/enumpoly/pelgmatr.cc rename to src/solvers/enumpoly/pelican/pelgmatr.cc diff --git a/src/tools/enumpoly/pelgmatr.h b/src/solvers/enumpoly/pelican/pelgmatr.h similarity index 100% rename from src/tools/enumpoly/pelgmatr.h rename to src/solvers/enumpoly/pelican/pelgmatr.h diff --git a/src/tools/enumpoly/pelgntyp.h b/src/solvers/enumpoly/pelican/pelgntyp.h similarity index 100% rename from src/tools/enumpoly/pelgntyp.h rename to src/solvers/enumpoly/pelican/pelgntyp.h diff --git a/src/tools/enumpoly/pelhomot.cc b/src/solvers/enumpoly/pelican/pelhomot.cc similarity index 100% rename from src/tools/enumpoly/pelhomot.cc rename to src/solvers/enumpoly/pelican/pelhomot.cc diff --git a/src/tools/enumpoly/pelhomot.h b/src/solvers/enumpoly/pelican/pelhomot.h similarity index 100% rename from src/tools/enumpoly/pelhomot.h rename to src/solvers/enumpoly/pelican/pelhomot.h diff --git a/src/tools/enumpoly/pelpred.cc b/src/solvers/enumpoly/pelican/pelpred.cc similarity index 100% rename from src/tools/enumpoly/pelpred.cc rename to src/solvers/enumpoly/pelican/pelpred.cc diff --git a/src/tools/enumpoly/pelpred.h b/src/solvers/enumpoly/pelican/pelpred.h similarity index 100% rename from src/tools/enumpoly/pelpred.h rename to src/solvers/enumpoly/pelican/pelpred.h diff --git a/src/tools/enumpoly/pelprgen.cc b/src/solvers/enumpoly/pelican/pelprgen.cc similarity index 100% rename from src/tools/enumpoly/pelprgen.cc rename to src/solvers/enumpoly/pelican/pelprgen.cc diff --git a/src/tools/enumpoly/pelprgen.h b/src/solvers/enumpoly/pelican/pelprgen.h similarity index 100% rename from src/tools/enumpoly/pelprgen.h rename to src/solvers/enumpoly/pelican/pelprgen.h diff --git a/src/tools/enumpoly/pelproc.cc b/src/solvers/enumpoly/pelican/pelproc.cc similarity index 100% rename from src/tools/enumpoly/pelproc.cc rename to src/solvers/enumpoly/pelican/pelproc.cc diff --git a/src/tools/enumpoly/pelproc.h b/src/solvers/enumpoly/pelican/pelproc.h similarity index 100% rename from src/tools/enumpoly/pelproc.h rename to src/solvers/enumpoly/pelican/pelproc.h diff --git a/src/tools/enumpoly/pelpscon.h b/src/solvers/enumpoly/pelican/pelpscon.h similarity index 100% rename from src/tools/enumpoly/pelpscon.h rename to src/solvers/enumpoly/pelican/pelpscon.h diff --git a/src/tools/enumpoly/pelpsys.cc b/src/solvers/enumpoly/pelican/pelpsys.cc similarity index 100% rename from src/tools/enumpoly/pelpsys.cc rename to src/solvers/enumpoly/pelican/pelpsys.cc diff --git a/src/tools/enumpoly/pelpsys.h b/src/solvers/enumpoly/pelican/pelpsys.h similarity index 100% rename from src/tools/enumpoly/pelpsys.h rename to src/solvers/enumpoly/pelican/pelpsys.h diff --git a/src/tools/enumpoly/pelqhull.cc b/src/solvers/enumpoly/pelican/pelqhull.cc similarity index 100% rename from src/tools/enumpoly/pelqhull.cc rename to src/solvers/enumpoly/pelican/pelqhull.cc diff --git a/src/tools/enumpoly/pelqhull.h b/src/solvers/enumpoly/pelican/pelqhull.h similarity index 100% rename from src/tools/enumpoly/pelqhull.h rename to src/solvers/enumpoly/pelican/pelqhull.h diff --git a/src/tools/enumpoly/pelsymbl.cc b/src/solvers/enumpoly/pelican/pelsymbl.cc similarity index 100% rename from src/tools/enumpoly/pelsymbl.cc rename to src/solvers/enumpoly/pelican/pelsymbl.cc diff --git a/src/tools/enumpoly/pelsymbl.h b/src/solvers/enumpoly/pelican/pelsymbl.h similarity index 100% rename from src/tools/enumpoly/pelsymbl.h rename to src/solvers/enumpoly/pelican/pelsymbl.h diff --git a/src/tools/enumpoly/pelutils.cc b/src/solvers/enumpoly/pelican/pelutils.cc similarity index 100% rename from src/tools/enumpoly/pelutils.cc rename to src/solvers/enumpoly/pelican/pelutils.cc diff --git a/src/tools/enumpoly/pelutils.h b/src/solvers/enumpoly/pelican/pelutils.h similarity index 100% rename from src/tools/enumpoly/pelutils.h rename to src/solvers/enumpoly/pelican/pelutils.h diff --git a/src/tools/enumpoly/poly.cc b/src/solvers/enumpoly/poly.cc similarity index 100% rename from src/tools/enumpoly/poly.cc rename to src/solvers/enumpoly/poly.cc diff --git a/src/tools/enumpoly/poly.h b/src/solvers/enumpoly/poly.h similarity index 100% rename from src/tools/enumpoly/poly.h rename to src/solvers/enumpoly/poly.h diff --git a/src/tools/enumpoly/poly.imp b/src/solvers/enumpoly/poly.imp similarity index 100% rename from src/tools/enumpoly/poly.imp rename to src/solvers/enumpoly/poly.imp diff --git a/src/tools/enumpoly/prepoly.cc b/src/solvers/enumpoly/prepoly.cc similarity index 100% rename from src/tools/enumpoly/prepoly.cc rename to src/solvers/enumpoly/prepoly.cc diff --git a/src/tools/enumpoly/prepoly.h b/src/solvers/enumpoly/prepoly.h similarity index 100% rename from src/tools/enumpoly/prepoly.h rename to src/solvers/enumpoly/prepoly.h diff --git a/src/tools/enumpoly/quiksolv.cc b/src/solvers/enumpoly/quiksolv.cc similarity index 100% rename from src/tools/enumpoly/quiksolv.cc rename to src/solvers/enumpoly/quiksolv.cc diff --git a/src/tools/enumpoly/quiksolv.h b/src/solvers/enumpoly/quiksolv.h similarity index 99% rename from src/tools/enumpoly/quiksolv.h rename to src/solvers/enumpoly/quiksolv.h index 5929c7454..b68ae7fc1 100644 --- a/src/tools/enumpoly/quiksolv.h +++ b/src/solvers/enumpoly/quiksolv.h @@ -30,7 +30,7 @@ #include "gpoly.h" #include "gpolylst.h" #include "gpartltr.h" -#include "pelqhull.h" +#include "pelican/pelqhull.h" #include "pelclass.h" /* diff --git a/src/tools/enumpoly/quiksolv.imp b/src/solvers/enumpoly/quiksolv.imp similarity index 100% rename from src/tools/enumpoly/quiksolv.imp rename to src/solvers/enumpoly/quiksolv.imp diff --git a/src/tools/enumpoly/rectangl.cc b/src/solvers/enumpoly/rectangl.cc similarity index 100% rename from src/tools/enumpoly/rectangl.cc rename to src/solvers/enumpoly/rectangl.cc diff --git a/src/tools/enumpoly/rectangl.h b/src/solvers/enumpoly/rectangl.h similarity index 100% rename from src/tools/enumpoly/rectangl.h rename to src/solvers/enumpoly/rectangl.h diff --git a/src/tools/enumpoly/rectangl.imp b/src/solvers/enumpoly/rectangl.imp similarity index 100% rename from src/tools/enumpoly/rectangl.imp rename to src/solvers/enumpoly/rectangl.imp diff --git a/src/tools/enumpoly/sfg.cc b/src/solvers/enumpoly/sfg.cc similarity index 100% rename from src/tools/enumpoly/sfg.cc rename to src/solvers/enumpoly/sfg.cc diff --git a/src/tools/enumpoly/sfg.h b/src/solvers/enumpoly/sfg.h similarity index 100% rename from src/tools/enumpoly/sfg.h rename to src/solvers/enumpoly/sfg.h diff --git a/src/tools/enumpoly/sfstrat.cc b/src/solvers/enumpoly/sfstrat.cc similarity index 100% rename from src/tools/enumpoly/sfstrat.cc rename to src/solvers/enumpoly/sfstrat.cc diff --git a/src/tools/enumpoly/sfstrat.h b/src/solvers/enumpoly/sfstrat.h similarity index 100% rename from src/tools/enumpoly/sfstrat.h rename to src/solvers/enumpoly/sfstrat.h diff --git a/src/tools/lp/efglp.cc b/src/solvers/lp/efglp.cc similarity index 100% rename from src/tools/lp/efglp.cc rename to src/solvers/lp/efglp.cc diff --git a/src/tools/lp/efglp.h b/src/solvers/lp/efglp.h similarity index 100% rename from src/tools/lp/efglp.h rename to src/solvers/lp/efglp.h diff --git a/src/tools/lp/nfglp.cc b/src/solvers/lp/nfglp.cc similarity index 100% rename from src/tools/lp/nfglp.cc rename to src/solvers/lp/nfglp.cc diff --git a/src/tools/lp/nfglp.h b/src/solvers/lp/nfglp.h similarity index 100% rename from src/tools/lp/nfglp.h rename to src/solvers/lp/nfglp.h diff --git a/src/tools/enumpoly/enumpoly.cc b/src/tools/enumpoly/enumpoly.cc index 9ea837470..00b32b001 100644 --- a/src/tools/enumpoly/enumpoly.cc +++ b/src/tools/enumpoly/enumpoly.cc @@ -25,7 +25,7 @@ #include #include #include "gambit.h" -#include "nfghs.h" +#include "solvers/enumpoly/nfghs.h" int g_numDecimals = 6; bool g_verbose = false; @@ -58,8 +58,8 @@ void PrintHelp(char *progname) exit(1); } -extern void SolveStrategic(const Gambit::Game &); -extern void SolveExtensive(const Gambit::Game &); +extern void EnumPolySolveStrategic(const Gambit::Game &); +extern void EnumPolySolveExtensive(const Gambit::Game &); int main(int argc, char *argv[]) { @@ -136,15 +136,15 @@ int main(int argc, char *argv[]) if (!game->IsTree() || useStrategic) { if (useHeuristic) { - gbtNfgHs algorithm(0); - algorithm.Solve(game); + gbtNfgHs algorithm(0); + algorithm.Solve(game); } else { - SolveStrategic(game); + EnumPolySolveStrategic(game); } } else { - SolveExtensive(game); + EnumPolySolveExtensive(game); } return 0; } diff --git a/src/tools/lp/lp.cc b/src/tools/lp/lp.cc index d320400ce..44f88d3fd 100644 --- a/src/tools/lp/lp.cc +++ b/src/tools/lp/lp.cc @@ -27,8 +27,8 @@ #include #include #include "gambit.h" -#include "efglp.h" -#include "nfglp.h" +#include "solvers/lp/efglp.h" +#include "solvers/lp/nfglp.h" using namespace Gambit; From 181cb4951e22f958e3850f8f9a00577fe1256c11 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 12 Oct 2023 10:32:32 +0100 Subject: [PATCH 04/10] Revises methods to change strategies for a player: * Moves `Player.strategies.add()` to `Game.add_strategy()`, with the old call deprecated, to be removed in 16.2. * Implements `Game.delete_strategy()` to remove a strategy from a strategic game. --- ChangeLog | 1 + doc/pygambit.api.rst | 2 ++ src/pygambit/gambit.pxd | 2 ++ src/pygambit/game.pxi | 54 ++++++++++++++++++++++++++++++ src/pygambit/player.pxi | 4 +++ src/pygambit/tests/test_players.py | 24 +++++++++++-- 6 files changed, 84 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 787c83198..16c220ba0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,6 +8,7 @@ - Migrated to pytest for testing of pygambit. - ValueErrors raised for mixed behavior profiles when payoff, action_value, or infoset_value are called with the chance player. +- Implemented Game.delete_strategy to remove a strategy from a strategic game. ## [16.1.0a3] - 2023-09-29 diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index cb00415c7..579031f8e 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -76,6 +76,8 @@ Transforming game components Game.add_outcome Game.delete_outcome Game.set_outcome + Game.add_strategy + Game.delete_strategy Information about the game diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index d7dd2a274..a9bc20364 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -87,6 +87,8 @@ cdef extern from "games/game.h": string GetLabel() void SetLabel(string) + void DeleteStrategy() + cdef cppclass c_GameActionRep "GameActionRep": int GetNumber() c_GameInfoset GetInfoset() diff --git a/src/pygambit/game.pxi b/src/pygambit/game.pxi index 5d43b1d8f..c7199ce5c 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -1390,3 +1390,57 @@ class Game: return resolved_outcome = cython.cast(Outcome, self._resolve_outcome(outcome, 'set_outcome')) resolved_node.node.deref().SetOutcome(resolved_outcome.outcome) + + def add_strategy(self, player: typing.Union[Player, str], label: str = None) -> Strategy: + """Add a new strategy to the set of strategies for `player`. + + Parameters + ---------- + player : Player or str + The player to create the new strategy for + label : str, optional + The label to assign to the new strategy + + Returns + ------- + Strategy + The newly-created strategy + + Raises + ------ + MismatchError + If `player` is a `Player` from a different game. + UndefinedOperationError + If called on a game which has an extensive representation. + """ + if self.is_tree: + raise UndefinedOperationError("Adding strategies is only applicable to games in strategic form") + resolved_player = cython.cast(Player, self._resolve_player(player, 'add_strategy')) + s = Strategy() + s.strategy = resolved_player.player.deref().NewStrategy() + if label is not None: + s.label = str(label) + return s + + def delete_strategy(self, strategy: typing.Union[Strategy, str]) -> None: + """Delete `strategy` from the game. + + Parameters + ---------- + strategy : Strategy or str + The strategy to delete + + Raises + ------ + MismatchError + If `strategy` is a `strategy` from a different game. + UndefinedOperationError + If called on a game which has an extensive representation, or if `strategy` is the + only strategy for its player. + """ + if self.is_tree: + raise UndefinedOperationError("Deleting strategies is only applicable to games in strategic form") + resolved_strategy = cython.cast(Strategy, self._resolve_strategy(strategy, 'delete_strategy')) + if len(resolved_strategy.player.strategies) == 1: + raise UndefinedOperationError("Cannot delete the only strategy for a player") + resolved_strategy.strategy.deref().DeleteStrategy() diff --git a/src/pygambit/player.pxi b/src/pygambit/player.pxi index d7ea55139..7ca3065c9 100644 --- a/src/pygambit/player.pxi +++ b/src/pygambit/player.pxi @@ -19,6 +19,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +from deprecated import deprecated @cython.cclass class Infosets(Collection): @@ -42,6 +43,9 @@ class Strategies(Collection): """The set of strategies available to a player.""" player = cython.declare(c_GamePlayer) + @deprecated(version='16.1.0', + reason='Use Game.add_strategy() instead of Player.strategies.add()', + category=FutureWarning) def add(self, label="") -> Strategy: """Add a new strategy to the set of the player's strategies. diff --git a/src/pygambit/tests/test_players.py b/src/pygambit/tests/test_players.py index 20b0a3158..68e154de0 100644 --- a/src/pygambit/tests/test_players.py +++ b/src/pygambit/tests/test_players.py @@ -70,15 +70,33 @@ def test_extensive_game_add_player(): def test_strategic_game_add_strategy(): game = gbt.Game.new_table([2, 2]) - game.players[0].strategies.add("new strategy") + game.add_strategy(game.players[0], "new strategy") assert len(game.players[0].strategies) == 3 def test_extensive_game_add_strategy(): game = gbt.Game.new_tree(["Alice"]) assert len(game.players["Alice"].strategies) == 1 - with pytest.raises(TypeError): - game.players["Alice"].strategies.add("new strategy") + with pytest.raises(gbt.UndefinedOperationError): + game.add_strategy(game.players["Alice"], "new strategy") + + +def test_strategic_game_delete_strategy(): + game = gbt.Game.new_table([2, 2]) + game.delete_strategy(game.players[0].strategies[0]) + assert len(game.players[0].strategies) == 1 + + +def test_strategic_game_delete_last_strategy(): + game = gbt.Game.new_table([1, 2]) + with pytest.raises(gbt.UndefinedOperationError): + game.delete_strategy(game.players[0].strategies[0]) + + +def test_extensive_game_delete_strategy(): + game = gbt.Game.new_tree(["Alice"]) + with pytest.raises(gbt.UndefinedOperationError): + game.delete_strategy(game.players["Alice"].strategies[0]) def test_player_strategy_by_label(): From ba3a73dd276ba8bf9afee8d148f583622cb807bd Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 12 Oct 2023 13:34:21 +0100 Subject: [PATCH 05/10] Implement regret for mixed strategy and behavior profiles. * Implement regret calculation for mixed strategy profiles; this had been omitted previously in both C++ and Python. * Correct implementation of regret for mixed behavior profiles; this was computing something non-standard (a non-conditional expected payoff loss relative to the actual agent strategy, and not the best response) --- ChangeLog | 5 ++ doc/pygambit.api.rst | 1 + src/games/behav.h | 2 +- src/games/behav.imp | 92 +++++++++++++++++--------------- src/games/mixed.h | 7 +++ src/games/mixed.imp | 16 ++++++ src/pygambit/behav.pxi | 9 +++- src/pygambit/gambit.pxd | 2 + src/pygambit/mixed.pxi | 29 ++++++++++ src/pygambit/tests/test_behav.py | 88 ++++-------------------------- src/pygambit/tests/test_mixed.py | 14 +++++ 11 files changed, 139 insertions(+), 126 deletions(-) diff --git a/ChangeLog b/ChangeLog index 16c220ba0..bd3ff0dab 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,11 @@ - ValueErrors raised for mixed behavior profiles when payoff, action_value, or infoset_value are called with the chance player. - Implemented Game.delete_strategy to remove a strategy from a strategic game. +- Implemented regret for mixed strategy profiles. + +### Fixed +- Regret on mixed behavior profiles now implements the standard definition of regret + (loss in expected payoff relative to best response conditional on reaching the information set). ## [16.1.0a3] - 2023-09-29 diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index 579031f8e..242a91704 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -194,6 +194,7 @@ Probability distributions over strategies MixedStrategyProfile.__getitem__ MixedStrategyProfile.__setitem__ MixedStrategyProfile.payoff + MixedStrategyProfile.regret MixedStrategyProfile.strategy_value MixedStrategyProfile.strategy_value_deriv MixedStrategyProfileDouble.game diff --git a/src/games/behav.h b/src/games/behav.h index 6c06a4123..90fb2ec6f 100644 --- a/src/games/behav.h +++ b/src/games/behav.h @@ -46,7 +46,7 @@ template class MixedBehaviorProfile : public DVector { // structures for storing cached data: actions mutable DVector m_actionValues; // aka conditional payoffs - mutable DVector m_gripe; + mutable DVector m_regret; const T &ActionValue(const GameAction &act) const { return m_actionValues(act->GetInfoset()->GetPlayer()->GetNumber(), diff --git a/src/games/behav.imp b/src/games/behav.imp index f8698525b..03f388c3d 100644 --- a/src/games/behav.imp +++ b/src/games/behav.imp @@ -41,61 +41,61 @@ MixedBehaviorProfile::MixedBehaviorProfile(const MixedBehaviorProfile &p_p m_nodeValues(p_profile.m_nodeValues), m_infosetValues(p_profile.m_infosetValues), m_actionValues(p_profile.m_actionValues), - m_gripe(p_profile.m_gripe) + m_regret(p_profile.m_regret) { m_realizProbs = (T) 0.0; m_beliefs = (T) 0.0; m_nodeValues = (T) 0.0; m_infosetValues = (T) 0.0; m_actionValues = (T) 0.0; - m_gripe = (T) 0.0; + m_regret = (T) 0.0; } template MixedBehaviorProfile::MixedBehaviorProfile(const Game &p_game) - : DVector(p_game->NumActions()), + : DVector(p_game->NumActions()), m_support(BehaviorSupportProfile(p_game)), m_cacheValid(false), m_realizProbs(p_game->NumNodes()), m_beliefs(p_game->NumNodes()), - m_nvals(p_game->NumNodes()), + m_nvals(p_game->NumNodes()), m_bvals(p_game->NumNodes()), m_nodeValues(p_game->NumNodes(), p_game->NumPlayers()), m_infosetValues(p_game->NumInfosets()), m_actionValues(p_game->NumActions()), - m_gripe(p_game->NumActions()) + m_regret(p_game->NumActions()) { m_realizProbs = (T) 0.0; m_beliefs = (T) 0.0; m_nodeValues = (T) 0.0; m_infosetValues = (T) 0.0; m_actionValues = (T) 0.0; - m_gripe = (T) 0.0; + m_regret = (T) 0.0; SetCentroid(); } template MixedBehaviorProfile::MixedBehaviorProfile(const BehaviorSupportProfile &p_support) - : DVector(p_support.NumActions()), + : DVector(p_support.NumActions()), m_support(p_support), m_cacheValid(false), m_realizProbs(p_support.GetGame()->NumNodes()), m_beliefs(p_support.GetGame()->NumNodes()), - m_nvals(p_support.GetGame()->NumNodes()), + m_nvals(p_support.GetGame()->NumNodes()), m_bvals(p_support.GetGame()->NumNodes()), m_nodeValues(p_support.GetGame()->NumNodes(), p_support.GetGame()->NumPlayers()), m_infosetValues(p_support.GetGame()->NumInfosets()), m_actionValues(p_support.GetGame()->NumActions()), - m_gripe(p_support.GetGame()->NumActions()) + m_regret(p_support.GetGame()->NumActions()) { m_realizProbs = (T) 0.0; m_beliefs = (T) 0.0; m_nodeValues = (T) 0.0; m_infosetValues = (T) 0.0; m_actionValues = (T) 0.0; - m_gripe = (T) 0.0; + m_regret = (T) 0.0; SetCentroid(); } @@ -155,7 +155,7 @@ void MixedBehaviorProfile::RealizationProbs(const MixedStrategyProfile &mp template MixedBehaviorProfile::MixedBehaviorProfile(const MixedStrategyProfile &p_profile) - : DVector(p_profile.GetGame()->NumActions()), + : DVector(p_profile.GetGame()->NumActions()), m_support(p_profile.GetGame()), m_cacheValid(false), m_realizProbs(m_support.GetGame()->NumNodes()), @@ -166,14 +166,14 @@ MixedBehaviorProfile::MixedBehaviorProfile(const MixedStrategyProfile &p_p m_support.GetGame()->NumPlayers()), m_infosetValues(m_support.GetGame()->NumInfosets()), m_actionValues(m_support.GetGame()->NumActions()), - m_gripe(m_support.GetGame()->NumActions()) + m_regret(m_support.GetGame()->NumActions()) { m_realizProbs = (T) 0.0; m_beliefs = (T) 0.0; m_nodeValues = (T) 0.0; m_infosetValues = (T) 0.0; m_actionValues = (T) 0.0; - m_gripe = (T) 0.0; + m_regret = (T) 0.0; ((Vector &) *this).operator=((T)0); @@ -477,8 +477,8 @@ template const T &MixedBehaviorProfile::GetRegret(const GameAction &act) const { ComputeSolutionData(); - return m_gripe(act->GetInfoset()->GetPlayer()->GetNumber(), - act->GetInfoset()->GetNumber(), act->GetNumber()); + return m_regret(act->GetInfoset()->GetPlayer()->GetNumber(), + act->GetInfoset()->GetNumber(), act->GetNumber()); } template @@ -688,36 +688,40 @@ void MixedBehaviorProfile::ComputeSolutionDataPass1(const GameNode &node) con template void MixedBehaviorProfile::ComputeSolutionData() const { - if (!m_cacheValid) { - m_actionValues = (T) 0; - m_nodeValues = (T) 0; - m_infosetValues = (T) 0; - m_gripe = (T) 0; - ComputeSolutionDataPass1(m_support.GetGame()->GetRoot()); - ComputeSolutionDataPass2(m_support.GetGame()->GetRoot()); - - // At this point, mark the cache as value, so calls to GetPayoff() - // don't create a loop. - m_cacheValid = true; - - for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { - for (int iset = 1; iset <= m_support.GetGame()->NumInfosets()[pl]; iset++) { - GameInfoset infoset = m_support.GetGame()->GetPlayer(pl)->GetInfoset(iset); - - m_infosetValues(infoset->GetPlayer()->GetNumber(), infoset->GetNumber()) = (T) 0; - for (int act = 1; act <= infoset->NumActions(); act++) { - GameAction action = infoset->GetAction(act); - m_infosetValues(infoset->GetPlayer()->GetNumber(), - infoset->GetNumber()) += GetActionProb(action) * ActionValue(action); - } + if (m_cacheValid) { + return; + } + m_actionValues = (T) 0; + m_nodeValues = (T) 0; + m_infosetValues = (T) 0; + m_regret = (T) 0; + ComputeSolutionDataPass1(m_support.GetGame()->GetRoot()); + ComputeSolutionDataPass2(m_support.GetGame()->GetRoot()); + + // At this point, mark the cache as value, so calls to GetPayoff() + // don't create a loop. + m_cacheValid = true; + + for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { + for (int iset = 1; iset <= m_support.GetGame()->NumInfosets()[pl]; iset++) { + GameInfoset infoset = m_support.GetGame()->GetPlayer(pl)->GetInfoset(iset); + + m_infosetValues(infoset->GetPlayer()->GetNumber(), infoset->GetNumber()) = (T) 0; + for (int act = 1; act <= infoset->NumActions(); act++) { + GameAction action = infoset->GetAction(act); + m_infosetValues(infoset->GetPlayer()->GetNumber(), + infoset->GetNumber()) += GetActionProb(action) * ActionValue(action); + } - for (int act = 1; act <= infoset->NumActions(); act++) { - GameAction action = infoset->GetAction(act); - m_gripe(action->GetInfoset()->GetPlayer()->GetNumber(), - action->GetInfoset()->GetNumber(), - action->GetNumber()) = - (ActionValue(action) - GetPayoff(infoset)) * GetRealizProb(infoset); - } + T brpayoff = ActionValue(infoset->GetAction(1)); + for (int act = 1; act <= infoset->NumActions(); act++) { + brpayoff = std::max(brpayoff, ActionValue(infoset->GetAction(act))); + } + for (int act = 1; act <= infoset->NumActions(); act++) { + GameAction action = infoset->GetAction(act); + m_regret(action->GetInfoset()->GetPlayer()->GetNumber(), + action->GetInfoset()->GetNumber(), + act) = brpayoff - ActionValue(action); } } } diff --git a/src/games/mixed.h b/src/games/mixed.h index d4fc62c3c..138d9708f 100644 --- a/src/games/mixed.h +++ b/src/games/mixed.h @@ -52,6 +52,8 @@ template class MixedStrategyProfileRep { virtual T GetPayoff(int pl) const = 0; virtual T GetPayoffDeriv(int pl, const GameStrategy &) const = 0; virtual T GetPayoffDeriv(int pl, const GameStrategy &, const GameStrategy &) const = 0; + + T GetRegret(const GameStrategy &) const; }; template class TreeMixedStrategyProfileRep @@ -253,6 +255,11 @@ template class MixedStrategyProfile { T GetPayoff(const GameStrategy &p_strategy) const { return GetPayoffDeriv(p_strategy->GetPlayer()->GetNumber(), p_strategy); } + /// Computes the regret to playing the pure strategy compared to the payoff + /// of the best response + T GetRegret(const GameStrategy &p_strategy) const + { return m_rep->GetRegret(p_strategy); } + /// \brief Computes the Lyapunov value of the profile /// /// Computes the Lyapunov value of the profile. This is a nonnegative diff --git a/src/games/mixed.imp b/src/games/mixed.imp index 8f9daf2ba..cab14ac56 100644 --- a/src/games/mixed.imp +++ b/src/games/mixed.imp @@ -120,6 +120,22 @@ template void MixedStrategyProfileRep::Randomize(int p_denom) } } +template +T MixedStrategyProfileRep::GetRegret(const GameStrategy &p_strategy) const +{ + GamePlayer player = p_strategy->GetPlayer(); + T payoff = GetPayoffDeriv(player->GetNumber(), p_strategy); + T brpayoff = payoff; + for (int st = 1; st <= player->NumStrategies(); st++) { + if (st != p_strategy->GetNumber()) { + brpayoff = std::max(brpayoff, + GetPayoffDeriv(player->GetNumber(), player->GetStrategy(st))); + } + } + return brpayoff - payoff; +} + + //======================================================================== // TreeMixedStrategyProfileRep //======================================================================== diff --git a/src/pygambit/behav.pxi b/src/pygambit/behav.pxi index 6f056a7f3..a74afc619 100644 --- a/src/pygambit/behav.pxi +++ b/src/pygambit/behav.pxi @@ -359,8 +359,13 @@ class MixedBehaviorProfile: return self._infoset_prob(self.game._resolve_infoset(infoset, 'infoset_prob')) def regret(self, action: typing.Union[Action, str]): - """Returns the regret associated with `action`. The regret is the loss of - payoff relative to the best response to the profile. + """Returns the regret to playing `action`, if all other + players play according to the profile. + + The regret is defined as the difference between the payoff of the + best-response action and the payoff of `action`. Payoffs are computed + conditional on reaching the information set. By convention, the + regret is always non-negative. Parameters ---------- diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index a9bc20364..844e836c8 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -246,6 +246,7 @@ cdef extern from "games/mixed.h": double getitem_strategy "operator[]"(c_GameStrategy) except +IndexError double GetPayoff(c_GamePlayer) double GetPayoff(c_GameStrategy) + double GetRegret(c_GameStrategy) double GetPayoffDeriv(int, c_GameStrategy, c_GameStrategy) double GetLiapValue() c_MixedStrategyProfileDouble ToFullSupport() @@ -263,6 +264,7 @@ cdef extern from "games/mixed.h": c_Rational getitem_strategy "operator[]"(c_GameStrategy) except +IndexError c_Rational GetPayoff(c_GamePlayer) c_Rational GetPayoff(c_GameStrategy) + c_Rational GetRegret(c_GameStrategy) c_Rational GetPayoffDeriv(int, c_GameStrategy, c_GameStrategy) c_Rational GetLiapValue() c_MixedStrategyProfileRational ToFullSupport() diff --git a/src/pygambit/mixed.pxi b/src/pygambit/mixed.pxi index 7295b5773..6dc8225cd 100644 --- a/src/pygambit/mixed.pxi +++ b/src/pygambit/mixed.pxi @@ -191,6 +191,29 @@ class MixedStrategyProfile: """ return self._strategy_value(self.game._resolve_strategy(strategy, 'strategy_value')) + def regret(self, strategy: typing.Union[Strategy, str]): + """Returns the regret to playing `strategy`, if all other + players play according to the profile. + + The regret is defined as the difference between the payoff of the + best-response strategy and the payoff of `strategy`. By convention, the + regret is always non-negative. + + Parameters + ---------- + strategy : Strategy or str + The strategy to get the regret for. If a string is passed, the + strategy is determined by finding the strategy with that label, if any. + + Raises + ------ + MismatchError + If `strategy` is a `Strategy` from a different game. + KeyError + If `strategy` is a string and no strategy in the game has that label. + """ + return self._regret(self.game._resolve_strategy(strategy, 'regret')) + def strategy_value_deriv(self, strategy: typing.Union[Strategy, str], other: typing.Union[Strategy, str]): """Returns the derivative of the payoff to playing `strategy`, with respect to the probability that `other` is played. @@ -227,6 +250,9 @@ class MixedStrategyProfileDouble(MixedStrategyProfile): def _strategy_value(self, strategy: Strategy) -> float: return deref(self.profile).GetPayoff(strategy.strategy) + def _regret(self, strategy: Strategy) -> float: + return deref(self.profile).GetRegret(strategy.strategy) + def _strategy_value_deriv(self, strategy: Strategy, other: Strategy) -> float: return deref(self.profile).GetPayoffDeriv( strategy.player.number + 1, strategy.strategy, other.strategy @@ -325,6 +351,9 @@ class MixedStrategyProfileRational(MixedStrategyProfile): def _strategy_value(self, strategy: Strategy) -> Rational: return rat_to_py(deref(self.profile).GetPayoff(strategy.strategy)) + def _regret(self, strategy: Strategy) -> Rational: + return rat_to_py(deref(self.profile).GetRegret(strategy.strategy)) + def _strategy_value_deriv(self, strategy: Strategy, other: Strategy) -> Rational: return rat_to_py(deref(self.profile).GetPayoffDeriv( strategy.player.number + 1, strategy.strategy, other.strategy diff --git a/src/pygambit/tests/test_behav.py b/src/pygambit/tests/test_behav.py index 5f5697a7b..4a39d5b34 100644 --- a/src/pygambit/tests/test_behav.py +++ b/src/pygambit/tests/test_behav.py @@ -540,85 +540,15 @@ def test_action_value_by_string(self): assert self.profile_rational.action_value("D3") == gbt.Rational("3/1") def test_regret(self): - "Test to retrieve regret value associated to an action" - assert ( - self.profile_double.regret( - self.game.players[0].infosets[0].actions[0] - ) == 0.0 - ) - assert ( - self.profile_double.regret( - self.game.players[0].infosets[0].actions[1] - ) == 0.0 - ) - assert ( - self.profile_double.regret( - self.game.players[1].infosets[0].actions[0] - ) == 0.0 - ) - assert ( - self.profile_double.regret( - self.game.players[1].infosets[0].actions[1] - ) == 0.0 - ) - assert ( - self.profile_double.regret( - self.game.players[2].infosets[0].actions[0] - ) == 0.25 - ) - assert ( - self.profile_double.regret( - self.game.players[2].infosets[0].actions[1] - ) == -0.25 - ) - assert ( - self.profile_rational.regret( - self.game.players[0].infosets[0].actions[0] - ) == gbt.Rational("0/1") - ) - assert ( - self.profile_rational.regret( - self.game.players[0].infosets[0].actions[1] - ) == gbt.Rational("0/1") - ) - assert ( - self.profile_rational.regret( - self.game.players[1].infosets[0].actions[0] - ) == gbt.Rational("0/1") - ) - assert ( - self.profile_rational.regret( - self.game.players[1].infosets[0].actions[1] - ) == gbt.Rational("0/1") - ) - assert ( - self.profile_rational.regret( - self.game.players[2].infosets[0].actions[0] - ) == gbt.Rational("1/4") - ) - assert ( - self.profile_rational.regret( - self.game.players[2].infosets[0].actions[1] - ) == gbt.Rational(-1, 4) - ) - - def test_regret_by_string(self): - """Test to retrieve regret value associated to an action - by string values - """ - assert self.profile_double.regret("U1") == 0.0 - assert self.profile_double.regret("D1") == 0.0 - assert self.profile_double.regret("U2") == 0.0 - assert self.profile_double.regret("D2") == 0.0 - assert self.profile_double.regret("U3") == 0.25 - assert self.profile_double.regret("D3") == -0.25 - - assert self.profile_rational.regret("U1") == gbt.Rational("0/1") - assert self.profile_rational.regret("D1") == gbt.Rational("0/1") - assert self.profile_rational.regret("U2") == gbt.Rational("0/1") - assert self.profile_rational.regret("D2") == gbt.Rational("0/1") - assert self.profile_rational.regret("U3") == gbt.Rational(1, 4) - assert self.profile_rational.regret("D3") == gbt.Rational(-1, 4) + for profile in [self.profile_double, self.profile_rational]: + for player in self.game.players: + for infoset in player.infosets: + for action in infoset.actions: + assert ( + profile.regret(action) == + max(profile.action_value(a) for a in infoset.actions) - + profile.action_value(action) + ) def test_liap_values(self): "Test to retrieve Lyapunov values" diff --git a/src/pygambit/tests/test_mixed.py b/src/pygambit/tests/test_mixed.py index b628b0189..2e2500338 100644 --- a/src/pygambit/tests/test_mixed.py +++ b/src/pygambit/tests/test_mixed.py @@ -1,4 +1,5 @@ import pytest +import numpy as np import pygambit import pygambit as gbt @@ -70,6 +71,19 @@ def test_strategy_value(): assert game.mixed_strategy_profile(rational=True).strategy_value(strategy) == 0 +def test_strategy_regret(): + m = np.array([[8, 2], [10, 5]]) + game = gbt.Game.from_arrays(m, np.transpose(m)) + profile = game.mixed_strategy_profile(rational=False) + for player in game.players: + for strategy in player.strategies: + assert ( + profile.regret(strategy) == + max(profile.strategy_value(s) for s in player.strategies) - + profile.strategy_value(strategy) + ) + + def test_strategy_value_by_label(): game = _create_strategic_game() assert game.mixed_strategy_profile(rational=False).strategy_value("defect") == 0 From b4000b9298ca9242686f19b95edca080a1f12294 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 13 Oct 2023 12:14:42 +0100 Subject: [PATCH 06/10] Extend user guide discussion of properties of mixed profiles. --- doc/pygambit.api.rst | 2 ++ doc/pygambit.user.rst | 80 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index 242a91704..531bbf0a1 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -191,6 +191,7 @@ Probability distributions over strategies .. autosummary:: :toctree: api/ + MixedStrategyProfile MixedStrategyProfile.__getitem__ MixedStrategyProfile.__setitem__ MixedStrategyProfile.payoff @@ -214,6 +215,7 @@ Probability distributions over behavior .. autosummary:: :toctree: api/ + MixedBehaviorProfile MixedBehaviorProfile.__getitem__ MixedBehaviorProfile.__setitem__ MixedBehaviorProfile.payoff diff --git a/doc/pygambit.user.rst b/doc/pygambit.user.rst index 4ed5a6255..5c313cbd6 100644 --- a/doc/pygambit.user.rst +++ b/doc/pygambit.user.rst @@ -349,16 +349,17 @@ the extensive representation. Assuming that ``g`` refers to the game len(eqa) The result of the calculation is a list of :py:class:`~pygambit.gambit.MixedBehaviorProfile`. -Such a profile specifies action probabilities for each information set. -The profile represents these hierarchically, with information sets grouped by player. -We can just focus on the strategy of one player by indexing the profile by that -player, +A mixed behavior profile specifies, for each information set, the probability distribution over +actions at that information set. +Indexing a :py:class:`.MixedBehaviorProfile` by a player gives the probability distributions +over each of that player's information sets: + .. ipython:: python eqa[0]["Alice"] -In this case, at Alice's first information set, where she has the King, she always raises. +In this case, at Alice's first information set, the one at which she has the King, she always raises. At her second information set, where she has the Queen, she sometimes bluffs, raising with probability one-third. Looking at Bob's strategy, @@ -368,6 +369,43 @@ probability one-third. Looking at Bob's strategy, Bob meets Alice's raise two-thirds of the time. +Because this is an equilibrium, the fact that Bob randomizes at his information set must mean he +is indifferent between the two actions at his information set. :py:meth:`.MixedBehaviorProfile.action_value` +returns the expected payoff of taking an action, conditional on reaching that action's information set: + +.. ipython:: python + + [eqa[0].action_value(action) for action in g.players["Bob"].infosets[0].actions] + +Bob's indifference between his actions arises because of his beliefs given Alice's strategy. +:py:meth:`.MixedBehaviorProfile.belief` returns the probability of reaching a node, conditional on +its information set being reached: + +.. ipython:: python + + [eqa[0].belief(node) for node in g.players["Bob"].infosets[0].members] + +Bob believes that, conditional on Alice raising, there's a 75% chance that she has the king; +therefore, the expected payoff to meeting is in fact -1 as computed. +:py:meth:`.MixedBehaviorProfile.infoset_prob` returns the probability that an information set is +reached: + +.. ipython:: python + + eqa[0].infoset_prob(g.players["Bob"].infosets[0]) + +The corresponding probability that a node is reached in the play of the game is given +by :py:meth:`.MixedBehaviorProfile.realiz_prob`, and the expected payoff to a player +conditional on reaching a node is given by :py:meth:`.MixedBehaviorProfile.node_value`. + +The overall expected payoff to a player given the behavior profile is returned by +:py:meth:`.MixedBehaviorProfile.payoff`: + +.. ipython:: python + + eqa[0].payoff("Alice") + eqa[0].payoff("Bob") + The equilibrium computed expresses probabilities in rational numbers. Because the numerical data of games in Gambit :ref:`are represented exactly `, methods which are specialized to two-player games, :py:func:`.lp_solve`, :py:func:`.lcp_solve`, @@ -380,7 +418,7 @@ on that representation. It is also possible to compute using the strategic repr .. ipython:: python - [s.label for s in g.players["Alice"].strategies] + [s.label for s in g.players["Alice"].strategies] In the strategic form of this game, Alice has four strategies. The generated strategy labels list the action numbers taken at each information set. We can therefore apply a method which @@ -396,10 +434,34 @@ process in floating-point arithmetic, so it returns profiles with probabilities floating-point numbers. This method operates on the strategic representation of the game, so the returned results are of type :py:class:`~pygambit.gambit.MixedStrategyProfile`, and specify, for each player, a probability distribution over that player's strategies. -We can convert freely between :py:class:`~pygambit.gambit.MixedStrategyProfile` and -:py:class:`~pygambit.gambit.MixedBehaviorProfile` representations +Indexing a :py:class:`.MixedStrategyProfile` by a player gives the probability distribution +over that player's strategies only. .. ipython:: python - eqa[0].as_behavior() + eqa[0]["Alice"] + eqa[0]["Bob"] + +The expected payoff to a strategy is provided by :py:meth:`.MixedStrategyProfile.strategy_value`: + +.. ipython:: python + + [eqa[0].strategy_value(strategy) for strategy in g.players["Alice"].strategies] + [eqa[0].strategy_value(strategy) for strategy in g.players["Bob"].strategies] +The overall expected payoff to a player is returned by :py:meth:`.MixedStrategyProfile.payoff`: + +.. ipython:: python + + eqa[0].payoff("Alice") + eqa[0].payoff("Bob") + +When a game has an extensive representation, we can convert freely between +:py:class:`~pygambit.gambit.MixedStrategyProfile` and the corresponding +:py:class:`~pygambit.gambit.MixedBehaviorProfile` representation of the same strategies +using :py:meth:`.MixedStrategyProfile.as_behavior` and :py:meth:`.MixedBehaviorProfile.as_strategy`. + +.. ipython:: python + + eqa[0].as_behavior() + eqa[0].as_behavior().as_strategy() From 26ab4c7bc5030b1f387aba85c73764ccf8b6f833 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 13 Oct 2023 13:37:28 +0100 Subject: [PATCH 07/10] Implement MixedBehaviorProfile.node_value() This adds direct access to the expected payoff to a player conditional on reaching a specified node. --- doc/pygambit.api.rst | 1 + doc/pygambit.user.rst | 4 ++++ src/games/behav.h | 1 + src/games/behav.imp | 12 +++++++++-- src/pygambit/behav.pxi | 37 ++++++++++++++++++++++++++++++++ src/pygambit/gambit.pxd | 2 ++ src/pygambit/tests/test_behav.py | 10 +++++++++ 7 files changed, 65 insertions(+), 2 deletions(-) diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index 531bbf0a1..5a3252a9a 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -222,6 +222,7 @@ Probability distributions over behavior MixedBehaviorProfile.regret MixedBehaviorProfile.action_value MixedBehaviorProfile.infoset_value + MixedBehaviorProfile.node_value MixedBehaviorProfile.infoset_prob MixedBehaviorProfile.belief MixedBehaviorProfile.is_defined_at diff --git a/doc/pygambit.user.rst b/doc/pygambit.user.rst index 5c313cbd6..20033fac1 100644 --- a/doc/pygambit.user.rst +++ b/doc/pygambit.user.rst @@ -398,6 +398,10 @@ The corresponding probability that a node is reached in the play of the game is by :py:meth:`.MixedBehaviorProfile.realiz_prob`, and the expected payoff to a player conditional on reaching a node is given by :py:meth:`.MixedBehaviorProfile.node_value`. +.. ipython:: python + + [eqa[0].node_value("Bob", node) for node in g.players["Bob"].infosets[0].members] + The overall expected payoff to a player given the behavior profile is returned by :py:meth:`.MixedBehaviorProfile.payoff`: diff --git a/src/games/behav.h b/src/games/behav.h index 90fb2ec6f..00e4a0efd 100644 --- a/src/games/behav.h +++ b/src/games/behav.h @@ -175,6 +175,7 @@ template class MixedBehaviorProfile : public DVector { T GetRealizProb(const GameInfoset &iset) const; const T &GetBeliefProb(const GameNode &node) const; Vector GetPayoff(const GameNode &node) const; + const T &GetPayoff(const GamePlayer &player, const GameNode &node) const; const T &GetPayoff(const GameInfoset &iset) const; const T &GetPayoff(const GameAction &act) const; T GetActionProb(const GameAction &act) const; diff --git a/src/games/behav.imp b/src/games/behav.imp index 03f388c3d..7ae58f9df 100644 --- a/src/games/behav.imp +++ b/src/games/behav.imp @@ -440,6 +440,14 @@ Vector MixedBehaviorProfile::GetPayoff(const GameNode &node) const return m_nodeValues.Row(node->GetNumber()); } +template +const T &MixedBehaviorProfile::GetPayoff(const GamePlayer &p_player, const GameNode &p_node) const +{ + ComputeSolutionData(); + return m_nodeValues(p_node->GetNumber(), p_player->GetNumber()); +} + + template const T &MixedBehaviorProfile::GetPayoff(const GameInfoset &iset) const { @@ -648,8 +656,8 @@ void MixedBehaviorProfile::ComputeSolutionDataPass2(const GameNode &node) con GameAction act = childNode->GetPriorAction(); for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { - m_nodeValues(node->GetNumber(), pl) += - GetActionProb(act) * m_nodeValues(childNode->GetNumber(), pl); + m_nodeValues(node->GetNumber(), pl) += + GetActionProb(act) * m_nodeValues(childNode->GetNumber(), pl); } if (!iset->IsChanceInfoset()) { diff --git a/src/pygambit/behav.pxi b/src/pygambit/behav.pxi index a74afc619..56233eed9 100644 --- a/src/pygambit/behav.pxi +++ b/src/pygambit/behav.pxi @@ -292,6 +292,37 @@ class MixedBehaviorProfile: raise ValueError("payoff() is not defined for the chance player") return self._payoff(resolved_player) + def node_value(self, player: typing.Union[Player, str], + node: typing.Union[Node, str]): + """Returns the expected payoff to `player` conditional on play reaching `node`, + if all players play according to the profile. + + Parameters + ---------- + player : Player or str + The player to get the payoff for. If a string is passed, the + player is determined by finding the player with that label, if any. + node : Node or str + The node to get the payoff att. If a string is passed, the + node is determined by finding the node with that label, if any. + + Raises + ------ + MismatchError + If `player` is a `Player` from a different game or `node` is a `Node` + from a different game. + KeyError + If `player` is a string and no player in the game has that label, or + `node` is a string and no node in the game has that label. + ValueError + If `player` resolves to the chance player + """ + resolved_player = self.game._resolve_player(player, 'node_value') + resolved_node = self.game._resolve_node(node, 'node_value') + if resolved_player.is_chance: + raise ValueError("node_value() is not defined for the chance player") + return self._node_value(resolved_player, resolved_node) + def infoset_value(self, infoset: typing.Union[Infoset, str]): """Returns the expected payoff to the player conditional on reaching an information set, if all players play according to the profile. @@ -411,6 +442,9 @@ class MixedBehaviorProfileDouble(MixedBehaviorProfile): def _infoset_value(self, infoset: Infoset) -> float: return deref(self.profile).GetPayoff(infoset.infoset) + def _node_value(self, player: Player, node: Node) -> float: + return deref(self.profile).GetPayoff(player.player, node.node) + def _action_value(self, action: Action) -> float: return deref(self.profile).GetPayoff(action.action) @@ -506,6 +540,9 @@ class MixedBehaviorProfileRational(MixedBehaviorProfile): def _infoset_value(self, infoset: Infoset) -> Rational: return rat_to_py(deref(self.profile).GetPayoff(infoset.infoset)) + def _node_value(self, player: Player, node: Node) -> Rational: + return rat_to_py(deref(self.profile).GetPayoff(player.player, node.node)) + def _action_value(self, action: Action) -> Rational: return rat_to_py(deref(self.profile).GetPayoff(action.action)) diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 844e836c8..26a474e30 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -286,6 +286,7 @@ cdef extern from "games/behav.h": double GetBeliefProb(c_GameNode) double GetRealizProb(c_GameInfoset) double GetPayoff(c_GameInfoset) + double GetPayoff(c_GamePlayer, c_GameNode) double GetPayoff(c_GameAction) double GetRegret(c_GameAction) double GetLiapValue() @@ -309,6 +310,7 @@ cdef extern from "games/behav.h": c_Rational GetBeliefProb(c_GameNode) c_Rational GetRealizProb(c_GameInfoset) c_Rational GetPayoff(c_GameInfoset) + c_Rational GetPayoff(c_GamePlayer, c_GameNode) c_Rational GetPayoff(c_GameAction) c_Rational GetRegret(c_GameAction) c_Rational GetLiapValue() diff --git a/src/pygambit/tests/test_behav.py b/src/pygambit/tests/test_behav.py index 4a39d5b34..03ca179ea 100644 --- a/src/pygambit/tests/test_behav.py +++ b/src/pygambit/tests/test_behav.py @@ -550,6 +550,16 @@ def test_regret(self): profile.action_value(action) ) + def test_node_value(self): + # Another good node_value test (to be written!) is its martingale property: it should + # be the expected value of its children's node_values, given the probability + # distribution at the node. + for profile in [self.profile_double, self.profile_rational]: + for player in self.game.players: + assert ( + profile.node_value(player, self.game.root) == profile.payoff(player) + ) + def test_liap_values(self): "Test to retrieve Lyapunov values" assert self.profile_double.liap_value() == 0.0625 From b07f68f0dba30c43e5be7d0fd6899e6e9435c93a Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 13 Oct 2023 14:38:22 +0100 Subject: [PATCH 08/10] Revise CI config to avoid duplicate runs on pull requests from this repo. --- .github/workflows/lint.yml | 2 ++ .github/workflows/python.yml | 2 ++ .github/workflows/tools.yml | 3 +++ 3 files changed, 7 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f08577622..ff9b708fb 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,6 +7,7 @@ on: jobs: clang-tidy: runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - uses: actions/checkout@v4 - name: Install dependencies @@ -29,6 +30,7 @@ jobs: flake8: runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name strategy: matrix: python-version: ['3.12'] diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 8feb8181b..bad9d7a2c 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -7,6 +7,7 @@ on: jobs: linux: runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name strategy: matrix: python-version: ['3.8', '3.12'] @@ -35,6 +36,7 @@ jobs: windows: runs-on: windows-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name strategy: matrix: python-version: ['3.12'] diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 40fafbde1..7d8adf9f1 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -7,6 +7,7 @@ on: jobs: linux: runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - uses: actions/checkout@v4 - name: Install dependencies @@ -41,6 +42,7 @@ jobs: macos: runs-on: macos-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - uses: actions/checkout@v4 - name: Install dependencies @@ -58,6 +60,7 @@ jobs: windows: runs-on: windows-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name defaults: run: shell: msys2 {0} From 8c6b8c799e131f7d4c1a2638053e50586f8f9ea9 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 13 Oct 2023 14:17:07 +0100 Subject: [PATCH 09/10] Make interface to mixed profiles uniform. This creates a uniform approach to implementing mixed strategy and mixed behavior profiles. In all cases, member functions are defined in the base (precision-agnostic) class, and then the implementation is delegated to a private member in the derived class. The documentation index has also been updated to include all public members of the class API. --- doc/pygambit.api.rst | 29 ++++---- src/pygambit/behav.pxi | 104 +++++++++++++++-------------- src/pygambit/mixed.pxi | 147 +++++++++++++++++++---------------------- 3 files changed, 135 insertions(+), 145 deletions(-) diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index 5a3252a9a..6d75ce509 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -192,21 +192,18 @@ Probability distributions over strategies :toctree: api/ MixedStrategyProfile + MixedStrategyProfile.game MixedStrategyProfile.__getitem__ MixedStrategyProfile.__setitem__ MixedStrategyProfile.payoff MixedStrategyProfile.regret MixedStrategyProfile.strategy_value MixedStrategyProfile.strategy_value_deriv - MixedStrategyProfileDouble.game - MixedStrategyProfileDouble.liap_value - MixedStrategyProfileDouble.as_behavior - MixedStrategyProfileDouble.randomize - MixedStrategyProfileRational.game - MixedStrategyProfileRational.liap_value - MixedStrategyProfileRational.as_behavior - MixedStrategyProfileRational.randomize - + MixedStrategyProfile.liap_value + MixedStrategyProfile.as_behavior + MixedStrategyProfile.randomize + MixedStrategyProfile.normalize + MixedStrategyProfile.copy Probability distributions over behavior @@ -216,6 +213,7 @@ Probability distributions over behavior :toctree: api/ MixedBehaviorProfile + MixedBehaviorProfile.game MixedBehaviorProfile.__getitem__ MixedBehaviorProfile.__setitem__ MixedBehaviorProfile.payoff @@ -226,14 +224,11 @@ Probability distributions over behavior MixedBehaviorProfile.infoset_prob MixedBehaviorProfile.belief MixedBehaviorProfile.is_defined_at - MixedBehaviorProfileDouble.game - MixedBehaviorProfileDouble.liap_value - MixedBehaviorProfileDouble.as_strategy - MixedBehaviorProfileDouble.randomize - MixedBehaviorProfileRational.game - MixedBehaviorProfileRational.liap_value - MixedBehaviorProfileRational.as_strategy - MixedBehaviorProfileRational.randomize + MixedBehaviorProfile.liap_value + MixedBehaviorProfile.as_strategy + MixedBehaviorProfile.randomize + MixedBehaviorProfile.normalize + MixedBehaviorProfile.copy Computation on supports diff --git a/src/pygambit/behav.pxi b/src/pygambit/behav.pxi index 56233eed9..f14d00fe2 100644 --- a/src/pygambit/behav.pxi +++ b/src/pygambit/behav.pxi @@ -116,6 +116,11 @@ class MixedBehaviorProfile: + r"\right]$" ) + @property + def game(self) -> Game: + """The game on which this mixed behaviour profile is defined.""" + return self._game + def __getitem__(self, index: typing.Union[Player, Infoset, Action, str]): """Returns a probability, mixed agent strategy, or mixed behavior strategy. @@ -413,6 +418,42 @@ class MixedBehaviorProfile: """ return self._regret(self.game._resolve_action(action, 'regret')) + def liap_value(self): + """Returns the Lyapunov value (see [McK91]_) of the strategy profile. + + The Lyapunov value is a non-negative number which is zero exactly at + Nash equilibria. + """ + return self._liap_value() + + def as_strategy(self) -> MixedStrategyProfile: + """Returns a `MixedStrategyProfile` which is equivalent + to the profile. + """ + return self._as_strategy() + + def randomize(self, denom: typing.Optional[int] = None) -> None: + """Randomizes the probabilities in the profile. These are + generated as uniform distributions over the actions at each + information set. If + ``denom`` is specified, all probabilities are divisible by + ``denom``, that is, the distribution is uniform over a discrete + grid of mixed strategies. + """ + if denom is not None and denom <= 0: + raise ValueError("randomize(): denominator must be a positive integer") + self._randomize(denom) + + def normalize(self) -> MixedBehaviorProfile: + """Create a profile with the same action proportions as this + one, but normalised so probabilities for each infoset sum to one. + """ + return self._normalize() + + def copy(self) -> MixedBehaviorProfile: + """Creates a copy of the behavior strategy profile.""" + return self._copy() + @cython.cclass class MixedBehaviorProfileDouble(MixedBehaviorProfile): @@ -457,52 +498,32 @@ class MixedBehaviorProfileDouble(MixedBehaviorProfile): deref(self.profile) == deref(cython.cast(MixedBehaviorProfileDouble, other).profile) ) - def copy(self) -> MixedBehaviorProfileDouble: - """Creates a copy of the behavior strategy profile.""" + def _copy(self) -> MixedBehaviorProfileDouble: behav = MixedBehaviorProfileDouble() behav.profile = make_shared[c_MixedBehaviorProfileDouble](deref(self.profile)) return behav - def as_strategy(self) -> MixedStrategyProfileDouble: - """Returns a `MixedStrategyProfile` which is equivalent - to the profile. - """ + def _as_strategy(self) -> MixedStrategyProfileDouble: mixed = MixedStrategyProfileDouble() mixed.profile = make_shared[c_MixedStrategyProfileDouble](deref(self.profile).ToMixedProfile()) return mixed - def liap_value(self) -> float: - """Returns the Lyapunov value (see [McK91]_) of the strategy profile. The - Lyapunov value is a non-negative number which is zero exactly at - Nash equilibria. - """ + def _liap_value(self) -> float: return deref(self.profile).GetLiapValue() - def normalize(self) -> MixedBehaviorProfileDouble: - """Create a profile with the same action proportions as this - one, but normalised so probabilities for each infoset sum to one. - """ + def _normalize(self) -> MixedBehaviorProfileDouble: profile = MixedBehaviorProfileDouble() profile.profile = make_shared[c_MixedBehaviorProfileDouble](deref(self.profile).Normalize()) return profile - def randomize(self, denom=None) -> None: - """Randomizes the probabilities in the profile. These are - generated as uniform distributions over the actions at each - information set. If - ``denom`` is specified, all probabilities are divisible by - ``denom``, that is, the distribution is uniform over a discrete - grid of mixed strategies. - """ + def _randomize(self, denom: typing.Optional[int] = None) -> None: if denom is None: deref(self.profile).Randomize() else: deref(self.profile).Randomize(denom) @property - def game(self) -> Game: - """The game on which this mixed behaviour profile is defined. - """ + def _game(self) -> Game: g = Game() g.game = deref(self.profile).GetGame() return g @@ -555,46 +576,31 @@ class MixedBehaviorProfileRational(MixedBehaviorProfile): deref(self.profile) == deref(cython.cast(MixedBehaviorProfileRational, other).profile) ) - def copy(self) -> MixedBehaviorProfileRational: - """Creates a copy of the behavior strategy profile.""" + def _copy(self) -> MixedBehaviorProfileRational: behav = MixedBehaviorProfileRational() behav.profile = make_shared[c_MixedBehaviorProfileRational](deref(self.profile)) return behav - def as_strategy(self) -> MixedStrategyProfileRational: - """Returns a `MixedStrategyProfile` which is equivalent - to the profile. - """ + def _as_strategy(self) -> MixedStrategyProfileRational: mixed = MixedStrategyProfileRational() mixed.profile = make_shared[c_MixedStrategyProfileRational](deref(self.profile).ToMixedProfile()) return mixed - def liap_value(self) -> Rational: - """Returns the Lyapunov value (see [McK91]_) of the strategy profile. The - Lyapunov value is a non-negative number which is zero exactly at - Nash equilibria. - """ + def _liap_value(self) -> Rational: return rat_to_py(deref(self.profile).GetLiapValue()) - def normalize(self) -> MixedBehaviorProfileRational: - """Create a profile with the same action proportions as this - one, but normalised so probabilites for each infoset sum to one. - """ + def _normalize(self) -> MixedBehaviorProfileRational: profile = MixedBehaviorProfileRational() profile.profile = make_shared[c_MixedBehaviorProfileRational](deref(self.profile).Normalize()) return profile - def randomize(self, denom) -> None: - """Randomizes the probabilities in the profile. These are - generated as uniform distributions over the actions at each - information set. - """ + def _randomize(self, denom: typing.Optional[int] = None) -> None: + if denom is None: + raise ValueError("randomize() on rational-precision profiles requires a denominator") deref(self.profile).Randomize(denom) @property - def game(self) -> Game: - """The game on which this mixed behaviour profile is defined. - """ + def _game(self) -> Game: g = Game() g.game = deref(self.profile).GetGame() return g diff --git a/src/pygambit/mixed.pxi b/src/pygambit/mixed.pxi index 6dc8225cd..1a4bc3098 100644 --- a/src/pygambit/mixed.pxi +++ b/src/pygambit/mixed.pxi @@ -72,6 +72,11 @@ class MixedStrategyProfile: def _repr_latex_(self): return r"$\left[" + ",".join([ self[player]._repr_latex_().replace("$","") for player in self.game.players ]) + r"\right]$" + @property + def game(self) -> Game: + """The game on which this mixed strategy profile is defined.""" + return self._game + def __getitem__(self, index: typing.Union[Player, Strategy, str]): """Returns a probability or mixed strategy. @@ -230,6 +235,55 @@ class MixedStrategyProfile: self.game._resolve_strategy(strategy, 'strategy_value_deriv', 'other') ) + def liap_value(self): + """Returns the Lyapunov value (see [McK91]_) of the strategy profile. + + The Lyapunov value is a non-negative number which is zero exactly at + Nash equilibria. + """ + return self._liap_value() + + def as_behavior(self) -> MixedBehaviorProfile: + """Creates a mixed behavior profile which is equivalent to this + mixed strategy profile. + + Returns + ------- + MixedBehaviorProfile + The equivalent mixed behavior profile. + + Raises + ------ + UndefinedOperationError + If the game does not have a tree representation. + """ + if not self.game.is_tree: + raise UndefinedOperationError( + "Mixed behavior profiles are not defined for strategic games" + ) + return self._as_behavior() + + def randomize(self, denom: typing.Optional[int] = None) -> None: + """Randomizes the probabilities in the profile. These are + generated as uniform distributions over each mixed strategy. If + ``denom`` is specified, all probabilities are divisible by + ``denom``, that is, the distribution is uniform over a discrete + grid of mixed strategies. + """ + if denom is not None and denom <= 0: + raise ValueError("randomize(): denominator must be a positive integer") + self._randomize(denom) + + def normalize(self) -> MixedStrategyProfile: + """Create a profile with the same strategy proportions as this + one, but normalised so probabilities for each player sum to one. + """ + return self._normalize() + + def copy(self) -> MixedStrategyProfile: + """Creates a copy of the mixed strategy profile.""" + return self._copy() + @cython.cclass class MixedStrategyProfileDouble(MixedStrategyProfile): @@ -264,66 +318,32 @@ class MixedStrategyProfileDouble(MixedStrategyProfile): deref(self.profile) == deref(cython.cast(MixedStrategyProfileDouble, other).profile) ) - def liap_value(self) -> float: - """ - Returns the Lyapunov value (see [McK91]_) of the strategy profile. - The Lyapunov value is a non-negative number which is zero exactly at - Nash equilibria. - """ + def _liap_value(self) -> float: return deref(self.profile).GetLiapValue() - def copy(self) -> MixedStrategyProfileDouble: - """Creates a copy of the mixed strategy profile.""" + def _copy(self) -> MixedStrategyProfileDouble: mixed = MixedStrategyProfileDouble() mixed.profile = make_shared[c_MixedStrategyProfileDouble](deref(self.profile)) return mixed - def as_behavior(self) -> MixedBehaviorProfileDouble: - """Creates a mixed behavior profile which is equivalent to this - mixed strategy profile. - - Returns - ------- - MixedBehaviorProfileDouble - The equivalent mixed behavior profile. - - Raises - ------ - UndefinedOperationError - If the game does not have a tree representation. - """ - if not self.game.is_tree: - raise UndefinedOperationError( - "Mixed behavior profiles are not defined for strategic games" - ) + def _as_behavior(self) -> MixedBehaviorProfileDouble: behav = MixedBehaviorProfileDouble() behav.profile = make_shared[c_MixedBehaviorProfileDouble](deref(self.profile)) return behav - def normalize(self) -> MixedStrategyProfileDouble: - """Create a profile with the same strategy proportions as this - one, but normalised so probabilities for each player sum to one. - """ + def _normalize(self) -> MixedStrategyProfileDouble: profile = MixedStrategyProfileDouble() profile.profile = make_shared[c_MixedStrategyProfileDouble](deref(self.profile).Normalize()) return profile - def randomize(self, denom=None) -> None: - """Randomizes the probabilities in the profile. These are - generated as uniform distributions over each mixed strategy. If - ``denom`` is specified, all probabilities are divisible by - ``denom``, that is, the distribution is uniform over a discrete - grid of mixed strategies. - """ + def _randomize(self, denom: typing.Optional[int] = None) -> None: if denom is None: deref(self.profile).Randomize() else: deref(self.profile).Randomize(denom) @property - def game(self) -> Game: - """The game on which this mixed strategy profile is defined. - """ + def _game(self) -> Game: g = Game() g.game = deref(self.profile).GetGame() return g @@ -365,62 +385,31 @@ class MixedStrategyProfileRational(MixedStrategyProfile): deref(self.profile) == deref(cython.cast(MixedStrategyProfileRational, other).profile) ) - def liap_value(self) -> Rational: - """ - Returns the Lyapunov value (see [McK91]_) of the strategy profile. - The Lyapunov value is a non-negative number which is zero exactly at - Nash equilibria. - """ + def _liap_value(self) -> Rational: return rat_to_py(deref(self.profile).GetLiapValue()) - def copy(self) -> MixedStrategyProfileRational: - """Creates a copy of the mixed strategy profile.""" + def _copy(self) -> MixedStrategyProfileRational: mixed = MixedStrategyProfileRational() mixed.profile = make_shared[c_MixedStrategyProfileRational](deref(self.profile)) return mixed - def as_behavior(self) -> MixedBehaviorProfileRational: - """Creates a mixed behavior profile which is equivalent to this - mixed strategy profile. - - Returns - ------- - MixedBehaviorProfileDouble - The equivalent mixed behavior profile. - - Raises - ------ - UndefinedOperationError - If the game does not have a tree representation. - """ - if not self.game.is_tree: - raise UndefinedOperationError("Mixed behavior profiles are not " - "defined for strategic games") + def _as_behavior(self) -> MixedBehaviorProfileRational: behav = MixedBehaviorProfileRational() behav.profile = make_shared[c_MixedBehaviorProfileRational](deref(self.profile)) return behav - def normalize(self) -> MixedStrategyProfileRational: - """Create a profile with the same strategy proportions as this - one, but normalised so probabilities for each player sum to one. - """ + def _normalize(self) -> MixedStrategyProfileRational: profile = MixedStrategyProfileRational() profile.profile = make_shared[c_MixedStrategyProfileRational](deref(self.profile).Normalize()) return profile - def randomize(self, denom) -> None: - """Randomizes the probabilities in the profile. These are - generated as uniform distributions over each mixed strategy. If - ``denom`` is specified, all probabilities are divisible by - ``denom``, that is, the distribution is uniform over a discrete - grid of mixed strategies. - """ + def _randomize(self, denom: typing.Optional[int] = None) -> None: + if denom is None: + raise ValueError("randomize() on rational-precision profiles requires a denominator") deref(self.profile).Randomize(denom) @property - def game(self) -> Game: - """The game on which this mixed strategy profile is defined. - """ + def _game(self) -> Game: g = Game() g.game = deref(self.profile).GetGame() return g From d401f7cd2663e7faa472dd9f30a18b034007ec51 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 13 Oct 2023 15:03:35 +0100 Subject: [PATCH 10/10] Update version number to 16.1.0a4. --- ChangeLog | 2 +- configure.ac | 2 +- contrib/mac/Info.plist | 8 ++++---- doc/conf.py | 2 +- doc/tools.convert.rst | 4 ++-- doc/tools.enummixed.rst | 4 ++-- doc/tools.enumpoly.rst | 2 +- doc/tools.enumpure.rst | 6 +++--- doc/tools.gnm.rst | 2 +- doc/tools.ipa.rst | 2 +- doc/tools.lcp.rst | 2 +- doc/tools.liap.rst | 2 +- doc/tools.logit.rst | 2 +- doc/tools.lp.rst | 2 +- doc/tools.simpdiv.rst | 2 +- setup.py | 4 ++-- src/pygambit/__init__.py | 2 +- 17 files changed, 25 insertions(+), 25 deletions(-) diff --git a/ChangeLog b/ChangeLog index bd3ff0dab..e9e8e9d44 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,6 @@ # Changelog -## [16.1.0a4] - unreleased +## [16.1.0a4] - 2023-10-13 ### Changed - Empty or all-whitespace strings cannot be used to access members of games in pygambit. diff --git a/configure.ac b/configure.ac index 1838d9bcb..09a8ba3af 100644 --- a/configure.ac +++ b/configure.ac @@ -20,7 +20,7 @@ dnl along with this program; if not, write to the Free Software dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. dnl -AC_INIT([gambit],[16.1.0a3]) +AC_INIT([gambit],[16.1.0a4]) AC_CONFIG_SRCDIR([src/gambit.h]) AM_INIT_AUTOMAKE([subdir-objects foreign]) dnl AC_CONFIG_MACRO_DIR([m4]) diff --git a/contrib/mac/Info.plist b/contrib/mac/Info.plist index d6e976413..cc7875dc1 100644 --- a/contrib/mac/Info.plist +++ b/contrib/mac/Info.plist @@ -19,13 +19,13 @@ CFBundleSignature ???? CFBundleVersion - 16.1.0a3 + 16.1.0a4 CFBundleShortVersionString - 16.1.0a3 + 16.1.0a4 CFBundleGetInfoString - Gambit version 16.1.0a3, (c) 1994-2023 The Gambit Project + Gambit version 16.1.0a4, (c) 1994-2023 The Gambit Project CFBundleLongVersionString - 16.1.0a3, (c) 1994-2023 The Gambit Project + 16.1.0a4, (c) 1994-2023 The Gambit Project NSHumanReadableCopyright Copyright 1994-2023 The Gambit Project LSRequiresCarbon diff --git a/doc/conf.py b/doc/conf.py index 0ecd38408..200f62221 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -51,7 +51,7 @@ # The short X.Y version. version = '16.1' # The full version, including alpha/beta/rc tags. -release = '16.1.0a3' +release = '16.1.0a4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/tools.convert.rst b/doc/tools.convert.rst index a74380232..0fbe907fb 100644 --- a/doc/tools.convert.rst +++ b/doc/tools.convert.rst @@ -40,7 +40,7 @@ Example invocation for HTML output:: $ gambit-convert -O html 2x2.nfg Convert games among various file formats - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project This is free software, distributed under the GNU GPL

Two person 2 x 2 game with unique mixed equilibrium

@@ -55,7 +55,7 @@ Example invocation for LaTeX output:: $ gambit-convert -O sgame 2x2.nfg Convert games among various file formats - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project This is free software, distributed under the GNU GPL \begin{game}{2}{2}[Player 1][Player 2] diff --git a/doc/tools.enummixed.rst b/doc/tools.enummixed.rst index dbdb445e7..a1bb1936f 100644 --- a/doc/tools.enummixed.rst +++ b/doc/tools.enummixed.rst @@ -70,7 +70,7 @@ in Figure 2 of Selten (International Journal of Game Theory, $ gambit-enummixed e02.nfg Compute Nash equilibria by enumerating extreme points - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project Enumeration code based on lrslib 4.2b, Copyright (C) 1995-2005 by David Avis (avis@cs.mcgill.ca) This is free software, distributed under the GNU GPL @@ -84,7 +84,7 @@ information using the `-c` switch:: $ gambit-enummixed -c e02.nfg Compute Nash equilibria by enumerating extreme points - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project Enumeration code based on lrslib 4.2b, Copyright (C) 1995-2005 by David Avis (avis@cs.mcgill.ca) This is free software, distributed under the GNU GPL diff --git a/doc/tools.enumpoly.rst b/doc/tools.enumpoly.rst index 27445e11d..f408c7002 100644 --- a/doc/tools.enumpoly.rst +++ b/doc/tools.enumpoly.rst @@ -75,7 +75,7 @@ Computing equilibria of the extensive game :download:`e01.efg $ gambit-enumpoly e01.efg Compute Nash equilibria by solving polynomial systems - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project Heuristic search implementation Copyright (C) 2006, Litao Wei This is free software, distributed under the GNU GPL diff --git a/doc/tools.enumpure.rst b/doc/tools.enumpure.rst index 1e947b900..0c758543b 100644 --- a/doc/tools.enumpure.rst +++ b/doc/tools.enumpure.rst @@ -66,7 +66,7 @@ Computing the pure-strategy equilibria of extensive game :download:`e02.efg $ gambit-enumpure e02.efg Search for Nash equilibria in pure strategies - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,0,0,1,0 @@ -77,7 +77,7 @@ strategies:: $ gambit-enumpure -S e02.efg Search for Nash equilibria in pure strategies - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,0,1,0 @@ -88,7 +88,7 @@ only one information set; therefore the set of solutions is larger:: $ gambit-enumpure -A e02.efg Search for Nash equilibria in pure strategies - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,1,0,1,0 diff --git a/doc/tools.gnm.rst b/doc/tools.gnm.rst index 8b4a489cd..9fdaa01b9 100644 --- a/doc/tools.gnm.rst +++ b/doc/tools.gnm.rst @@ -47,7 +47,7 @@ the reduced strategic form of the example in Figure 2 of Selten $ gambit-gnm e02.nfg Compute Nash equilibria using a global Newton method Gametracer version 0.2, Copyright (C) 2002, Ben Blum and Christian Shelton - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,2.99905e-12,0.5,0.5 diff --git a/doc/tools.ipa.rst b/doc/tools.ipa.rst index 8e5ce8e95..2830da1e4 100644 --- a/doc/tools.ipa.rst +++ b/doc/tools.ipa.rst @@ -33,7 +33,7 @@ the reduced strategic form of the example in Figure 2 of Selten $ gambit-ipa e02.nfg Compute Nash equilibria using iterated polymatrix approximation Gametracer version 0.2, Copyright (C) 2002, Ben Blum and Christian Shelton - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project This is free software, distributed under the GNU GPL NE,1.000000,0.000000,0.000000,1.000000,0.000000 diff --git a/doc/tools.lcp.rst b/doc/tools.lcp.rst index 93c1c8e42..367f3062d 100644 --- a/doc/tools.lcp.rst +++ b/doc/tools.lcp.rst @@ -73,7 +73,7 @@ Computing an equilibrium of extensive game :download:`e02.efg $ gambit-lcp e02.efg Compute Nash equilibria by solving a linear complementarity program - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,1/2,1/2,1/2,1/2 diff --git a/doc/tools.liap.rst b/doc/tools.liap.rst index deeaf5ddd..e541298ed 100644 --- a/doc/tools.liap.rst +++ b/doc/tools.liap.rst @@ -67,7 +67,7 @@ Computing an equilibrium in mixed strategies of :download:`e02.efg $ gambit-liap e02.nfg Compute Nash equilibria by minimizing the Lyapunov function - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project This is free software, distributed under the GNU GPL NE, 0.998701, 0.000229, 0.001070, 0.618833, 0.381167 diff --git a/doc/tools.logit.rst b/doc/tools.logit.rst index 347eeb81f..74be09e81 100644 --- a/doc/tools.logit.rst +++ b/doc/tools.logit.rst @@ -94,7 +94,7 @@ in Figure 2 of Selten (International Journal of Game Theory, $ gambit-logit e02.nfg Compute a branch of the logit equilibrium correspondence - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project This is free software, distributed under the GNU GPL 0.000000,0.333333,0.333333,0.333333,0.5,0.5 diff --git a/doc/tools.lp.rst b/doc/tools.lp.rst index 557d7c401..464736a3b 100644 --- a/doc/tools.lp.rst +++ b/doc/tools.lp.rst @@ -65,7 +65,7 @@ strategies each, with a unique equilibrium in mixed strategies:: $ gambit-lp 2x2const.nfg Compute Nash equilibria by solving a linear program - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project This is free software, distributed under the GNU GPL NE,1/3,2/3,1/3,2/3 diff --git a/doc/tools.simpdiv.rst b/doc/tools.simpdiv.rst index 16d7c7acf..929f3861b 100644 --- a/doc/tools.simpdiv.rst +++ b/doc/tools.simpdiv.rst @@ -74,7 +74,7 @@ Computing an equilibrium in mixed strategies of :download:`e02.efg $ gambit-simpdiv e02.nfg Compute Nash equilibria using simplicial subdivision - Gambit version 16.1.0a3, Copyright (C) 1994-2023, The Gambit Project + Gambit version 16.1.0a4, Copyright (C) 1994-2023, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,0,1,0 diff --git a/setup.py b/setup.py index bf6ad658b..4628f1ff0 100644 --- a/setup.py +++ b/setup.py @@ -64,8 +64,8 @@ def readme(): setuptools.setup( name="pygambit", - version="16.1.0a3", - description="Package for computation in game theory", + version="16.1.0a4", + description="The package for doing computation in game theory", long_description=readme(), classifiers=[ "Development Status :: 4 - Beta", diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index f12624a65..9cbb7f54d 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -27,4 +27,4 @@ from . import gte # noqa: F401 from . import qre # noqa: F401 -__version__ = "16.1.0a3" +__version__ = "16.1.0a4"