Skip to content

Commit

Permalink
Merge branch 'gambitproject:master' into add-to-arrays-function
Browse files Browse the repository at this point in the history
  • Loading branch information
d-kad authored Jul 15, 2024
2 parents 31967ae + 86a14c9 commit ae01060
Show file tree
Hide file tree
Showing 30 changed files with 1,301 additions and 1,927 deletions.
15 changes: 15 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
"features": {
"ghcr.io/devcontainers/features/python:1": {
"installTools": true,
"version": "3.11"
},
"ghcr.io/devcontainers-contrib/features/gdbgui:2": {
"version": "latest"
},
"ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
"packages": "automake,autoconf,gdb"
}
}
}
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Run clang-format style check for C/C++
uses: jidicula/clang-format-action@v4.11.0
uses: jidicula/clang-format-action@v4.13.0
with:
clang-format-version: '17'
check-path: 'src'
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/tools.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ jobs:
- run: make
- run: sudo make install
- run: make osx-dmg
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: artifact-osx
path: "*.dmg"

windows:
Expand All @@ -81,6 +82,7 @@ jobs:
cp gambit* installer
"${WIX}bin/candle" gambit.wxs
"${WIX}bin/light" -ext WixUIExtension gambit.wixobj
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: artifact-msw
path: "*.msi"
2 changes: 1 addition & 1 deletion .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ jobs:
python -m cibuildwheel --output-dir wheelhouse/
env:
CIBW_SKIP: "pp*"
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
path: ./wheelhouse/*.whl
23 changes: 22 additions & 1 deletion ChangeLog
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
# Changelog

## [16.2.0] - unreleased
## [16.3.0] - unreleased

### Added
- Implemented maximum-likelihood estimation for agent logit QRE, to parallel existing support
for strategic logit QRE. Strategic logit QRE function names have been modified to provide
parallel naming. Estimation using the correspondence now supports an option to stop at the
first interior local maximizer found (if one exists).
- Maximum-likelihood estimation for logit QRE using empirical payoffs has an improved internal
calculation of log-likelihood, and returns the estimated profile instead of just a list of
probabilities.
- Reorganized naming conventions in pygambit for functions for computing QRE in both strategic
and agent versions, and added a corresponding section in the user guide.


## [16.1.2] - unreleased

### Fixed
- Corrected an internal implementation error in `Game.reveal()` in resolving references to
information sets and players (#453)


## [16.2.0] - 2024-04-05

### Fixed
- `gnm_solve`/`gambit-gnm` now correctly handles the degenerate case of a game where all
Expand Down
3 changes: 1 addition & 2 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -519,9 +519,8 @@ gambit_logit_SOURCES = \
src/solvers/logit/logbehav.imp \
src/solvers/logit/path.cc \
src/solvers/logit/path.h \
src/solvers/logit/efglogit.h \
src/solvers/logit/logit.h \
src/solvers/logit/efglogit.cc \
src/solvers/logit/nfglogit.h \
src/solvers/logit/nfglogit.cc \
src/tools/logit/logit.cc

Expand Down
2 changes: 1 addition & 1 deletion doc/formats.bagg.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ separated by whitespaces. Lines with starting '#' are treated as comments and ar

#. utility function for each action node: same as in `the AGG format`_.

.. _the AGG format: file-formats-agg_
.. _the AGG format: _file-formats-agg
2 changes: 1 addition & 1 deletion doc/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ Downloading Gambit
==================

Gambit source code and built binaries can be downloaded from the project
`GitHub repository releases section`<https://github.com/gambitproject/gambit/releases>.
`GitHub repository releases section <https://github.com/gambitproject/gambit/releases>`_.

Older versions of Gambit can be downloaded from
`http://sourceforge.net/projects/gambit/files
Expand Down
6 changes: 4 additions & 2 deletions doc/pygambit.api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,8 @@ Computation of quantal response equilibria
.. autosummary::
:toctree: api/

fit_empirical
fit_fixedpoint
logit_solve_branch
logit_solve_lambda
logit_estimate
LogitQREMixedStrategyFitResult
LogitQREMixedBehaviorFitResult
67 changes: 57 additions & 10 deletions doc/pygambit.user.rst
Original file line number Diff line number Diff line change
Expand Up @@ -689,14 +689,13 @@ from different starting points.
gbt.nash.simpdiv_solve(g.random_strategy_profile(denom=10, gen=gen))
Estimating quantal response equilibria
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Quantal response equilibrium
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Alongside computing quantal response equilibria, Gambit can also perform maximum likelihood
estimation, computing the QRE which best fits an empirical distribution of play.

As an example we consider an asymmetric matching pennies game studied in [Och95]_,
analysed in [McKPal95]_ using QRE.
Gambit implements the idea of [McKPal95]_ and [McKPal98]_ to compute Nash equilibria
via path-following a branch of the logit quantal response equilibrium (LQRE) correspondence
using the function :py:func:`.logit_solve`. As an example, we will consider an
asymmetric matching pennies game from [Och95]_ as analyzed in [McKPal95]_.

.. ipython:: python
Expand All @@ -705,13 +704,52 @@ analysed in [McKPal95]_ using QRE.
[[0, 1.1141], [1.1141, 0]],
title="Ochs (1995) asymmetric matching pennies as transformed in McKelvey-Palfrey (1995)"
)
data = g.mixed_strategy_profile([[128*0.527, 128*(1-0.527)], [128*0.366, 128*(1-0.366)]])
gbt.nash.logit_solve(g)
:py:func:`.logit_solve` returns only the limiting (approximate) Nash equilibrium found.
Profiles along the QRE correspondence are frequently of interest in their own right.
Gambit offers several functions for more detailed examination of branches of the
QRE correspondence.

The function :py:func:`.logit_solve_branch` uses the same procedure as :py:func:`.logit_solve`,
but returns a list of LQRE profiles computed along the branch instead of just the limiting
approximate Nash equilibrium.

.. ipython:: python
qres = gbt.qre.logit_solve_branch(g)
len(qres)
qres[0]
qres[5]
Estimation of QRE is done using :py:func:`.fit_fixedpoint`.
:py:func:`.logit_solve_branch` uses an adaptive step size heuristic to find points on
the branch. The parameters `first_step` and `max_accel` are used to adjust the initial
step size and the maximum rate at which the step size changes adaptively. The step size
used is computed as the distance traveled along the path, and, importantly, not the
distance as measured by changes in the precision parameter lambda. As a result the
lambda values for which profiles are computed cannot be controlled in advance.
In some situations, the LQRE profiles at specified values of lambda are of interest.
For this, Gambit provides :py:func:`.logit_solve_lambda`. This function provides
accurate values of strategy profiles at one or more specified values of lambda.

.. ipython:: python
fit = gbt.qre.fit_fixedpoint(data)
qres = gbt.qre.logit_solve_lambda(g, lam=[1, 2, 3])
qres[0]
qres[1]
qres[2]
LQRE are frequently taken to data by using maximum likelihood estimation to find the
LQRE profile that best fits an observed profile of play. This is provided by
the function :py:func:`.logit_estimate`. We replicate the analysis of a block
of the data from [Och95]_ for which [McKPal95]_ estimated an LQRE.

.. ipython:: python
data = g.mixed_strategy_profile([[128*0.527, 128*(1-0.527)], [128*0.366, 128*(1-0.366)]])
fit = gbt.qre.logit_estimate(data)
The returned :py:class:`.LogitQREMixedStrategyFitResult` object contains the results of the
estimation.
Expand All @@ -726,6 +764,15 @@ log-likelihood is correct for use in likelihoood-ratio tests. [#f1]_
print(fit.profile)
print(fit.log_like)
All of the functions above also support working with the agent LQRE of [McKPal98]_.
Agent QRE are computed as the default behavior whenever the game has a extensive (tree)
representation. For :py:func:`.logit_solve`, :py:func:`.logit_solve_branch`, and
:py:func:`.logit_solve_lambda`, this can be overriden by passing `use_strategic=True`;
this will compute LQRE using the reduced strategy set of the game instead.
Likewise, :py:func:`.logit_estimate` will perform estimation using agent LQRE if the
data passed are a :py:class:`.MixedBehaviorProfile`, and will return a
:py:class:`.LogitQREMixedBehaviorFitResult` object.

.. rubric:: Footnotes

.. [#f1] The log-likelihoods quoted in [McKPal95]_ are exactly a factor of 10 larger than
Expand Down
8 changes: 7 additions & 1 deletion doc/tools.logit.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,13 @@ the beliefs at such information sets as being uniform across all member nodes.
.. cmdoption:: -l

While tracing, compute the logit equilibrium points
with parameter LAMBDA accurately.
with parameter LAMBDA accurately. This option may be specified multiple times,
in which case points are found for each successive lambda, in the order specified,
along the branch.

.. versionchanged:: 16.3.0

Added support for specifying multiple lambda values.

.. cmdoption:: -S

Expand Down
19 changes: 14 additions & 5 deletions src/games/behavmixed.cc
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,21 @@ template <class T>
MixedBehaviorProfile<T> &
MixedBehaviorProfile<T>::operator=(const MixedBehaviorProfile<T> &p_profile)
{
if (this != &p_profile && m_support == p_profile.m_support) {
InvalidateCache();
m_probs = p_profile.m_probs;
m_support = p_profile.m_support;
m_gameversion = p_profile.m_gameversion;
if (this == &p_profile) {
return *this;
}
if (m_support != p_profile.m_support) {
throw MismatchException();
}
InvalidateCache();
m_probs = p_profile.m_probs;
m_gameversion = p_profile.m_gameversion;
map_realizProbs = p_profile.map_realizProbs;
map_beliefs = p_profile.map_beliefs;
map_nodeValues = p_profile.map_nodeValues;
map_infosetValues = p_profile.map_infosetValues;
map_actionValues = p_profile.map_actionValues;
map_regret = p_profile.map_regret;
return *this;
}

Expand Down
56 changes: 34 additions & 22 deletions src/pygambit/gambit.pxd
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from libcpp cimport bool
from libcpp.string cimport string
from libcpp.memory cimport shared_ptr
from libcpp.list cimport list as clist


cdef extern from "gambit.h":
Expand Down Expand Up @@ -395,6 +396,9 @@ cdef extern from "util.h":
shared_ptr[c_LogitQREMixedStrategyProfile] copyitem_list_qrem "sharedcopyitem"(
c_List[c_LogitQREMixedStrategyProfile], int
) except +
shared_ptr[c_LogitQREMixedBehaviorProfile] copyitem_list_qreb "sharedcopyitem"(
c_List[c_LogitQREMixedBehaviorProfile], int
) except +


cdef extern from "solvers/enumpure/enumpure.h":
Expand Down Expand Up @@ -455,42 +459,50 @@ cdef extern from "solvers/gnm/gnm.h":
int p_localNewtonInterval, int p_localNewtonMaxits
) except +RuntimeError

cdef extern from "solvers/logit/nfglogit.h":
c_List[c_MixedStrategyProfileDouble] LogitStrategySolve(c_Game,
double,
double,
double) except +RuntimeError

cdef extern from "solvers/logit/efglogit.h":
c_List[c_MixedBehaviorProfileDouble] LogitBehaviorSolve(c_Game,
double,
double,
double) except +RuntimeError
cdef extern from "solvers/logit/logit.h":
cdef cppclass c_LogitQREMixedBehaviorProfile "LogitQREMixedBehaviorProfile":
c_LogitQREMixedBehaviorProfile(c_Game) except +
c_LogitQREMixedBehaviorProfile(c_LogitQREMixedBehaviorProfile) except +
c_Game GetGame() except +
c_MixedBehaviorProfileDouble GetProfile() # except + doesn't compile
double GetLambda() except +
double GetLogLike() except +
int size() except +
double getitem "operator[]"(int) except +IndexError

cdef extern from "solvers/logit/nfglogit.h":
cdef cppclass c_LogitQREMixedStrategyProfile "LogitQREMixedStrategyProfile":
c_LogitQREMixedStrategyProfile(c_Game) except +
c_LogitQREMixedStrategyProfile(c_LogitQREMixedStrategyProfile) except +
c_Game GetGame() except +
c_MixedStrategyProfileDouble GetProfile() # except + doesn't compile
double GetLambda() except +
double GetLogLike() except +
int MixedProfileLength() except +
int size() except +
double getitem "operator[]"(int) except +IndexError

cdef cppclass c_StrategicQREEstimator "StrategicQREEstimator":
c_StrategicQREEstimator() except +
c_LogitQREMixedStrategyProfile Estimate(c_LogitQREMixedStrategyProfile,
c_MixedStrategyProfileDouble,
double, double, double) except +RuntimeError

cdef extern from "nash.h":
shared_ptr[c_LogitQREMixedStrategyProfile] _logit_estimate "logit_estimate"(
shared_ptr[c_MixedStrategyProfileDouble], double, double
c_List[c_MixedBehaviorProfileDouble] LogitBehaviorSolveWrapper(
c_Game, double, double, double
) except +
shared_ptr[c_LogitQREMixedStrategyProfile] _logit_atlambda "logit_atlambda"(
c_List[c_LogitQREMixedBehaviorProfile] LogitBehaviorPrincipalBranchWrapper(
c_Game, double, double, double
) except +
c_List[c_LogitQREMixedStrategyProfile] _logit_principal_branch "logit_principal_branch"(
clist[shared_ptr[c_LogitQREMixedBehaviorProfile]] LogitBehaviorAtLambdaWrapper(
c_Game, clist[double], double, double
) except +
shared_ptr[c_LogitQREMixedBehaviorProfile] LogitBehaviorEstimateWrapper(
shared_ptr[c_MixedBehaviorProfileDouble], bool, double, double
) except +
c_List[c_MixedStrategyProfileDouble] LogitStrategySolveWrapper(
c_Game, double, double, double
) except +
c_List[c_LogitQREMixedStrategyProfile] LogitStrategyPrincipalBranchWrapper(
c_Game, double, double, double
) except +
clist[shared_ptr[c_LogitQREMixedStrategyProfile]] LogitStrategyAtLambdaWrapper(
c_Game, clist[double], double, double
) except +
shared_ptr[c_LogitQREMixedStrategyProfile] LogitStrategyEstimateWrapper(
shared_ptr[c_MixedStrategyProfileDouble], bool, double, double
) except +
2 changes: 1 addition & 1 deletion src/pygambit/game.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -1580,7 +1580,7 @@ class Game:
"""
resolved_infoset = cython.cast(Infoset, self._resolve_infoset(infoset, "reveal"))
resolved_player = cython.cast(Player, self._resolve_player(player, "reveal"))
resolved_infoset.deref().Reveal(resolved_player)
resolved_infoset.infoset.deref().Reveal(resolved_player.player)

def add_player(self, label: str = "") -> Player:
"""Add a new player to the game.
Expand Down
Loading

0 comments on commit ae01060

Please sign in to comment.