diff --git a/.gitignore b/.gitignore index ebc21507c..9acfeeb47 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ _build build *.egg-info *.pyc +*.so config.guess config.sub @@ -37,3 +38,5 @@ gambit .python-version dist .venv +*.dmg +Gambit.app/* \ No newline at end of file diff --git a/.readthedocs.yml b/.readthedocs.yml index 1dd08d262..af921b438 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -11,6 +11,7 @@ build: python: "3.13" apt_packages: - libgmp-dev + - pandoc python: install: diff --git a/doc/conf.py b/doc/conf.py index 57de0b53a..21b5325ba 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -26,6 +26,7 @@ "IPython.sphinxext.ipython_console_highlighting", "IPython.sphinxext.ipython_directive", "sphinx_design", + "nbsphinx", ] # IPython directive configuration diff --git a/doc/developer.contributing.rst b/doc/developer.contributing.rst index daf27fcac..65857f180 100644 --- a/doc/developer.contributing.rst +++ b/doc/developer.contributing.rst @@ -22,6 +22,8 @@ When reporting a bug, please be sure to include the following: sample game file or files if appropriate; it is often helpful to simplify the game if possible. +.. _contributing-code: + Contributing code ---------------- @@ -57,31 +59,31 @@ The project is hosted on GitHub, and contributions can be made via pull requests Editing this documentation -------------------------- -1. If you haven't already, clone the Gambit repository from GitHub: :: +1. `Install Pandoc `_ for your OS + +2. If you haven't already, clone the Gambit repository from GitHub: :: git clone https://github.com/gambitproject/gambit.git cd gambit -2. Either install the docs requirements into your existing PyGambit development environment, or create a new virtual environment and install both the requirements and PyGambit there. For example, you can use `venv` to create a new environment: :: +3. Either install the docs requirements into your existing PyGambit development environment, or create a new virtual environment and install both the requirements and PyGambit there. For example, you can use `venv` to create a new environment: :: python -m venv docenv source docenv/bin/activate -3. Install the requirements and make the docs: :: +4. Install the requirements and make the docs: :: pip install . cd doc pip install -r requirements.txt make html # or make livehtml for live server with auto-rebuild -4. Open ``doc/_build/html/index.html`` in your browser to view the documentation. +5. Open ``doc/_build/html/index.html`` in your browser to view the documentation. -5. Make any changes you want to the `.rst` files in the ``doc`` directory and rebuld the documentation to check your changes. +6. Make any changes you want to the `.rst` files in the ``doc`` directory and rebuild the documentation to check your changes. -6. Follow the usual GitHub workflow to commit your changes and push them to the repository. +7. Follow the usual GitHub workflow (see :ref:`contributing-code` above) to commit your changes and push them to the repository. -7. Core developers will review your changes and merge to the master branch, which automatically deploys the documentation via the ReadTheDocs service. +8. Core developers will review your changes and merge to the master branch, which automatically deploys the documentation via the ReadTheDocs service. -.. TODO: Add instructions for the GitHub workflow during contributor docs refactoring. - See https://github.com/gambitproject/gambit/issues/541 diff --git a/doc/gui.rst b/doc/gui.rst index 361681c40..9e83e59ab 100644 --- a/doc/gui.rst +++ b/doc/gui.rst @@ -25,6 +25,68 @@ To build larger games or to explore parameter spaces of a game systematically, it is recommended to use :ref:`the Python package `. +Installation +------------ + +To install the Gambit GUI, visit the `Gambit releases page on GitHub `_ and download the appropriate installer or package for your operating system. +Each release includes pre-built binaries for Windows, macOS, and Linux distributions, accessible under the "Assets" section of each release. + +.. dropdown:: Manual macOS Build Instructions + :class-container: sd-border-0 + + To build and install the Gambit GUI from source on macOS, follow these steps: + + 1. **Install build dependencies:** + + .. code-block:: bash + + brew install automake autoconf libtool + + .. note:: + If you encounter interpreter errors with autom4te, you may need to ensure + your Perl installation is correct or reinstall the autotools: + + .. code-block:: bash + + brew reinstall automake autoconf libtool + + 2. **Download and build wxWidgets:** + + .. code-block:: bash + + curl -L -O https://github.com/wxWidgets/wxWidgets/releases/download/v3.2.8/wxWidgets-3.2.8.tar.bz2 + tar xjf wxWidgets-3.2.8.tar.bz2 + cd wxWidgets-3.2.8 + mkdir build-release + cd build-release + ../configure --disable-shared --disable-sys-libs + make -j4 + sudo make install + + 3. **Build and install Gambit:** + + Navigate back to the Gambit source directory and run: + + .. code-block:: bash + + aclocal + automake --add-missing + autoconf + ./configure + make + sudo make install + + 4. **Create macOS application bundle:** + + To create a distributable DMG file: + + .. code-block:: bash + + make osx-dmg + + 5. **Install the application:** + + After creating the DMG file, open it and drag the Gambit application to your Applications folder. .. toctree:: :maxdepth: 2 diff --git a/doc/index.rst b/doc/index.rst index 2fcb284f3..410c72cbe 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -7,13 +7,13 @@ construction and analysis of finite extensive and strategic games. .. grid:: - .. grid-item-card:: Python user guide + .. grid-item-card:: Python tutorials and user guide :columns: 6 An introduction to using the ``pygambit`` package in Python. - .. button-ref:: pygambit-user + .. button-ref:: pygambit :ref-type: ref :click-parent: :color: secondary diff --git a/doc/pygambit.external_programs.rst b/doc/pygambit.external_programs.rst new file mode 100644 index 000000000..8877ad241 --- /dev/null +++ b/doc/pygambit.external_programs.rst @@ -0,0 +1,31 @@ +Using external programs to compute Nash equilibria +================================================== + +Because the problem of finding Nash equilibria can be expressed in various +mathematical formulations (see [McKMcL96]_), it is helpful to make use +of other software packages designed specifically for solving those problems. + +There are currently two integrations offered for using external programs to solve +for equilibria: + +- :py:func:`.enummixed_solve` supports enumeration of equilibria in + two-player games via `lrslib`. [#lrslib]_ +- :py:func:`.enumpoly_solve` supports computation of totally-mixed equilibria + on supports in strategic games via `PHCpack`. [#phcpack]_ + +For both calls, using the external program requires passing the path to the +executable (via the `lrsnash_path` and `phcpack_path` arguments, respectively). + +The user must download and compile or install these programs on their own; these are +not packaged with Gambit. The solver calls do take care of producing the required +input files, and reading the output to convert into Gambit objects for further +processing. + + +.. [#lrslib] http://cgm.cs.mcgill.ca/~avis/C/lrs.html + +.. [#phcpack] https://homepages.math.uic.edu/~jan/PHCpack/phcpack.html + +.. [McKMcL96] McKelvey, Richard D. and McLennan, Andrew M. (1996) Computation of equilibria + in finite games. In Handbook of Computational Economics, Volume 1, + pages 87-142. diff --git a/doc/pygambit.rst b/doc/pygambit.rst index 556a80afb..39cc4f4c8 100644 --- a/doc/pygambit.rst +++ b/doc/pygambit.rst @@ -4,18 +4,70 @@ PyGambit ======== -Gambit provides a Python package, ``pygambit``, which is available on `PyPI -`_. +The Gambit Python package, ``pygambit``, is available on `PyPI `_ and can be installed with pip:: -Installation ------------- + pip install pygambit -To install the package, use the following command:: - pip install pygambit +For newcomers to Gambit, we recommend reading through the PyGambit tutorials, which demonstrate the API's key capabilities for analyzing and solving Game Theory games. +These tutorials are available to be run interactively as Jupyter notebooks, see :ref:`local_tutorials`. +All of the tutorials assume a basic knowledge of programming in Python. + +Tutorials **1-3** assume no prior knowledge of Game Theory or the PyGambit API and provide detailed explanations of the concepts and code. + +.. toctree:: + :maxdepth: 2 + + tutorials/01_quickstart + tutorials/02_extensive_form + tutorials/03_poker + +Tutorials **4-5** assume some familiarity with the PyGambit API and Game Theory terminology and concepts including: + +- Nash equilibria +- Pure and mixed strategies +- Simplex representations of available strategies +- Logit quantal response equilibrium (LQRE) correspondence + +.. toctree:: + :maxdepth: 2 + + tutorials/04_starting_points + tutorials/05_quantal_response + tutorials/06_gambit_with_openspiel + +You may also wish to read: + +.. toctree:: + :maxdepth: 2 + + tutorials/running_locally + pygambit.external_programs + +Algorithms for computing Nash equilibria +---------------------------------------- + +Interfaces to algorithms for computing Nash equilibria are provided in :py:mod:`pygambit.nash`. +The table below summarizes the available PyGambit functions and the corresponding Gambit CLI commands. + +========================================== ======================================== +CLI command PyGambit function +========================================== ======================================== +:ref:`gambit-enumpure ` :py:func:`pygambit.nash.enumpure_solve` +:ref:`gambit-enummixed ` :py:func:`pygambit.nash.enummixed_solve` +:ref:`gambit-lp ` :py:func:`pygambit.nash.lp_solve` +:ref:`gambit-lcp ` :py:func:`pygambit.nash.lcp_solve` +:ref:`gambit-liap ` :py:func:`pygambit.nash.liap_solve` +:ref:`gambit-logit ` :py:func:`pygambit.nash.logit_solve` +:ref:`gambit-simpdiv ` :py:func:`pygambit.nash.simpdiv_solve` +:ref:`gambit-ipa ` :py:func:`pygambit.nash.ipa_solve` +:ref:`gambit-gnm ` :py:func:`pygambit.nash.gnm_solve` +========================================== ======================================== + +API documentation +---------------- .. toctree:: :maxdepth: 2 - pygambit.user - pygambit.api + pygambit.api \ No newline at end of file diff --git a/doc/pygambit.user.rst b/doc/pygambit.user.rst deleted file mode 100644 index 293ff3565..000000000 --- a/doc/pygambit.user.rst +++ /dev/null @@ -1,872 +0,0 @@ -.. _pygambit-user: - -User guide ----------- - -Example: One-shot trust game with binary actions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -[Kre90]_ introduced a game commonly referred to as the **trust game**. -We will build a one-shot version of this game using ``pygambit``'s game transformation -operations. - -There are two players, a **Buyer** and a **Seller**. -The Buyer moves first and has two actions, **Trust** or **Not trust**. -If the Buyer chooses **Not trust**, then the game ends, and both players -receive payoffs of 0. -If the Buyer chooses **Trust**, then the Seller has a choice with two actions, -**Honor** or **Abuse**. -If the Seller chooses **Honor**, both players receive payoffs of 1; -if the Seller chooses **Abuse**, the Buyer receives a payoff of -1 and the Seller -receives a payoff of 2. - -We create a game with an extensive representation using :py:meth:`.Game.new_tree`: - -.. ipython:: python - - import pygambit as gbt - g = gbt.Game.new_tree(players=["Buyer", "Seller"], - title="One-shot trust game, after Kreps (1990)") - - -The tree of the game contains just a root node, with no children: - -.. ipython:: python - - g.root - g.root.children - - -To extend a game from an existing terminal node, use :py:meth:`.Game.append_move`: - -.. ipython:: python - - g.append_move(g.root, "Buyer", ["Trust", "Not trust"]) - g.root.children - -We can then also add the Seller's move in the situation after the Buyer chooses Trust: - -.. ipython:: python - - g.append_move(g.root.children[0], "Seller", ["Honor", "Abuse"]) - -Now that we have the moves of the game defined, we add payoffs. Payoffs are associated with -an :py:class:`.Outcome`; each :py:class:`Outcome` has a vector of payoffs, one for each player, -and optionally an identifying text label. First we add the outcome associated with the -Seller proving themselves trustworthy: - -.. ipython:: python - - g.set_outcome(g.root.children[0].children[0], g.add_outcome([1, 1], label="Trustworthy")) - -Next, the outcome associated with the scenario where the Buyer trusts but the Seller does -not return the trust: - -.. ipython:: python - - g.set_outcome(g.root.children[0].children[1], g.add_outcome([-1, 2], label="Untrustworthy")) - -And, finally the outcome associated with the Buyer opting out of the interaction: - -.. ipython:: python - - g.set_outcome(g.root.children[1], g.add_outcome([0, 0], label="Opt-out")) - -Nodes without an outcome attached are assumed to have payoffs of zero for all players. -Therefore, adding the outcome to this latter terminal node is not strictly necessary in Gambit, -but it is useful to be explicit for readability. - -.. [Kre90] Kreps, D. (1990) "Corporate Culture and Economic Theory." - In J. Alt and K. Shepsle, eds., *Perspectives on Positive Political Economy*, - Cambridge University Press. - - -.. _pygambit.user.poker: - -Example: A one-card poker game with private information -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To illustrate games in extensive form, [Mye91]_ presents a one-card poker game. -A version of this game also appears in [RUW08]_, as a classroom game under the -name "stripped-down poker". This is perhaps the simplest interesting game -with imperfect information. - -In our version of the game, there are two players, **Alice** and **Bob**. -There is a deck of cards, with equal numbers of **King** and **Queen** cards. -The game begins with each player putting $1 in the pot. -One card is dealt at random to Alice; Alice observes her card but Bob does not. -After Alice observes her card, she can choose either to **Raise** or to **Fold**. -If she chooses to Fold, Bob wins the pot and the game ends. -If she chooses to Raise, she adds another $1 to the pot. -Bob then chooses either to **Meet** or **Pass**. If he chooses to Pass, -Alice wins the pot and the game ends. -If he chooses to Meet, he adds another $1 to the pot. -There is then a showdown, in which Alice reveals her card. If she has a King, -then she wins the pot; if she has a Queen, then Bob wins the pot. - -We can build this game using the following script:: - - g = gbt.Game.new_tree(players=["Alice", "Bob"], - title="One card poker game, after Myerson (1991)") - g.append_move(g.root, g.players.chance, ["King", "Queen"]) - for node in g.root.children: - g.append_move(node, "Alice", ["Raise", "Fold"]) - g.append_move(g.root.children[0].children[0], "Bob", ["Meet", "Pass"]) - g.append_infoset(g.root.children[1].children[0], - g.root.children[0].children[0].infoset) - alice_winsbig = g.add_outcome([2, -2], label="Alice wins big") - alice_wins = g.add_outcome([1, -1], label="Alice wins") - bob_winsbig = g.add_outcome([-2, 2], label="Bob wins big") - bob_wins = g.add_outcome([-1, 1], label="Bob wins") - g.set_outcome(g.root.children[0].children[0].children[0], alice_winsbig) - g.set_outcome(g.root.children[0].children[0].children[1], alice_wins) - g.set_outcome(g.root.children[0].children[1], bob_wins) - g.set_outcome(g.root.children[1].children[0].children[0], bob_winsbig) - g.set_outcome(g.root.children[1].children[0].children[1], alice_wins) - g.set_outcome(g.root.children[1].children[1], bob_wins) - -All extensive games have a chance (or nature) player, accessible as -``.Game.players.chance``. Moves belonging to the chance player can be added in the same -way as to personal players. At any new move created for the chance player, the action -probabilities default to uniform randomization over the actions at the move. - -In this game, information structure is important. Alice knows her card, so the two nodes -at which she has the move are part of different information sets. The loop:: - - for node in g.root.children: - g.append_move(node, "Alice", ["Raise", "Fold"]) - -causes each of the newly-appended moves to be in new information sets. In contrast, Bob -does not know Alice's card, and therefore cannot distinguish between the two nodes at which -he has the decision. This is implemented in the following lines:: - - g.append_move(g.root.children[0].children[0], "Bob", ["Meet", "Pass"]) - g.append_infoset(g.root.children[1].children[0], - g.root.children[0].children[0].infoset) - -The call :py:meth:`.Game.append_infoset` adds a move at a terminal node as part of -an existing information set (represented in ``pygambit`` as an :py:class:`.Infoset`). - - -.. [Mye91] Myerson, Roger B. (1991) *Game Theory: Analysis of Conflict*. - Cambridge: Harvard University Press. - -.. [RUW08] Reiley, David H., Michael B. Urbancic and Mark Walker. (2008) - "Stripped-down poker: A classroom game with signaling and bluffing." - *The Journal of Economic Education* 39(4): 323-341. - - - -Building a strategic game -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Games in strategic form, also referred to as normal form, are represented solely -by a collection of payoff tables, one per player. The most direct way to create -a strategic game is via :py:meth:`.Game.from_arrays`. This function takes one -n-dimensional array per player, where n is the number of players in the game. -The arrays can be any object that can be indexed like an n-times-nested Python list; -so, for example, `numpy` arrays can be used directly. - -For example, to create a standard prisoner's dilemma game in which the cooperative -payoff is 8, the betrayal payoff is 10, the sucker payoff is 2, and the noncooperative -payoff is 5: - -.. ipython:: python - - import numpy as np - m = np.array([[8, 2], [10, 5]]) - g = gbt.Game.from_arrays(m, np.transpose(m)) - g - -The arrays passed to :py:meth:`.Game.from_arrays` are all indexed in the same sense, that is, -the top level index is the choice of the first player, the second level index of the second player, -and so on. Therefore, to create a two-player symmetric game, as in this example, the payoff matrix -for the second player is transposed before passing to :py:meth:`.Game.from_arrays`. - -There is a reverse function :py:meth:`.Game.to_arrays` that produces -the players' payoff tables given a strategic game. The output is the list of ``numpy`` arrays, -where the number of produced arrays is equal to the number of players. - -.. ipython:: python - - m, m_transposed = g.to_arrays() - m - -The optional parameter `dtype`` controls the data type of the payoffs in the generated arrays. - -.. ipython:: python - - m, m_transposed = g.to_arrays(dtype=float) - m - -The function supports any type which can convert from Python's `fractions.Fraction` type. -For example, to convert the payoffs to their string representations via `str`: - -.. ipython:: python - - m, m_transposed = g.to_arrays(dtype=str) - m - -.. _pygambit.user.numbers: - -Representation of numerical data of a game -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Payoffs to players and probabilities of actions at chance information sets are specified -as numbers. Gambit represents the numerical values in a game in exact precision, -using either decimal or rational representations. - -To illustrate, we consider a trivial game which just has one move for the chance player: - -.. ipython:: python - - import pygambit as gbt - g = gbt.Game.new_tree() - g.append_move(g.root, g.players.chance, ["a", "b", "c"]) - [act.prob for act in g.root.infoset.actions] - -The default when creating a new move for chance is that all actions are chosen with -equal probability. These probabilities are represented as rational numbers, -using ``pygambit``'s :py:class:`.Rational` class, which is derived from Python's -`fractions.Fraction`. Numerical data can be set as rational numbers: - -.. ipython:: python - - g.set_chance_probs(g.root.infoset, - [gbt.Rational(1, 4), gbt.Rational(1, 2), gbt.Rational(1, 4)]) - [act.prob for act in g.root.infoset.actions] - -They can also be explicitly specified as decimal numbers: - -.. ipython:: python - - g.set_chance_probs(g.root.infoset, - [gbt.Decimal(".25"), gbt.Decimal(".50"), gbt.Decimal(".25")]) - [act.prob for act in g.root.infoset.actions] - -Although the two representations above are mathematically equivalent, ``pygambit`` -remembers the format in which the values were specified. - -Expressing rational or decimal numbers as above is verbose and tedious. -``pygambit`` offers a more concise way to express numerical data in games: -when setting numerical game data, ``pygambit`` will attempt to convert text strings to -their rational or decimal representation. The above can therefore be written -more compactly using string representations: - -.. ipython:: python - - g.set_chance_probs(g.root.infoset, ["1/4", "1/2", "1/4"]) - [act.prob for act in g.root.infoset.actions] - - g.set_chance_probs(g.root.infoset, [".25", ".50", ".25"]) - [act.prob for act in g.root.infoset.actions] - -As a further convenience, ``pygambit`` will accept Python ``int`` and ``float`` values. -``int`` values are always interpreted as :py:class:`.Rational` values. -``pygambit`` attempts to render `float` values in an appropriate :py:class:`.Decimal` -equivalent. In the majority of cases, this creates no problems. -For example, - -.. ipython:: python - - g.set_chance_probs(g.root.infoset, [.25, .50, .25]) - [act.prob for act in g.root.infoset.actions] - -However, rounding can cause difficulties when attempting to use `float` values to -represent values which do not have an exact decimal representation - -.. ipython:: python - :okexcept: - - g.set_chance_probs(g.root.infoset, [1/3, 1/3, 1/3]) - -This behavior can be slightly surprising, especially in light of the fact that -in Python, - -.. ipython:: python - - 1/3 + 1/3 + 1/3 - -In checking whether these probabilities sum to one, ``pygambit`` first converts each -of the probabilitiesto a :py:class:`.Decimal` representation, via the following method - -.. ipython:: python - - gbt.Decimal(str(1/3)) - -and the sum-to-one check then fails because - -.. ipython:: python - - gbt.Decimal(str(1/3)) + gbt.Decimal(str(1/3)) + gbt.Decimal(str(1/3)) - -Setting payoffs for players also follows the same rules. Representing probabilities -and payoffs exactly is essential, because ``pygambit`` offers (in particular for two-player -games) the possibility of computation of equilibria exactly, because the Nash equilibria -of any two-player game with rational payoffs and chance probabilities can be expressed exactly -in terms of rational numbers. - -It is therefore advisable always to specify the numerical data of games either in terms -of :py:class:`.Decimal` or :py:class:`.Rational` values, or their string equivalents. -It is safe to use `int` values, but `float` values should be used with some care to ensure -the values are recorded as intended. - - -Reading a game from a file -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Games stored in existing Gambit savefiles can be loaded using :meth:`.read_efg` or :meth:`.read_nfg`: - -.. ipython:: python - :suppress: - - cd ../contrib/games - - -.. ipython:: python - - g = gbt.read_nfg("e02.nfg") - g - -.. ipython:: python - :suppress: - - cd ../../doc - - -Lifetime of a game object and its elements -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A game is only deallocated when all variables referring to the game either directly -or indirectly have gone out of scope. Indirect references to games include objects such -as :py:class:`~pygambit.gambit.MixedStrategyProfile` or :py:class:`~pygambit.gambit.MixedBehaviorProfile`, -or variables referring to individual elements of a game. - -So for example, the following sequence of operations is valid: - -.. ipython:: python - :suppress: - - cd ../contrib/games - - -.. ipython:: python - - g = gbt.read_efg("e02.efg") - p = g.players[0] - print(p) - g = gbt.read_efg("poker.efg") - print(p) - print(g) - -.. ipython:: python - :suppress: - - cd ../../doc - -The variable `p` refers to a player in the game read from ``e02.efg``. -So, when ``poker.efg`` is read and assigned to the variable `g`, the game from -``e02.efg`` is still referred to indirectly via `p`. The game object from the -first game can therefore still be obtained from the object referring to the -player: - -.. ipython:: python - - print(p.game) - - -Computing Nash equilibria -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Interfaces to algorithms for computing Nash equilibria are provided in :py:mod:`pygambit.nash`. - -========================================== ======================================== -Method Python function -========================================== ======================================== -:ref:`gambit-enumpure ` :py:func:`pygambit.nash.enumpure_solve` -:ref:`gambit-enummixed ` :py:func:`pygambit.nash.enummixed_solve` -:ref:`gambit-lp ` :py:func:`pygambit.nash.lp_solve` -:ref:`gambit-lcp ` :py:func:`pygambit.nash.lcp_solve` -:ref:`gambit-liap ` :py:func:`pygambit.nash.liap_solve` -:ref:`gambit-logit ` :py:func:`pygambit.nash.logit_solve` -:ref:`gambit-simpdiv ` :py:func:`pygambit.nash.simpdiv_solve` -:ref:`gambit-ipa ` :py:func:`pygambit.nash.ipa_solve` -:ref:`gambit-gnm ` :py:func:`pygambit.nash.gnm_solve` -========================================== ======================================== - -We take as an example the :ref:`one-card poker game `. This is a two-player, -constant sum game, and so all of the equilibrium-finding methods can be applied to it. - -For two-player games, :py:func:`.lcp_solve` can compute Nash equilibria directly using -the extensive representation. Assuming that ``g`` refers to the game - -.. ipython:: python - :suppress: - - g = gbt.read_efg("poker.efg") - -.. ipython:: python - - result = gbt.nash.lcp_solve(g) - result - len(result.equilibria) - -The result of the calculation is returned as a :py:class:`.NashComputationResult` object. -The set of equilibria found is reported in :py:attr:`.NashComputationResult.equilibria`; -in this case, this is a list of mixed behavior profiles. -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 a :py:class:`.MixedBehavior`, -which specifies probability distributions at each of the player's information sets: - -.. ipython:: python - - eqm = result.equilibria[0] - eqm["Alice"] - -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. -The probability distribution at an information set is represented by a :py:class:`.MixedAction`. -:py:meth:`.MixedBehavior.mixed_actions` iterates over these for the player: - -.. ipython:: python - - for infoset, mixed_action in eqm["Alice"].mixed_actions(): - print(infoset) - print(mixed_action) - -So we could extract Alice's probabilities of raising at her respective information sets -like this: - -.. ipython:: python - - {infoset: mixed_action["Raise"] for infoset, mixed_action in eqm["Alice"].mixed_actions()} - -In larger games, labels may not always be the most convenient way to refer to specific -actions. We can also index profiles directly with :py:class:`.Action` objects. -So an alternative way to extract the probabilities of playing "Raise" would be by -iterating Alice's list of actions: - -.. ipython:: python - - {action.infoset: eqm[action] for action in g.players["Alice"].actions if action.label == "Raise"} - - -Looking at Bob's strategy, - -.. ipython:: python - - eqm["Bob"] - -Bob meets Alice's raise two-thirds of the time. The label "Raise" is used in more than one -information set for Alice, so in the above we had to specify information sets when indexing. -When there is no ambiguity, we can specify action labels directly. So for example, because -Bob has only one action named "Meet" in the game, we can extract the probability that Bob plays -"Meet" by: - -.. ipython:: python - - eqm["Bob"]["Meet"] - -Moreover, this is the only action with that label in the game, so we can index the -profile directly using the action label without any ambiguity: - -.. ipython:: python - - eqm["Meet"] - -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 - - {action: eqm.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 - - {node: eqm.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 - - eqm.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 - - {node: eqm.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 - - eqm.payoff("Alice") - eqm.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`, -and :py:func:`.enummixed_solve`, can report exact probabilities for equilibrium strategy -profiles. This is enabled by default for these methods. - -When a game has an extensive representation, equilibrium finding methods default to computing -on that representation. It is also possible to compute using the strategic representation. -``pygambit`` transparently computes the reduced strategic form representation of an extensive game - -.. ipython:: python - - [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 -operates on a strategic game to any game with an extensive representation - -.. ipython:: python - - result = gbt.nash.gnm_solve(g) - result - -:py:func:`.gnm_solve` can be applied to any game with any number of players, and uses a path-following -process in floating-point arithmetic, so it returns profiles with probabilities expressed as -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. -Indexing a :py:class:`.MixedStrategyProfile` by a player gives the probability distribution -over that player's strategies only. - -.. ipython:: python - - eqm = result.equilibria[0] - eqm["Alice"] - eqm["Bob"] - -The expected payoff to a strategy is provided by :py:meth:`.MixedStrategyProfile.strategy_value`: - -.. ipython:: python - - {strategy: eqm.strategy_value(strategy) for strategy in g.players["Alice"].strategies} - {strategy: eqm.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 - - eqm.payoff("Alice") - eqm.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 - - eqm.as_behavior() - eqm.as_behavior().as_strategy() - - -.. _pygambit-nash-maxregret: - -Acceptance criteria for Nash equilibria -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Some methods for computing Nash equilibria operate using floating-point arithmetic and/or -generate candidate equilibrium profiles using methods which involve some form of successive -approximations. The outputs of these methods therefore are in general -:math:`\varepsilon`-equilibria, for some positive :math:`\varepsilon`. - -To provide a uniform interface across methods, where relevant Gambit provides a parameter -`maxregret`, which specifies the acceptance criterion for labeling the output of the -algorithm as an equilibrium. -This parameter is interpreted *proportionally* to the range of payoffs in the game. -Any profile returned as an equilibrium is guaranteed to be an -:math:`\varepsilon`-equilibrium, for :math:`\varepsilon` no more than `maxregret` -times the difference of the game's maximum and minimum payoffs. - -As an example, consider solving the standard one-card poker game using -:py:func:`.logit_solve`. The range of the payoffs in this game is 4 (from +2 to -2). - -.. ipython:: python - - g = gbt.read_efg("poker.efg") - g.max_payoff, g.min_payoff - -:py:func:`.logit_solve` is a globally-convergent method, in that it computes a -sequence of profiles which is guaranteed to have a subsequence that converges to a -Nash equilibrium. The default value of `maxregret` for this method is set at -:math:`10^{-8}`: - -.. ipython:: python - - result = gbt.nash.logit_solve(g, maxregret=1e-8) - result.equilibria - result.equilibria[0].max_regret() - -The value of :py:meth:`.MixedBehaviorProfile.max_regret` of the computed profile exceeds -:math:`10^{-8}` measured in payoffs of the game. However, when considered relative -to the scale of the game's payoffs, we see it is less than :math:`10^{-8}` of -the payoff range, as requested: - -.. ipython:: python - - result.equilibria[0].max_regret() / (g.max_payoff - g.min_payoff) - - -In general, for globally-convergent methods especially, there is a tradeoff between -precision and running time. Some methods may be slow to converge on some games, and -it may be useful instead to get a more coarse approximation to an equilibrium. -We could instead ask only for an :math:`\varepsilon`-equilibrium with a -(scaled) :math:`\varepsilon` of no more than :math:`10^{-4}`: - -.. ipython:: python - - result = gbt.nash.logit_solve(g, maxregret=1e-4) - result.equilibria[0] - result.equilibria[0].max_regret() - result.equilibria[0].max_regret() / (g.max_payoff - g.min_payoff) - -The convention of expressing `maxregret` scaled by the game's payoffs standardises the -behavior of methods across games. For example, consider solving the poker game instead -using :py:meth:`.liap_solve`. - -.. ipython:: python - - result = gbt.nash.liap_solve(g.mixed_behavior_profile(), maxregret=1.0e-4) - result.equilibria[0] - result.equilibria[0].max_regret() - result.equilibria[0].max_regret() / (g.max_payoff - g.min_payoff) - -If, instead, we double all payoffs, the output of the method is unchanged. - -.. ipython:: python - - for outcome in g.outcomes: - outcome["Alice"] = outcome["Alice"] * 2 - outcome["Bob"] = outcome["Bob"] * 2 - - result = gbt.nash.liap_solve(g.mixed_behavior_profile(), maxregret=1.0e-4) - result.equilibria[0] - result.equilibria[0].max_regret() - result.equilibria[0].max_regret() / (g.max_payoff - g.min_payoff) - - -Generating starting points for algorithms -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Some methods for computation of Nash equilibria take as an initial condition a -:py:class:`.MixedStrategyProfile` or :py:class:`MixedBehaviorProfile` which is used -as a starting point. The equilibria found will depend on which starting point is -selected. To facilitate generating starting points, :py:class:`.Game` provides -methods :py:meth:`.Game.random_strategy_profile` and :py:meth:`.Game.random_behavior_profile`, -to generate profiles which are drawn from the uniform distribution on the product -of simplices. - -As an example, we consider a three-player game from McKelvey and McLennan (1997), -in which each player has two strategies. This game has nine equilibria in total, and -in particular has two totally mixed Nash equilibria, which is the maximum possible number -of regular totally mixed equilbria in games of this size. - -We first consider finding Nash equilibria in this game using :py:func:`.liap_solve`. -If we run this method starting from the centroid (uniform randomization across all -strategies for each player), :py:func:`.liap_solve` finds one of the totally-mixed equilibria. - -.. ipython:: python - - g = gbt.read_nfg("2x2x2.nfg") - gbt.nash.liap_solve(g.mixed_strategy_profile()) - -Which equilibrium is found depends on the starting point. With a different starting point, -we can find, for example, one of the pure-strategy equilibria. - -.. ipython:: python - - gbt.nash.liap_solve(g.mixed_strategy_profile([[.9, .1], [.9, .1], [.9, .1]])) - -To search for more equilibria, we can instead generate strategy profiles at random. - -.. ipython:: python - - gbt.nash.liap_solve(g.random_strategy_profile()) - -Note that methods which take starting points do record the starting points used in the -result object returned. However, the random profiles which are generated will differ -in different runs of a program. To support making the generation of random strategy -profiles reproducible, and for finer-grained control of the generation of these profiles -if desired, :py:meth:`.Game.random_strategy_profile` and :py:meth:`.Game.random_behavior_profile` -optionally take a :py:class:`numpy.random.Generator` object, which is used as the source -of randomness for creating the profile. - -.. ipython:: python - - import numpy as np - gen = np.random.default_rng(seed=1234567890) - p1 = g.random_strategy_profile(gen=gen) - p1 - gen = np.random.default_rng(seed=1234567890) - p2 = g.random_strategy_profile(gen=gen) - p2 - p1 == p2 - -When creating profiles in which probabilities are represented as floating-point numbers, -:py:meth:`.Game.random_strategy_profile` and :py:meth:`.Game.random_behavior_profile` -internally use the Dirichlet distribution for each simplex to generate correctly uniform -sampling over probabilities. However, in some applications generation of random profiles -with probabilities as rational numbers is desired. For example, :py:func:`.simpdiv_solve` -takes such a starting point, because it operates by successively refining a triangulation -over the space of mixed strategy profiles. -:py:meth:`.Game.random_strategy_profile` and :py:meth:`.Game.random_behavior_profile` -both take an optional parameter `denom` which, if specified, generates a profile in which -probabilities are generated uniformly from the grid in each simplex in which all probabilities -have denominator `denom`. - -.. ipython:: python - - gen = np.random.default_rng(seed=1234567890) - g.random_strategy_profile(denom=10, gen=gen) - g.random_strategy_profile(denom=10, gen=gen) - -These can then be used in conjunction with :py:func:`.simpdiv_solve` to search for equilibria -from different starting points. - -.. ipython:: python - - gbt.nash.simpdiv_solve(g.random_strategy_profile(denom=10, gen=gen)) - gbt.nash.simpdiv_solve(g.random_strategy_profile(denom=10, gen=gen)) - gbt.nash.simpdiv_solve(g.random_strategy_profile(denom=10, gen=gen)) - - -Quantal response equilibrium -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -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 - - g = gbt.Game.from_arrays( - [[1.1141, 0], [0, 0.2785]], - [[0, 1.1141], [1.1141, 0]], - title="Ochs (1995) asymmetric matching pennies as transformed in McKelvey-Palfrey (1995)" - ) - 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] - -: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 - - 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. -The results replicate those reported in [McKPal95]_, including the estimated value of lambda, -the QRE profile probabilities, and the log-likelihood. -Because `data` contains the empirical counts of play, and not just frequencies, the resulting -log-likelihood is correct for use in likelihoood-ratio tests. [#f1]_ - -.. ipython:: python - - print(fit.lam) - 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 - those obtained by replicating the calculation. - - -Using external programs to compute Nash equilbria -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Because the problem of finding Nash equilibria can be expressed in various -mathematical formulations (see [McKMcL96]_), it is helpful to make use -of other software packages designed specifically for solving those problems. - -There are currently two integrations offered for using external programs to solve -for equilibria: - -- :py:func:`.enummixed_solve` supports enumeration of equilibria in - two-player games via `lrslib`. [#lrslib]_ -- :py:func:`.enumpoly_solve` supports computation of totally-mixed equilibria - on supports in strategic games via `PHCpack`. [#phcpack]_ - -For both calls, using the external program requires passing the path to the -executable (via the `lrsnash_path` and `phcpack_path` arguments, respectively). - -The user must download and compile or install these programs on their own; these are -not packaged with Gambit. The solver calls do take care of producing the required -input files, and reading the output to convert into Gambit objects for further -processing. - - -.. [#lrslib] http://cgm.cs.mcgill.ca/~avis/C/lrs.html - -.. [#phcpack] https://homepages.math.uic.edu/~jan/PHCpack/phcpack.html - -.. [McKMcL96] McKelvey, Richard D. and McLennan, Andrew M. (1996) Computation of equilibria - in finite games. In Handbook of Computational Economics, Volume 1, - pages 87-142. diff --git a/doc/requirements.txt b/doc/requirements.txt index 5909d77ee..61d16db80 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -4,7 +4,9 @@ scipy==1.16.1 pydata-sphinx-theme==0.16.1 sphinx_design==0.6.1 sphinx-autobuild==2024.10.3 +nbsphinx==0.9.7 ipython==9.4.0 matplotlib==3.10.5 pickleshare==0.7.5 -jupyter==1.1.1 \ No newline at end of file +jupyter==1.1.1 +open_spiel==1.6.1 \ No newline at end of file diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb new file mode 100644 index 000000000..3aa751cc8 --- /dev/null +++ b/doc/tutorials/01_quickstart.ipynb @@ -0,0 +1,487 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "88c376d0", + "metadata": {}, + "source": [ + "# 1) Getting started with Gambit\n", + "\n", + "In this tutorial, we'll demo the basic features of the Gambit library for game theory, using the `PyGambit` Python package.\n", + "\n", + "This includes creating a `Game` object and using it to set up a strategic (normal) form game, the Prisoner's Dilemma, one of the most famous games in game theory.\n", + "\n", + "We'll then use Gambit's built-in functions to analyze the game and find its Nash equilibria.\n", + "\n", + "**The Prisoner's Dilemma**\n", + "\n", + "The Prisoner's Dilemma is a classic example in game theory that illustrates why two rational individuals who cannot communicate might not cooperate, even if it appears that it is in their best interest to do so. After being caught by the police for committing a crime, the two prisoners are separately offered a deal:\n", + "\n", + "- If both stay silent (cooperate), they get light sentences.\n", + "- If one defects (betrays the other) while the other stays silent, the defector goes free and the silent one gets a heavy sentence.\n", + "- If both defect, they both get moderate sentences." + ] + }, + { + "cell_type": "markdown", + "id": "b563d13d", + "metadata": {}, + "source": [ + "## Creating a strategic form game\n", + "\n", + "Let's start by importing PyGambit and creating a game object.\n", + "Since Prisoner's Dilemma is a strategic form game, it can be created in a tabular fashion with `Game.new_table`.\n", + "\n", + "To do this, we need to know the number of players, which in Prisoner's Dilemma is 2, and the number of strategies for each player, which is in both cases is 2 (Cooperate and Defect).\n", + "We'll define a list as long as the number of players, specifying the number of strategies for each player to pass into the `Game.new_table` function." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "2060c1ed", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pygambit.gambit.Game" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pygambit as gbt\n", + "\n", + "n_strategies = [2, 2]\n", + "g = gbt.Game.new_table(n_strategies, title=\"Prisoner's Dilemma\")\n", + "type(g)" + ] + }, + { + "cell_type": "markdown", + "id": "903376dc", + "metadata": {}, + "source": [ + "Now let's name the players and each of their possible strategies, in both cases \"Cooperate\" and \"Defect\".\n", + "\n", + "Note: it's not necessary to specify labels for players and strategies when defining a game, however doing so makes the game easier to understand and work with." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9d8203e8", + "metadata": {}, + "outputs": [], + "source": [ + "g.players[0].label = \"Tom\"\n", + "g.players[0].strategies[0].label = \"Cooperate\"\n", + "g.players[0].strategies[1].label = \"Defect\"\n", + "\n", + "g.players[1].label = \"Jerry\"\n", + "g.players[1].strategies[0].label = \"Cooperate\"\n", + "g.players[1].strategies[1].label = \"Defect\"" + ] + }, + { + "cell_type": "markdown", + "id": "60bfe828", + "metadata": {}, + "source": [ + "Now let's assign payoffs for each of the game's possible outcomes, based on the standard payoffs for the Prisoner's Dilemma:\n", + "- Both players cooperate and receive the lightest sentence: `(-1, -1)`\n", + "- Tom cooperates, but Jerry defects (betrays Tom): `(0, -3)`\n", + "- Tom defects, Jerry cooperates: `(-3, 0)`\n", + "- Both defect: `(-2, -2)`" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "61030607", + "metadata": {}, + "outputs": [], + "source": [ + "# Both cooperate\n", + "g[\"Cooperate\", \"Cooperate\"][\"Tom\"] = -1\n", + "g[\"Cooperate\", \"Cooperate\"][\"Jerry\"] = -1\n", + "\n", + "# Tom cooperates, Jerry defects\n", + "g[\"Cooperate\", \"Defect\"][\"Tom\"] = -3\n", + "g[\"Cooperate\", \"Defect\"][\"Jerry\"] = 0\n", + "\n", + "# Tom defects, Jerry cooperates\n", + "g[\"Defect\", \"Cooperate\"][\"Tom\"] = 0\n", + "g[\"Defect\", \"Cooperate\"][\"Jerry\"] = -3\n", + "\n", + "# Both defect\n", + "g[\"Defect\", \"Defect\"][\"Tom\"] = -2\n", + "g[\"Defect\", \"Defect\"][\"Jerry\"] = -2" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "caecc334", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

Prisoner's Dilemma

\n", + "
CooperateDefect
Cooperate-1,-1-3,0
Defect0,-3-2,-2
\n" + ], + "text/plain": [ + "Game(title='Prisoner's Dilemma')" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# View the payout matrix\n", + "g" + ] + }, + { + "cell_type": "markdown", + "id": "659fc2c5", + "metadata": {}, + "source": [ + "The payout matrix structure shows what in Game Theory is described as the \"strategic form\" (also \"normal form\") representation of a game.\n", + "\n", + "The matrix presents the players' strategies and their expected payoff following their played strategies.\n", + "\n", + "The strategic form assumes players choose their strategies simultaneously, and the outcome depends on the combination." + ] + }, + { + "cell_type": "markdown", + "id": "5e9fe410", + "metadata": {}, + "source": [ + "## Creating games from arrays\n", + "\n", + "The most direct way to create a strategic form game is via `Game.from_arrays()`.\n", + "\n", + "This function takes one n-dimensional array per player, where n is the number of players in the game.\n", + "\n", + "The arrays can be any object that can be indexed like an n-times-nested Python list; so, for example, numpy arrays can be used directly.\n", + "\n", + "To create a two-player symmetric game, we can simply transpose the payoff matrix for the second player before passing to `Game.from_arrays()`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "843ba7f3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

Another Prisoner's Dilemma

\n", + "
12
1-1,-1-3,0
20,-3-2,-2
\n" + ], + "text/plain": [ + "Game(title='Another Prisoner's Dilemma')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "player1_payoffs = np.array([[-1, -3], [0, -2]])\n", + "player2_payoffs = np.transpose(player1_payoffs)\n", + "\n", + "g1 = gbt.Game.from_arrays(\n", + " player1_payoffs,\n", + " player2_payoffs,\n", + " title=\"Another Prisoner's Dilemma\"\n", + ")\n", + "\n", + "g1" + ] + }, + { + "cell_type": "markdown", + "id": "696d83cb", + "metadata": {}, + "source": [ + "You can retrieve the players’ payoff tables from a game object using the `Game.to_arrays()` method, which produces a list of numpy arrays representing the payoffs for each player.\n", + "\n", + "The optional parameter `dtype` controls the data type of the payoffs in the generated arrays." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5ee752c4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-1\n", + "\n" + ] + } + ], + "source": [ + "tom_payoffs, jerry_payoffs = g.to_arrays(\n", + " # dtype=float\n", + ")\n", + "print(tom_payoffs[0][0])\n", + "print(type(tom_payoffs[0][0]))" + ] + }, + { + "cell_type": "markdown", + "id": "f2e6645e", + "metadata": {}, + "source": [ + "Computing the Nash equilibria in one line of code\n", + "-----------------------------\n", + "\n", + "We can use Gambit to compute the Nash equilibria for our Prisoner's Dilemma game in a single line of code; a Nash equilibrium tells us the strategies that players can adopt to maximize their payoffs, given the setup of the game.\n", + "\n", + "For a two-player normal form game, let's use `enumpure_solve` to search for a pure-strategy Nash equilibria.\n", + "The returned object will be a `NashComputationResult`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a81c06c7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pygambit.nash.NashComputationResult" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = gbt.nash.enumpure_solve(g)\n", + "type(result)" + ] + }, + { + "cell_type": "markdown", + "id": "7d8076f8", + "metadata": {}, + "source": [ + "Let's inspect our result further to see how many equilibria were found." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "bd395180", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(result.equilibria)" + ] + }, + { + "cell_type": "markdown", + "id": "5fb009be", + "metadata": {}, + "source": [ + "For a given equilibria, we can then look at the \"mixed strategy profile\", which maps each strategy in a game to the corresponding probability with which that strategy is played." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "76570ebc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[\\left[0,1\\right],\\left[0,1\\right]\\right]$" + ], + "text/plain": [ + "[[Rational(0, 1), Rational(1, 1)], [Rational(0, 1), Rational(1, 1)]]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "msp = result.equilibria[0]\n", + "msp" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6e8cfcde", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pygambit.gambit.MixedStrategyProfileRational" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(msp)" + ] + }, + { + "cell_type": "markdown", + "id": "f937e1ab", + "metadata": {}, + "source": [ + "The mixed strategy profile can show us the expected payoffs for each player when playing the strategies as specified by an equilibrium.\n", + "\n", + "The profile `[[0,1],[0,1]]` indicates that both players' strategy is to play \"Cooperate\" with probability 0 and \"Defect\" with probability 1:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "980bf6b1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tom plays the equilibrium strategy:\n", + "Probability of cooperating: 0\n", + "Probability of defecting: 1\n", + "Payoff: -2\n", + "\n", + "Jerry plays the equilibrium strategy:\n", + "Probability of cooperating: 0\n", + "Probability of defecting: 1\n", + "Payoff: -2\n", + "\n" + ] + } + ], + "source": [ + "for player in g.players:\n", + " print(f\"{player.label} plays the equilibrium strategy:\")\n", + " print(f\"Probability of cooperating: {msp[player.label]['Cooperate']}\")\n", + " print(f\"Probability of defecting: {msp[player.label]['Defect']}\")\n", + " print(f\"Payoff: {msp.payoff(player.label)}\")\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "id": "24f36b0d", + "metadata": {}, + "source": [ + "The equilibrium shows that both players are playing their dominant strategy, which is to defect. This is because defecting is the best response to the other player's strategy, regardless of what that strategy is.\n", + "\n", + "Saving and reading strategic form games to and from file\n", + "--------------------\n", + "\n", + "You can use Gambit to save games to, and read from files.\n", + "The specific format depends on whether the game is normal or extensive form.\n", + "\n", + "Here we'll save the Prisoner's Dilemma (normal form) to the `.nfg` format." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "f58eaa77", + "metadata": {}, + "outputs": [], + "source": [ + "g.to_nfg(\"games/prisoners_dilemma.nfg\")" + ] + }, + { + "cell_type": "markdown", + "id": "e373be1e", + "metadata": {}, + "source": [ + "You can easily restore the game object from file like so:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "4119a2ac", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pygambit.gambit.Game" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "restored_game = gbt.read_nfg(\"games/prisoners_dilemma.nfg\")\n", + "type(restored_game)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "gambitvenv313", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb new file mode 100644 index 000000000..60279286d --- /dev/null +++ b/doc/tutorials/02_extensive_form.ipynb @@ -0,0 +1,336 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "96019084", + "metadata": {}, + "source": [ + "# 2) Extensive form games\n", + "\n", + "In the first tutorial, we used Gambit to set up the Prisoner's Dilemma, an example of a normal (strategic) form game.\n", + "\n", + "Gambit can also be used to set up extensive form games; the game is represented as a tree, where each node represents a decision point for a player, and the branches represent the possible actions they can take.\n", + "\n", + "**Example: One-shot trust game with binary actions**\n", + "\n", + "[Kreps (1990)](#references) introduced a game commonly referred to as the **trust game**.\n", + "We will build a one-shot version of this game using Gambit's game transformation operations.\n", + "\n", + "The game can be defined as follows:\n", + "- There are two players, a **Buyer** and a **Seller**.\n", + "- The Buyer moves first and has two actions, **Trust** or **Not trust**.\n", + "- If the Buyer chooses **Not trust**, then the game ends, and both players receive payoffs of `0`.\n", + "- If the Buyer chooses **Trust**, then the Seller has a choice with two actions, **Honor** or **Abuse**.\n", + "- If the Seller chooses **Honor**, both players receive payoffs of `1`;\n", + "- If the Seller chooses **Abuse**, the Buyer receives a payoff of `-1` and the Seller receives a payoff of `2`.\n", + "\n", + "We create a game with an extensive representation using `Game.new_tree`:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5946289b", + "metadata": {}, + "outputs": [], + "source": [ + "import pygambit as gbt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "91ed4dfb", + "metadata": {}, + "outputs": [], + "source": [ + "g = gbt.Game.new_tree(\n", + " players=[\"Buyer\", \"Seller\"],\n", + " title=\"One-shot trust game, after Kreps (1990)\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "e1903069", + "metadata": {}, + "source": [ + "The tree of the game contains just a root node, with no children:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "3cd94917", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(g.root.children)" + ] + }, + { + "cell_type": "markdown", + "id": "962b4e52", + "metadata": {}, + "source": [ + "To extend a game from an existing terminal node, use `Game.append_move`. To begin with, the sole root node is the terminal node.\n", + "\n", + "Here we extend the game from the root node by adding the first move for the \"Buyer\" player, creating two child nodes (one for each possible action)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5d27a07a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "g.append_move(\n", + " g.root, # This is the node to append the move to\n", + " player=\"Buyer\",\n", + " actions=[\"Trust\", \"Not trust\"]\n", + ")\n", + "len(g.root.children)" + ] + }, + { + "cell_type": "markdown", + "id": "43e28b1e", + "metadata": {}, + "source": [ + "We can also optionally specify labels for nodes when defining a game.\n", + "This isn't strictly necessary, but doing so makes the game easier to understand and work with than referring to nodes by their indices.\n", + "\n", + "Here we'll label the nodes according to the actions that precede them in the game tree." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "65b21e37", + "metadata": {}, + "outputs": [], + "source": [ + "for node in g.root.children:\n", + " node.label = node.prior_action.label" + ] + }, + { + "cell_type": "markdown", + "id": "bba61594", + "metadata": {}, + "source": [ + "We can then also add the Seller's move in the situation after the Buyer chooses Trust:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "47c4a31b", + "metadata": {}, + "outputs": [], + "source": [ + "g.append_move(\n", + " g.root.children[\"Trust\"],\n", + " player=\"Seller\",\n", + " actions=[\"Honor\", \"Abuse\"]\n", + ")\n", + "for node in g.root.children[\"Trust\"].children:\n", + " node.label = node.prior_action.label" + ] + }, + { + "cell_type": "markdown", + "id": "382ba37d", + "metadata": {}, + "source": [ + "Now that we have the moves of the game defined, we add payoffs.\n", + "\n", + "Payoffs are associated with an `Outcome`; each `Outcome` has a vector of payoffs, one for each player, and optionally an identifying text label.\n", + "\n", + "First we add the outcome associated with the Seller proving themselves trustworthy:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "716e9b9a", + "metadata": {}, + "outputs": [], + "source": [ + "g.set_outcome(\n", + " g.root.children[\"Trust\"].children[\"Honor\"],\n", + " outcome=g.add_outcome(\n", + " payoffs=[1, 1],\n", + " label=\"Trustworthy\"\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "df082b10", + "metadata": {}, + "source": [ + "Next, the outcome associated with the scenario where the Buyer trusts but the Seller does not return the trust:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "695b1aad", + "metadata": {}, + "outputs": [], + "source": [ + "g.set_outcome(\n", + " g.root.children[\"Trust\"].children[\"Abuse\"],\n", + " outcome=g.add_outcome(\n", + " payoffs=[-1, 2],\n", + " label=\"Untrustworthy\"\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "48335eb8", + "metadata": {}, + "source": [ + "And, finally the outcome associated with the Buyer opting out of the interaction:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "0704ef86", + "metadata": {}, + "outputs": [], + "source": [ + "g.set_outcome(\n", + " g.root.children[\"Not trust\"],\n", + " g.add_outcome(\n", + " payoffs=[0, 0],\n", + " label=\"Opt-out\"\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "09ef5e2e", + "metadata": {}, + "source": [ + "Nodes without an outcome attached are assumed to have payoffs of zero for all players.\n", + "\n", + "Therefore, adding the outcome to this latter terminal node is not strictly necessary in Gambit, but it is useful to be explicit for readability." + ] + }, + { + "cell_type": "markdown", + "id": "cfc52edc", + "metadata": {}, + "source": [ + "Saving and reading extensive form games to and from file\n", + "--------------------\n", + "\n", + "You can use Gambit to save games to, and read from files.\n", + "The specific format depends on whether the game is normal or extensive form.\n", + "\n", + "Here we'll save the Trust game (extensive form) to the `.efg` format." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "37c51152", + "metadata": {}, + "outputs": [], + "source": [ + "g.to_efg(\"games/trust_game.efg\")" + ] + }, + { + "cell_type": "markdown", + "id": "0eb31525", + "metadata": {}, + "source": [ + "You can easily restore the game object from file like so:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "0d86a750", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pygambit.gambit.Game" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "restored_game = gbt.read_efg(\"games/trust_game.efg\")\n", + "type(restored_game)" + ] + }, + { + "cell_type": "markdown", + "id": "be034836", + "metadata": {}, + "source": [ + "#### References\n", + "\n", + "Kreps, D. (1990) \"Corporate Culture and Economic Theory.\" In J. Alt and K. Shepsle, eds., *Perspectives on Positive Political Economy*, Cambridge University Press." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "gambitvenv313", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/tutorials/03_poker.ipynb b/doc/tutorials/03_poker.ipynb new file mode 100644 index 000000000..5bc30f462 --- /dev/null +++ b/doc/tutorials/03_poker.ipynb @@ -0,0 +1,1635 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "98eb65d8", + "metadata": {}, + "source": [ + "# 3) A one-card poker game with private information\n", + "\n", + "In this tutorial, we'll create an extensive form representation of a one-card poker game [[Mye91](#references)] and use it to demonstrate and explain the following with Gambit:\n", + "\n", + "1. Setting up an extensive form game with imperfect information using [information sets](#information-sets)\n", + "2. [Computing and interpreting Nash equilibria](#computing-and-interpreting-nash-equilibria) and understanding mixed behaviour and mixed strategy profiles\n", + "3. [Acceptance criteria for Nash equilibria](#acceptance-criteria-for-nash-equilibria)\n", + "\n", + "A version of this game also appears in [[RUW08](#references)], as a classroom game under the name \"stripped-down poker\".\n", + "This is perhaps the simplest interesting game with imperfect information.\n", + "\n", + "In our version of the game, there are two players, **Alice** and **Bob**, and a deck of cards, with equal numbers of **King** and **Queen** cards.\n", + "\n", + "- The game begins with each player putting $1 in the pot.\n", + "- A card is dealt at random to Alice\n", + " - Alice observes her card\n", + " - Bob does not observe the card\n", + "- Alice then chooses either to **Raise** or to **Fold**.\n", + " - If she chooses to Fold, Bob wins the pot and the game ends.\n", + " - If she chooses to Raise, she adds another $1 to the pot.\n", + "- Bob then chooses either to **Meet** or **Pass**.\n", + " - If he chooses to Pass, Alice wins the pot and the game ends.\n", + " - If he chooses to Meet, he adds another $1 to the pot.\n", + "- There is then a showdown, in which Alice reveals her card.\n", + " - If she has a King, then she wins the pot;\n", + " - If she has a Queen, then Bob wins the pot." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "69cbfe81", + "metadata": {}, + "outputs": [], + "source": [ + "import pygambit as gbt" + ] + }, + { + "cell_type": "markdown", + "id": "70819881", + "metadata": {}, + "source": [ + "Create the game with two players." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ad6a1119", + "metadata": {}, + "outputs": [], + "source": [ + "g = gbt.Game.new_tree(\n", + " players=[\"Alice\", \"Bob\"], \n", + " title=\"One card poker\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "d9796238", + "metadata": {}, + "source": [ + "In addition to the two named players, Gambit also instantiates a chance player." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "841f9f74", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Player(game=Game(title='One card poker'), label='Alice')\n", + "Player(game=Game(title='One card poker'), label='Bob')\n", + "ChancePlayer(game=Game(title='One card poker'))\n" + ] + } + ], + "source": [ + "print(g.players[\"Alice\"])\n", + "print(g.players[\"Bob\"])\n", + "print(g.players.chance)" + ] + }, + { + "cell_type": "markdown", + "id": "0d4c7f5b", + "metadata": {}, + "source": [ + "Moves belonging to the chance player can be added in the same way as to other players.\n", + "\n", + "At any new move created for the chance player, the action probabilities default to uniform randomization over the actions at the move.\n", + "\n", + "The first step in this game is that Alice is dealt a card which could be a King or Queen, each with probability 1/2.\n", + "\n", + "To simulate this in Gambit, we create a chance player move at the root node of the game.\n", + "\n", + "Note: throughout this tutorial, we'll also label nodes according to the actions that precede them in the game tree to improve code readability." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "fe80c64c", + "metadata": {}, + "outputs": [], + "source": [ + "g.append_move(\n", + " g.root,\n", + " player=g.players.chance,\n", + " actions=[\"King\", \"Queen\"] # By default, chance actions have equal probabilities\n", + ")\n", + "for node in g.root.children: # Add labels to the new child nodes to improve code readability\n", + " node.label = node.prior_action.label" + ] + }, + { + "cell_type": "markdown", + "id": "5cf73f0a", + "metadata": {}, + "source": [ + "## Information sets\n", + "\n", + "In this game, information structure is important.\n", + "Alice knows her card, so the two nodes at which she has the move are part of different **information sets**.\n", + "\n", + "We'll therefore need to append Alice's move separately for each of the root node's children, i.e. the scenarios where she has a King or a Queen.\n", + "Let's now add both of these possible moves." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "0e3bb5ef", + "metadata": {}, + "outputs": [], + "source": [ + "for node in g.root.children:\n", + " g.append_move(\n", + " node,\n", + " player=\"Alice\",\n", + " actions=[\"Raise\", \"Fold\"]\n", + " )\n", + " for child_node in node.children:\n", + " child_node.label = child_node.prior_action.label" + ] + }, + { + "cell_type": "markdown", + "id": "4c8d0343", + "metadata": {}, + "source": [ + "The loop above causes each of the newly-appended moves to be in new information sets, reflecting the fact that Alice's decision depends on the knowledge of which card she holds.\n", + "\n", + "In contrast, Bob does not know Alice’s card, and therefore cannot distinguish between the two nodes at which he has to make his decision:\n", + "\n", + " - Chance player chooses King, then Alice Raises: `g.root.children[\"King\"].children[\"Raise\"]`\n", + " - Chance player chooses Queen, then Alice Raises: `g.root.children[\"Queen\"].children[\"Raise\"]`\n", + "\n", + "In other words, Bob's decision when Alice raises with a Queen should be part of the same information set as Bob's decision when Alice raises with a King.\n", + "\n", + "To set this scenario up in Gambit, we'll need to use `Game.append_infoset` to add a move as part of an existing information set (represented in Gambit as an `Infoset`).\n", + "\n", + "First, let's add Bob's move to the node where Alice has raised with a King." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "dbfa7035", + "metadata": {}, + "outputs": [], + "source": [ + "g.append_move(\n", + " g.root.children[\"King\"].children[\"Raise\"],\n", + " player=\"Bob\",\n", + " actions=[\"Meet\", \"Pass\"]\n", + ")\n", + "for node in g.root.children[\"King\"].children[\"Raise\"].children:\n", + " node.label = node.prior_action.label" + ] + }, + { + "cell_type": "markdown", + "id": "689ce12c", + "metadata": {}, + "source": [ + "Now let's add the information set we created at the node where Alice raised with a King, to the node where Alice raised with a Queen." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "655cdae3", + "metadata": {}, + "outputs": [], + "source": [ + "g.append_infoset(\n", + " g.root.children[\"Queen\"].children[\"Raise\"],\n", + " infoset=g.root.children[\"King\"].children[\"Raise\"].infoset\n", + ")\n", + "for node in g.root.children[\"Queen\"].children[\"Raise\"].children:\n", + " node.label = node.prior_action.label" + ] + }, + { + "cell_type": "markdown", + "id": "c4eeb65f", + "metadata": {}, + "source": [ + "In game theory terms, this creates \"imperfect information\".\n", + "Bob cannot distinguish between these two nodes in the game tree, so he must use the same strategy (same probabilities for Meet vs. Pass) in both situations.\n", + "\n", + "This is crucial in games where players must make decisions without complete knowledge of their opponents' private information.\n", + "\n", + "Let's now set up the four possible payoff outcomes for the game." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "87c988be", + "metadata": {}, + "outputs": [], + "source": [ + "alice_winsbig = g.add_outcome([2, -2], label=\"Alice wins big\")\n", + "alice_wins = g.add_outcome([1, -1], label=\"Alice wins\")\n", + "bob_winsbig = g.add_outcome([-2, 2], label=\"Bob wins big\")\n", + "bob_wins = g.add_outcome([-1, 1], label=\"Bob wins\")" + ] + }, + { + "cell_type": "markdown", + "id": "467a2c39", + "metadata": {}, + "source": [ + "Finally, we should assign an outcome to each of the terminal nodes in the game tree." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "29aa60a0", + "metadata": {}, + "outputs": [], + "source": [ + "# Alice folds, Bob wins small\n", + "g.set_outcome(g.root.children[\"King\"].children[\"Fold\"], bob_wins)\n", + "g.set_outcome(g.root.children[\"Queen\"].children[\"Fold\"], bob_wins)\n", + "\n", + "# Bob sees Alice raise and calls, correctly believing she is bluffing, Bob wins big\n", + "g.set_outcome(g.root.children[\"Queen\"].children[\"Raise\"].children[\"Meet\"], bob_winsbig)\n", + "\n", + "# Bob sees Alice raise and calls, incorrectly believing she is bluffing, Alice wins big\n", + "g.set_outcome(g.root.children[\"King\"].children[\"Raise\"].children[\"Meet\"], alice_winsbig)\n", + "\n", + "# Bob does not call Alice's raise, Alice wins small\n", + "g.set_outcome(g.root.children[\"King\"].children[\"Raise\"].children[\"Pass\"], alice_wins)\n", + "g.set_outcome(g.root.children[\"Queen\"].children[\"Raise\"].children[\"Pass\"], alice_wins)" + ] + }, + { + "cell_type": "markdown", + "id": "17eb6af5", + "metadata": {}, + "source": [ + "## Computing and interpreting Nash equilibria\n", + "\n", + "\n", + "Since our one-card poker game is extensive form and has two players, we can use the `lcp_solve` algorithm in Gambit to compute the Nash equilibria." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "4d92c8d9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "NashComputationResult(method='lcp', rational=True, use_strategic=False, equilibria=[[[[Rational(1, 1), Rational(0, 1)], [Rational(1, 3), Rational(2, 3)]], [[Rational(2, 3), Rational(1, 3)]]]], parameters={'stop_after': 0, 'max_depth': 0})" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = gbt.nash.lcp_solve(g)\n", + "result" + ] + }, + { + "cell_type": "markdown", + "id": "e5946077", + "metadata": {}, + "source": [ + "The result of the calculation is returned as a `NashComputationResult` object.\n", + "\n", + "The set of equilibria found is reported in `NashComputationResult.equilibria`; in this case, this is a list of `MixedBehaviorProfile`'s.\n", + "\n", + "For one-card poker, we expect to find a single equilibrium (one `MixedBehaviorProfile`):" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "9967d6f7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of equilibria found: 1\n" + ] + } + ], + "source": [ + "print(\"Number of equilibria found:\", len(result.equilibria))\n", + "eqm = result.equilibria[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "3293e818", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pygambit.gambit.MixedBehaviorProfileRational" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Note: MixedBehaviorProfileRational is a subclass of MixedBehaviorProfile that uses rational numbers for probabilities.\n", + "type(eqm)" + ] + }, + { + "cell_type": "markdown", + "id": "69f67b5b", + "metadata": {}, + "source": [ + "A mixed behavior profile specifies, for each information set, the probability distribution over actions at that information set.\n", + "\n", + "Indexing a mixed behaviour profile by a player gives a `MixedBehavior`, which specifies probability distributions at each of the player's information sets:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "4cf38264", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pygambit.gambit.MixedBehavior" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(eqm[\"Alice\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "85e7fdda", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[\\left[1,0\\right],\\left[\\frac{1}{3},\\frac{2}{3}\\right]\\right]$" + ], + "text/plain": [ + "[[Rational(1, 1), Rational(0, 1)], [Rational(1, 3), Rational(2, 3)]]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eqm[\"Alice\"]" + ] + }, + { + "cell_type": "markdown", + "id": "6615115d", + "metadata": {}, + "source": [ + "In this case, at Alice's first information set, the one at which she has the King, she always raises.\n", + "\n", + "At her second information set, where she has the Queen, she sometimes bluffs, raising with probability one-third.\n", + "\n", + "The probability distribution at an information set is represented by a `MixedAction`.\n", + "\n", + "`MixedBehavior.mixed_actions` iterates over these for the player:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "f45a82b6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "At information set 0, Alice plays Raise with probability: 1 and Fold with probability: 0\n", + "At information set 1, Alice plays Raise with probability: 1/3 and Fold with probability: 2/3\n" + ] + } + ], + "source": [ + "for infoset, mixed_action in eqm[\"Alice\"].mixed_actions():\n", + " print(\n", + " f\"At information set {infoset.number}, \"\n", + " f\"Alice plays Raise with probability: {mixed_action['Raise']}\"\n", + " f\" and Fold with probability: {mixed_action['Fold']}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "9eeae046", + "metadata": {}, + "source": [ + "We can alternatively iterate through each of a player's actions like so:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "83bbd3e5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "At information set 0, Alice plays Raise with probability: 1\n", + "At information set 0, Alice plays Fold with probability: 0\n", + "At information set 1, Alice plays Raise with probability: 1/3\n", + "At information set 1, Alice plays Fold with probability: 2/3\n" + ] + } + ], + "source": [ + "for action in g.players[\"Alice\"].actions:\n", + " print(\n", + " f\"At information set {action.infoset.number}, \"\n", + " f\"Alice plays {action.label} with probability: {eqm[action]}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "1f121d48", + "metadata": {}, + "source": [ + "Now let's look at Bob’s strategy:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "6bf51b38", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[\\left[\\frac{2}{3},\\frac{1}{3}\\right]\\right]$" + ], + "text/plain": [ + "[[Rational(2, 3), Rational(1, 3)]]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eqm[\"Bob\"]" + ] + }, + { + "cell_type": "markdown", + "id": "e906c4c4", + "metadata": {}, + "source": [ + "Bob meets Alice’s raise two-thirds of the time.\n", + "The label “Raise” is used in more than one information set for Alice, so in the above we had to specify information sets when indexing.\n", + "\n", + "When there is no ambiguity, we can specify action labels directly.\n", + "So for example, because Bob has only one action named “Meet” in the game, we can extract the probability that Bob plays “Meet” by:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "2966e700", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\frac{2}{3}$" + ], + "text/plain": [ + "Rational(2, 3)" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eqm[\"Bob\"][\"Meet\"]" + ] + }, + { + "cell_type": "markdown", + "id": "2ec69f8c", + "metadata": {}, + "source": [ + "Moreover, this is the only action with that label in the game, so we can index the profile directly using the action label without any ambiguity:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "f5a7f110", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\frac{2}{3}$" + ], + "text/plain": [ + "Rational(2, 3)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eqm[\"Meet\"]" + ] + }, + { + "cell_type": "markdown", + "id": "db19411b", + "metadata": {}, + "source": [ + "Because this is an equilibrium, Bob is indifferent between the two actions at his information set, meaning he has no reason to prefer one action over the other, given Alice's expected strategy.\n", + "\n", + "`MixedBehaviorProfile.action_value` returns the expected payoff of taking an action, conditional on reaching that action's information set:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "a7d3816d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "When Bob plays Meet he can expect the payoff: -1\n", + "When Bob plays Pass he can expect the payoff: -1\n" + ] + } + ], + "source": [ + "# Remember that Bob has a single information set\n", + "for action in g.players[\"Bob\"].infosets[0].actions:\n", + " print(\n", + " f\"When Bob plays {action.label} he can expect the payoff: {eqm.action_value(action)}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "6491fdda", + "metadata": {}, + "source": [ + "Bob's indifference between his actions arises because of his beliefs given Alice's strategy.\n", + "\n", + "`MixedBehaviorProfile.belief` returns the probability of reaching a node, conditional on its information set being reached.\n", + "\n", + "Recall that the two nodes in Bob's only information set are `g.root.children[\"King\"].children[\"Raise\"]` and `g.root.children[\"Queen\"].children[\"Raise\"]`):" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "4a54b20c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Bob's belief in reaching the King -> Raise node is: 3/4\n", + "Bob's belief in reaching the Queen -> Raise node is: 1/4\n" + ] + } + ], + "source": [ + "for node in g.players[\"Bob\"].infosets[0].members:\n", + " print(\n", + " f\"Bob's belief in reaching the {node.parent.label} -> {node.label} node is: {eqm.belief(node)}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "351bb3ce", + "metadata": {}, + "source": [ + "Bob believes that, conditional on Alice raising, there's a 3/4 chance that she has the King; therefore, the expected payoff to meeting is in fact -1 as computed.\n", + "\n", + "`MixedBehaviorProfile.infoset_prob` returns the probability that an information set is reached:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "b250c1cd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\frac{2}{3}$" + ], + "text/plain": [ + "Rational(2, 3)" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eqm.infoset_prob(g.players[\"Bob\"].infosets[0])" + ] + }, + { + "cell_type": "markdown", + "id": "9216ea34", + "metadata": {}, + "source": [ + "The corresponding probability that a node is reached in the play of the game is given by `MixedBehaviorProfile.realiz_prob`, and the expected payoff to a player conditional on reaching a node is given by `MixedBehaviorProfile.node_value`." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "6f01846b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The probability that the node King -> Raise is reached is: 1/2. Bob's expected payoff conditional on reaching this node is: -5/3\n", + "The probability that the node Queen -> Raise is reached is: 1/6. Bob's expected payoff conditional on reaching this node is: 1\n" + ] + } + ], + "source": [ + "for node in g.players[\"Bob\"].infosets[0].members:\n", + " print(\n", + " f\"The probability that the node {node.parent.label} -> {node.label} is reached is: {eqm.realiz_prob(node)}. \",\n", + " f\"Bob's expected payoff conditional on reaching this node is: {eqm.node_value(\"Bob\", node)}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "5ba0c241", + "metadata": {}, + "source": [ + "The overall expected payoff to a player given the behavior profile is returned by `MixedBehaviorProfile.payoff`:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "5079d231", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\frac{1}{3}$" + ], + "text/plain": [ + "Rational(1, 3)" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eqm.payoff(\"Alice\")" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "c55f2c7a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\frac{-1}{3}$" + ], + "text/plain": [ + "Rational(-1, 3)" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eqm.payoff(\"Bob\")" + ] + }, + { + "cell_type": "markdown", + "id": "26d5e8ff", + "metadata": {}, + "source": [ + "The equilibrium computed expresses probabilities in rational numbers.\n", + "\n", + "Because the numerical data of games in Gambit [are represented exactly](#representation-of-numerical-data-of-a-game), methods which are specialized to two-player games, `lp_solve`, `lcp_solve`, and `enummixed_solve`, can report exact probabilities for equilibrium strategy profiles.\n", + "\n", + "This is enabled by default for these methods.\n", + "\n", + "When a game has an extensive representation, equilibrium finding methods default to computing on that representation.\n", + "It is also possible to compute using the strategic representation.\n", + "`pygambit` transparently computes the reduced strategic form representation of an extensive game." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "d4ecff88", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['11', '12', '21', '22']" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[s.label for s in g.players[\"Alice\"].strategies]" + ] + }, + { + "cell_type": "markdown", + "id": "a9bf9b73", + "metadata": {}, + "source": [ + "In the strategic form of this game, Alice has four strategies.\n", + "\n", + "The generated strategy labels list the action numbers taken at each information set.\n", + "For example, label '11' refers to the strategy gets dealt the King, then raises.\n", + "\n", + "We can therefore apply a method which operates on a strategic game to any game with an extensive representation." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "24e4b6e8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "NashComputationResult(method='gnm', rational=False, use_strategic=True, equilibria=[[[0.33333333333866677, 0.6666666666613335, 0.0, 0.0], [0.6666666666559997, 0.3333333333440004]]], parameters={'perturbation': [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0]], 'end_lambda': -10.0, 'steps': 100, 'local_newton_interval': 3, 'local_newton_maxits': 10})" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gnm_result = gbt.nash.gnm_solve(g)\n", + "gnm_result" + ] + }, + { + "cell_type": "markdown", + "id": "d88b736b", + "metadata": {}, + "source": [ + "`gnm_solve` can be applied to any game with any number of players, and uses a path-following process in floating-point arithmetic, so it returns profiles with probabilities expressed as floating-point numbers.\n", + "\n", + "This method operates on the strategic representation of the game, so the returned results are of type `MixedStrategyProfile` (specifically `MixedStrategyProfileDouble`)." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "d9ffb4b8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pygambit.gambit.MixedStrategyProfileDouble" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gnm_eqm = gnm_result.equilibria[0]\n", + "type(gnm_eqm)" + ] + }, + { + "cell_type": "markdown", + "id": "102d22c2", + "metadata": {}, + "source": [ + "Indexing a `MixedStrategyProfile` by a player gives the probability distribution over that player's strategies only.\n", + "\n", + "The expected payoff to a strategy is provided by `MixedStrategyProfile.strategy_value` and the overall expected payoff to a player is returned by `MixedStrategyProfile.payoff`:" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "56e2f847", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Alice's expected payoffs playing:\n", + "Strategy 11: 0.3333\n", + "Strategy 12: 0.3333\n", + "Strategy 21: -1.0000\n", + "Strategy 22: -1.0000\n", + "Alice's overall expected payoff: 0.3333\n", + "\n", + "Bob's expected payoffs playing:\n", + "Strategy 1: -0.3333\n", + "Strategy 2: -0.3333\n", + "Bob's overall expected payoff: -0.3333\n", + "\n" + ] + } + ], + "source": [ + "for player in g.players:\n", + " print(\n", + " f\"{player.label}'s expected payoffs playing:\"\n", + " )\n", + " for strategy in player.strategies:\n", + " print(\n", + " f\"Strategy {strategy.label}: {gnm_eqm.strategy_value(strategy):.4f}\"\n", + " )\n", + " print(\n", + " f\"{player.label}'s overall expected payoff: {gnm_eqm.payoff(player):.4f}\"\n", + " )\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "id": "874be231", + "metadata": {}, + "source": [ + "When a game has an extensive representation, we can convert freely between a mixed strategy profile and the corresponding mixed behaviour profile representation of the same strategies using `MixedStrategyProfile.as_behavior` and `MixedBehaviorProfile.as_strategy`.\n", + "\n", + "- A mixed **strategy** profile maps each strategy in a game to the corresponding probability with which that strategy is played.\n", + "- A mixed **behaviour** profile maps each action at each information set in a game to the corresponding probability with which the action is played, conditional on that information set being reached.\n", + "\n", + "Let's convert the equilibrium we found using `gnm_solve` to a mixed behaviour profile and iterate through the players actions to show their expected payoffs, comparing as we go with the payoffs found by `lcp_solve`:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "d18a91f0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Alice's expected payoffs:\n", + "At information set 0, when playing Raise - gnm: 1.6667, lcp: 1.6667\n", + "At information set 0, when playing Fold - gnm: -1.0000, lcp: -1.0000\n", + "At information set 1, when playing Raise - gnm: -1.0000, lcp: -1.0000\n", + "At information set 1, when playing Fold - gnm: -1.0000, lcp: -1.0000\n", + "\n", + "Bob's expected payoffs:\n", + "At information set 0, when playing Meet - gnm: -1.0000, lcp: -1.0000\n", + "At information set 0, when playing Pass - gnm: -1.0000, lcp: -1.0000\n", + "\n" + ] + } + ], + "source": [ + "for player in g.players:\n", + " print(\n", + " f\"{player.label}'s expected payoffs:\"\n", + " )\n", + " for action in player.actions:\n", + " print(\n", + " f\"At information set {action.infoset.number}, \"\n", + " f\"when playing {action.label} - \"\n", + " f\"gnm: {gnm_eqm.as_behavior().action_value(action):.4f}\"\n", + " f\", lcp: {eqm.action_value(action):.4f}\"\n", + " )\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "id": "b2867dca", + "metadata": {}, + "source": [ + "Acceptance criteria for Nash equilibria\n", + "---------------------------------------\n", + "\n", + "Some methods for computing Nash equilibria operate using floating-point arithmetic and/or generate candidate equilibrium profiles using methods which involve some form of successive approximations.\n", + "The outputs of these methods therefore are in general $\\varepsilon$-equilibria, for some positive $\\varepsilon$.\n", + "\n", + "$\\varepsilon$-equilibria (from [Wikipedia](https://en.wikipedia.org/wiki/Epsilon-equilibrium)):\n", + "\n", + "> In game theory, an epsilon-equilibrium, or near-Nash equilibrium, is a strategy profile that approximately satisfies the condition of Nash equilibrium. In a Nash equilibrium, no player has an incentive to change his behavior. In an approximate Nash equilibrium, this requirement is weakened to allow the possibility that a player may have a small incentive to do something different.\n", + "\n", + "> Given a game and a real non-negative parameter $\\varepsilon$, a strategy profile is said to be an $\\varepsilon$-equilibrium if it is not possible for any player to gain more than $\\varepsilon$ in expected payoff by unilaterally deviating from his strategy. Every Nash Equilibrium is equivalent to an $\\varepsilon$-equilibrium where $\\varepsilon = 0$.\n", + "\n", + "\n", + "To provide a uniform interface across methods, where relevant Gambit provides a parameter\n", + "`maxregret`, which specifies the acceptance criterion for labeling the output of the\n", + "algorithm as an equilibrium.\n", + "This parameter is interpreted *proportionally* to the range of payoffs in the game.\n", + "Any profile returned as an equilibrium is guaranteed to be an $\\varepsilon$-equilibrium, for $\\varepsilon$ no more than `maxregret`\n", + "times the difference of the game's maximum and minimum payoffs.\n", + "\n", + "As an example, consider solving our one-card poker game using `logit_solve`. The range of the payoffs in this game is 4 (from +2 to -2).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "0c55f745", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(Rational(2, 1), Rational(-2, 1))" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "g.max_payoff, g.min_payoff" + ] + }, + { + "cell_type": "markdown", + "id": "6263ad6e", + "metadata": {}, + "source": [ + "`logit_solve` is a globally-convergent method, in that it computes a sequence of profiles which is guaranteed to have a subsequence that converges to a\n", + "Nash equilibrium.\n", + "\n", + "The default value of `maxregret` for this method is set at $10^{-8}$:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "101598c6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "logit_solve_result = gbt.nash.logit_solve(g, maxregret=1e-8)\n", + "len(logit_solve_result.equilibria)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "9b142728", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3.987411578698641e-08" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ls_eqm = logit_solve_result.equilibria[0]\n", + "ls_eqm.max_regret()" + ] + }, + { + "cell_type": "markdown", + "id": "a2ba06c4", + "metadata": {}, + "source": [ + "The value of `MixedBehaviorProfile.max_regret` of the computed profile exceeds $10^{-8}$ measured in payoffs of the game.\n", + "However, when considered relative to the scale of the game's payoffs, we see it is less than $10^{-8}$ of the payoff range, as requested:" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "ff405409", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "9.968528946746602e-09" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ls_eqm.max_regret() / (g.max_payoff - g.min_payoff)" + ] + }, + { + "cell_type": "markdown", + "id": "54635455", + "metadata": {}, + "source": [ + "In general, for globally-convergent methods especially, there is a tradeoff between precision and running time.\n", + "\n", + "We could instead ask only for an $\\varepsilon$-equilibrium with a (scaled) $\\varepsilon$ of no more than $10^{-4}$:" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "31b0143c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "9.395259956013202e-05" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.nash.logit_solve(g, maxregret=1e-4).equilibria[0].max_regret() / (g.max_payoff - g.min_payoff)" + ] + }, + { + "cell_type": "markdown", + "id": "dc8c8509", + "metadata": {}, + "source": [ + "The tradeoff comes from some methods being slow to converge on some games, making it useful instead to get a more coarse approximation to an equilibrium (higher `maxregret` value) which is faster to calculate. " + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "7cfba34a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 10.5 ms, sys: 269 μs, total: 10.7 ms\n", + "Wall time: 10.7 ms\n" + ] + }, + { + "data": { + "text/plain": [ + "NashComputationResult(method='logit', rational=False, use_strategic=False, equilibria=[[[[1.0, 0.0], [0.3338351656285655, 0.666164834417892]], [[0.6670407651644307, 0.3329592348608147]]]], parameters={'first_step': 0.03, 'max_accel': 1.1})" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "gbt.nash.logit_solve(g, maxregret=1e-4)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "6f1809a7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 20.1 ms, sys: 610 μs, total: 20.7 ms\n", + "Wall time: 21.5 ms\n" + ] + }, + { + "data": { + "text/plain": [ + "NashComputationResult(method='logit', rational=False, use_strategic=False, equilibria=[[[[1.0, 0.0], [0.33333338649882943, 0.6666666135011706]], [[0.6666667065407631, 0.3333332934592369]]]], parameters={'first_step': 0.03, 'max_accel': 1.1})" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "gbt.nash.logit_solve(g, maxregret=1e-8)" + ] + }, + { + "cell_type": "markdown", + "id": "76461069", + "metadata": {}, + "source": [ + "The convention of expressing `maxregret` scaled by the game's payoffs standardises the behavior of methods across games.\n", + "\n", + "For example, consider solving the poker game instead using `liap_solve()`." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "414b6f65", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5.509533871672634e-05" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.nash.liap_solve(g.mixed_behavior_profile(), maxregret=1.0e-4).equilibria[0].max_regret() / (g.max_payoff - g.min_payoff)" + ] + }, + { + "cell_type": "markdown", + "id": "c6853432", + "metadata": {}, + "source": [ + "If, instead, we double all payoffs, the output of the method is unchanged." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "a892dc2b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5.509533871672634e-05" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "for outcome in g.outcomes:\n", + " outcome[\"Alice\"] = outcome[\"Alice\"] * 2\n", + " outcome[\"Bob\"] = outcome[\"Bob\"] * 2\n", + "\n", + "gbt.nash.liap_solve(g.mixed_behavior_profile(), maxregret=1.0e-4).equilibria[0].max_regret() / (g.max_payoff - g.min_payoff)" + ] + }, + { + "cell_type": "markdown", + "id": "5f1f66e0", + "metadata": {}, + "source": [ + "## Representation of numerical data of a game\n", + "\n", + "Payoffs to players and probabilities of actions at chance information sets are specified as numbers.\n", + "Gambit represents the numerical values in a game in exact precision, using either decimal or rational representations.\n", + "\n", + "To illustrate, consider a trivial game which just has one move for the chance player:" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "2f79695a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Rational(1, 3), Rational(1, 3), Rational(1, 3)]" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "small_game = gbt.Game.new_tree()\n", + "small_game.append_move(small_game.root, small_game.players.chance, [\"a\", \"b\", \"c\"])\n", + "[act.prob for act in small_game.root.infoset.actions]" + ] + }, + { + "cell_type": "markdown", + "id": "dc4522b5", + "metadata": {}, + "source": [ + "The default when creating a new move for chance is that all actions are chosen with equal probability.\n", + "These probabilities are represented as rational numbers, using `pygambit`'s `Rational` class, which is derived from Python's `fractions.Fraction`.\n", + "\n", + "Numerical data can be set as rational numbers. Here we update the chance action probabilities with `Rational` numbers:" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "5de6acb2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Rational(1, 4), Rational(1, 2), Rational(1, 4)]" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "small_game.set_chance_probs(\n", + " small_game.root.infoset,\n", + " [gbt.Rational(1, 4), gbt.Rational(1, 2), gbt.Rational(1, 4)]\n", + ")\n", + "[act.prob for act in small_game.root.infoset.actions]" + ] + }, + { + "cell_type": "markdown", + "id": "23263b21", + "metadata": {}, + "source": [ + "Numerical data can also be explicitly specified as decimal numbers:" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "c47d2ab6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Decimal('0.25'), Decimal('0.50'), Decimal('0.25')]" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "small_game.set_chance_probs(\n", + " small_game.root.infoset,\n", + " [gbt.Decimal(\".25\"), gbt.Decimal(\".50\"), gbt.Decimal(\".25\")]\n", + ")\n", + "[act.prob for act in small_game.root.infoset.actions]" + ] + }, + { + "cell_type": "markdown", + "id": "bffda303", + "metadata": {}, + "source": [ + "Although the two representations above are mathematically equivalent, `pygambit` remembers the format in which the values were specified.\n", + "\n", + "Expressing rational or decimal numbers as above is verbose and tedious.\n", + "`pygambit` offers a more concise way to express numerical data in games: when setting numerical game data, `pygambit` will attempt to convert text strings to their rational or decimal representation.\n", + "The above can therefore be written more compactly using string representations:" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "04329084", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Rational(1, 4), Rational(1, 2), Rational(1, 4)]" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "small_game.set_chance_probs(small_game.root.infoset, [\"1/4\", \"1/2\", \"1/4\"])\n", + "[act.prob for act in small_game.root.infoset.actions]" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "9015e129", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Decimal('0.25'), Decimal('0.50'), Decimal('0.25')]" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "small_game.set_chance_probs(small_game.root.infoset, [\".25\", \".50\", \".25\"])\n", + "[act.prob for act in small_game.root.infoset.actions]" + ] + }, + { + "cell_type": "markdown", + "id": "9f22d40d", + "metadata": {}, + "source": [ + "As a further convenience, `pygambit` will accept Python `int` and `float` values.\n", + "`int` values are always interpreted as `Rational` values.\n", + "\n", + "`pygambit` attempts to render `float` values in an appropriate `Decimal` equivalent.\n", + "In the majority of cases, this creates no problems.\n", + "For example," + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "0a019aa5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Decimal('0.25'), Decimal('0.5'), Decimal('0.25')]" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "small_game.set_chance_probs(small_game.root.infoset, [.25, .50, .25])\n", + "[act.prob for act in small_game.root.infoset.actions]" + ] + }, + { + "cell_type": "markdown", + "id": "d53adcd4", + "metadata": {}, + "source": [ + "However, rounding can cause difficulties when attempting to use `float` values to represent values which do not have an exact decimal representation" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "1991d288", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ValueError: set_chance_probs(): must specify non-negative probabilities that sum to one\n" + ] + } + ], + "source": [ + "try:\n", + " small_game.set_chance_probs(small_game.root.infoset, [1/3, 1/3, 1/3])\n", + "except ValueError as e:\n", + " print(\"ValueError:\", e)\n" + ] + }, + { + "cell_type": "markdown", + "id": "89fefd34", + "metadata": {}, + "source": [ + "This behavior can be slightly surprising, especially in light of the fact that\n", + "in Python," + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "b1dc37fd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.0" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1/3 + 1/3 + 1/3" + ] + }, + { + "cell_type": "markdown", + "id": "a06699af", + "metadata": {}, + "source": [ + "In checking whether these probabilities sum to one, `pygambit` first converts each of the probabilities to a `Decimal` representation, via the following method" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "dc1edea2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Decimal('0.3333333333333333')" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.Decimal(str(1/3))" + ] + }, + { + "cell_type": "markdown", + "id": "4bfff415", + "metadata": {}, + "source": [ + "and the sum-to-one check then fails because" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "1edd90d6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Decimal('0.9999999999999999')" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.Decimal(str(1/3)) + gbt.Decimal(str(1/3)) + gbt.Decimal(str(1/3))" + ] + }, + { + "cell_type": "markdown", + "id": "5208b7a4", + "metadata": {}, + "source": [ + "Setting payoffs for players also follows the same rules.\n", + "Representing probabilities and payoffs exactly is essential, because `pygambit` offers (in particular for two-player games) the possibility of computation of equilibria exactly, because the Nash equilibria of any two-player game with rational payoffs and chance probabilities can be expressed exactly in terms of rational numbers.\n", + "\n", + "It is therefore advisable always to specify the numerical data of games either in terms of `Decimal` or `Rational` values, or their string equivalents.\n", + "It is safe to use `int` values, but `float` values should be used with some care to ensure the values are recorded as intended." + ] + }, + { + "cell_type": "markdown", + "id": "65def67e", + "metadata": {}, + "source": [ + "#### References\n", + "\n", + "Myerson, Roger B. (1991) *Game Theory: Analysis of Conflict*. Cambridge: Harvard University Press.\n", + "\n", + "Reiley, David H., Michael B. Urbancic and Mark Walker. (2008) \"Stripped-down poker: A classroom game with signaling and bluffing.\" *The Journal of Economic Education* 39(4): 323-341." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "gambitvenv313", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/tutorials/04_starting_points.ipynb b/doc/tutorials/04_starting_points.ipynb new file mode 100644 index 000000000..c3555943c --- /dev/null +++ b/doc/tutorials/04_starting_points.ipynb @@ -0,0 +1,449 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6818538c", + "metadata": {}, + "source": [ + "# 4) Generating starting points for algorithms\n", + "\n", + "In the previous tutorial, we demonstrated how to calculate the Nash equilibria of a game set up using Gambit and interpret the `MixedStrategyProfile` or `MixedBehaviorProfile` objects returned by the solver.\n", + "In this tutorial, we will demonstrate how to use a `MixedStrategyProfile` or `MixedBehaviorProfile` as an initial condition, a starting point, for some methods of computing Nash equilibria.\n", + "The equilibria found will depend on which starting point is selected.\n", + "\n", + "To facilitate generating starting points, Gambit's `Game` class provides the methods `random_strategy_profile` and `random_behavior_profile`, to generate profiles which are drawn from the uniform distribution on the product of simplices. In other words, the profiles are sampled from a uniform distribution so that each possible mixed strategy profile (or mixed behaviour profile) is equally likely to be selected.\n", + "\n", + "As an example, we consider a three-player game from McKelvey and McLennan (1997), in which each player has two strategies.\n", + "This game has nine equilibria in total, and in particular has two totally mixed Nash equilibria, which is the maximum possible number of regular totally mixed equilbria in games of this size.\n", + "\n", + "Pure and mixed strategies:\n", + "\n", + "- **Pure strategy**: A player chooses the action with probability 1 (always picks the same move)\n", + "- **Mixed strategy**: A player assigns probabilities to their available actions (some actions may have probability 0)\n", + "- **Totally mixed strategy**: Mixed strategy where every available action is chosen with strictly positive probability (no action has probability 0)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "493cafb8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

2x2x2 Example from McKelvey-McLennan, with 9 Nash equilibria, 2 totally mixed

\n", + "
Subtable with strategies:
Player 3 Strategy 1
12
19,8,120,0,0
20,0,09,8,2
Subtable with strategies:
Player 3 Strategy 2
12
10,0,03,4,6
23,4,60,0,0
\n" + ], + "text/plain": [ + "Game(title='2x2x2 Example from McKelvey-McLennan, with 9 Nash equilibria, 2 totally mixed')" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pygambit as gbt\n", + "g = gbt.read_nfg(\"../2x2x2.nfg\")\n", + "g" + ] + }, + { + "cell_type": "markdown", + "id": "1e68a5bd", + "metadata": {}, + "source": [ + "We first consider finding Nash equilibria in this game using `liap_solve`.\n", + "If we run this method starting from the centroid (uniform randomization across all strategies for each player), `liap_solve` finds one of the totally-mixed equilibria. Without providing a list to `Game.mixed_strategy_profile`, the method will return the centroid mixed strategy profile." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b32adf22", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[[0.5, 0.5],[0.5, 0.5],[0.5, 0.5]\\right]$" + ], + "text/plain": [ + "[[0.5, 0.5], [0.5, 0.5], [0.5, 0.5]]" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "centroid_start = g.mixed_strategy_profile()\n", + "centroid_start" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c0b62502", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[[0.3999999026224355, 0.6000000973775644],[0.49999981670851457, 0.5000001832914854],[0.3333329684317666, 0.6666670315682334]\\right]$" + ], + "text/plain": [ + "[[0.3999999026224355, 0.6000000973775644], [0.49999981670851457, 0.5000001832914854], [0.3333329684317666, 0.6666670315682334]]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.nash.liap_solve(centroid_start).equilibria[0]" + ] + }, + { + "cell_type": "markdown", + "id": "df507eda", + "metadata": {}, + "source": [ + "As you can see, in this totally mixed strategy equilibrium, no action has probability 0.\n", + "\n", + "Which equilibrium is found depends on the starting point.\n", + "With a different starting point, we can find, for example, one of the pure-strategy equilibria." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cf22064e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[[0.9, 0.1],[0.9, 0.1],[0.9, 0.1]\\right]$" + ], + "text/plain": [ + "[[0.9, 0.1], [0.9, 0.1], [0.9, 0.1]]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "new_start = g.mixed_strategy_profile([[.9, .1], [.9, .1], [.9, .1]])\n", + "new_start" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "08a22505", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[[1.0, 0.0],[0.9999999944750116, 5.524988446860122e-09],[0.9999999991845827, 8.154173380971617e-10]\\right]$" + ], + "text/plain": [ + "[[1.0, 0.0], [0.9999999944750116, 5.524988446860122e-09], [0.9999999991845827, 8.154173380971617e-10]]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.nash.liap_solve(new_start).equilibria[0]" + ] + }, + { + "cell_type": "markdown", + "id": "3977088f", + "metadata": {}, + "source": [ + "To search for more equilibria, we can instead generate strategy profiles at random." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cfbc2714", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[[0.7187961367413075, 0.2812038632586925],[0.1291105793795489, 0.8708894206204512],[0.12367227612277114, 0.876327723877229]\\right]$" + ], + "text/plain": [ + "[[0.7187961367413075, 0.2812038632586925], [0.1291105793795489, 0.8708894206204512], [0.12367227612277114, 0.876327723877229]]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "random_start = g.random_strategy_profile()\n", + "random_start" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "eb53062a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[[0.5000003932357804, 0.4999996067642197],[0.3999998501612186, 0.6000001498387814],[0.2500001518113522, 0.7499998481886477]\\right]$" + ], + "text/plain": [ + "[[0.5000003932357804, 0.4999996067642197], [0.3999998501612186, 0.6000001498387814], [0.2500001518113522, 0.7499998481886477]]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.nash.liap_solve(random_start).equilibria[0]" + ] + }, + { + "cell_type": "markdown", + "id": "185c6abb", + "metadata": {}, + "source": [ + "Note that methods which take starting points do record the starting points used in the result object returned.\n", + "However, the random profiles which are generated will differ in different runs of a program.\n", + "\n", + "To support making the generation of random strategy profiles reproducible, and for finer-grained control of the generation of these profiles if desired, `Game.random_strategy_profile` and `Game.random_behavior_profile` optionally take a `numpy.random.Generator` object, which is used as the source of randomness for creating the profile." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "4293343a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "gen = np.random.default_rng(seed=1234567890)\n", + "p1 = g.random_strategy_profile(gen=gen)\n", + "gen = np.random.default_rng(seed=1234567890)\n", + "p2 = g.random_strategy_profile(gen=gen)\n", + "p1 == p2" + ] + }, + { + "cell_type": "markdown", + "id": "a98e0b66", + "metadata": {}, + "source": [ + "When creating profiles in which probabilities are represented as floating-point numbers, `Game.random_strategy_profile` and `Game.random_behavior_profile` internally use the Dirichlet distribution for each simplex to generate correctly uniform sampling over probabilities.\n", + "However, in some applications generation of random profiles with probabilities as rational numbers is desired.\n", + "\n", + "For example, `simpdiv_solve` takes such a starting point, because it operates by successively refining a triangulation over the space of mixed strategy profiles.\n", + "`Game.random_strategy_profile` and `Game.random_behavior_profile` both take an optional parameter `denom` which, if specified, generates a profile in which probabilities are generated uniformly from the grid in each simplex in which all probabilities have denominator `denom`.\n", + "\n", + "These can then be used in conjunction with `simpdiv_solve` to search for equilibria from different starting points." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "e9716ae0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[\\left[\\frac{1}{2},\\frac{1}{2}\\right],\\left[\\frac{7}{10},\\frac{3}{10}\\right],\\left[0,1\\right]\\right]$" + ], + "text/plain": [ + "[[Rational(1, 2), Rational(1, 2)], [Rational(7, 10), Rational(3, 10)], [Rational(0, 1), Rational(1, 1)]]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gen = np.random.default_rng(seed=1234567890)\n", + "rsp = g.random_strategy_profile(denom=10, gen=gen)\n", + "rsp" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "c153918a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[\\left[1,0\\right],\\left[1,0\\right],\\left[1,0\\right]\\right]$" + ], + "text/plain": [ + "[[Rational(1, 1), Rational(0, 1)], [Rational(1, 1), Rational(0, 1)], [Rational(1, 1), Rational(0, 1)]]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.nash.simpdiv_solve(rsp).equilibria[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "70a57b26", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[\\left[\\frac{1}{10},\\frac{9}{10}\\right],\\left[\\frac{3}{5},\\frac{2}{5}\\right],\\left[\\frac{3}{5},\\frac{2}{5}\\right]\\right]$" + ], + "text/plain": [ + "[[Rational(1, 10), Rational(9, 10)], [Rational(3, 5), Rational(2, 5)], [Rational(3, 5), Rational(2, 5)]]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rsp1 = g.random_strategy_profile(denom=10, gen=gen)\n", + "rsp1" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "11995836", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[\\left[0,1\\right],\\left[0,1\\right],\\left[1,0\\right]\\right]$" + ], + "text/plain": [ + "[[Rational(0, 1), Rational(1, 1)], [Rational(0, 1), Rational(1, 1)], [Rational(1, 1), Rational(0, 1)]]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.nash.simpdiv_solve(rsp1).equilibria[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "2791ffe2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[\\left[\\frac{7}{10},\\frac{3}{10}\\right],\\left[\\frac{4}{5},\\frac{1}{5}\\right],\\left[0,1\\right]\\right]$" + ], + "text/plain": [ + "[[Rational(7, 10), Rational(3, 10)], [Rational(4, 5), Rational(1, 5)], [Rational(0, 1), Rational(1, 1)]]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rsp2 = g.random_strategy_profile(denom=10, gen=gen)\n", + "rsp2" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "2ab2caa4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[\\left[1,0\\right],\\left[1,0\\right],\\left[1,0\\right]\\right]$" + ], + "text/plain": [ + "[[Rational(1, 1), Rational(0, 1)], [Rational(1, 1), Rational(0, 1)], [Rational(1, 1), Rational(0, 1)]]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.nash.simpdiv_solve(rsp2).equilibria[0]" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "gambitvenv313", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/tutorials/05_quantal_response.ipynb b/doc/tutorials/05_quantal_response.ipynb new file mode 100644 index 000000000..d04b94ae7 --- /dev/null +++ b/doc/tutorials/05_quantal_response.ipynb @@ -0,0 +1,336 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ef7d397e", + "metadata": {}, + "source": [ + "# 5) Quantal response equilibria\n", + "\n", + "Gambit implements the idea of [McKPal95](https://gambitproject.readthedocs.io/en/latest/biblio.html#general-game-theory-articles-and-texts) and [McKPal98](https://gambitproject.readthedocs.io/en/latest/biblio.html#general-game-theory-articles-and-texts) to compute Nash equilibria via path-following a branch of the logit quantal response equilibrium (LQRE) correspondence using the function `logit_solve`.\n", + "As an example, we will consider an asymmetric matching pennies game from [Och95](https://gambitproject.readthedocs.io/en/latest/biblio.html#general-game-theory-articles-and-texts) as analyzed in [McKPal95](https://gambitproject.readthedocs.io/en/latest/biblio.html#general-game-theory-articles-and-texts)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ebc4c60e", + "metadata": {}, + "outputs": [], + "source": [ + "import pygambit as gbt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "202786ef", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[[0.5000000234106035, 0.49999997658939654],[0.19998563837426647, 0.8000143616257336]\\right]$" + ], + "text/plain": [ + "[[0.5000000234106035, 0.49999997658939654], [0.19998563837426647, 0.8000143616257336]]" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "g = gbt.Game.from_arrays(\n", + " [[1.1141, 0], [0, 0.2785]],\n", + " [[0, 1.1141], [1.1141, 0]],\n", + " title=\"Ochs (1995) asymmetric matching pennies as transformed in McKelvey-Palfrey (1995)\"\n", + ")\n", + "gbt.nash.logit_solve(g).equilibria[0]" + ] + }, + { + "cell_type": "markdown", + "id": "1ce76964", + "metadata": {}, + "source": [ + "`logit_solve` returns only the limiting (approximate) Nash equilibrium found.\n", + "Profiles along the QRE correspondence are frequently of interest in their own right.\n", + "Gambit offers several functions for more detailed examination of branches of the QRE correspondence.\n", + "\n", + "The function `logit_solve_branch` uses the same procedure as `logit_solve`, but returns a list of LQRE profiles computed along the branch instead of just the limiting approximate Nash equilibrium." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "840d9203", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "193" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qres = gbt.qre.logit_solve_branch(g)\n", + "len(qres)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "be419db2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[[0.5, 0.5],[0.5, 0.5]\\right]$" + ], + "text/plain": [ + "[[0.5, 0.5], [0.5, 0.5]]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qres[0].profile" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "582838de", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[[0.5182276540742868, 0.4817723459257562],[0.49821668880066783, 0.5017833111993909]\\right]$" + ], + "text/plain": [ + "[[0.5182276540742868, 0.4817723459257562], [0.49821668880066783, 0.5017833111993909]]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qres[5].profile" + ] + }, + { + "cell_type": "markdown", + "id": "61e86949", + "metadata": {}, + "source": [ + "`logit_solve_branch` uses an adaptive step size heuristic to find points on the branch.\n", + "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.\n", + "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.\n", + "As a result the lambda values for which profiles are computed cannot be controlled in advance.\n", + "\n", + "In some situations, the LQRE profiles at specified values of lambda are of interest.\n", + "For this, Gambit provides `logit_solve_lambda`.\n", + "This function provides accurate values of strategy profiles at one or more specified values of lambda." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "ce354b49", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[[0.5867840364385154, 0.4132159635614846],[0.4518070316997103, 0.5481929683002897]\\right]$" + ], + "text/plain": [ + "[[0.5867840364385154, 0.4132159635614846], [0.4518070316997103, 0.5481929683002897]]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qres = gbt.qre.logit_solve_lambda(g, lam=[1, 2, 3])\n", + "qres[0].profile" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "280fa428", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[[0.6175219458400859, 0.3824780541599141],[0.3719816648492249, 0.6280183351507751]\\right]$" + ], + "text/plain": [ + "[[0.6175219458400859, 0.3824780541599141], [0.3719816648492249, 0.6280183351507751]]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qres[1].profile" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3dee57df", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[[0.6168968501329284, 0.3831031498670716],[0.31401636202001226, 0.6859836379799877]\\right]$" + ], + "text/plain": [ + "[[0.6168968501329284, 0.3831031498670716], [0.31401636202001226, 0.6859836379799877]]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qres[2].profile" + ] + }, + { + "cell_type": "markdown", + "id": "5601be33", + "metadata": {}, + "source": [ + "LQRE are frequently taken to data by using maximum likelihood estimation to find the LQRE profile that best fits an observed profile of play.\n", + "This is provided by the function `logit_estimate`.\n", + "We replicate the analysis of a block of the data from [Och95](https://gambitproject.readthedocs.io/en/latest/biblio.html#general-game-theory-articles-and-texts) for which [McKPal95](https://gambitproject.readthedocs.io/en/latest/biblio.html#general-game-theory-articles-and-texts) estimated an LQRE." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b34a9278", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pygambit.qre.LogitQREMixedStrategyFitResult" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data = g.mixed_strategy_profile([[128*0.527, 128*(1-0.527)], [128*0.366, 128*(1-0.366)]])\n", + "fit = gbt.qre.logit_estimate(data)\n", + "type(fit)" + ] + }, + { + "cell_type": "markdown", + "id": "12534924", + "metadata": {}, + "source": [ + "The returned `LogitQREMixedStrategyFitResult` object contains the results of the estimation.\n", + "The results replicate those reported in [McKPal95](https://gambitproject.readthedocs.io/en/latest/biblio.html#general-game-theory-articles-and-texts), including the estimated value of lambda, the QRE profile probabilities, and the log-likelihood.\n", + "\n", + "Because `data` contains the empirical counts of play, and not just frequencies, the resulting log-likelihood is correct for use in likelihoood-ratio tests.\n", + "[[1](#f1)]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "e10e9abd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.8456097536855862\n", + "[[0.615651314427859, 0.3843486855721409], [0.38329094004562914, 0.6167090599543709]]\n", + "-174.76453191087447\n" + ] + } + ], + "source": [ + "print(fit.lam)\n", + "print(fit.profile)\n", + "print(fit.log_like)" + ] + }, + { + "cell_type": "markdown", + "id": "0316795f", + "metadata": {}, + "source": [ + "All of the functions above also support working with the agent LQRE of [McKPal98](https://gambitproject.readthedocs.io/en/latest/biblio.html#general-game-theory-articles-and-texts).\n", + "Agent QRE are computed as the default behavior whenever the game has a extensive (tree) representation.\n", + "\n", + "For `logit_solve`, `logit_solve_branch`, and `logit_solve_lambda`, this can be overriden by passing `use_strategic=True`;\n", + "this will compute LQRE using the reduced strategy set of the game instead.\n", + "\n", + "Likewise, `logit_estimate` will perform estimation using agent LQRE if the data passed are a `MixedBehaviorProfile`, and will return a `LogitQREMixedBehaviorFitResult` object." + ] + }, + { + "cell_type": "markdown", + "id": "486f68a7", + "metadata": {}, + "source": [ + "**Footnotes:**\n", + "\n", + " The log-likelihoods quoted in [McKPal95](https://gambitproject.readthedocs.io/en/latest/biblio.html#general-game-theory-articles-and-texts) are exactly a factor of 10 larger than those obtained by replicating the calculation." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "gambitvenv313", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/tutorials/06_gambit_with_openspiel.ipynb b/doc/tutorials/06_gambit_with_openspiel.ipynb new file mode 100644 index 000000000..20f6ff4ef --- /dev/null +++ b/doc/tutorials/06_gambit_with_openspiel.ipynb @@ -0,0 +1,1213 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 6) Using Gambit with OpenSpiel\n", + "\n", + "This tutorial demonstrates the interoperability of the Gambit and OpenSpiel Python packages for game-theoretic analysis.\n", + "\n", + "Where Gambit is used to compute exact equilibria for games, OpenSpiel provides a variety of iterative learning algorithms that can be used to approximate strategies. Another key distinction is that the PyGambit API allows the user a simple way to define custom games (see tutorials 1-3). This is also possible in OpenSpiel for normal form games, and you can load `.efg` files created from Gambit for extensive form, however some of the key functionality for iterated learning of strategies is only available for games from the built-in library (see the [OpenSpiel documentation](https://openspiel.readthedocs.io/en/latest/games.html)).\n", + "\n", + "This tutorial demonstrates:\n", + "\n", + "1. Transferring examples of normal (strategic) form and extensive form games between OpenSpiel and Gambit\n", + "2. Simulating evolutionary dynamics of populations of strategies in OpenSpiel for normal form games\n", + "3. Training agents using self-play of extensive form games in OpenSpiel to create strategies\n", + "4. Comparing the strategies from OpenSpiel against equilibria strategies computed with Gambit\n", + "\n", + "Note:\n", + "- The version of OpenSpiel used in this tutorial is `1.6.1`. If you are running this tutorial locally, this will be the version installed via the included `requirements.txt` file.\n", + "- The OpenSpiel code was adapted from the introductory tutorial for the OpenSpiel API on colab [here](https://colab.research.google.com/github/deepmind/open_spiel/blob/master/open_spiel/colabs/OpenSpielTutorial.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ebb78322", + "metadata": {}, + "outputs": [], + "source": [ + "from io import StringIO\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from open_spiel.python import rl_environment\n", + "from open_spiel.python.algorithms import tabular_qlearner\n", + "from open_spiel.python.algorithms.gambit import export_gambit\n", + "from open_spiel.python.egt import dynamics\n", + "from open_spiel.python.egt.utils import game_payoffs_array\n", + "\n", + "import pyspiel\n", + "\n", + "import pygambit as gbt" + ] + }, + { + "cell_type": "markdown", + "id": "fd324814", + "metadata": {}, + "source": [ + "## OpenSpiel game library\n", + "\n", + "The [library of games](https://openspiel.readthedocs.io/en/latest/games.html) included in OpenSpiel is extensive. Many of these games will not be amenable to equilibrium computation with Gambit, due to their size. For the purposes of this tutorial, we'll pick small games from the list below." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b3eb3671", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['2048', 'add_noise', 'amazons', 'backgammon', 'bargaining', 'battleship', 'blackjack', 'blotto', 'breakthrough', 'bridge', 'bridge_uncontested_bidding', 'cached_tree', 'catch', 'checkers', 'chess', 'cliff_walking', 'clobber', 'coin_game', 'colored_trails', 'connect_four', 'coop_box_pushing', 'coop_to_1p', 'coordinated_mp', 'crazy_eights', 'cribbage', 'cursor_go', 'dark_chess', 'dark_hex', 'dark_hex_ir', 'deep_sea', 'dots_and_boxes', 'dou_dizhu', 'efg_game', 'einstein_wurfelt_nicht', 'euchre', 'first_sealed_auction', 'gin_rummy', 'go', 'goofspiel', 'hanabi', 'havannah', 'hearts', 'hex', 'hive', 'kriegspiel', 'kuhn_poker', 'laser_tag', 'leduc_poker', 'lewis_signaling', 'liars_dice', 'liars_dice_ir', 'lines_of_action', 'maedn', 'mancala', 'markov_soccer', 'matching_pennies_3p', 'matrix_bos', 'matrix_brps', 'matrix_cd', 'matrix_coordination', 'matrix_mp', 'matrix_pd', 'matrix_rps', 'matrix_rpsw', 'matrix_sh', 'matrix_shapleys_game', 'mfg_crowd_modelling', 'mfg_crowd_modelling_2d', 'mfg_dynamic_routing', 'mfg_garnet', 'misere', 'mnk', 'morpion_solitaire', 'negotiation', 'nfg_game', 'nim', 'nine_mens_morris', 'normal_form_extensive_game', 'oh_hell', 'oshi_zumo', 'othello', 'oware', 'pathfinding', 'pentago', 'phantom_go', 'phantom_ttt', 'phantom_ttt_ir', 'pig', 'quoridor', 'rbc', 'repeated_game', 'restricted_nash_response', 'sheriff', 'skat', 'solitaire', 'spades', 'start_at', 'stones_and_gems', 'tarok', 'tic_tac_toe', 'tiny_bridge_2p', 'tiny_bridge_4p', 'tiny_hanabi', 'trade_comm', 'turn_based_simultaneous_game', 'twixt', 'ultimate_tic_tac_toe', 'universal_poker', 'y', 'zerosum']\n" + ] + } + ], + "source": [ + "print(pyspiel.registered_names())" + ] + }, + { + "cell_type": "markdown", + "id": "e628a86d", + "metadata": {}, + "source": [ + "## Normal form games from the OpenSpiel library\n", + "\n", + "Let's start with a simple normal form game of rock-paper-scissors, in which the payoffs can be represented by a 3x3 matrix.\n", + "\n", + "Load matrix rock-paper-scissors from OpenSpiel:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "ops_matrix_rps_game = pyspiel.load_game(\"matrix_rps\")" + ] + }, + { + "cell_type": "markdown", + "id": "fda1204e", + "metadata": {}, + "source": [ + "In order to simulate a playthrough of the game, you can first initialise a game state:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1bcdb97b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Terminal? false\n", + "Row actions: Rock Paper Scissors \n", + "Col actions: Rock Paper Scissors \n", + "Utility matrix:\n", + "0,0 -1,1 1,-1 \n", + "1,-1 0,0 -1,1 \n", + "-1,1 1,-1 0,0 " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "state = ops_matrix_rps_game.new_initial_state()\n", + "state" + ] + }, + { + "cell_type": "markdown", + "id": "eeee015a", + "metadata": {}, + "source": [ + "The possible actions for both players (player 0 and player 1) are Rock, Paper and Scissors, but these are not labelled and must be accessed via integer indices:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "70575dc7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2]\n", + "[0, 1, 2]\n" + ] + } + ], + "source": [ + "print(state.legal_actions(0)) # Player 0 (row) actions\n", + "print(state.legal_actions(1)) # Player 1 (column) actions" + ] + }, + { + "cell_type": "markdown", + "id": "fdea7e5b", + "metadata": {}, + "source": [ + "Since Rock-paper-scissors is a 1-step simultaneous-move normal form game, we'll apply a list of player actions in one step to reach the terminal state.\n", + "\n", + "Let's simulate player 0 playing Rock (0) and player 1 playing Paper (1):" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a532321e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Terminal? true\n", + "History: 0, 1\n", + "Returns: -1,1\n", + "Row actions: \n", + "Col actions: \n", + "Utility matrix:\n", + "0,0 -1,1 1,-1 \n", + "1,-1 0,0 -1,1 \n", + "-1,1 1,-1 0,0 " + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "state.apply_actions([0, 1])\n", + "state" + ] + }, + { + "cell_type": "markdown", + "id": "045cf8dd", + "metadata": {}, + "source": [ + "OpenSpiel can generate an NFG representation of the game loadable in Gambit:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f5fa4e42", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'NFG 1 R \"OpenSpiel export of matrix_rps()\"\\n{ \"Player 0\" \"Player 1\" } { 3 3 }\\n\\n0 0\\n1 -1\\n-1 1\\n-1 1\\n0 0\\n1 -1\\n1 -1\\n-1 1\\n0 0\\n'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nfg_matrix_rps_game = pyspiel.game_to_nfg_string(ops_matrix_rps_game)\n", + "nfg_matrix_rps_game" + ] + }, + { + "cell_type": "markdown", + "id": "70d1df64", + "metadata": {}, + "source": [ + "Now let's load the NFG in Gambit. Since Gambit's `read_nfg` function expects a file like object, we'll convert the string with `io.StringIO`.\n", + "We can also add labels for the actions to make the output more interpretable." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b684325e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

Rock-Paper-Scissors

\n", + "
RockPaperScissors
Rock0,0-1,11,-1
Paper1,-10,0-1,1
Scissors-1,11,-10,0
\n" + ], + "text/plain": [ + "Game(title='Rock-Paper-Scissors')" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt_matrix_rps_game = gbt.read_nfg(StringIO(nfg_matrix_rps_game))\n", + "\n", + "gbt_matrix_rps_game.title = \"Rock-Paper-Scissors\"\n", + "\n", + "for player in gbt_matrix_rps_game.players:\n", + " player.strategies[0].label = \"Rock\"\n", + " player.strategies[1].label = \"Paper\"\n", + " player.strategies[2].label = \"Scissors\"\n", + "\n", + "gbt_matrix_rps_game" + ] + }, + { + "cell_type": "markdown", + "id": "6d7da6f3", + "metadata": {}, + "source": [ + "The equilibrium mixed strategy profile for both players is to choose rock, paper, and scissors with equal probability:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "707c6c30", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[\\left[\\frac{1}{3},\\frac{1}{3},\\frac{1}{3}\\right],\\left[\\frac{1}{3},\\frac{1}{3},\\frac{1}{3}\\right]\\right]$" + ], + "text/plain": [ + "[[Rational(1, 3), Rational(1, 3), Rational(1, 3)], [Rational(1, 3), Rational(1, 3), Rational(1, 3)]]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.nash.lcp_solve(gbt_matrix_rps_game).equilibria[0]" + ] + }, + { + "cell_type": "markdown", + "id": "966e7e3f", + "metadata": {}, + "source": [ + "We can use OpenSpiel's dynamics module to demonstrate evolutionary game theory dynamics, or \"replicator dynamics\", which models how a mixed strategy profile evolves over time based on how the strategies (e.g., choice of actions A, B, C with probabilities X, Y, Z) perform against one another.\n", + "\n", + "Let's start with an initial profile that is not at equilibrium, but weighted towards scissors with proportions: 30% Rock, 30% Paper, 40% Scissors:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "cf1acdeb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0.03, -0.03, 0. ])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "matrix_rps_payoffs = game_payoffs_array(ops_matrix_rps_game)\n", + "dyn = dynamics.SinglePopulationDynamics(matrix_rps_payoffs, dynamics.replicator)\n", + "x = np.array([0.3, 0.3, 0.4])\n", + "dyn(x)" + ] + }, + { + "cell_type": "markdown", + "id": "fa382753", + "metadata": {}, + "source": [ + "`dyn(x)` calculates the rate of change (derivative) for each strategy in the current profile and returns how fast each strategy's frequency is changing.\n", + "\n", + "In replicator dynamics, a strategy that performs well against others will increase in frequency, while strategies performing worse will decrease.\n", + "In our rock-paper-scissors example, the performance of each strategy depends on the probability it is assigned in the mixed strategy profile. At the start, whilst there are more players choosing scissors as their action, then rock will perform well and increase in frequency (be more likely to get played in subsequent rounds), while paper will perform poorly and decrease in frequency. We can plot how the frequency of each strategy changes over time:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "b9a352c5", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def plot_rps_dynamics(proportions, steps=100, alpha=0.1, plot_average_strategy=False):\n", + " x = np.array(proportions)\n", + " rock_proportions = [x[0]]\n", + " paper_proportions = [x[1]]\n", + " scissors_proportions = [x[2]]\n", + " y = []\n", + " for _ in range(steps):\n", + " x += alpha * dyn(x)\n", + " rock_proportions.append(x[0])\n", + " paper_proportions.append(x[1])\n", + " scissors_proportions.append(x[2])\n", + " if plot_average_strategy:\n", + " y.append([np.mean(rock_proportions), np.mean(paper_proportions), np.mean(scissors_proportions)])\n", + " else:\n", + " y.append(x.copy())\n", + " y = np.array(y)\n", + "\n", + " plt.plot(y[:, 0], label=\"Rock\")\n", + " plt.plot(y[:, 1], label=\"Paper\")\n", + " plt.plot(y[:, 2], label=\"Scissors\")\n", + " plt.xlabel(\"Time step\")\n", + " if plot_average_strategy:\n", + " plt.ylabel(\"Strategy frequency average up to time step\")\n", + " else:\n", + " plt.ylabel(\"Strategy frequency\")\n", + " plt.legend()\n", + " plt.show()\n", + "\n", + "plot_rps_dynamics([0.3, 0.3, 0.4])" + ] + }, + { + "cell_type": "markdown", + "id": "8569aef4", + "metadata": {}, + "source": [ + "Through the dynamics, we can see that the population proportions oscillate around the equilibrium point (1/3, 1/3, 1/3) without converging to it, because the best strategy depends on the likelihood of the opponents' actions, as defined by the current action probabilities.\n", + "\n", + "However, if we start with the initial population already at the equilibrium mixed strategy profile computed by Gambit (each action is chosen exactly 1/3 of the time), the strategy frequencies will remain constant over time (at the equilibrium point):" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "86c6aa52", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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BKT333/faz1VqlT2iJEOHTrIgAEDEvLaAAAAAiMg6Yg1AACA5CzBapAAAABSbAuSDuXXB9TGxbx58+JzTQAAAIHVgqTTcy9fvlw2b97s2bZlyxZZsWKFDffX/e4FAAAgRQSk3Llzy9NPP221SNpCpMv+/fulTZs2Vqz96aefepa4mjRpkoSGhkr69OmlevXqsnHjxliP1ferUqWKZM2aVUJCQqRChQo2gi66oUOHSokSJWx/tmzZ7EG6GzZs8DpG309bwqIvo0aN8vV2AACAZMjnh9VqCFq7dq0UL17ca/vevXvlH//4h5w7d86nC5g7d660b99epk6dauFo3Lhx8uWXX9r5cuXKdcvxP/74o/z1118WgNKmTSsLFy6UV199Vb777jtp1KiRHfP555/ba4sUKSJ///23jbzTc+7bt8+u3x2QOnXqJJ07d/acO3PmzBaq4oKH1QIAEHji+vntcwvSzZs3Zc+ePbds121RUVE+X+jYsWMtpHTs2FFKlSplQSljxozyySefxHh8nTp1rA6qZMmS9uy33r17S7ly5Sy0uT377LPWaqQBqXTp0vYeekN27NjhdS4NRHny5PEstwtHN27csHNEXwAAQPLkc0DSIKMtLxo6NJToMmbMGHnxxRdtny/Cw8OtfknDjOeCUqWy9XXr1t3x9dr4pfVQ2tpUq1atWN/jo48+srRYvnx5r33apZYjRw6pWLGivPvuuxb+YhMWFuZVX1WgQAGfflYAAJCMR7G999571tqioUhn0FZ58+aV119/3bq6fHH27FmJjIy0uqbodD2mVio3bRbLnz+/teqkTp1aJk+eLA0aNPA6Rrve2rZtK9euXbPrW7p0qeTMmdOzv1evXlKpUiXJnj27/PzzzzbJpf48Gvxiovv79u3rWdcWJEISAADJk88BSVt43njjDVvc3Uz3ugZHu8a2bdsmV65csRYkDS7anabdb25169a1YzSETZs2zQrLtVDbXdcUPexoF53WM7300kvWUpQuXbpb3lO3xbQdAAAkP/GaKFK7opYtWyZffPGFZ06k48ePW2DxhbboaAvQqVOnvLbrurZSxXrRqVJJsWLFbASbtlo99dRTFmyi03oiPebhhx+Wjz/+WIKDg+1rbLRAXH+ugwcP+vQzAACA5MfngHTo0CEpW7asPPHEE9K9e3c5c+aMbR89erS89tprPp1LW20qV65srUBuWuit6zVq1IjzefQ12t12N8doa5MGr5hGzgEAgJTF5y42HTWm8xBt377dCpzddGRZ9CHzcaVdXfqgWz1ntWrVbJj/1atXPQXfOgWA1hu5W4j0qx6rI9g08CxatMjmQZoyZYrt19eOHDlSmjdvbrVH2sWm8ywdO3ZMWrdubcdoAbh2t2k3nHbX6forr7wizz33nM2bBAAAUjafA9KaNWusqFlbf6LTeYU0hPhKJ5jUVqjBgwfLyZMnrdts8eLFnsLtw4cPW8uOmwagbt26ydGjRyVDhgw2H9KsWbPsPEq77LTAe/r06RaONMRVrVrVrluH/CutJZozZ45NKKkhq3DhwhaQotclAQCAlMvniSK1heWnn36yOYu09UVbkrRAWof7t2rV6pZ6ouSKiSIBAAg8iTZRZMOGDa0bzE2LtLU4e8iQIdK0adP4XzEAAECgtiAdOXJEGjdubJM0/vHHH1YPpF91RNrq1atTTJEzLUgAACTfz2+fA5LS4fD6DDXtXtPWI51wsV27dlYTlFIQkAAACDyJEpAiIiKsKFpnqdZnoaVkBCQAAAJPotQgpUmTRq5fv54Q1wcAAJBk+VykrZND6qSQt3uwKwAAQIqaB2nTpk020/UPP/xgM2rrIz2imzdvXkJeHwAAQNIPSFmzZrX5jpDwoiIj5a/L///RLQAApHTZMt8vqVKnTroBacGCBdKkSROrQfr0008T/6pSKA1Hdb5p4O/LAAAgSfjxiaWSI2vsD6/3ew2SPmftwoULnkd5nD59OrGvCwAAwG/i1IJ0//33y/r16+Xxxx+3CSJ19mwkTlOipmUAACD2uZikA1LXrl3liSeesGCkS548sTd3RUZGJuT1pSjaz+qvpkQAAOBjQNKn3rdt21b27dsnzZs3tzokLdYGAABI0aPYdAZtXfShtK1bt5aMGTMm7pUBAAD4SbyexQYeNQIAQCBKlEeNAAAApAQEJAAAAAcCEgAAwN0GpP379/v6EgAAgOQdkIoVKyZ169aVWbNmyfXr1xPnqgAAAAIpIG3dulXKlSsnffv2tQkjX3rpJdm4cWPiXB0AAEAgBKQKFSrIBx98IMePH5dPPvlETpw4ITVr1pQyZcrI2LFj5cwZnkYPAABSaJF2cHCwtGzZUr788ksZPXq0zbL92muvSYECBaR9+/YWnAAAAFJUQNq8ebN069ZN8ubNay1HGo7+/PNPWbp0qbUu6bPbAAAAkvWjRtw0DOmz2Pbu3StNmzaVGTNm2NdUqf5/1ipcuLB89tlnEhoamhjXCwAAkPQC0pQpU+Rf//qXvPDCC9Z6FJNcuXLJxx9/nBDXBwAAcM/xLLZ44llsAAAk389vn1uQduzYEeP2oKAgSZ8+vRQsWFDSpUvn62kBAACSjOD4DPPXMBSbNGnSSJs2beTDDz+0wAQAAJDsR7HNnz9fHnzwQfnoo49k27Zttuj3xYsXl88//9xqj1asWCFvvfVW4lwxAABAUmtBGjlypE0U2ahRI8+2smXLygMPPCCDBg2yWbVDQkLk1Vdflffeey+hrxcAACDptSD9+uuvUqhQoVu26zbd5+6GY6JIAACQYgJSiRIlZNSoURIeHu7ZFhERYdt0nzp27Jjkzp07Ya8UAAAgqXaxTZo0SZo3b25davrQWqUtR5GRkbJw4UJb379/v82yDQAAkGLmQbp8+bLMnj1bfv/9d1vXAu1nn31WMmfOLCkF8yABABB4Em0eJKVBqGvXrndzfQAAAMnrYbUzZ86UmjVrSr58+eTQoUO27f3335dvvvkmoa8PAAAg6QckfRZb3759pUmTJvLXX39Z7ZHKli2bjBs3LjGuEQAAIGkHpAkTJsi0adNk4MCBEhz8vx66KlWqeIb5AwAApKiAdODAAalYseIt2/X5a1evXo3XRejIuNDQUHs0SfXq1W2yydjMmzfPwljWrFltQkqdc0m7/KIbOnSoTTmg+7Vlq379+rJhwwavY86fPy/t2rWzAi09V6dOneTKlSvxun4AAJDCA1LhwoXt8SJOixcvlpIlS/p8AXPnzrUuuyFDhsjWrVulfPnyNkv36dOnYzw+e/bs1nq1bt06e3Bux44dbVmyZInnmIceekgmTpxoLVpr16618NWwYUM5c+aM5xgNRzt37pSlS5fa9ASrV6+WLl26+Hz9AAAgGXL5aNq0aa78+fO75syZ4woJCXF98cUXrrffftvzva+qVavm6t69u2c9MjLSlS9fPldYWFicz1GxYkXXW2+9Fev+ixcv6lQGrmXLltn6rl27bH3Tpk2eY77//ntXUFCQ69ixY3F6T/c59SsAAAgMcf389nmY/4svvigZMmSwh9Feu3bN5j/S0Wz6fLa2bdv6dC6djXvLli0yYMAAz7ZUqVJZl5i2EMUh3NmDcffu3SujR4+O9T30Ybo654G2Tik9t3araVedm76nvrd2xT355JO3nOfGjRu2RJ9HAQAAJE/xmgdJu6d00YCkdTu5cuWK15ufPXvWRsE5H0ui63v27In1dTq5U/78+S2wpE6dWiZPniwNGjTwOka7zTSw6TXmzZvXutJy5sxp+06ePHnLNWvBuXbf6b6YhIWFybBhw+L1cwIAgGReg/Too4/KhQsX7PuMGTN6goa2qOi+e0EnqtQ6qE2bNsnIkSOthunHH3/0OqZu3bp2zM8//yyNGzeWp59+Ota6prjQVi4NZu7lyJEjCfCTAACAZNGCpEEk+oNq3a5fvy5r1qzx6VzaoqMtQKdOnfLarut58uSJ9XXaFVasWDH7Xkex7d6921p46tSp4zlGR7DpMbo8/PDD8uCDD8rHH39sQUfP7QxLN2/etJFtsb2vjtLTBQAAJH9xDkg6Ysxt165dXl1R2k2mo9i028sXadOmlcqVK8vy5culRYsWti0qKsrWe/ToEefz6Gui1wfd6ZgaNWpYK5jWP+n7K61l0mN0mgEAAJCyxTkgaUtNUFCQLTF1pWnhtk4i6SvtHuvQoYMVTFerVs1m49b5lHTovmrfvr0FL20hUvpVjy1atKgFnkWLFtk8SDrDt9LXardb8+bNrfZI65x0nqVjx45J69at7RidjkC73Tp37ixTp06ViIgIC2Ras6QF5wAAIGUL9mWCSB01VqRIEZvI8f777/dqCdJaJO0u81WbNm1sfqLBgwdbq5QGMW2NchduHz582LrU3DQAdevWTY4ePWqhTCeEnDVrlp1H6TVogff06dMtHOXIkUOqVq1q3X+lS5f2nGf27NkWiurVq2fnb9WqlYwfP97n6wcAAMlPkI719/dFBCItStepA7RgW2fjBgAAyefzO17D/N11SNq64yzY1q4tAACAQOZzQNq/f79NpKiP8dB6JHcDlH7vLtgGAABIUfMg9e7d257HpsPkdR4kfZ6ZPsdMC6edcxEBAACkiBYkfUyHDonXOYy0uFmXmjVr2uiyXr16yS+//JI4VwoAAJBUW5C0C01nslYako4fP27fFypUyJ6JBgAAkOJakMqUKSPbt2+3bjadVPGdd96xYf76QFidAgAAACDFBaS33nrL5iJSw4cPl8cee0z++c9/2nxDc+fOTYxrBAAACLx5kPQZZtmyZfOMZEsJmAcJAIDk+/ntUw2SPpIjODhYfvvtN6/t2bNnT1HhCAAAJG8+BaQ0adJIwYIFmesIAAAkaz6PYhs4cKC8+eab1q0GAACQHPlcpD1x4kTZt2+fPfVeh/aHhIR47d+6dWtCXh8AAEDSD0hPPPEE9UYAACBZS5BRbCkRo9gAAAg8iTKKTelkkOfOnbtl+4ULF5goEgAAJAs+B6SDBw/GOIrtxo0bcvTo0YS6LgAAgKRfg7RgwQLP90uWLLHmKTcNTMuXL7fHjwAAAKSYgNSiRQv7qgXaHTp0uGV+pNDQUBkzZkzCXyEAAEBSDUhRUVH2VVuJNm3aJDlz5kzM6wIAAAicYf4HDhxInCsBAAAItCLtdevWycKFC722zZgxw1qUcuXKJV26dLFCbQAAgBQTkIYPHy47d+70rP/666/SqVMnqV+/vvTv31++/fZbCQsLS6zrBAAASHoBadu2bVKvXj3P+pw5c6R69eoybdo06du3r4wfP17+85//JNZ1AgAAJL2A9Ndff0nu3Lk966tWrZImTZp41qtWrSpHjhxJ+CsEAABIqgFJw5G7QDs8PNweSvvwww979l++fNmG+wMAAKSYgNS0aVOrNVqzZo0MGDBAMmbMKP/85z89+3fs2CFFixZNrOsEAABIesP8R4wYIS1btpTatWtLpkyZZPr06ZI2bVrP/k8++UQaNmyYWNcJAABwzwS5XC6XLy/Qp99qQEqdOrXX9vPnz9v26KEpOYvr04ABAEDgfX77PFFk9GewRZc9e3ZfTwUAABDYNUgAAAApBQEJAADAgYAEAADgQEACAABwICABAAA4EJAAAAAcCEgAAAAOBCQAAAAHAhIAAIADAQkAACApBqRJkyZJaGiopE+fXqpXry4bN26M9dh58+ZJlSpVJGvWrBISEiIVKlSQmTNnevZHRERIv379pGzZsrY/X7580r59ezl+/LjXefT9goKCvJZRo0Yl6s8JAAACg98D0ty5c6Vv374yZMgQ2bp1q5QvX14aNWokp0+fjvWZbwMHDpR169bJjh07pGPHjrYsWbLE9l+7ds3OM2jQIPuqgWrv3r3SvHnzW841fPhwOXHihGfp2bNnov+8AAAg6QtyuVwuf16AthhVrVpVJk6caOtRUVFSoEABCyv9+/eP0zkqVaokzZo1kxEjRsS4f9OmTVKtWjU5dOiQFCxY0NOC1KdPH1sS82nAAAAg6Yjr57dfW5DCw8Nly5YtUr9+/f9dUKpUtq4tRHei2W758uXWQlSrVq1Yj9OboF1o2i0XnXap5ciRQypWrCjvvvuu3Lx5M9Zz3Lhxw25q9AUAACRPwf5887Nnz0pkZKTkzp3ba7uu79mz57aBJ3/+/BZaUqdOLZMnT5YGDRrEeOz169etJumZZ57xSoq9evWyliftsvv5559lwIAB1s02duzYGM8TFhYmw4YNi/fPCgAAAodfA1J8Zc6cWbZt2yZXrlyxFiStYSpSpIjUqVPH6zgt2H766aetpWnKlCle+/Q1buXKlZO0adPKSy+9ZEEoXbp0t7ynBqjor9EWJO0KBAAAyY9fA1LOnDmtBejUqVNe23U9T548sb5Ou+GKFStm3+sott27d1uwiR6Q3OFI645WrFhxxzohrYXSLraDBw9K8eLFb9mvoSmm4AQAAJIfv9YgaatN5cqVrRXITYu0db1GjRpxPo++RrvbnOHojz/+kGXLllmd0Z1oi5QGr1y5csXjJwEAAMmJ37vYtNuqQ4cONreRjjQbN26cXL161YbuK53DSOuNtIVI6Vc9tmjRohaKFi1aZPMgubvQNBw99dRTNsR/4cKFVuN08uRJ26f1RhrKtAB8w4YNUrduXeuu0/VXXnlFnnvuOcmWLZsf7wYAAEgK/B6Q2rRpI2fOnJHBgwdbkNEus8WLF3sKtw8fPmwtO24anrp16yZHjx6VDBkySIkSJWTWrFl2HnXs2DFZsGCBfa/nim7lypXWDaddZXPmzJGhQ4dayCpcuLAFpOg1RgAAIOXy+zxIgYp5kAAACDwBMQ8SAABAUkRAAgAAcCAgAQAAOBCQAAAAHAhIAAAADgQkAAAABwISAACAAwEJAADAgYAEAADgQEACAABwICABAAA4EJAAAAAcCEgAAAAOBCQAAAAHAhIAAIADAQkAAMCBgAQAAOBAQAIAAHAgIAEAADgQkAAAABwISAAAAA4EJAAAAAcCEgAAgAMBCQAAwIGABAAA4EBAAgAAcCAgAQAAOBCQAAAAHAhIAAAADgQkAAAABwISAACAAwEJAADAgYAEAADgQEACAABwICABAAA4EJAAAAAcCEgAAAAOBCQAAAAHAhIAAIADAQkAACApBqRJkyZJaGiopE+fXqpXry4bN26M9dh58+ZJlSpVJGvWrBISEiIVKlSQmTNnevZHRERIv379pGzZsrY/X7580r59ezl+/LjXec6fPy/t2rWT++67z87VqVMnuXLlSqL+nAAAIDD4PSDNnTtX+vbtK0OGDJGtW7dK+fLlpVGjRnL69OkYj8+ePbsMHDhQ1q1bJzt27JCOHTvasmTJEtt/7do1O8+gQYPsqwaqvXv3SvPmzb3Oo+Fo586dsnTpUlm4cKGsXr1aunTpck9+ZgAAkLQFuVwulz8vQFuMqlatKhMnTrT1qKgoKVCggPTs2VP69+8fp3NUqlRJmjVrJiNGjIhx/6ZNm6RatWpy6NAhKViwoOzevVtKlSpl27U1Si1evFiaNm0qR48etVYnpxs3btjidunSJbvOixcvWisUAABI+vTzO0uWLHf8/PZrC1J4eLhs2bJF6tev/78LSpXK1rWF6E402y1fvtxaiGrVqhXrcXoTgoKCrCtN6bn1e3c4Uvqe+t4bNmyI8RxhYWF2Q92LhiMAAJA8+TUgnT17ViIjIyV37txe23X95MmTtw08mTJlkrRp01rL0YQJE6RBgwYxHnv9+nWrSXrmmWc8SVHPnStXLq/jgoODrfsutvcdMGCAva97OXLkSDx+YgAAEAiCJQBlzpxZtm3bZkXV2oKkNUxFihSROnXqeB2nBdtPP/20tTRNmTLlrt4zXbp0tgAAgOTPrwEpZ86ckjp1ajl16pTXdl3PkydPrK/TrrBixYrZ9zqKTWuKtAssekByhyOtO1qxYoVXP6Oe21kEfvPmTRvZdrv3BQAAKYNfu9i0i6xy5crWCuSmRdq6XqNGjTifR18TvYDaHY7++OMPWbZsmeTIkcPreD33hQsXrP7JTUOUnkeLxgEAQMrm9y427R7r0KGDFUzrSLNx48bJ1atXbei+0jmM8ufPby1ESr/qsUWLFrVQtGjRIpsHyd2FpuHoqaeesiH+Onxfa5zcdUVaY6ShrGTJktK4cWPp3LmzTJ061V7To0cPadu2bYwj2AAAQMri94DUpk0bOXPmjAwePNiCjHaZ6ZB7d+H24cOHrUvNTcNTt27dbDh+hgwZpESJEjJr1iw7jzp27JgsWLDAvtdzRbdy5UpPN9zs2bMtFNWrV8/O36pVKxk/fvw9/MkBAEBS5fd5kJL7PAoAACDpCIh5kAAAAJIiAhIAAIADAQkAAMCBgAQAAOBAQAIAAHAgIAEAADgQkAAAABwISAAAAA4EJAAAAAcCEgAAgAMBCQAAwIGABAAA4EBAAgAAcCAgAQAAOBCQAAAAHAhIAAAADgQkAAAABwISAACAAwEJAADAgYAEAADgQEACAABwICABAAA4EJAAAAAcCEgAAAAOBCQAAAAHAhIAAIADAQkAAMCBgAQAAOBAQAIAAHAgIAEAADgQkAAAABwISAAAAA4EJAAAAAcCEgAAgAMBCQAAwIGABAAA4EBAAgAAcCAgAQAAOBCQAAAAklpAmjRpkoSGhkr69OmlevXqsnHjxliPnTdvnlSpUkWyZs0qISEhUqFCBZk5c+YtxzRs2FBy5MghQUFBsm3btlvOU6dOHdsXfenatWui/HwAACDw+DUgzZ07V/r27StDhgyRrVu3Svny5aVRo0Zy+vTpGI/Pnj27DBw4UNatWyc7duyQjh072rJkyRLPMVevXpWaNWvK6NGjb/venTt3lhMnTniWd955J8F/PgAAEJiCXC6Xy19vri1GVatWlYkTJ9p6VFSUFChQQHr27Cn9+/eP0zkqVaokzZo1kxEjRnhtP3jwoBQuXFh++eUXa2lytiDptnHjxsX72i9duiRZsmSRixcvyn333Rfv8wAAgHsnrp/fweIn4eHhsmXLFhkwYIBnW6pUqaR+/frWQnQnmutWrFghe/fuvWNrUUxmz54ts2bNkjx58sjjjz8ugwYNkowZM8Z6/I0bN2xx0xvrvtEAACAwuD+379Q+5LeAdPbsWYmMjJTcuXN7bdf1PXv2xPo6DSb58+e3sJI6dWqZPHmyNGjQwKf3fvbZZ6VQoUKSL18+66rr16+fBS2tX4pNWFiYDBs27Jbt2uIFAAACy+XLl60lKckFpPjKnDmzFV5fuXJFli9fbjVMRYoUsW6zuOrSpYvn+7Jly0revHmlXr168ueff0rRokVjfI22dOl7uWl34Pnz5z3F4AmZbDV0HTlyhK67RMa9vre43/cO9/re4V4H3r3WliMNR9pIcjt+C0g5c+a0FqBTp055bdd17faKjXbDFStWzL7XOqLdu3db644vASmmWii1b9++WANSunTpbIlOR9MlFv3l83+2e4N7fW9xv+8d7vW9w70OrHt9u5Yjv49iS5s2rVSuXNlagaK3yuh6jRo14nwefU302qD4cE8FoC1JAAAAfu1i0y6rDh062NxG1apVs1FlOkxfh+6r9u3bW72RthAp/arHaiuPhqJFixbZPEhTpkzxnFO7vQ4fPizHjx+3da0tUtoqpYt2o33++efStGlT6x7TGqRXXnlFatWqJeXKlfPLfQAAAEmLXwNSmzZt5MyZMzJ48GA5efKkdZktXrzYU7itQUe71Nw0PHXr1k2OHj0qGTJkkBIlSthIND2P24IFCzwBS7Vt29a+6lxLQ4cOtZarZcuWecKY9me2atVK3nrrLUkKtBtPr9XZnYeEx72+t7jf9w73+t7hXiffe+3XeZAAAACSIr8/agQAACCpISABAAA4EJAAAAAcCEgAAAAOBKQkZtKkSRIaGirp06e3CSw3btzo70sKeDo9hD4UWWdhz5Url7Ro0cIz/YPb9evXpXv37jb1Q6ZMmWxko3MSU/hm1KhRNst8nz59PNu4zwnr2LFj8txzz9n91JG9+mSAzZs3e/brGBwdJaxzvOl+fdblH3/84ddrDkT6WCx9Xqc+AF3vo041ow9Ijz7GiXsdP6tXr7bnoeqs1vrvxddff+21Py73Vaf3adeunU0eqRM4d+rUyZ62cbcISEnI3LlzbW4oHca4detWKV++vDRq1EhOnz7t70sLaKtWrbIP5fXr18vSpUslIiJCGjZsaNM8uOlcWN9++618+eWXdrzOo9WyZUu/Xncg27Rpk3z44Ye3zC3GfU44f/31lzzyyCOSJk0a+f7772XXrl0yZswYyZYtm+eYd955R8aPHy9Tp06VDRs2SEhIiP2bokEVcacPRNf59iZOnGhPb9B1vbcTJkzwHMO9jh/9d1g/67RxICZxua8ajnbu3Gn/vi9cuNBCV/RHisWbDvNH0lCtWjVX9+7dPeuRkZGufPnyucLCwvx6XcnN6dOn9c8+16pVq2z9woULrjRp0ri+/PJLzzG7d++2Y9atW+fHKw1Mly9fdj344IOupUuXumrXru3q3bu3bec+J6x+/fq5atasGev+qKgoV548eVzvvvuuZ5v+DtKlS+f64osv7tFVJg/NmjVz/etf//La1rJlS1e7du3se+51wtB/C+bPn+9Zj8t93bVrl71u06ZNnmO+//57V1BQkOvYsWN3dT20ICUR4eHhsmXLFms+dNNJMnV93bp1fr225ObixYv2NXv27PZV77u2KkW/9zoJacGCBbn38aCtdc2aNfO6n4r7nLB0Ulx9skDr1q2t67hixYoybdo0z/4DBw7YBLzR77c+f0q77rnfvvnHP/5hj8H6/fffbX379u2ydu1aadKkia1zrxNHXO6rftVuNf3/gpser5+f2uIUsDNp43/Onj1r/dzuWcTddH3Pnj1+u67kRp/dpzUx2jVRpkwZ26b/B9QZ1p0PH9Z7r/sQd3PmzLHuYe1ic+I+J6z9+/dbt492y7/55pt2z3v16mX3WB/h5L6nMf2bwv32Tf/+/e1J8hro9SHr+m/1yJEjrWtHca8TR1zuq37VPxCiCw4Otj+A7/beE5CQ4lo3fvvtN/vrDwnryJEj0rt3b6sD0EEGSPywr381/9///Z+tawuS/rettRoakJBw/vOf/8js2bPtOZ6lS5e2B5zrH1paWMy9Tr7oYksicubMaX+ZOEf06Lo+ZBd3r0ePHlbAt3LlSnnggQc82/X+ahfnhQsXvI7n3vtGu9B0QEGlSpXsLzhdtBBbCyz1e/2rj/uccHRUT6lSpby2lSxZ0p5hqdz3lH9T7t7rr79urUj6bE8dKfj888/bgAP3g9S514kjLvdVvzoHMt28edNGtt3tvScgJRHaLF65cmXr547+F6Ku16hRw6/XFui09k/D0fz582XFihU2VDc6ve86Eij6vddpAPSDhnsfd/Xq1ZNff/3V/rp2L9rCod0Q7u+5zwlHu4md01VojUyhQoXse/3vXD8got9v7SbSugzut2+uXbvm9eB0pX/Q6r/RinudOOJyX/Wr/tGlf6C56b/z+rvRWqW7clcl3khQc+bMser8zz77zCrzu3Tp4sqaNavr5MmT/r60gPbyyy+7smTJ4vrxxx9dJ06c8CzXrl3zHNO1a1dXwYIFXStWrHBt3rzZVaNGDVtwd6KPYlPc54SzceNGV3BwsGvkyJGuP/74wzV79mxXxowZXbNmzfIcM2rUKPs35JtvvnHt2LHD9cQTT7gKFy7s+vvvv/167YGmQ4cOrvz587sWLlzoOnDggGvevHmunDlzut544w3PMdzr+I96/eWXX2zRSDJ27Fj7/tChQ3G+r40bN3ZVrFjRtWHDBtfatWttFO0zzzzjulsEpCRmwoQJ9gGSNm1aG/a/fv16f19SwNP/08W0fPrpp55j9P9s3bp1c2XLls0+ZJ588kkLUUjYgMR9Tljffvutq0yZMvaHVYkSJVwfffSR134dJj1o0CBX7ty57Zh69eq59u7d67frDVSXLl2y/4713+b06dO7ihQp4ho4cKDrxo0bnmO41/GzcuXKGP991lAa1/t67tw5C0SZMmVy3Xfffa6OHTta8LpbQfo/d9cGBQAAkLxQgwQAAOBAQAIAAHAgIAEAADgQkAAAABwISAAAAA4EJAAAAAcCEgAAgAMBCQAAwIGABCDgvPDCC9KiRQt/XwaAZCzY3xcAANEFBQXddv+QIUPkgw8+sIcQJyU//vij1K1bV/766y/JmjWrvy8HwF0iIAFIUk6cOOH5fu7cuTJ48GCvp9ZnypTJFgBITHSxAUhS8uTJ41myZMliLUrRt2k4cnax1alTR3r27Cl9+vSRbNmySe7cuWXatGly9epV6dixo2TOnFmKFSsm33//vdd7/fbbb9KkSRM7p77m+eefl7Nnz8Z6bYcOHZLHH3/c3iMkJERKly4tixYtkoMHD1rrkdJ9es16jSoqKkrCwsKkcOHCkiFDBilfvrz897//9Wp50uO/++47KVeunKRPn14efvhhuzYA/kNAApAsTJ8+XXLmzCkbN260sPTyyy9L69at5R//+Ids3bpVGjZsaAHo2rVrdvyFCxfk0UcflYoVK8rmzZtl8eLFcurUKXn66adjfY/u3bvLjRs3ZPXq1fLrr7/K6NGjLVwVKFBAvvrqKztGW7u0FUy7AZWGoxkzZsjUqVNl586d8sorr8hzzz0nq1at8jr366+/LmPGjJFNmzbJ/fffb0EsIiIiUe8ZgNtwAUAS9emnn7qyZMlyy/YOHTq4nnjiCc967dq1XTVr1vSs37x50xUSEuJ6/vnnPdtOnDihRUuudevW2fqIESNcDRs29DrvkSNH7Ji9e/fGeD1ly5Z1DR06NMZ9K1eutNf+9ddfnm3Xr193ZcyY0fXzzz97HdupUyfXM8884/W6OXPmePafO3fOlSFDBtfcuXNvc3cAJCZqkAAkC9o95ZY6dWrJkSOHlC1b1rNNu9DU6dOn7ev27dtl5cqVMdYz/fnnn/LQQw/dsr1Xr17WMvXDDz9I/fr1pVWrVl7v67Rv3z5rsWrQoIHX9vDwcGu5iq5GjRqe77Nnzy7FixeX3bt3x/GnB5DQCEgAkoU0adJ4rWtdT/Rt7tFxWhOkrly5Yt1Y2k3mlDdv3hjf48UXX5RGjRpZvZCGJO0+024x7dKLib6H0uPz58/vtS9dunQ+/4wA7h0CEoAUqVKlSlY3FBoaKsHBcf+nUOuNunbtasuAAQOsGFwDUtq0aW1/ZGSk59hSpUpZEDp8+LDUrl37tuddv369FCxY0L7XqQJ+//13KVmyZLx/PgB3hyJtACmSFlyfP39ennnmGSuM1m61JUuW2Ki36CEnOh0lp8ccOHDACr+1i84dYgoVKmStVAsXLpQzZ85Y65GOnnvttdesMFuLyPU99HUTJkyw9eiGDx8uy5cvt9FrOgJOC86ZDBPwHwISgBQpX7588tNPP1kY0hFuWq+kAUgneUyVKuZ/GvVYDVYaiho3bmx1SpMnT7Z92oU2bNgw6d+/v9U79ejRw7aPGDFCBg0aZN1x7tdpl5sO+49u1KhR0rt3b6lcubKcPHlSvv32W0+rFIB7L0grtf3wvgAAZuAGkixakAAAABwISAAAAA50sQEAADjQggQAAOBAQAIAAHAgIAEAADgQkAAAABwISAAAAA4EJAAAAAcCEgAAgAMBCQAAQLz9P8/d4fC5ZmL0AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_rps_dynamics([1/3, 1/3, 1/3])" + ] + }, + { + "cell_type": "markdown", + "id": "a1f6662e", + "metadata": {}, + "source": [ + "When starting from an unbalanced initial mixed strategy profile, the strategy frequencies will oscillate around the equilibrium point without converging to it. However, if we plot the average strategy frequencies over time, we can see that this begins to converge to the equilibrium point:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "189f898f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_rps_dynamics([0.3, 0.3, 0.4], plot_average_strategy=True)" + ] + }, + { + "cell_type": "markdown", + "id": "078a21e0", + "metadata": {}, + "source": [ + "## Normal form games created with Gambit\n", + "\n", + "You can also set up a normal form game in Gambit and export it to OpenSpiel. Here we demonstrate this with the Prisoner's Dilemma game from tutorial 1." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "cdd0bfe0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

Prisoner's Dilemma

\n", + "
CooperateDefect
Cooperate-1,-1-3,0
Defect0,-3-2,-2
\n" + ], + "text/plain": [ + "Game(title='Prisoner's Dilemma')" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt_prisoners_dilemma_game = gbt.read_nfg(\"games/prisoners_dilemma.nfg\")\n", + "gbt_prisoners_dilemma_game" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "d42e6545", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[\\left[0,1\\right],\\left[0,1\\right]\\right]$" + ], + "text/plain": [ + "[[Rational(0, 1), Rational(1, 1)], [Rational(0, 1), Rational(1, 1)]]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.nash.lcp_solve(gbt_prisoners_dilemma_game).equilibria[0]" + ] + }, + { + "cell_type": "markdown", + "id": "15dd432d", + "metadata": {}, + "source": [ + "As expected, Gambit computes the equilibrium strategy for both players as choosing cooperate with probability 0 and defect with probability 1.\n", + "\n", + "To re-create the game in OpenSpiel we extract the player payoffs to NumPy arrays, which are then used to create a matrix game in OpenSpiel:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "fcd42af0", + "metadata": {}, + "outputs": [], + "source": [ + "p1_payoffs, p2_payoffs = gbt_prisoners_dilemma_game.to_arrays(dtype=float)\n", + "ops_prisoners_dilemma_game = pyspiel.create_matrix_game(\n", + " gbt_prisoners_dilemma_game.title,\n", + " \"Classic Prisoner's Dilemma\", # description\n", + " [strategy.label for strategy in gbt_prisoners_dilemma_game.players[0].strategies],\n", + " [strategy.label for strategy in gbt_prisoners_dilemma_game.players[1].strategies],\n", + " p1_payoffs,\n", + " p2_payoffs\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "625a35a4", + "metadata": {}, + "source": [ + "Like rock-paper-scissors, the Prisoner's Dilemma is a 1-step simultaneous-move normal form game; we'll apply a list of player actions in one step to reach the terminal state. Let's have both player choose to defect (1):" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "7ce6f2e2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Terminal? true\n", + "History: 1, 1\n", + "Returns: -2,-2\n", + "Row actions: \n", + "Col actions: \n", + "Utility matrix:\n", + "-1,-1 -3,0 \n", + "0,-3 -2,-2 " + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "state = ops_prisoners_dilemma_game.new_initial_state()\n", + "state.apply_actions([1, 1])\n", + "state" + ] + }, + { + "cell_type": "markdown", + "id": "1fea0224", + "metadata": {}, + "source": [ + "Unlike in rock-paper-scissors, the Prisoner's Dilemma has a dominant strategy equilibrium, in which both players defect.\n", + "Using evolutionary dynamics, we can see that a population starting with a mix of cooperators and defectors will evolve towards all defectors over time:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "d1495c7c", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "matrix_pd_payoffs = game_payoffs_array(ops_prisoners_dilemma_game)\n", + "pd_dyn = dynamics.SinglePopulationDynamics(matrix_pd_payoffs, dynamics.replicator)\n", + "\n", + "def plot_pd_dynamics(proportions, steps=100, alpha=0.1):\n", + " x = np.array(proportions)\n", + " y = []\n", + " for _ in range(steps):\n", + " x += alpha * pd_dyn(x)\n", + " y.append(x.copy())\n", + " y = np.array(y)\n", + " plt.plot(y[:, 0], label=\"Cooperate\")\n", + " plt.plot(y[:, 1], label=\"Defect\")\n", + " plt.xlabel(\"Time step\")\n", + " plt.ylabel(\"Frequency\")\n", + " plt.legend()\n", + " plt.show()\n", + "\n", + "plot_pd_dynamics([0.8, 0.2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "" + ] + }, + { + "cell_type": "markdown", + "id": "b12f6330", + "metadata": {}, + "source": [ + "## Extensive form games from the OpenSpiel library\n", + "\n", + "For extensive form games, OpenSpiel can export to the EFG format used by Gambit. Here we demonstrate this with **Tiny Hanabi**, loaded from the OpenSpiel [game library](https://openspiel.readthedocs.io/en/latest/games.html)." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "02a42600", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'EFG 2 R \"tiny_hanabi()\" { \"Pl0\" \"Pl1\" } \\nc \"\" 1 \"\" { \"d0\" 0.5000000000000000 \"d1\" 0.5000000000000000 } 0\\n c \"p0:d0\" 2 \"\" { \"d0\" 0.5000000000000000 \"d1\" 0.5000000000000000 } 0\\n p \"\" 1 1 \"\" { \"p0a0\" \"p0a1\" \"p0a2\" } 0\\n p \"\" 2 1 \"\" { \"p1a0\" \"p1a1\" \"p1a2\" } 0\\n t \"\" 1 \"\" { 10.0 10.0 }\\n t \"\" 2 \"\" { 0.0 0.0 }\\n t \"\" 3 \"\" { 0.0 0.0 }\\n p \"\" 2 2 \"\" { \"p1a0\" \"p1a1\" \"p1a2\" } 0\\n t \"\" 4 \"\" { 4.0 4.0 }\\n t \"\" 5 \"\" { 8.0 8.0 }\\n t \"\" 6 \"\" { 4.0 4.0 }\\n p \"\" 2 3 \"\" { \"p1a0\" \"p1a1\" \"p1a2\" } 0\\n t \"\" 7 \"\" { 10.0 10.0 }\\n t \"\" 8 \"\" { 0.0 0.0 }\\n t \"\" 9 \"\" { 0.0 0.0 }\\n p \"\" 1 1 \"\" { \"p0a0\" \"p0a1\" \"p0a2\" } 0\\n p \"\" 2 4 \"\" { \"p1a0\" \"p1a1\" \"p1a2\" } 0\\n t \"\" 10 \"\" { 0.0 0.0 }\\n t \"\" 11 \"\" { 0.0 0.0 }\\n t \"\" 12 \"\" { 10.0 10.0 }\\n p \"\" 2 5 \"\" { \"p1a0\" \"p1a1\" \"p1a2\" } 0\\n t \"\" 13 \"\" { 4.0 4.0 }\\n t \"\" 14 \"\" { 8.0 8.0 }\\n t \"\" 15 \"\" { 4.0 4.0 }\\n p \"\" 2 6 \"\" { \"p1a0\" \"p1a1\" \"p1a2\" } 0\\n t \"\" 16 \"\" { 0.0 0.0 }\\n t \"\" 17 \"\" { 0.0 0.0 }\\n t \"\" 18 \"\" { 10.0 10.0 }\\n c \"p0:d1\" 3 \"\" { \"d0\" 0.5000000000000000 \"d1\" 0.5000000000000000 } 0\\n p \"\" 1 2 \"\" { \"p0a0\" \"p0a1\" \"p0a2\" } 0\\n p \"\" 2 1 \"\" { \"p1a0\" \"p1a1\" \"p1a2\" } 0\\n t \"\" 19 \"\" { 0.0 0.0 }\\n t \"\" 20 \"\" { 0.0 0.0 }\\n t \"\" 21 \"\" { 10.0 10.0 }\\n p \"\" 2 2 \"\" { \"p1a0\" \"p1a1\" \"p1a2\" } 0\\n t \"\" 22 \"\" { 4.0 4.0 }\\n t \"\" 23 \"\" { 8.0 8.0 }\\n t \"\" 24 \"\" { 4.0 4.0 }\\n p \"\" 2 3 \"\" { \"p1a0\" \"p1a1\" \"p1a2\" } 0\\n t \"\" 25 \"\" { 0.0 0.0 }\\n t \"\" 26 \"\" { 0.0 0.0 }\\n t \"\" 27 \"\" { 0.0 0.0 }\\n p \"\" 1 2 \"\" { \"p0a0\" \"p0a1\" \"p0a2\" } 0\\n p \"\" 2 4 \"\" { \"p1a0\" \"p1a1\" \"p1a2\" } 0\\n t \"\" 28 \"\" { 10.0 10.0 }\\n t \"\" 29 \"\" { 0.0 0.0 }\\n t \"\" 30 \"\" { 0.0 0.0 }\\n p \"\" 2 5 \"\" { \"p1a0\" \"p1a1\" \"p1a2\" } 0\\n t \"\" 31 \"\" { 4.0 4.0 }\\n t \"\" 32 \"\" { 8.0 8.0 }\\n t \"\" 33 \"\" { 4.0 4.0 }\\n p \"\" 2 6 \"\" { \"p1a0\" \"p1a1\" \"p1a2\" } 0\\n t \"\" 34 \"\" { 10.0 10.0 }\\n t \"\" 35 \"\" { 0.0 0.0 }\\n t \"\" 36 \"\" { 0.0 0.0 }\\n'" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ops_hanabi_game = pyspiel.load_game(\"tiny_hanabi\")\n", + "efg_hanabi_game = export_gambit(ops_hanabi_game)\n", + "efg_hanabi_game" + ] + }, + { + "cell_type": "markdown", + "id": "fa354c9f", + "metadata": {}, + "source": [ + "Now let's load the EFG in Gambit.\n", + "We can then compute equilibria strategies for the players as usual." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "1a534e25", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pl0\n", + "Pl1\n" + ] + } + ], + "source": [ + "gbt_hanabi_game = gbt.read_efg(StringIO(efg_hanabi_game))\n", + "eqm = gbt.nash.lcp_solve(gbt_hanabi_game).equilibria[0]\n", + "for player in gbt_hanabi_game.players:\n", + " print(player.label)" + ] + }, + { + "cell_type": "markdown", + "id": "cdfe924e", + "metadata": {}, + "source": [ + "We can look at player 0's equilibrium strategy:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "1ec19b1c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[\\left[0,0,1\\right],\\left[0,1,0\\right]\\right]$" + ], + "text/plain": [ + "[[Rational(0, 1), Rational(0, 1), Rational(1, 1)], [Rational(0, 1), Rational(1, 1), Rational(0, 1)]]" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eqm['Pl0']" + ] + }, + { + "cell_type": "markdown", + "id": "b54411c0", + "metadata": {}, + "source": [ + "...and use Gambit to explore what those numbers actually mean for player 0:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "ae9fc7a7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "At information set 0, Player 0 plays action 0 with probability: 0 and action 1 with probability: 0 and action 2 with probability: 1\n", + "At information set 1, Player 0 plays action 0 with probability: 0 and action 1 with probability: 1 and action 2 with probability: 0\n" + ] + } + ], + "source": [ + "for infoset, mixed_action in eqm[\"Pl0\"].mixed_actions():\n", + " print(\n", + " f\"At information set {infoset.number}, \"\n", + " f\"Player 0 plays action 0 with probability: {mixed_action['p0a0']}\"\n", + " f\" and action 1 with probability: {mixed_action['p0a1']}\"\n", + " f\" and action 2 with probability: {mixed_action['p0a2']}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "eac73a24", + "metadata": {}, + "source": [ + "For player 1, we can do the same:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "8528e1bd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\left[\\left[0,0,1\\right],\\left[0,1,0\\right],\\left[1,0,0\\right],\\left[0,0,1\\right],\\left[0,1,0\\right],\\left[0,0,1\\right]\\right]$" + ], + "text/plain": [ + "[[Rational(0, 1), Rational(0, 1), Rational(1, 1)], [Rational(0, 1), Rational(1, 1), Rational(0, 1)], [Rational(1, 1), Rational(0, 1), Rational(0, 1)], [Rational(0, 1), Rational(0, 1), Rational(1, 1)], [Rational(0, 1), Rational(1, 1), Rational(0, 1)], [Rational(0, 1), Rational(0, 1), Rational(1, 1)]]" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eqm['Pl1']" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "2965aed0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "At information set 0, Player 1 plays action 0 with probability: 0 and action 1 with probability: 0 and action 2 with probability: 1\n", + "At information set 1, Player 1 plays action 0 with probability: 0 and action 1 with probability: 1 and action 2 with probability: 0\n", + "At information set 2, Player 1 plays action 0 with probability: 1 and action 1 with probability: 0 and action 2 with probability: 0\n", + "At information set 3, Player 1 plays action 0 with probability: 0 and action 1 with probability: 0 and action 2 with probability: 1\n", + "At information set 4, Player 1 plays action 0 with probability: 0 and action 1 with probability: 1 and action 2 with probability: 0\n", + "At information set 5, Player 1 plays action 0 with probability: 0 and action 1 with probability: 0 and action 2 with probability: 1\n" + ] + } + ], + "source": [ + "for infoset, mixed_action in eqm[\"Pl1\"].mixed_actions():\n", + " print(\n", + " f\"At information set {infoset.number}, \"\n", + " f\"Player 1 plays action 0 with probability: {mixed_action['p1a0']}\"\n", + " f\" and action 1 with probability: {mixed_action['p1a1']}\"\n", + " f\" and action 2 with probability: {mixed_action['p1a2']}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "d628c0d5", + "metadata": {}, + "source": [ + "Let's now train 2 agents using independent Q-learning on Tiny Hanabi, and play them against eachother.\n", + "\n", + "We can compare the learned strategies played to the equilibrium strategies computed by Gambit.\n", + "\n", + "First let's open the RL environment for Tiny Hanabi and create the agents, one for each player (2 players in this case):" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "4e72c924", + "metadata": {}, + "outputs": [], + "source": [ + "# Create the environment\n", + "env = rl_environment.Environment(\"tiny_hanabi\")\n", + "num_players = env.num_players\n", + "num_actions = env.action_spec()[\"num_actions\"]\n", + "\n", + "# Create the agents\n", + "agents = [\n", + " tabular_qlearner.QLearner(player_id=idx, num_actions=num_actions)\n", + " for idx in range(num_players)\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "4bf9eea4", + "metadata": {}, + "source": [ + "Now we can train the Q-learning agents in self-play." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "53547263", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Episodes: 0\n", + "Episodes: 10000\n", + "Episodes: 20000\n", + "Episodes: 30000\n" + ] + } + ], + "source": [ + "for cur_episode in range(30000):\n", + " if cur_episode % 10000 == 0:\n", + " print(f\"Episodes: {cur_episode}\")\n", + "\n", + " time_step = env.reset()\n", + " while not time_step.last():\n", + " player_id = time_step.observations[\"current_player\"]\n", + " agent_output = agents[player_id].step(time_step)\n", + " time_step = env.step([agent_output.action])\n", + "\n", + " # Episode is over, step all agents with final info state.\n", + " for agent in agents:\n", + " agent.step(time_step)\n", + "\n", + "print(f\"Episodes: {cur_episode+1}\")" + ] + }, + { + "cell_type": "markdown", + "id": "75cddd36", + "metadata": {}, + "source": [ + "Let's check out the strategies our agents have learned by playing them against eachother again, this time in evaluation mode (setting `is_evaluation=True`):" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "d71bc733", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "p0:d0 p1:d1\n", + "Agent 0 chooses p0a2\n", + "\n", + "p0:d0 p1:d1 p0:a2\n", + "Agent 1 chooses p1a2\n", + "\n", + "p0:d0 p1:d1 p0:a2 p1:a2\n", + "Rewards: [10.0, 10.0]\n" + ] + } + ], + "source": [ + "time_step = env.reset()\n", + "\n", + "while not time_step.last():\n", + " print(\"\")\n", + " print(env.get_state)\n", + "\n", + " player_id = time_step.observations[\"current_player\"]\n", + " agent_output = agents[player_id].step(time_step, is_evaluation=True)\n", + " print(f\"Agent {player_id} chooses {env.get_state.action_to_string(agent_output.action)}\")\n", + " time_step = env.step([agent_output.action])\n", + "\n", + "print(\"\")\n", + "print(env.get_state)\n", + "print(f\"Rewards: {time_step.rewards}\")" + ] + }, + { + "cell_type": "markdown", + "id": "f1e9b174", + "metadata": {}, + "source": [ + "Are the learned strategies chosen by p0 and p1 consistent with an equilibrium computed by Gambit?\n", + "\n", + "When I ran the above I got the final game state `p0:d0 p1:d0 p0:a2 p1:a0` with payoffs `[10.0, 10.0]`. This is consistent with the equilibrium computed by Gambit:\n", + "- The node `p0:d0 p1:d0` is part of player 0's information set 0.\n", + "- p0 picks a2 which matches the first equilibrium strategy in `eqm['Pl0']` where action `p0a2` is played with probability 1.0.\n", + "- This puts player 1 in their information set 2, and player 1 picks action 0, which is consistent with `eqm['Pl1']` where action `p1a0` is played with probability 1.0." + ] + }, + { + "cell_type": "markdown", + "id": "6f356383", + "metadata": {}, + "source": [ + "## Extensive form games created with Gambit\n", + "\n", + "It's also possible to create an extensive form game in Gambit and export it to OpenSpiel. Here we demonstrate this with the one-card poker game introduced in tutorial 3." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "07340e32", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "efg_game()" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with open(\"../poker.efg\", \"r\") as f:\n", + " poker_efg_string = f.read()\n", + " ops_one_card_poker = pyspiel.load_efg_game(poker_efg_string)\n", + "ops_one_card_poker" + ] + }, + { + "cell_type": "markdown", + "id": "ef6939f6", + "metadata": {}, + "source": [ + "Games loaded from EFG in OpenSpiel do not take advantage of the full functionality of the package, for example, it is not possible to carry out training with RL algorithms on these games, as in the example above with Tiny Hanabi. The OpenSpiel documentation explains [how to submit new games to the library](https://openspiel.readthedocs.io/en/latest/developer_guide.html#adding-a-game) if you wish to add your own games.\n", + "\n", + "We can however use the state representation and play through the game step by step:" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "c01c4d6f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ops_one_card_poker.num_distinct_actions()" + ] + }, + { + "cell_type": "markdown", + "id": "9986860c", + "metadata": {}, + "source": [ + "The one-card poker game has 4 distinct actions, 2 are for the first player (Alice in the example game): \"Raise\" and \"Fold\", and 2 for the second player (Bob): \"Meet\" and \"Pass\".\n", + "\n", + "Initialising the game state, we can see the current player at the start is the chance player, who deals the cards:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "3b9cc43b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0: Chance: 1 King 0.5 Queen 0.5" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "state = ops_one_card_poker.new_initial_state()\n", + "state" + ] + }, + { + "cell_type": "markdown", + "id": "7b0959f9", + "metadata": {}, + "source": [ + "Let's make the chance player's action dealing a King (action 0):" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "4dd5d504", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Player: 1 1 Raise Fold" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "state.apply_action(0)\n", + "state" + ] + }, + { + "cell_type": "markdown", + "id": "b4291f07", + "metadata": {}, + "source": [ + "As expected, it's now the first player's (Alice's) turn.\n", + "Let's have Alice choose to \"Raise\" (action 0):" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "bd15369f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3: Player: 2 1 Meet Pass" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "state.apply_action(0)\n", + "state" + ] + }, + { + "cell_type": "markdown", + "id": "cd63f7d7", + "metadata": {}, + "source": [ + "As expected, the current player is now player 2 (Bob), let's check the legal actions available to Bob:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "8d81ff6b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[2, 3]" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "state.legal_actions()" + ] + }, + { + "cell_type": "markdown", + "id": "fdb5194f", + "metadata": {}, + "source": [ + "Whereas player 1 (Alice) had the option to \"Raise\" (action 0) and \"Fold\" (action 1), player 2 (Bob) now has the option to \"Meet\" (action 2) or \"Pass\" (action 3).\n", + "Let's have Bob choose to \"Pass\":" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "97913fe5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6: Terminal: Alice wins 1 -1" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "state.apply_action(3)\n", + "state" + ] + }, + { + "cell_type": "markdown", + "id": "1bf09576", + "metadata": {}, + "source": [ + "Since Bob passed, Alice takes the small win and we reach a terminal state." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "gbt_pygraphviz", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/tutorials/games/prisoners_dilemma.nfg b/doc/tutorials/games/prisoners_dilemma.nfg new file mode 100644 index 000000000..a551362f6 --- /dev/null +++ b/doc/tutorials/games/prisoners_dilemma.nfg @@ -0,0 +1,14 @@ +NFG 1 R "Prisoner's Dilemma" { "Tom" "Jerry" } + +{ { "Cooperate" "Defect" } +{ "Cooperate" "Defect" } +} +"" + +{ +{ "" -1, -1 } +{ "" 0, -3 } +{ "" -3, 0 } +{ "" -2, -2 } +} +1 2 3 4 diff --git a/doc/tutorials/games/trust_game.efg b/doc/tutorials/games/trust_game.efg new file mode 100644 index 000000000..5b85cac9d --- /dev/null +++ b/doc/tutorials/games/trust_game.efg @@ -0,0 +1,8 @@ +EFG 2 R "One-shot trust game, after Kreps (1990)" { "Buyer" "Seller" } +"" + +p "" 1 1 "" { "Trust" "Not trust" } 0 +p "Trust" 2 1 "" { "Honor" "Abuse" } 0 +t "Honor" 1 "Trustworthy" { 1, 1 } +t "Abuse" 2 "Untrustworthy" { -1, 2 } +t "Not trust" 3 "Opt-out" { 0, 0 } diff --git a/doc/tutorials/running_locally.rst b/doc/tutorials/running_locally.rst new file mode 100644 index 000000000..f17eae6e2 --- /dev/null +++ b/doc/tutorials/running_locally.rst @@ -0,0 +1,22 @@ +.. _local_tutorials: + +How to run PyGambit tutorials on your computer +============================================== + +The PyGambit tutorials are available as Jupyter notebooks and can be run interactively using any program that supports Jupyter notebooks, such as JupyterLab or VSCode. +You will need a working installation of Python 3 (tested with 3.9 and later) on your machine. + +1. To download the tutorials, open your OS's command prompt and clone the Gambit repository from GitHub, then navigate to the tutorials directory: :: + + git clone https://github.com/gambitproject/gambit.git + cd gambit/doc/tutorials + +2. Install `PyGambit` and `JupyterLab`. We recommend creating a new virtual environment and installing both the requirements there. e.g. :: + + python -m venv pygambit-env + source pygambit-env/bin/activate + pip install pygambit jupyterlab + +3. Open `JupyterLab` and click on any of the tutorial notebooks (files ending in `.ipynb`) :: + + jupyter lab