diff --git a/build/pkgs/gambit/SPKG.rst b/build/pkgs/gambit/SPKG.rst deleted file mode 100644 index f266379f6ff..00000000000 --- a/build/pkgs/gambit/SPKG.rst +++ /dev/null @@ -1,30 +0,0 @@ -gambit: Computations on finite, noncooperative games -==================================================== - -Description ------------ - -Gambit is a set of software tools for doing computation on finite, -noncooperative games. The Gambit Project was founded in the mid-1980s by -Richard McKelvey at the California Institute of Technology. - -License -------- - -GPL v2+ - - -Upstream Contact ----------------- - -- Website: http://www.gambit-project.org/ -- Mailing List: http://sourceforge.net/p/gambit/mailman/gambit-devel/ - -Dependencies ------------- - -- python -- cython -- setuptools -- IPython -- scipy diff --git a/build/pkgs/gambit/checksums.ini b/build/pkgs/gambit/checksums.ini deleted file mode 100644 index 132796d9573..00000000000 --- a/build/pkgs/gambit/checksums.ini +++ /dev/null @@ -1,4 +0,0 @@ -tarball=gambit-VERSION.tar.gz -sha1=603dd52e8c0c2881bc2fdc8523bd8cbd9106b36f -md5=db47a02f66644806dbd43f77dc41ebeb -cksum=2352708160 diff --git a/build/pkgs/gambit/distros/homebrew.txt b/build/pkgs/gambit/distros/homebrew.txt deleted file mode 100644 index c08942b85ca..00000000000 --- a/build/pkgs/gambit/distros/homebrew.txt +++ /dev/null @@ -1 +0,0 @@ -gambit diff --git a/build/pkgs/gambit/distros/repology.txt b/build/pkgs/gambit/distros/repology.txt deleted file mode 100644 index 748786b4f51..00000000000 --- a/build/pkgs/gambit/distros/repology.txt +++ /dev/null @@ -1 +0,0 @@ -gambit-game-theory diff --git a/build/pkgs/gambit/package-version.txt b/build/pkgs/gambit/package-version.txt deleted file mode 100644 index 1b67f294f5f..00000000000 --- a/build/pkgs/gambit/package-version.txt +++ /dev/null @@ -1 +0,0 @@ -15.1.1.p0 diff --git a/build/pkgs/gambit/patches/b91115633dbf6f64745fda2db7fbb83f918dd500.patch b/build/pkgs/gambit/patches/b91115633dbf6f64745fda2db7fbb83f918dd500.patch deleted file mode 100644 index f99d88daa7f..00000000000 --- a/build/pkgs/gambit/patches/b91115633dbf6f64745fda2db7fbb83f918dd500.patch +++ /dev/null @@ -1,24 +0,0 @@ -From b91115633dbf6f64745fda2db7fbb83f918dd500 Mon Sep 17 00:00:00 2001 -From: Ted Turocy -Date: Fri, 7 Jul 2017 12:56:56 +0100 -Subject: [PATCH] Const-correctness fix in shared_ptr.h - -This corrects the parameter to weak_ptr::swap(), which had been -incorrectly labeled as const. ---- - src/libgambit/shared_ptr.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/libgambit/shared_ptr.h b/src/libgambit/shared_ptr.h -index 959510e5..4379840e 100644 ---- a/src/libgambit/shared_ptr.h -+++ b/src/libgambit/shared_ptr.h -@@ -131,7 +131,7 @@ template class weak_ptr { - long use_count(void) const { return *m_count; } - bool expired(void) const { return *m_count == 0; } - -- void swap(const weak_ptr &other) // never throws -+ void swap(weak_ptr &other) // never throws - { - std::swap(m_ptr, other.m_ptr); - std::swap(m_count, other.m_count); diff --git a/build/pkgs/gambit/spkg-install.in b/build/pkgs/gambit/spkg-install.in deleted file mode 100644 index 61b6e43c0d4..00000000000 --- a/build/pkgs/gambit/spkg-install.in +++ /dev/null @@ -1,15 +0,0 @@ -cd src - -sdh_configure --disable-gui -sdh_make -sdh_make_install - - -cd src/python - -# Remove outdated source file (https://github.com/gambitproject/gambit/pull/232) -rm gambit/lib/libgambit.cpp - -# pip doesn't work (https://github.com/gambitproject/gambit/issues/207) -sdh_setup_bdist_wheel -sdh_store_and_pip_install_wheel . diff --git a/build/pkgs/gambit/type b/build/pkgs/gambit/type deleted file mode 100644 index 9839eb20815..00000000000 --- a/build/pkgs/gambit/type +++ /dev/null @@ -1 +0,0 @@ -experimental diff --git a/build/pkgs/pygambit/SPKG.rst b/build/pkgs/pygambit/SPKG.rst new file mode 100644 index 00000000000..baeb650ac1f --- /dev/null +++ b/build/pkgs/pygambit/SPKG.rst @@ -0,0 +1,18 @@ +pygambit: The package for computation in game theory +==================================================== + +Description +----------- + +The package for computation in game theory + +License +------- + +GPL2+ + +Upstream Contact +---------------- + +https://pypi.org/project/pygambit/ + diff --git a/build/pkgs/gambit/dependencies b/build/pkgs/pygambit/dependencies similarity index 56% rename from build/pkgs/gambit/dependencies rename to build/pkgs/pygambit/dependencies index e026bfaacf9..67275503521 100644 --- a/build/pkgs/gambit/dependencies +++ b/build/pkgs/pygambit/dependencies @@ -1,4 +1,4 @@ -cython | $(PYTHON_TOOLCHAIN) $(PYTHON) +numpy scipy | $(PYTHON_TOOLCHAIN) cython $(PYTHON) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/gambit/math b/build/pkgs/pygambit/math similarity index 100% rename from build/pkgs/gambit/math rename to build/pkgs/pygambit/math diff --git a/build/pkgs/pygambit/requirements.txt b/build/pkgs/pygambit/requirements.txt new file mode 100644 index 00000000000..1f17aea867b --- /dev/null +++ b/build/pkgs/pygambit/requirements.txt @@ -0,0 +1 @@ +pygambit diff --git a/build/pkgs/pygambit/type b/build/pkgs/pygambit/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/pygambit/type @@ -0,0 +1 @@ +optional diff --git a/src/doc/en/developer/sage_manuals.rst b/src/doc/en/developer/sage_manuals.rst index 13f6a3da94a..d64ecb458a8 100644 --- a/src/doc/en/developer/sage_manuals.rst +++ b/src/doc/en/developer/sage_manuals.rst @@ -248,6 +248,10 @@ by Sage, you can link toward it without specifying its full path: - ``:ppl:`Linear_Expression ``` - :ppl:`Linear_Expression ` + * - :ref:`pygambit ` + - ``:pygambit:`pygambit.nash.lcp_solve``` + - :pygambit:`pygambit.nash.lcp_solve` + * - :ref:`QEPCAD ` - ``:qepcad:`QEPCAD: Entering formulas ``` - :qepcad:`QEPCAD: Entering formulas ` diff --git a/src/sage/features/gambit.py b/src/sage/features/gambit.py new file mode 100644 index 00000000000..22f97db36c7 --- /dev/null +++ b/src/sage/features/gambit.py @@ -0,0 +1,42 @@ +# sage_setup: distribution = sagemath-environment +r""" +Check for pygambit +""" + +# **************************************************************************** +# Copyright (C) 2024 Matthias Koeppe +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from . import PythonModule + + +class pygambit(PythonModule): + r""" + A :class:`sage.features.Feature` describing the presence of the + Python package :ref:`pygambit `. + + EXAMPLES:: + + sage: from sage.features.gambit import pygambit + sage: pygambit().is_present() # optional - pygambit + FeatureTestResult('pygambit', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.gambit import pygambit + sage: isinstance(pygambit(), pygambit) + True + """ + PythonModule.__init__(self, 'pygambit', spkg="pygambit") + + +def all_features(): + return [pygambit()] diff --git a/src/sage/game_theory/gambit_docs.py b/src/sage/game_theory/gambit_docs.py index e6acc5e6b69..2de7c57ad21 100644 --- a/src/sage/game_theory/gambit_docs.py +++ b/src/sage/game_theory/gambit_docs.py @@ -18,8 +18,8 @@ Here is an example that constructs the Prisoner's Dilemma:: - In [1]: import gambit - In [2]: g = gambit.Game.new_table([2,2]) + In [1]: import pygambit + In [2]: g = pygambit.Game.new_table([2,2]) In [3]: g.title = "A prisoner's dilemma game" In [4]: g.players[0].label = "Alphonse" In [5]: g.players[1].label = "Gaston" @@ -79,7 +79,7 @@ Here is how to use the ``ExternalEnumPureSolver``:: - In [21]: solver = gambit.nash.ExternalEnumPureSolver() + In [21]: solver = pygambit.nash.ExternalEnumPureSolver() In [22]: solver.solve(g) Out[22]: [] @@ -87,8 +87,8 @@ pure strategy pairs. This will fail to find all Nash equilibria in certain games. For example here is an implementation of Matching Pennies:: - In [1]: import gambit - In [2]: g = gambit.Game.new_table([2,2]) + In [1]: import pygambit + In [2]: g = pygambit.Game.new_table([2,2]) In [3]: g[0, 0][0] = 1 In [4]: g[0, 0][1] = -1 In [5]: g[0, 1][0] = -1 @@ -97,13 +97,13 @@ In [8]: g[1, 0][1] = 1 In [9]: g[1, 1][0] = 1 In [10]: g[1, 1][1] = -1 - In [11]: solver = gambit.nash.ExternalEnumPureSolver() + In [11]: solver = pygambit.nash.ExternalEnumPureSolver() In [12]: solver.solve(g) Out[12]: [] If we solve this with the ``LCP`` solver we get the expected Nash equilibrium:: - In [13]: solver = gambit.nash.ExternalLCPSolver() + In [13]: solver = pygambit.nash.ExternalLCPSolver() In [14]: solver.solve(g) Out[14]: [] @@ -116,9 +116,9 @@ converted to Python integers (due to the preparser). Here is an example showing the Battle of the Sexes:: - sage: # optional - gambit - sage: import gambit - sage: g = gambit.Game.new_table([2,2]) + sage: # optional - pygambit + sage: import pygambit + sage: g = pygambit.Game.new_table([2,2]) sage: g[int(0), int(0)][int(0)] = int(2) sage: g[int(0), int(0)][int(1)] = int(1) sage: g[int(0), int(1)][int(0)] = int(0) @@ -127,7 +127,7 @@ sage: g[int(1), int(0)][int(1)] = int(0) sage: g[int(1), int(1)][int(0)] = int(1) sage: g[int(1), int(1)][int(1)] = int(2) - sage: solver = gambit.nash.ExternalLCPSolver() + sage: solver = pygambit.nash.ExternalLCPSolver() sage: solver.solve(g) [, , diff --git a/src/sage/game_theory/normal_form_game.py b/src/sage/game_theory/normal_form_game.py index ce5002b3e9f..29410c3ea4d 100644 --- a/src/sage/game_theory/normal_form_game.py +++ b/src/sage/game_theory/normal_form_game.py @@ -6,17 +6,18 @@ [NN2007]_. At present the following algorithms are implemented to compute equilibria of these games: - * ``'enumeration'`` - An implementation of the support enumeration - algorithm built in Sage. +* ``'enumeration'`` -- An implementation of the support enumeration + algorithm built in Sage. - * ``'LCP'`` - An interface with the 'gambit' solver's implementation - of the Lemke-Howson algorithm. +* ``'LCP'`` -- An interface with the :ref:`pygambit ` + solver's implementation of the Lemke-Howson algorithm. - * ``'lp'`` - A built-in Sage implementation (with a gambit alternative) - of a zero-sum game solver using linear programming. See - :class:`MixedIntegerLinearProgram` for more on MILP solvers in Sage. +* ``'lp'`` -- A built-in Sage implementation (with a gambit alternative) + of a zero-sum game solver using linear programming. See + :class:`MixedIntegerLinearProgram` for more on MILP solvers in Sage. - * ``'lrs'`` - A solver interfacing with the 'lrslib' library. +* ``'lrs'`` -- A solver interfacing with the :ref:`lrslib ` + library. The architecture for the class is based on the gambit architecture to ensure an easy transition between gambit and Sage. At present the @@ -222,22 +223,22 @@ * ``'lp'``: A solver for constant sum 2 player games using linear programming. This constructs a - :mod:`MixedIntegerLinearProgram ` using the + :mod:`MixedIntegerLinearProgram ` using the solver which was passed in with ``solver`` to solve the linear programming representation of the game. See :class:`MixedIntegerLinearProgram` for more on MILP solvers in Sage. * ``'lrs'``: Reverse search vertex enumeration for 2 player games. This - algorithm uses the optional 'lrslib' package. To install it, type + algorithm uses the optional :ref:`lrslib ` package. To install it, type ``sage -i lrslib`` in the shell. For more information, see [Av2000]_. * ``'LCP'``: Linear complementarity program algorithm for 2 player games. - This algorithm uses the open source game theory package: - `Gambit `_ [Gambit]_. At present this is - the only gambit algorithm available in sage but further development will + This algorithm uses the open source game theory package + :ref:`pygambit ` [Gambit]_. At present this is + the only gambit algorithm available in Sage but further development will hope to implement more algorithms (in particular for games with more than 2 players). To install it, - type ``sage -i gambit`` in the shell. + type ``sage -i pygambit`` in the shell. * ``'enumeration'``: Support enumeration for 2 player games. This algorithm is hard coded in Sage and checks through all potential @@ -251,11 +252,11 @@ sage: matching_pennies.obtain_nash(algorithm='lrs') # optional - lrslib [[(1/2, 1/2), (1/2, 1/2)]] - sage: matching_pennies.obtain_nash(algorithm='LCP') # optional - gambit + sage: matching_pennies.obtain_nash(algorithm='LCP') # optional - pygambit [[(0.5, 0.5), (0.5, 0.5)]] sage: matching_pennies.obtain_nash(algorithm='lp', solver='PPL') [[(1/2, 1/2), (1/2, 1/2)]] - sage: matching_pennies.obtain_nash(algorithm='lp', solver='gambit') # optional - gambit + sage: matching_pennies.obtain_nash(algorithm='lp', solver='gambit') # optional - pygambit [[(0.5, 0.5), (0.5, 0.5)]] sage: matching_pennies.obtain_nash(algorithm='enumeration') [[(1/2, 1/2), (1/2, 1/2)]] @@ -425,13 +426,13 @@ [[(1/5, 4/5), (3/5, 2/5)]] sage: A = 2 * A sage: g = NormalFormGame([A, B]) - sage: g.obtain_nash(algorithm='LCP') # optional - gambit + sage: g.obtain_nash(algorithm='LCP') # optional - pygambit [[(0.2, 0.8), (0.6, 0.4)]] It is also possible to generate a Normal form game from a gambit Game:: - sage: # optional - gambit - sage: from gambit import Game + sage: # optional - pygambit + sage: from pygambit import Game sage: gambitgame= Game.new_table([2, 2]) sage: gambitgame[int(0), int(0)][int(0)] = int(8) sage: gambitgame[int(0), int(0)][int(1)] = int(8) @@ -484,7 +485,7 @@ sage: g = NormalFormGame([A, B]) sage: g.obtain_nash(algorithm='lrs') # optional - lrslib [[(0, 0, 0, 0, 0, 0, 0, 0, 1), (0, 0, 0, 0, 0, 0, 0, 0, 1)]] - sage: g.obtain_nash(algorithm='LCP') # optional - gambit + sage: g.obtain_nash(algorithm='LCP') # optional - pygambit [[(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0), (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0)]] @@ -572,7 +573,7 @@ sage: degenerate_game = NormalFormGame([A,B]) sage: degenerate_game.obtain_nash(algorithm='lrs') # random, optional - lrslib [[(0, 1/3, 2/3), (1/3, 2/3)], [(1, 0, 0), (1/2, 3)], [(1, 0, 0), (1, 3)]] - sage: degenerate_game.obtain_nash(algorithm='LCP') # optional - gambit + sage: degenerate_game.obtain_nash(algorithm='LCP') # optional - pygambit [[(0.0, 0.3333333333, 0.6666666667), (0.3333333333, 0.6666666667)], [(1.0, -0.0, 0.0), (0.6666666667, 0.3333333333)], [(1.0, 0.0, 0.0), (1.0, 0.0)]] @@ -584,7 +585,7 @@ sage: degenerate_game.is_degenerate() True -Note the 'negative' `-0.0` output by gambit. This is due to the numerical +Note the 'negative' `-0.0` output by pygambit. This is due to the numerical nature of the algorithm used. Here is an example with the trivial game where all payoffs are 0:: @@ -652,8 +653,8 @@ from sage.cpython.string import bytes_to_str try: - from gambit import Game - from gambit.nash import ExternalLPSolver, ExternalLCPSolver + from pygambit import Game + from pygambit.nash import ExternalLPSolver, ExternalLCPSolver except ImportError: Game = None ExternalLPSolver = None @@ -718,8 +719,8 @@ def __init__(self, generator=None): Can initialise a game from a gambit game object:: - sage: # optional - gambit - sage: from gambit import Game + sage: # optional - pygambit + sage: from pygambit import Game sage: gambitgame= Game.new_table([2, 2]) sage: gambitgame[int(0), int(0)][int(0)] = int(5) sage: gambitgame[int(0), int(0)][int(1)] = int(8) @@ -979,8 +980,8 @@ def _gambit_game(self, game): TESTS:: - sage: # optional - gambit - sage: from gambit import Game + sage: # optional - pygambit + sage: from pygambit import Game sage: testgame = Game.new_table([2, 2]) sage: testgame[int(0), int(0)][int(0)] = int(8) sage: testgame[int(0), int(0)][int(1)] = int(8) @@ -1020,8 +1021,8 @@ def _gambit_(self, as_integer=False, maximization=True): TESTS:: - sage: # optional - gambit - sage: from gambit import Game + sage: # optional - pygambit + sage: from pygambit import Game sage: A = matrix([[2, 1], [1, 2.5]]) sage: g = NormalFormGame([A]) sage: gg = g._gambit_(); gg @@ -1059,7 +1060,7 @@ def _gambit_(self, as_integer=False, maximization=True): :: - sage: # optional - gambit + sage: # optional - pygambit sage: A = matrix([[2, 1], [1, 2.5]]) sage: B = matrix([[3, 2], [5.5, 4]]) sage: g = NormalFormGame([A, B]) @@ -1098,7 +1099,7 @@ def _gambit_(self, as_integer=False, maximization=True): :: - sage: # optional - gambit + sage: # optional - pygambit sage: threegame = NormalFormGame() sage: threegame.add_player(2) sage: threegame.add_player(2) @@ -1523,7 +1524,7 @@ def obtain_nash(self, algorithm=False, maximization=True, solver=None): [[(0, 0, 3/4, 1/4), (1/28, 27/28, 0)]] sage: g.obtain_nash(algorithm='lrs') # optional - lrslib [[(0, 0, 3/4, 1/4), (1/28, 27/28, 0)]] - sage: g.obtain_nash(algorithm='LCP') # optional - gambit + sage: g.obtain_nash(algorithm='LCP') # optional - pygambit [[(0.0, 0.0, 0.75, 0.25), (0.0357142857, 0.9642857143, 0.0)]] 2 random matrices:: @@ -1543,7 +1544,7 @@ def obtain_nash(self, algorithm=False, maximization=True, solver=None): [[(1, 0, 0, 0, 0), (0, 1, 0, 0, 0)]] sage: fivegame.obtain_nash(algorithm='lrs') # optional - lrslib [[(1, 0, 0, 0, 0), (0, 1, 0, 0, 0)]] - sage: fivegame.obtain_nash(algorithm='LCP') # optional - gambit + sage: fivegame.obtain_nash(algorithm='LCP') # optional - pygambit [[(1.0, 0.0, 0.0, 0.0, 0.0), (0.0, 1.0, 0.0, 0.0, 0.0)]] Here are some examples of finding Nash equilibria for constant-sum games:: @@ -1556,7 +1557,7 @@ def obtain_nash(self, algorithm=False, maximization=True, solver=None): [[(0.5, 0.5), (0.5, 0.5)]] sage: cg.obtain_nash(algorithm='lp', solver='PPL') [[(1/2, 1/2), (1/2, 1/2)]] - sage: cg.obtain_nash(algorithm='lp', solver='gambit') # optional - gambit + sage: cg.obtain_nash(algorithm='lp', solver='gambit') # optional - pygambit [[(0.5, 0.5), (0.5, 0.5)]] sage: A = matrix([[2, 1], [1, 3]]) sage: cg = NormalFormGame([A]) @@ -1568,8 +1569,8 @@ def obtain_nash(self, algorithm=False, maximization=True, solver=None): [[[0.666667, 0.333333], [0.666667, 0.333333]]] sage: cg.obtain_nash(algorithm='lp', solver='PPL') [[(2/3, 1/3), (2/3, 1/3)]] - sage: ne = cg.obtain_nash(algorithm='lp', solver='gambit') # optional - gambit - sage: [[[round(el, 6) for el in v] for v in eq] for eq in ne] # optional - gambit + sage: ne = cg.obtain_nash(algorithm='lp', solver='gambit') # optional - pygambit + sage: [[[round(el, 6) for el in v] for v in eq] for eq in ne] # optional - pygambit [[[0.666667, 0.333333], [0.666667, 0.333333]]] sage: A = matrix([[1, 2, 1], [1, 1, 2], [2, 1, 1]]) sage: B = matrix([[2, 1, 2], [2, 2, 1], [1, 2, 2]]) @@ -1582,8 +1583,8 @@ def obtain_nash(self, algorithm=False, maximization=True, solver=None): [[[0.333333, 0.333333, 0.333333], [0.333333, 0.333333, 0.333333]]] sage: cg.obtain_nash(algorithm='lp', solver='PPL') [[(1/3, 1/3, 1/3), (1/3, 1/3, 1/3)]] - sage: ne = cg.obtain_nash(algorithm='lp', solver='gambit') # optional - gambit - sage: [[[round(el, 6) for el in v] for v in eq] for eq in ne] # optional - gambit + sage: ne = cg.obtain_nash(algorithm='lp', solver='gambit') # optional - pygambit + sage: [[[round(el, 6) for el in v] for v in eq] for eq in ne] # optional - pygambit [[[0.333333, 0.333333, 0.333333], [0.333333, 0.333333, 0.333333]]] sage: A = matrix([[160, 205, 44], ....: [175, 180, 45], @@ -1629,7 +1630,7 @@ def obtain_nash(self, algorithm=False, maximization=True, solver=None): [[(0, 1), (0, 1)], [(0, 1), (1, 0)], [(1, 0), (0, 1)], [(1, 0), (1, 0)]] sage: gg.obtain_nash(algorithm='lp', solver='glpk') [[(1.0, 0.0), (1.0, 0.0)]] - sage: gg.obtain_nash(algorithm='LCP') # optional - gambit + sage: gg.obtain_nash(algorithm='LCP') # optional - pygambit [[(1.0, 0.0), (1.0, 0.0)]] sage: gg.obtain_nash(algorithm='enumeration', maximization=False) [[(0, 1), (0, 1)], [(0, 1), (1, 0)], [(1, 0), (0, 1)], [(1, 0), (1, 0)]] @@ -1637,7 +1638,7 @@ def obtain_nash(self, algorithm=False, maximization=True, solver=None): [[(0, 1), (0, 1)], [(0, 1), (1, 0)], [(1, 0), (0, 1)], [(1, 0), (1, 0)]] sage: gg.obtain_nash(algorithm='lp', solver='glpk', maximization=False) [[(1.0, 0.0), (1.0, 0.0)]] - sage: gg.obtain_nash(algorithm='LCP', maximization=False) # optional - gambit + sage: gg.obtain_nash(algorithm='LCP', maximization=False) # optional - pygambit [[(1.0, 0.0), (1.0, 0.0)]] Note that outputs for all algorithms are as lists of lists of @@ -1650,20 +1651,20 @@ def obtain_nash(self, algorithm=False, maximization=True, solver=None): sage: lrs_eqs = g.obtain_nash(algorithm='lrs') # optional - lrslib sage: [[type(s) for s in eq] for eq in lrs_eqs] # optional - lrslib [[<... 'tuple'>, <... 'tuple'>], [<... 'tuple'>, <... 'tuple'>], [<... 'tuple'>, <... 'tuple'>]] - sage: LCP_eqs = g.obtain_nash(algorithm='LCP') # optional - gambit - sage: [[type(s) for s in eq] for eq in LCP_eqs] # optional - gambit + sage: LCP_eqs = g.obtain_nash(algorithm='LCP') # optional - pygambit + sage: [[type(s) for s in eq] for eq in LCP_eqs] # optional - pygambit [[<... 'tuple'>, <... 'tuple'>], [<... 'tuple'>, <... 'tuple'>], [<... 'tuple'>, <... 'tuple'>]] sage: enumeration_eqs == sorted(enumeration_eqs) True sage: lrs_eqs == sorted(lrs_eqs) # optional - lrslib True - sage: LCP_eqs == sorted(LCP_eqs) # optional - gambit + sage: LCP_eqs == sorted(LCP_eqs) # optional - pygambit True sage: lrs_eqs == enumeration_eqs # optional - lrslib True - sage: enumeration_eqs == LCP_eqs # optional - gambit + sage: enumeration_eqs == LCP_eqs # optional - pygambit False - sage: [[[round(float(p), 6) for p in str] for str in eq] for eq in enumeration_eqs] == [[[round(float(p), 6) for p in str] for str in eq] for eq in LCP_eqs] # optional - gambit + sage: [[[round(float(p), 6) for p in str] for str in eq] for eq in enumeration_eqs] == [[[round(float(p), 6) for p in str] for str in eq] for eq in LCP_eqs] # optional - pygambit True Also, not specifying a valid solver would lead to an error:: @@ -1790,7 +1791,7 @@ def _solve_LCP(self, maximization): sage: a = matrix([[1, 0], [1, 4]]) sage: b = matrix([[2, 3], [2, 4]]) sage: c = NormalFormGame([a, b]) - sage: c._solve_LCP(maximization=True) # optional - gambit + sage: c._solve_LCP(maximization=True) # optional - pygambit [[(0.0, 1.0), (0.0, 1.0)]] """ g = self._gambit_(maximization) @@ -1807,14 +1808,14 @@ def _solve_gambit_LP(self, maximization=True): sage: A = matrix([[2, 1], [1, 2.5]]) sage: g = NormalFormGame([A]) - sage: g._solve_gambit_LP() # optional - gambit + sage: g._solve_gambit_LP() # optional - pygambit [[(0.6, 0.4), (0.6, 0.4)]] sage: A = matrix.identity(2) sage: g = NormalFormGame([A]) - sage: g._solve_gambit_LP() # optional - gambit + sage: g._solve_gambit_LP() # optional - pygambit [[(0.5, 0.5), (0.5, 0.5)]] sage: g = NormalFormGame([A,A]) - sage: g._solve_gambit_LP() # optional - gambit + sage: g._solve_gambit_LP() # optional - pygambit Traceback (most recent call last): ... RuntimeError: Method only valid for constant-sum games. @@ -1846,7 +1847,7 @@ def _solve_LP(self, solver='glpk', maximization=True): sage: g = NormalFormGame([A]) sage: g._solve_LP() [[(0.5, 0.5), (0.5, 0.5)]] - sage: g._solve_LP('gambit') # optional - gambit + sage: g._solve_LP('gambit') # optional - pygambit [[(0.5, 0.5), (0.5, 0.5)]] sage: g._solve_LP('Coin') # optional - sage_numerical_backends_coin [[(0.5, 0.5), (0.5, 0.5)]] @@ -1857,8 +1858,8 @@ def _solve_LP(self, solver='glpk', maximization=True): sage: ne = g._solve_LP() sage: [[[round(el, 6) for el in v] for v in eq] for eq in ne] [[[0.666667, 0.333333], [0.666667, 0.333333]]] - sage: ne = g._solve_LP('gambit') # optional - gambit - sage: [[[round(el, 6) for el in v] for v in eq] for eq in ne] # optional - gambit + sage: ne = g._solve_LP('gambit') # optional - pygambit + sage: [[[round(el, 6) for el in v] for v in eq] for eq in ne] # optional - pygambit [[[0.666667, 0.333333], [0.666667, 0.333333]]] sage: ne = g._solve_LP('Coin') # optional - sage_numerical_backends_coin sage: [[[round(el, 6) for el in v] for v in eq] for eq in ne] # optional - sage_numerical_backends_coin @@ -2507,7 +2508,7 @@ def is_degenerate(self, certificate=False): [[(0, 0, 1, 0), (0, 1, 0, 0)], [(17/29, 0, 0, 12/29), (0, 0, 42/73, 31/73)], [(122/145, 0, 23/145, 0), (0, 1, 0, 0)]] - sage: d_game.obtain_nash(algorithm='LCP') # optional - gambit + sage: d_game.obtain_nash(algorithm='LCP') # optional - pygambit [[(0.5862068966, 0.0, 0.0, 0.4137931034), (0.0, 0.0, 0.5753424658, 0.4246575342)]] sage: d_game.obtain_nash(algorithm='enumeration') diff --git a/src/sage/game_theory/parser.py b/src/sage/game_theory/parser.py index 0fd8892a6b1..bd8ca155e56 100644 --- a/src/sage/game_theory/parser.py +++ b/src/sage/game_theory/parser.py @@ -195,10 +195,10 @@ def format_gambit(self, gambit_game): Here we construct a two by two game in gambit:: - sage: # optional - gambit - sage: import gambit + sage: # optional - pygambit + sage: import pygambit sage: from sage.game_theory.parser import Parser - sage: g = gambit.Game.new_table([2,2]) + sage: g = pygambit.Game.new_table([2,2]) sage: g[int(0), int(0)][int(0)] = int(2) sage: g[int(0), int(0)][int(1)] = int(1) sage: g[int(0), int(1)][int(0)] = int(0) @@ -207,28 +207,28 @@ def format_gambit(self, gambit_game): sage: g[int(1), int(0)][int(1)] = int(0) sage: g[int(1), int(1)][int(0)] = int(1) sage: g[int(1), int(1)][int(1)] = int(2) - sage: solver = gambit.nash.ExternalLCPSolver() + sage: solver = pygambit.nash.ExternalLCPSolver() Here is the output of the LCP algorithm:: - sage: LCP_output = solver.solve(g) # optional - gambit - sage: LCP_output # optional - gambit + sage: LCP_output = solver.solve(g) # optional - pygambit + sage: LCP_output # optional - pygambit [, , ] The Parser class outputs the equilibrium:: - sage: nasheq = Parser(LCP_output).format_gambit(g) # optional - gambit - sage: nasheq # optional - gambit + sage: nasheq = Parser(LCP_output).format_gambit(g) # optional - pygambit + sage: nasheq # optional - pygambit [[(1.0, 0.0), (1.0, 0.0)], [(0.6666666667, 0.3333333333), (0.3333333333, 0.6666666667)], [(0.0, 1.0), (0.0, 1.0)]] Here is another game:: - sage: # optional - gambit - sage: g = gambit.Game.new_table([2,2]) + sage: # optional - pygambit + sage: g = pygambit.Game.new_table([2,2]) sage: g[int(0), int(0)][int(0)] = int(4) sage: g[int(0), int(0)][int(1)] = int(8) sage: g[int(0), int(1)][int(0)] = int(0) @@ -237,24 +237,24 @@ def format_gambit(self, gambit_game): sage: g[int(1), int(0)][int(1)] = int(3) sage: g[int(1), int(1)][int(0)] = int(1) sage: g[int(1), int(1)][int(1)] = int(0) - sage: solver = gambit.nash.ExternalLCPSolver() + sage: solver = pygambit.nash.ExternalLCPSolver() Here is the LCP output:: - sage: LCP_output = solver.solve(g) # optional - gambit - sage: LCP_output # optional - gambit + sage: LCP_output = solver.solve(g) # optional - pygambit + sage: LCP_output # optional - pygambit [] The corresponding parsed equilibrium:: - sage: nasheq = Parser(LCP_output).format_gambit(g) # optional - gambit - sage: nasheq # optional - gambit + sage: nasheq = Parser(LCP_output).format_gambit(g) # optional - pygambit + sage: nasheq # optional - pygambit [[(1.0, 0.0), (1.0, 0.0)]] Here is a larger degenerate game:: - sage: # optional - gambit - sage: g = gambit.Game.new_table([3,3]) + sage: # optional - pygambit + sage: g = pygambit.Game.new_table([3,3]) sage: g[int(0), int(0)][int(0)] = int(-7) sage: g[int(0), int(0)][int(1)] = int(-9) sage: g[int(0), int(1)][int(0)] = int(-5) @@ -273,20 +273,20 @@ def format_gambit(self, gambit_game): sage: g[int(2), int(1)][int(1)] = int(6) sage: g[int(2), int(2)][int(0)] = int(1) sage: g[int(2), int(2)][int(1)] = int(-10) - sage: solver = gambit.nash.ExternalLCPSolver() + sage: solver = pygambit.nash.ExternalLCPSolver() Here is the LCP output:: - sage: LCP_output = solver.solve(g) # optional - gambit - sage: LCP_output # optional - gambit + sage: LCP_output = solver.solve(g) # optional - pygambit + sage: LCP_output # optional - pygambit [, , ] The corresponding parsed equilibrium:: - sage: nasheq = Parser(LCP_output).format_gambit(g) # optional - gambit - sage: nasheq # optional - gambit + sage: nasheq = Parser(LCP_output).format_gambit(g) # optional - pygambit + sage: nasheq # optional - pygambit [[(1.0, 0.0, 0.0), (0.0, 0.0, 1.0)], [(0.3333333333, 0.6666666667, 0.0), (0.1428571429, 0.0, 0.8571428571)], [(0.0, 1.0, 0.0), (1.0, 0.0, 0.0)]] diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index f3e5e93ec12..5ba9edc729c 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -528,6 +528,7 @@ def process_dollars(s): 'meson': ('https://mesonbuild.com/%s', 'Meson: %s'), 'polymake': ('https://polymake.org/doku.php/documentation/latest/%s', 'polymake: %s'), 'ppl': ('https://www.bugseng.com/products/ppl/documentation/user/ppl-user-1.2-html/%s.html', 'PPL: %s'), + 'pygambit': ('https://gambitproject.readthedocs.io/en/latest/api/%s.html', '%s'), 'qepcad': ('https://www.usna.edu/CS/qepcadweb/B/%s.html', 'QEPCAD: %s'), 'scip': ('https://scipopt.org/doc/html/%s.php', 'SCIP: %s'), 'singular': ('https://www.singular.uni-kl.de/Manual/4-3-2/%s.htm', 'Singular: %s'), diff --git a/src/tox.ini b/src/tox.ini index aab671441a9..dcd149b97d2 100644 --- a/src/tox.ini +++ b/src/tox.ini @@ -276,6 +276,7 @@ rst-roles = maxima, meson, polymake, + pygambit, ppl, qepcad, scip,