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 6cc2cc00a..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']
@@ -20,10 +21,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
@@ -31,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 6283ab1f8..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
@@ -22,15 +23,26 @@ 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
+ 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
@@ -48,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}
diff --git a/ChangeLog b/ChangeLog
index dabba37e1..e9e8e9d44 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,11 +1,19 @@
# 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.
- 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.
+- 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/MANIFEST.in b/MANIFEST.in
index 5e4d61cbe..6383aa876 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,12 +2,9 @@ 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/pygambit/*.pxd
include src/pygambit/*.pyx
include src/pygambit/*.pxi
-
+include src/pygambit/*.h
+include src/README.rst
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/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/pygambit.api.rst b/doc/pygambit.api.rst
index cb00415c7..6d75ce509 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
@@ -189,20 +191,19 @@ Probability distributions over strategies
.. autosummary::
: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
@@ -211,23 +212,23 @@ Probability distributions over behavior
.. autosummary::
:toctree: api/
+ MixedBehaviorProfile
+ MixedBehaviorProfile.game
MixedBehaviorProfile.__getitem__
MixedBehaviorProfile.__setitem__
MixedBehaviorProfile.payoff
MixedBehaviorProfile.regret
MixedBehaviorProfile.action_value
MixedBehaviorProfile.infoset_value
+ MixedBehaviorProfile.node_value
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/doc/pygambit.user.rst b/doc/pygambit.user.rst
index 4ed5a6255..20033fac1 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,47 @@ 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`.
+
+.. 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`:
+
+.. 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 +422,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 +438,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()
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 1b455eaca..4628f1ff0 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': (
@@ -66,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/games/behav.h b/src/games/behav.h
index 6c06a4123..00e4a0efd 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(),
@@ -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 f8698525b..7ae58f9df 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);
@@ -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
{
@@ -477,8 +485,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
@@ -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()) {
@@ -688,36 +696,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/__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"
diff --git a/src/pygambit/behav.pxi b/src/pygambit/behav.pxi
index fca206c3b..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.
@@ -284,8 +289,44 @@ 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 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,
@@ -303,8 +344,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 +368,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.
@@ -344,8 +395,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
----------
@@ -362,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):
@@ -391,6 +483,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)
@@ -403,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
@@ -486,6 +561,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))
@@ -498,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/gambit.pxd b/src/pygambit/gambit.pxd
index 5074c370e..26a474e30 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()
@@ -244,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()
@@ -261,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()
@@ -282,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()
@@ -305,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()
@@ -386,11 +392,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/pygambit/game.pxi b/src/pygambit/game.pxi
index eeed06319..4297207d6 100644
--- a/src/pygambit/game.pxi
+++ b/src/pygambit/game.pxi
@@ -1389,4 +1389,58 @@ class Game:
resolved_node.node.deref().SetOutcome(cython.cast(c_GameOutcome, NULL))
return
resolved_outcome = cython.cast(Outcome, self._resolve_outcome(outcome, 'set_outcome'))
- resolved_node.node.deref().SetOutcome(resolved_outcome.outcome)
\ No newline at end of file
+ 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()
\ No newline at end of file
diff --git a/src/pygambit/mixed.pxi b/src/pygambit/mixed.pxi
index 7295b5773..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.
@@ -191,6 +196,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.
@@ -207,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):
@@ -227,6 +304,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
@@ -238,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
@@ -325,6 +371,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
@@ -336,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
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_behav.py b/src/pygambit/tests/test_behav.py
index 7b9080976..c0b3ce15e 100644
--- a/src/pygambit/tests/test_behav.py
+++ b/src/pygambit/tests/test_behav.py
@@ -1,368 +1,699 @@
+import unittest
+
import pygambit
import pygambit as gbt
-def test_payoff():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- payoffs = game.mixed_behavior_profile(rational=False)
- assert payoffs.payoff(game.players[0]) == 3.0
- assert payoffs.payoff(game.players[1]) == 3.0
- assert payoffs.payoff(game.players[2]) == 3.25
- payoffs = game.mixed_behavior_profile(rational=True)
- assert payoffs.payoff(game.players[0]) == gbt.Rational(3, 1)
- assert payoffs.payoff(game.players[1]) == gbt.Rational(3, 1)
- assert payoffs.payoff(game.players[2]) == gbt.Rational(13, 4)
-
-
-def test_payoff_by_label():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- payoffs = game.mixed_behavior_profile(rational=False)
- assert payoffs.payoff("Player 1") == 3.0
- assert payoffs.payoff("Player 2") == 3.0
- assert payoffs.payoff("Player 3") == 3.25
- payoffs = game.mixed_behavior_profile(rational=True)
- assert payoffs.payoff("Player 1") == gbt.Rational(3, 1)
- assert payoffs.payoff("Player 2") == gbt.Rational(3, 1)
- assert payoffs.payoff("Player 3") == gbt.Rational(13, 4)
-
-
-def test_get_probabilities_action():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- action = game.players[0].infosets[0].actions[0]
- assert g(rational=False)[action] == 0.5
- assert g(rational=True)[action] == gbt.Rational("1/2")
- action = game.players[0].infosets[0].actions[1]
- assert g(rational=False)[action] == 0.5
- assert g(rational=True)[action] == gbt.Rational("1/2")
- action = game.players[1].infosets[0].actions[0]
- assert g(rational=False)[action] == 0.5
- assert g(rational=True)[action] == gbt.Rational("1/2")
- action = game.players[1].infosets[0].actions[1]
- assert g(rational=False)[action] == 0.5
- assert g(rational=True)[action] == gbt.Rational("1/2")
- action = game.players[2].infosets[0].actions[0]
- assert g(rational=False)[action] == 0.5
- assert g(rational=True)[action] == gbt.Rational("1/2")
- action = game.players[2].infosets[0].actions[1]
- assert g(rational=False)[action] == 0.5
- assert g(rational=True)[action] == gbt.Rational("1/2")
-
-
-def test_get_probabilities_action_by_label():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- assert g(rational=False)["U1"] == 0.5
- assert g(rational=True)["U1"] == gbt.Rational("1/2")
- assert g(rational=False)["D1"] == 0.5
- assert g(rational=True)["D1"] == gbt.Rational("1/2")
- assert g(rational=False)["U2"] == 0.5
- assert g(rational=True)["U2"] == gbt.Rational("1/2")
- assert g(rational=False)["D2"] == 0.5
- assert g(rational=True)["D2"] == gbt.Rational("1/2")
- assert g(rational=False)["U3"] == 0.5
- assert g(rational=True)["U3"] == gbt.Rational("1/2")
- assert g(rational=False)["D3"] == 0.5
- assert g(rational=True)["D3"] == gbt.Rational("1/2")
-
-
-def test_get_probabilities_infosets():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- infoset = game.players[0].infosets[0]
- assert g(rational=False)[infoset] == [0.5, 0.5]
- assert (
- g(rational=True)[infoset] == [gbt.Rational("1/2"), gbt.Rational("1/2")]
- )
- infoset = game.players[1].infosets[0]
- assert g(rational=False)[infoset] == [0.5, 0.5]
- assert (
- g(rational=True)[infoset] == [gbt.Rational("1/2"), gbt.Rational("1/2")]
- )
- infoset = game.players[2].infosets[0]
- assert g(rational=False)[infoset] == [0.5, 0.5]
- assert (
- g(rational=True)[infoset] == [gbt.Rational("1/2"), gbt.Rational("1/2")]
- )
-
-
-def test_get_probabilities_infosets_by_label():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- player = game.players[0]
- assert g(rational=False)[player]["Infoset 1:1"] == [0.5, 0.5]
- assert (
- g(rational=True)[player]["Infoset 1:1"] ==
- [gbt.Rational("1/2"), gbt.Rational("1/2")]
- )
-
-
-def test_get_probabilities_player():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- assert g(rational=False)[game.players[0]] == [[0.5, 0.5]]
- assert (
- g(rational=True)[game.players[0]] ==
- [[gbt.Rational("1/2"), gbt.Rational("1/2")]]
- )
-
-
-def test_get_probabilities_player_by_label():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- assert g(rational=False)[game.players[0]] == [[0.5, 0.5]]
- assert (
- g(rational=True)[game.players[0]] ==
- [[gbt.Rational("1/2"), gbt.Rational("1/2")]]
- )
-
-
-def test_set_probabilities_action():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- action = game.mixed_behavior_profile(rational=False)
- action[game.players[0].infosets[0].actions[0]] = 0.72
- assert action[game.players[0].infosets[0].actions[0]] == 0.72
- action[game.players[0].infosets[0].actions[1]] = 0.28
- assert action[game.players[0].infosets[0].actions[1]] == 0.28
- action = game.mixed_behavior_profile(rational=True)
- action[game.players[0].infosets[0].actions[0]] = gbt.Rational("2/9")
- assert (
- action[game.players[0].infosets[0].actions[0]] == gbt.Rational("2/9")
- )
- action[game.players[0].infosets[0].actions[1]] = gbt.Rational("7/9")
- assert (
- action[game.players[0].infosets[0].actions[1]] == gbt.Rational("7/9")
- )
-
-
-def test_set_probabilities_action_by_label():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- action = game.mixed_behavior_profile(rational=False)
- action["U1"] = 0.72
- assert action["U1"] == 0.72
- action["D1"] = 0.28
- assert action["D1"] == 0.28
- action = game.mixed_behavior_profile(rational=True)
- action["U1"] = gbt.Rational("2/9")
- assert action["U1"] == gbt.Rational("2/9")
- action["D1"] = gbt.Rational("7/9")
- assert action["D1"] == gbt.Rational("7/9")
-
-
-def test_set_probabilities_infoset():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- infoset = game.mixed_behavior_profile(rational=False)
- infst = game.players[0].infosets[0]
- infoset[infst] = [0.72, 0.28]
- assert infoset[infst] == [0.72, 0.28]
- infoset = game.mixed_behavior_profile(rational=True)
- infoset[infst] = [gbt.Rational("2/9"), gbt.Rational("7/9")]
- assert infoset[infst] == [gbt.Rational("2/9"), gbt.Rational("7/9")]
-
-
-def test_set_probabilities_infoset_by_label():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- infoset = game.mixed_behavior_profile(rational=False)
- infoset["Infoset 1:1"] = [0.72, 0.28]
- assert infoset["Infoset 1:1"] == [0.72, 0.28]
- infoset = game.mixed_behavior_profile(rational=True)
- infoset["Infoset 1:1"] = [gbt.Rational("2/9"), gbt.Rational("7/9")]
- assert infoset["Infoset 1:1"] == [gbt.Rational("2/9"), gbt.Rational("7/9")]
-
-
-def test_set_probabilities_player():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- profile = game.mixed_behavior_profile(rational=False)
- profile[game.players[0]] = [[0.72, 0.28]]
- assert profile[game.players[0]] == [[0.72, 0.28]]
- profile = game.mixed_behavior_profile(rational=True)
- profile[game.players[0]] = [[gbt.Rational("7/9"), gbt.Rational("2/9")]]
- assert (
- profile[game.players[0]] ==
- [[gbt.Rational("7/9"), gbt.Rational("2/9")]]
- )
-
-
-def test_set_probabilities_player_by_label():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- profile = game.mixed_behavior_profile(rational=False)
- profile["Player 1"] = [[0.72, 0.28]]
- assert profile["Player 1"] == [[0.72, 0.28]]
- profile = game.mixed_behavior_profile(rational=True)
- profile["Player 1"] = [[gbt.Rational("7/9"), gbt.Rational("2/9")]]
- assert profile["Player 1"] == [[gbt.Rational("7/9"), gbt.Rational("2/9")]]
-
-
-def test_infoset_prob():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- infoset = game.players[0].infosets[0]
- assert g(rational=False).infoset_prob(infoset) == 1.0
- assert g(rational=True).infoset_prob(infoset) == gbt.Rational("1/1")
- infoset = game.players[1].infosets[0]
- assert g(rational=False).infoset_prob(infoset) == 1.0
- assert g(rational=True).infoset_prob(infoset) == gbt.Rational("1/1")
- infoset = game.players[2].infosets[0]
- assert g(rational=False).infoset_prob(infoset) == 1.0
- assert g(rational=True).infoset_prob(infoset) == gbt.Rational("1/1")
-
-
-def test_infoset_prob_by_label():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- assert g(rational=False).infoset_prob("Infoset 1:1") == 1.0
- assert g(rational=True).infoset_prob("Infoset 1:1") == gbt.Rational("1/1")
- assert g(rational=False).infoset_prob("Infoset 2:1") == 1.0
- assert g(rational=True).infoset_prob("Infoset 2:1") == gbt.Rational("1/1")
- assert g(rational=False).infoset_prob("Infoset 3:1") == 1.0
- assert g(rational=True).infoset_prob("Infoset 3:1") == gbt.Rational("1/1")
-
-
-def test_infoset_payoff():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- infoset = game.players[0].infosets[0]
- assert g(rational=False).infoset_value(infoset) == 3.0
- assert g(rational=True).infoset_value(infoset) == 3.0
- infoset = game.players[1].infosets[0]
- assert g(rational=False).infoset_value(infoset) == 3.0
- assert g(rational=True).infoset_value(infoset) == 3.0
- infoset = game.players[2].infosets[0]
- assert g(rational=False).infoset_value(infoset) == 3.25
- assert g(rational=True).infoset_value(infoset) == gbt.Rational("13/4")
-
-
-def test_infoset_payoff_by_label():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- assert g(rational=False).infoset_value("Infoset 1:1") == 3.0
- assert g(rational=True).infoset_value("Infoset 1:1") == 3.0
- assert g(rational=False).infoset_value("Infoset 2:1") == 3.0
- assert g(rational=True).infoset_value("Infoset 2:1") == 3.0
- assert g(rational=False).infoset_value("Infoset 3:1") == 3.25
- assert (
- g(rational=True).infoset_value("Infoset 3:1") == gbt.Rational("13/4")
- )
-
-
-def test_action_payoff():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- action = game.players[0].infosets[0].actions[0]
- assert g(rational=False).action_value(action) == 3.0
- assert g(rational=True).action_value(action) == 3.0
- action = game.players[1].infosets[0].actions[0]
- assert g(rational=False).action_value(action) == 3.0
- assert g(rational=True).action_value(action) == 3.0
- action = game.players[2].infosets[0].actions[0]
- assert g(rational=False).action_value(action) == 3.5
- assert g(rational=True).action_value(action) == gbt.Rational("7/2")
-
-
-def test_action_payoff_by_label():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- assert g(rational=False).action_value("U1") == 3.0
- assert g(rational=True).action_value("U1") == 3.0
- assert g(rational=False).action_value("U2") == 3.0
- assert g(rational=True).action_value("U1") == 3.0
- assert g(rational=False).action_value("U3") == 3.5
- assert g(rational=True).action_value("U3") == gbt.Rational("7/2")
-
-
-def test_regret():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- gp = game.players
- assert g(rational=False).regret(gp[0].infosets[0].actions[0]) == 0.0
- assert g(rational=False).regret(gp[0].infosets[0].actions[1]) == 0.0
- assert g(rational=False).regret(gp[1].infosets[0].actions[0]) == 0.0
- assert g(rational=False).regret(gp[1].infosets[0].actions[1]) == 0.0
- assert g(rational=False).regret(gp[2].infosets[0].actions[0]) == 0.25
- assert g(rational=False).regret(gp[2].infosets[0].actions[1]) == -0.25
- assert (
- g(rational=True).regret(gp[0].infosets[0].actions[0]) ==
- gbt.Rational("0/1")
- )
- assert (
- g(rational=True).regret(gp[0].infosets[0].actions[1]) ==
- gbt.Rational("0/1")
- )
- assert (
- g(rational=True).regret(gp[1].infosets[0].actions[0]) ==
- gbt.Rational("0/1")
- )
- assert (
- g(rational=True).regret(gp[1].infosets[0].actions[1]) ==
- gbt.Rational("0/1")
- )
- assert (
- g(rational=True).regret(gp[2].infosets[0].actions[0]) ==
- gbt.Rational("1/4")
- )
- assert (
- g(rational=True).regret(gp[2].infosets[0].actions[1]) ==
- gbt.Rational(-1, 4)
- )
-
-
-def test_regret_by_label():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- assert g(rational=False).regret("U1") == 0.0
- assert g(rational=True).regret("U1") == gbt.Rational("0/1")
- assert g(rational=False).regret("D1") == 0.0
- assert g(rational=True).regret("D1") == gbt.Rational("0/1")
- assert g(rational=False).regret("U2") == 0.0
- assert g(rational=True).regret("U2") == gbt.Rational("0/1")
- assert g(rational=False).regret("D2") == 0.0
- assert g(rational=True).regret("D2") == gbt.Rational("0/1")
- assert g(rational=False).regret("U3") == 0.25
- assert g(rational=True).regret("U3") == gbt.Rational("1/4")
- assert g(rational=False).regret("D3") == -0.25
- assert g(rational=True).regret("D3") == gbt.Rational(-1, 4)
-
-
-def test_liap_values():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- assert g(rational=False).liap_value() == 0.0625
- assert g(rational=True).liap_value() == gbt.Rational("1/16")
-
-
-def test_as_strategy():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- assert (
- g(rational=False).as_strategy().as_behavior() ==
- g(rational=False)
- )
- assert (
- g(rational=True).as_strategy().as_behavior() ==
- g(rational=True)
- )
-
-
-def test_node_belief():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- action = game.mixed_behavior_profile(rational=False)
- action[game.players[0].infosets[0].actions[0]] = 0.8
- action[game.players[0].infosets[0].actions[1]] = 0.2
- assert game.mixed_behavior_profile(rational=False).belief(game.root) == 1
- gm = game.infosets[0].members[0]
- assert (abs(g(rational=False).belief(gm) - 0.8) > 1e-13)
-
-
-def test_infoset_belief():
- game = gbt.Game.read_game("test_games/mixed_behavior_game.efg")
- g = game.mixed_behavior_profile
- action = g(rational=False)
- action[game.players[0].infosets[0].actions[0]] = 0.8
- action[game.players[0].infosets[0].actions[1]] = 0.2
- assert g(rational=False).belief(game.infosets[0].members[0]) == 1.0
- action[game.players[0].infosets[0].actions[0]] = gbt.Rational("4/5")
- assert (
- g(rational=False).belief(game.infosets[0].members[0]) ==
- gbt.Rational("1/1")
- )
+class TestGambitMixedBehavGame(unittest.TestCase):
+ def setUp(self):
+ self.game = gbt.Game.read_game(
+ "test_games/mixed_behavior_game.efg"
+ )
+ 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
+ del self.profile_rational
+
+ def test_payoff(self):
+ "Test to ensure that payoffs are returned correctly"
+ assert self.profile_double.payoff(self.game.players[0]) == 3.0
+ assert self.profile_double.payoff(self.game.players[1]) == 3.0
+ assert self.profile_double.payoff(self.game.players[2]) == 3.25
+ assert (
+ self.profile_rational.payoff(self.game.players[0]) ==
+ gbt.Rational(3, 1)
+ )
+ assert (
+ self.profile_rational.payoff(self.game.players[1]) ==
+ gbt.Rational(3, 1)
+ )
+ assert (
+ self.profile_rational.payoff(self.game.players[2]) ==
+ gbt.Rational(13, 4)
+ )
+
+ def test_payoff_by_string(self):
+ "Test to find payoffs by string values"
+ assert self.profile_double.payoff("Player 1") == 3.0
+ assert self.profile_double.payoff("Player 2") == 3.0
+ assert self.profile_double.payoff("Player 3") == 3.25
+ assert (
+ self.profile_rational.payoff("Player 1") ==
+ gbt.Rational(3, 1)
+ )
+ assert (
+ self.profile_rational.payoff("Player 2") ==
+ gbt.Rational(3, 1)
+ )
+ assert (
+ self.profile_rational.payoff("Player 3") ==
+ gbt.Rational(13, 4)
+ )
+
+ def test_is_defined_at(self):
+ "Test to check if an infoset is defined"
+ assert self.profile_double.is_defined_at(
+ self.game.players[0].infosets[0]
+ )
+ assert self.profile_double.is_defined_at(
+ self.game.players[1].infosets[0]
+ )
+ assert self.profile_double.is_defined_at(
+ self.game.players[2].infosets[0]
+ )
+ assert self.profile_rational.is_defined_at(
+ self.game.players[0].infosets[0]
+ )
+ assert self.profile_rational.is_defined_at(
+ self.game.players[1].infosets[0]
+ )
+ assert self.profile_rational.is_defined_at(
+ self.game.players[2].infosets[0]
+ )
+
+ def test_is_defined_at_by_string(self):
+ "Test to check if an infoset is defined by string values"
+ assert self.profile_double.is_defined_at("Infoset 1:1")
+ assert self.profile_double.is_defined_at("Infoset 2:1")
+ assert self.profile_double.is_defined_at("Infoset 3:1")
+ assert self.profile_rational.is_defined_at("Infoset 1:1")
+ assert self.profile_rational.is_defined_at("Infoset 2:1")
+ assert self.profile_rational.is_defined_at("Infoset 3:1")
+
+ def test_get_probabilities_action(self):
+ "Test to retrieve probabilities from an action"
+ assert (
+ self.profile_double[self.game.players[0].infosets[0].actions[0]]
+ == 0.5
+ )
+ assert (
+ self.profile_double[self.game.players[0].infosets[0].actions[1]]
+ == 0.5
+ )
+ assert (
+ self.profile_double[self.game.players[1].infosets[0].actions[0]]
+ == 0.5
+ )
+ assert (
+ self.profile_double[self.game.players[1].infosets[0].actions[1]]
+ == 0.5
+ )
+ assert (
+ self.profile_double[self.game.players[2].infosets[0].actions[0]]
+ == 0.5
+ )
+ assert (
+ self.profile_double[self.game.players[2].infosets[0].actions[1]]
+ == 0.5
+ )
+ assert (
+ self.profile_rational[self.game.players[0].infosets[0].actions[0]]
+ == gbt.Rational("1/2")
+ )
+ assert (
+ self.profile_rational[self.game.players[0].infosets[0].actions[1]]
+ == gbt.Rational("1/2")
+ )
+ assert (
+ self.profile_rational[self.game.players[1].infosets[0].actions[0]]
+ == gbt.Rational("1/2")
+ )
+ assert (
+ self.profile_rational[self.game.players[1].infosets[0].actions[1]]
+ == gbt.Rational("1/2")
+ )
+ assert (
+ self.profile_rational[self.game.players[2].infosets[0].actions[0]]
+ == gbt.Rational("1/2")
+ )
+ assert (
+ self.profile_rational[self.game.players[2].infosets[0].actions[1]]
+ == gbt.Rational("1/2")
+ )
+
+ def test_get_probabilities_action_by_string(self):
+ "Test to retrieve probabilities from an action by string values"
+ assert self.profile_double["U1"] == 0.5
+ assert self.profile_double["D1"] == 0.5
+ assert self.profile_double["U2"] == 0.5
+ assert self.profile_double["D2"] == 0.5
+ assert self.profile_double["U3"] == 0.5
+ assert self.profile_double["D3"] == 0.5
+ assert self.profile_rational["U1"] == gbt.Rational("1/2")
+ assert self.profile_rational["D1"] == gbt.Rational("1/2")
+ assert self.profile_rational["U2"] == gbt.Rational("1/2")
+ assert self.profile_rational["D2"] == gbt.Rational("1/2")
+ assert self.profile_rational["U3"] == gbt.Rational("1/2")
+ assert self.profile_rational["D3"] == gbt.Rational("1/2")
+
+ def test_get_probabilities_infoset(self):
+ "Test to retrieve probabilities from an infoset"
+ assert (
+ self.profile_double[self.game.players[0].infosets[0]] == [0.5, 0.5]
+ )
+ assert (
+ self.profile_double[self.game.players[1].infosets[0]] == [0.5, 0.5]
+ )
+ assert (
+ self.profile_double[self.game.players[2].infosets[0]] ==
+ [0.5, 0.5]
+ )
+ assert (
+ self.profile_rational[self.game.players[0].infosets[0]] ==
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ )
+ assert (
+ self.profile_rational[self.game.players[1].infosets[0]] ==
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ )
+ assert (
+ self.profile_rational[self.game.players[2].infosets[0]] ==
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ )
+
+ def test_get_probabilities_infoset_by_string(self):
+ "Test to retrieve probabilities from an infoset by string values"
+ assert (
+ self.profile_double[self.game.players[0]]["Infoset 1:1"] ==
+ [0.5, 0.5]
+ )
+ assert self.profile_double["Infoset 1:1"] == [0.5, 0.5]
+ assert (
+ self.profile_double[self.game.players[1]]["Infoset 2:1"] ==
+ [0.5, 0.5]
+ )
+ assert self.profile_double["Infoset 2:1"] == [0.5, 0.5]
+ assert (
+ self.profile_double[self.game.players[2]]["Infoset 3:1"] ==
+ [0.5, 0.5]
+ )
+ assert self.profile_double["Infoset 3:1"] == [0.5, 0.5]
+ assert (
+ self.profile_rational[self.game.players[0]]["Infoset 1:1"] ==
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ )
+ assert (
+ self.profile_rational["Infoset 1:1"] ==
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ )
+ assert (
+ self.profile_rational[self.game.players[1]]["Infoset 2:1"] ==
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ )
+ assert (
+ self.profile_rational["Infoset 2:1"] ==
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ )
+ assert (
+ self.profile_rational[self.game.players[2]]["Infoset 3:1"] ==
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ )
+ assert (
+ self.profile_rational["Infoset 3:1"] ==
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ )
+
+ def test_get_probabilities_player(self):
+ "Test to retrieve probabilities from a player"
+ assert self.profile_double[self.game.players[0]] == [[0.5, 0.5]]
+ assert self.profile_double[self.game.players[1]] == [[0.5, 0.5]]
+ assert self.profile_double[self.game.players[2]] == [[0.5, 0.5]]
+ assert self.profile_rational[self.game.players[0]] == [
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ ]
+ assert self.profile_rational[self.game.players[1]] == [
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ ]
+ assert self.profile_rational[self.game.players[2]] == [
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ ]
+
+ def test_get_probabilities_player_by_string(self):
+ "Test to retrieve probabilities from a player by string values"
+ assert self.profile_double["Player 1"] == [[0.5, 0.5]]
+ assert self.profile_double["Player 2"] == [[0.5, 0.5]]
+ assert self.profile_double["Player 3"] == [[0.5, 0.5]]
+ assert self.profile_rational["Player 1"] == [
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ ]
+ assert self.profile_rational["Player 2"] == [
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ ]
+ assert self.profile_rational["Player 3"] == [
+ [gbt.Rational("1/2"), gbt.Rational("1/2")]
+ ]
+
+ def test_set_probabilities_action(self):
+ "Test to set probabilities"
+ self.profile_double[self.game.actions[0]] = 0.72
+ assert self.profile_double[self.game.actions[0]] == 0.72
+ self.profile_double[self.game.actions[1]] = 0.28
+ assert self.profile_double[self.game.actions[1]] == 0.28
+ self.profile_double[self.game.actions[2]] = 0.42
+ assert self.profile_double[self.game.actions[2]] == 0.42
+ self.profile_double[self.game.actions[3]] = 0.58
+ assert self.profile_double[self.game.actions[3]] == 0.58
+ self.profile_double[self.game.actions[4]] = 0.02
+ assert self.profile_double[self.game.actions[4]] == 0.02
+ self.profile_double[self.game.actions[5]] = 0.98
+ assert self.profile_double[self.game.actions[5]] == 0.98
+ self.profile_rational[self.game.actions[0]] = gbt.Rational("2/9")
+ assert self.profile_rational[self.game.actions[0]] == gbt.Rational("2/9")
+ self.profile_rational[self.game.actions[1]] = gbt.Rational("7/9")
+ assert self.profile_rational[self.game.actions[1]] == gbt.Rational("7/9")
+ self.profile_rational[self.game.actions[2]] = gbt.Rational("4/13")
+ assert self.profile_rational[self.game.actions[2]] == gbt.Rational("4/13")
+ self.profile_rational[self.game.actions[3]] = gbt.Rational("9/13")
+ assert self.profile_rational[self.game.actions[3]] == gbt.Rational("9/13")
+ self.profile_rational[self.game.actions[4]] = gbt.Rational("1/98")
+ assert self.profile_rational[self.game.actions[4]] == gbt.Rational("1/98")
+ self.profile_rational[self.game.actions[5]] = gbt.Rational("97/98")
+ assert self.profile_rational[self.game.actions[5]] == gbt.Rational("97/98")
+
+ def test_set_probabilities_action_by_string(self):
+ "Test to set probabilities by string values"
+ self.profile_double["U1"] = 0.72
+ assert self.profile_double["U1"] == 0.72
+ self.profile_double["D1"] = 0.28
+ assert self.profile_double["D1"] == 0.28
+ self.profile_double["U2"] = 0.42
+ assert self.profile_double["U2"] == 0.42
+ self.profile_double["D2"] = 0.58
+ assert self.profile_double["D2"] == 0.58
+ self.profile_double["U3"] = 0.02
+ assert self.profile_double["U3"] == 0.02
+ self.profile_double["D3"] = 0.98
+ assert self.profile_double["D3"] == 0.98
+
+ self.profile_rational["U1"] = gbt.Rational("2/9")
+ assert self.profile_rational["U1"] == gbt.Rational("2/9")
+ self.profile_rational["D1"] = gbt.Rational("7/9")
+ assert self.profile_rational["D1"] == gbt.Rational("7/9")
+ self.profile_rational["U2"] = gbt.Rational("4/13")
+ assert self.profile_rational["U2"] == gbt.Rational("4/13")
+ self.profile_rational["D2"] = gbt.Rational("9/13")
+ assert self.profile_rational["D2"] == gbt.Rational("9/13")
+ self.profile_rational["U3"] = gbt.Rational("1/98")
+ assert self.profile_rational["U3"] == gbt.Rational("1/98")
+ self.profile_rational["D3"] = gbt.Rational("97/98")
+ assert self.profile_rational["D3"] == gbt.Rational("97/98")
+
+ def test_set_probabilities_infoset(self):
+ "Test to set probabilities to an infoset"
+ self.profile_double[self.game.players[0].infosets[0]] = [0.72, 0.28]
+ assert self.profile_double[self.game.players[0].infosets[0]] == [0.72, 0.28]
+ self.profile_double[self.game.players[1].infosets[0]] = [0.42, 0.58]
+ assert self.profile_double[self.game.players[1].infosets[0]] == [0.42, 0.58]
+ self.profile_double[self.game.players[2].infosets[0]] = [0.02, 0.98]
+ assert self.profile_double[self.game.players[2].infosets[0]] == [0.02, 0.98]
+
+ self.profile_rational[self.game.players[0].infosets[0]] = [
+ gbt.Rational("2/9"), gbt.Rational("7/9")
+ ]
+ assert (
+ self.profile_rational[self.game.players[0].infosets[0]] ==
+ [gbt.Rational("2/9"), gbt.Rational("7/9")]
+ )
+ self.profile_rational[self.game.players[0].infosets[0]] = [
+ gbt.Rational("4/13"), gbt.Rational("9/13")
+ ]
+ assert (
+ self.profile_rational[self.game.players[0].infosets[0]] ==
+ [gbt.Rational("4/13"), gbt.Rational("9/13")]
+ )
+ self.profile_rational[self.game.players[0].infosets[0]] = [
+ gbt.Rational("1/98"), gbt.Rational("97/98")
+ ]
+ assert (
+ self.profile_rational[self.game.players[0].infosets[0]] ==
+ [gbt.Rational("1/98"), gbt.Rational("97/98")]
+ )
+
+ def test_set_probabilities_infoset_by_string(self):
+ "Test to set probabilities to an infoset by string values"
+ self.profile_double["Infoset 1:1"] = [0.72, 0.28]
+ assert self.profile_double["Infoset 1:1"] == [0.72, 0.28]
+ self.profile_double["Infoset 2:1"] = [0.42, 0.58]
+ assert self.profile_double["Infoset 2:1"] == [0.42, 0.58]
+ self.profile_double["Infoset 3:1"] = [0.02, 0.98]
+ assert self.profile_double["Infoset 3:1"] == [0.02, 0.98]
+
+ self.profile_rational["Infoset 1:1"] = [
+ gbt.Rational("2/9"), gbt.Rational("7/9")
+ ]
+ assert (
+ self.profile_rational["Infoset 1:1"] ==
+ [gbt.Rational("2/9"), gbt.Rational("7/9")]
+ )
+ self.profile_rational["Infoset 2:1"] = [
+ gbt.Rational("4/13"), gbt.Rational("9/13")
+ ]
+ assert (
+ self.profile_rational["Infoset 2:1"] ==
+ [gbt.Rational("4/13"), gbt.Rational("9/13")]
+ )
+ self.profile_rational["Infoset 3:1"] = [
+ gbt.Rational("1/98"), gbt.Rational("97/98")
+ ]
+ assert (
+ self.profile_rational["Infoset 3:1"] ==
+ [gbt.Rational("1/98"), gbt.Rational("97/98")]
+ )
+
+ def test_set_probabilities_player(self):
+ "Test to set probabilities to a player"
+ self.profile_double[self.game.players[0]] = [[0.72, 0.28]]
+ assert self.profile_double[self.game.players[0]] == [[0.72, 0.28]]
+ self.profile_double[self.game.players[1]] = [[0.42, 0.58]]
+ assert self.profile_double[self.game.players[1]] == [[0.42, 0.58]]
+ self.profile_double[self.game.players[2]] = [[0.02, 0.98]]
+ assert self.profile_double[self.game.players[2]] == [[0.02, 0.98]]
+
+ self.profile_rational[self.game.players[0]] = [
+ [gbt.Rational("2/9"), gbt.Rational("7/9")]
+ ]
+ assert (
+ self.profile_rational[self.game.players[0]] ==
+ [[gbt.Rational("2/9"), gbt.Rational("7/9")]]
+ )
+ self.profile_rational[self.game.players[1]] = [
+ [gbt.Rational("4/13"), gbt.Rational("9/13")]
+ ]
+ assert (
+ self.profile_rational[self.game.players[1]] ==
+ [[gbt.Rational("4/13"), gbt.Rational("9/13")]]
+ )
+ self.profile_rational[self.game.players[2]] = [
+ [gbt.Rational("1/98"), gbt.Rational("97/98")]
+ ]
+ assert (
+ self.profile_rational[self.game.players[2]] ==
+ [[gbt.Rational("1/98"), gbt.Rational("97/98")]]
+ )
+
+ def test_set_probabilities_player_by_string(self):
+ "Test to set probabilities to a player by string values"
+ self.profile_double["Player 1"] = [[0.72, 0.28]]
+ assert self.profile_double["Player 1"] == [[0.72, 0.28]]
+ self.profile_double["Player 2"] = [[0.42, 0.58]]
+ assert self.profile_double["Player 2"] == [[0.42, 0.58]]
+ self.profile_double["Player 3"] = [[0.02, 0.98]]
+ assert self.profile_double["Player 3"] == [[0.02, 0.98]]
+
+ self.profile_rational["Player 1"] = [
+ [gbt.Rational("2/9"), gbt.Rational("7/9")]
+ ]
+ assert (
+ self.profile_rational["Player 1"] ==
+ [[gbt.Rational("2/9"), gbt.Rational("7/9")]]
+ )
+ self.profile_rational["Player 2"] = [
+ [gbt.Rational("4/13"), gbt.Rational("9/13")]
+ ]
+ assert (
+ self.profile_rational["Player 2"] ==
+ [[gbt.Rational("4/13"), gbt.Rational("9/13")]]
+ )
+ self.profile_rational["Player 3"] = [
+ [gbt.Rational("1/98"), gbt.Rational("97/98")]
+ ]
+ assert (
+ self.profile_rational["Player 3"] ==
+ [[gbt.Rational("1/98"), gbt.Rational("97/98")]]
+ )
+
+ def test_infoset_prob(self):
+ """Test to retrieve the probability an information set is reached."""
+ assert (self.profile_double.infoset_prob(self.game.players[0].infosets[0]) == 1.0)
+ assert (self.profile_double.infoset_prob(self.game.players[1].infosets[0]) == 1.0)
+ assert (self.profile_double.infoset_prob(self.game.players[2].infosets[0]) == 1.0)
+ assert (self.profile_rational.infoset_prob(self.game.players[0].infosets[0]) ==
+ gbt.Rational("1/1"))
+ assert (self.profile_rational.infoset_prob(self.game.players[1].infosets[0]) ==
+ gbt.Rational("1/1"))
+ assert (self.profile_rational.infoset_prob(self.game.players[2].infosets[0]) ==
+ gbt.Rational("1/1"))
+
+ def test_infoset_prob_by_string(self):
+ """Test to retrieve the probability an information set is reached
+ using information set labels.
+ """
+ assert self.profile_double.infoset_prob("Infoset 1:1") == 1.0
+ assert self.profile_double.infoset_prob("Infoset 2:1") == 1.0
+ assert self.profile_double.infoset_prob("Infoset 3:1") == 1.0
+ assert self.profile_rational.infoset_prob("Infoset 1:1") == gbt.Rational("1/1")
+ assert self.profile_rational.infoset_prob("Infoset 2:1") == gbt.Rational("1/1")
+ assert self.profile_rational.infoset_prob("Infoset 3:1") == gbt.Rational("1/1")
+
+ def test_infoset_payoff(self):
+ "Test to retrieve expected payoff associated to an infoset"
+ assert self.profile_double.infoset_value(self.game.players[0].infosets[0]) == 3.0
+ assert self.profile_double.infoset_value(self.game.players[1].infosets[0]) == 3.0
+ assert self.profile_double.infoset_value(self.game.players[2].infosets[0]) == 3.25
+ assert self.profile_rational.infoset_value(self.game.players[0].infosets[0]) == 3
+ assert self.profile_rational.infoset_value(self.game.players[1].infosets[0]) == 3
+ assert (
+ self.profile_rational.infoset_value(self.game.players[2].infosets[0]) ==
+ gbt.Rational("13/4")
+ )
+
+ def test_infoset_payoff_by_string(self):
+ """Test to retrieve expected payoff associated to an infose
+ by string values"""
+ assert self.profile_double.infoset_value("Infoset 1:1") == 3.0
+ assert self.profile_double.infoset_value("Infoset 2:1") == 3.0
+ assert self.profile_double.infoset_value("Infoset 3:1") == 3.25
+ assert self.profile_rational.infoset_value("Infoset 1:1") == 3
+ assert self.profile_rational.infoset_value("Infoset 2:1") == 3
+ assert self.profile_rational.infoset_value("Infoset 3:1") == gbt.Rational("13/4")
+
+ def test_action_payoff(self):
+ "Test to retrieve expected payoff associated to an action"
+ assert (
+ self.profile_double.action_value(self.game.players[0].infosets[0].actions[0]) == 3.0
+ )
+ assert (
+ self.profile_double.action_value(self.game.players[0].infosets[0].actions[1]) == 3.0
+ )
+ assert (
+ self.profile_double.action_value(self.game.players[1].infosets[0].actions[0]) == 3.0
+ )
+ assert (
+ self.profile_double.action_value(self.game.players[1].infosets[0].actions[1]) == 3.0
+ )
+ assert (
+ self.profile_double.action_value(self.game.players[2].infosets[0].actions[0]) == 3.5
+ )
+ assert (
+ self.profile_double.action_value(self.game.players[2].infosets[0].actions[1]) == 3.0
+ )
+
+ assert (
+ self.profile_rational.action_value(self.game.players[0].infosets[0].actions[0]) ==
+ gbt.Rational("3/1")
+ )
+ assert (
+ self.profile_rational.action_value(self.game.players[0].infosets[0].actions[1]) ==
+ gbt.Rational("3/1")
+ )
+ assert (
+ self.profile_rational.action_value(self.game.players[1].infosets[0].actions[0]) ==
+ gbt.Rational("3/1")
+ )
+ assert (
+ self.profile_rational.action_value(self.game.players[1].infosets[0].actions[1]) ==
+ gbt.Rational("3/1")
+ )
+ assert (
+ self.profile_rational.action_value(self.game.players[2].infosets[0].actions[0]) ==
+ gbt.Rational("7/2")
+ )
+ assert (
+ self.profile_rational.action_value(self.game.players[2].infosets[0].actions[1]) ==
+ gbt.Rational("3/1")
+ )
+
+ def test_action_value_by_string(self):
+ """Test to retrieve expected payoff associated to an action
+ by string values
+ """
+ assert self.profile_double.action_value("U1") == 3.0
+ assert self.profile_double.action_value("D1") == 3.0
+ assert self.profile_double.action_value("U2") == 3.0
+ assert self.profile_double.action_value("D2") == 3.0
+ assert self.profile_double.action_value("U3") == 3.5
+ assert self.profile_double.action_value("D3") == 3.0
+
+ assert self.profile_rational.action_value("U1") == gbt.Rational("3/1")
+ assert self.profile_rational.action_value("D1") == gbt.Rational("3/1")
+ assert self.profile_rational.action_value("U2") == gbt.Rational("3/1")
+ assert self.profile_rational.action_value("D2") == gbt.Rational("3/1")
+ assert self.profile_rational.action_value("U3") == gbt.Rational("7/2")
+ assert self.profile_rational.action_value("D3") == gbt.Rational("3/1")
+
+ def test_regret(self):
+ 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_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
+ assert self.profile_rational.liap_value() == gbt.Rational("1/16")
+
+ def test_as_strategy(self):
+ """Test converting the behavior strategy profile to the equivalent
+ mixed strategy profile.
+ """
+ mixed_double = self.profile_double.as_strategy()
+ mixed_rational = self.profile_rational.as_strategy()
+ assert (
+ [mixed_double[strategy] for strategy in self.game.strategies] ==
+ [self.profile_double[action] for action in self.game.actions]
+ )
+ assert (
+ [mixed_rational[strategy] for strategy in self.game.strategies] ==
+ [self.profile_rational[action] for action in self.game.actions]
+ )
+
+ def test_node_belief(self):
+ "Test calculating belief probabilities on a node"
+ self.profile_double[self.game.actions[0]] = 0.8
+ self.profile_double[self.game.actions[1]] = 0.2
+ self.profile_double[self.game.actions[2]] = 1.0
+ self.profile_double[self.game.actions[3]] = 1.5
+ self.profile_double[self.game.actions[4]] = 0.0
+ self.profile_double[self.game.actions[5]] = 0.4
+ assert self.profile_double.belief(self.game.root) == 1
+ # Comparisons using 1e-13 as an arbitrary epsilon
+ assert (
+ abs(self.profile_double.belief(self.game.infosets[1].members[0]) -
+ 0.8) < 1e-13
+ )
+ assert (
+ abs(self.profile_double.belief(self.game.infosets[1].members[1]) -
+ 0.2) < 1e-13
+ )
+ assert (
+ abs(self.profile_double.belief(self.game.infosets[2].members[0]) -
+ 0.32) < 1e-13
+ )
+ assert (
+ abs(self.profile_double.belief(self.game.infosets[2].members[1]) -
+ 0.48) < 1e-13
+ )
+ assert (
+ abs(self.profile_double.belief(self.game.infosets[2].members[2]) -
+ 0.08) < 1e-13
+ )
+ assert (
+ abs(self.profile_double.belief(self.game.infosets[2].members[3]) -
+ 0.12) < 1e-13
+ )
+
+ self.profile_rational[self.game.actions[0]] = gbt.Rational(4, 5)
+ self.profile_rational[self.game.actions[1]] = gbt.Rational(1, 5)
+ self.profile_rational[self.game.actions[2]] = gbt.Rational(1, 1)
+ self.profile_rational[self.game.actions[3]] = gbt.Rational(3, 2)
+ self.profile_rational[self.game.actions[4]] = gbt.Rational(0, 1)
+ self.profile_rational[self.game.actions[5]] = gbt.Rational(2, 5)
+ assert (
+ self.profile_rational.belief(self.game.root) ==
+ gbt.Rational(1, 1)
+ )
+ assert (
+ self.profile_rational.belief(self.game.infosets[1].members[0]) ==
+ gbt.Rational(4, 5)
+ )
+ assert (
+ self.profile_rational.belief(self.game.infosets[1].members[1]) ==
+ gbt.Rational(1, 5)
+ )
+ assert (
+ self.profile_rational.belief(self.game.infosets[2].members[0]) ==
+ gbt.Rational(8, 25)
+ )
+ assert (
+ self.profile_rational.belief(self.game.infosets[2].members[1]) ==
+ gbt.Rational(12, 25)
+ )
+ assert (
+ self.profile_rational.belief(self.game.infosets[2].members[2]) ==
+ gbt.Rational(2, 25)
+ )
+ assert (
+ self.profile_rational.belief(self.game.infosets[2].members[3]) ==
+ gbt.Rational(3, 25)
+ )
+
+ def test_infoset_belief(self):
+ "Test calculating belief probabilities on an infoset"
+ self.profile_double[self.game.actions[0]] = 0.8
+ self.profile_double[self.game.actions[1]] = 0.2
+ self.profile_double[self.game.actions[2]] = 0.4
+ self.profile_double[self.game.actions[3]] = 0.6
+ self.profile_double[self.game.actions[4]] = 0.0
+ self.profile_double[self.game.actions[5]] = 1.0
+ assert self.profile_double.belief(self.game.infosets[0].members[0]) == 1.0
+
+ self.profile_rational[self.game.actions[0]] = gbt.Rational(4, 5)
+ self.profile_rational[self.game.actions[1]] = gbt.Rational(1, 5)
+ self.profile_rational[self.game.actions[2]] = gbt.Rational(2, 5)
+ self.profile_rational[self.game.actions[3]] = gbt.Rational(3, 5)
+ self.profile_rational[self.game.actions[4]] = gbt.Rational(0, 1)
+ self.profile_rational[self.game.actions[5]] = gbt.Rational(1, 1)
+ assert (
+ 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)
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
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():
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;