Skip to content

Commit

Permalink
Merge branch 'main' into improve-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
mmcky authored Nov 27, 2024
2 parents ad35eeb + 494603d commit 54d71fb
Show file tree
Hide file tree
Showing 17 changed files with 1,259 additions and 2 deletions.
3 changes: 3 additions & 0 deletions docs/source/game_theory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ Game theory

game_theory/brd
game_theory/fictplay
game_theory/game_converters
game_theory/howson_lcp
game_theory/lemke_howson
game_theory/localint
game_theory/logitdyn
game_theory/mclennan_tourky
game_theory/normal_form_game
game_theory/polymatrix_game
game_theory/pure_nash
game_theory/random
game_theory/repeated_game
Expand Down
7 changes: 7 additions & 0 deletions docs/source/game_theory/game_converters.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
game_converters
===============

.. automodule:: quantecon.game_theory.game_converters
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/source/game_theory/howson_lcp.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
howson_lcp
==========

.. automodule:: quantecon.game_theory.howson_lcp
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/source/game_theory/polymatrix_game.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
polymatrix_game
===============

.. automodule:: quantecon.game_theory.polymatrix_game
:members:
:undoc-members:
:show-inheritance:
1 change: 1 addition & 0 deletions docs/source/util.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Utilities
util/array
util/combinatorics
util/common_messages
util/compat
util/notebooks
util/numba
util/random
Expand Down
7 changes: 7 additions & 0 deletions docs/source/util/compat.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
compat
======

.. automodule:: quantecon.util.compat
:members:
:undoc-members:
:show-inheritance:
5 changes: 4 additions & 1 deletion quantecon/game_theory/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from .normal_form_game import Player, NormalFormGame
from .normal_form_game import pure2mixed, best_response_2p
from .random import (
random_game, covariance_game, random_pure_actions, random_mixed_actions
random_game, covariance_game, random_polymatrix_game,
random_pure_actions, random_mixed_actions
)
from .pure_nash import pure_nash_brute, pure_nash_brute_gen
from .support_enumeration import support_enumeration, support_enumeration_gen
Expand All @@ -21,3 +22,5 @@
from .localint import LocalInteraction
from .brd import BRD, KMR, SamplingBRD
from .logitdyn import LogitDynamics
from .polymatrix_game import PolymatrixGame
from .howson_lcp import polym_lcp_solver
82 changes: 82 additions & 0 deletions quantecon/game_theory/game_converters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""
Functions for converting between ways of storing games.
Examples
--------
Create a QuantEcon NormalFormGame from a gam file storing
a 3-player Minimum Effort Game
>>> filepath = "./tests/gam_files/minimum_effort_game.gam"
>>> nfg = qe_nfg_from_gam_file(filepath)
>>> print(nfg)
3-player NormalFormGame with payoff profile array:
[[[[ 1., 1., 1.], [ 1., 1., -9.], [ 1., 1., -19.]],
[[ 1., -9., 1.], [ 1., -9., -9.], [ 1., -9., -19.]],
[[ 1., -19., 1.], [ 1., -19., -9.], [ 1., -19., -19.]]],
<BLANKLINE>
[[[ -9., 1., 1.], [ -9., 1., -9.], [ -9., 1., -19.]],
[[ -9., -9., 1.], [ 2., 2., 2.], [ 2., 2., -8.]],
[[ -9., -19., 1.], [ 2., -8., 2.], [ 2., -8., -8.]]],
<BLANKLINE>
[[[-19., 1., 1.], [-19., 1., -9.], [-19., 1., -19.]],
[[-19., -9., 1.], [ -8., 2., 2.], [ -8., 2., -8.]],
[[-19., -19., 1.], [ -8., -8., 2.], [ 3., 3., 3.]]]]
"""

from .normal_form_game import NormalFormGame
from itertools import product


def qe_nfg_from_gam_file(filename: str) -> NormalFormGame:
"""
Makes a QuantEcon Normal Form Game from a gam file.
Gam files are described by GameTracer [1]_.
Parameters
----------
filename : str
path to gam file.
Returns
-------
NormalFormGame
The QuantEcon Normal Form Game described by the gam file.
References
----------
.. [1] Bem Blum, Daphne Kohler, Christian Shelton
http://dags.stanford.edu/Games/gametracer.html
"""
with open(filename, 'r') as file:
lines = file.readlines()
combined = [
token
for line in lines
for token in line.split()
]

i = iter(combined)
players = int(next(i))
actions = [int(next(i)) for _ in range(players)]

nfg = NormalFormGame(actions)

entries = [
{
tuple(reversed(action_combination)): float(next(i))
for action_combination in product(
*[range(a) for a in actions])
}
for _ in range(players)
]

for action_combination in product(*[range(a) for a in actions]):
nfg[action_combination] = tuple(
entries[p][action_combination] for p in range(players)
)

return nfg
213 changes: 213 additions & 0 deletions quantecon/game_theory/howson_lcp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
"""
Compute a Nash Equilibrium of a Polymatrix Game.
Examples
--------
Find a Nash Equilibrium of a Matching Pennies Game.
(This NE is unique so this works reliably.)
>>> matrices = {
... (0, 1): [[1., -1.], [-1., 1.]],
... (1, 0): [[-1., 1.], [1., -1.]]
... }
>>> polymg = PolymatrixGame(matrices)
>>> result = polym_lcp_solver(polymg, full_output=True)
>>> print(result[0])
(array([0.5, 0.5]), array([0.5, 0.5]))
>>> print(result[1])
NE: (array([0.5, 0.5]), array([0.5, 0.5]))
converged: True
init: {0: 0, 1: 0}
max_iter: -1
num_iter: 4
"""

import numpy as np
from .polymatrix_game import PolymatrixGame
from ..optimize.pivoting import _pivoting, _lex_min_ratio_test
from ..optimize.lcp_lemke import _get_solution
from .utilities import NashResult
from typing import Sequence, Union, Optional
from numpy.typing import NDArray


def polym_lcp_solver(
polymg: PolymatrixGame,
starting_player_actions: Optional[Sequence[int]] = None,
max_iter: int = -1,
full_output: bool = False,
) -> Union[tuple[NDArray], NashResult]:
"""
Finds a Nash Equilbrium of a Polymatrix Game.
Uses Howson's algorithm which utilises
linear complementarity [1]_.
Parameters
----------
polymg : PolymatrixGame
Polymatrix game to solve.
starting_player_actions : Sequence[int], optional
Pure actions for each player at which the algorithm begins.
Defaults to each player playing their first action.
max_iter : int, optional
Maximum number of iterations of the complementary
pivoting before giving up. Howson proves that with enough
iterations, it will reach a Nash Equilibrium.
Defaults to never giving up.
full_output : bool, optional
When True, adds information about the run to the output
actions and puts them in a NashResult. Defaults to False.
Returns
-------
tuple(ndarray(float, ndim=1)) or NashResult
The mixed actions at termination, a Nash Equilibrium if
not stopped early by reaching `max_iter`. If `full_output`,
then the number of iterations, whether it has converged,
and the initial conditions of the algorithm are included
in the returned `NashResult` alongside the actions.
References
----------
.. [1] Howson, Joseph T. “Equilibria of Polymatrix Games.”
Management Science 18, no. 5 (1972): 312–18.
http://www.jstor.org/stable/2634798.
"""
LOW_AVOIDER = 2.0
# makes all of the costs greater than 0
positive_cost_maker = polymg.range_of_payoffs()[1] + LOW_AVOIDER
# Construct the LCP like Howson:
M = np.vstack([
np.hstack([
np.zeros(
(polymg.nums_actions[player], polymg.nums_actions[player])
) if p2 == player
else (positive_cost_maker - polymg.polymatrix[(player, p2)])
for p2 in range(polymg.N)
] + [
-np.outer(np.ones(
polymg.nums_actions[player]), np.eye(polymg.N)[player])
])
for player in range(polymg.N)
] + [
np.hstack([
np.hstack([
np.outer(np.eye(polymg.N)[player], np.ones(
polymg.nums_actions[player]))
for player in range(polymg.N)
]
),
np.zeros((polymg.N, polymg.N))
])
]
)
total_actions = sum(polymg.nums_actions)
q = np.hstack([np.zeros(total_actions), -np.ones(polymg.N)])

n = np.shape(M)[0]
tableau = np.hstack([
np.eye(n),
-M,
np.reshape(q, (-1, 1))
])

basis = np.array(range(n))
z = np.empty(n)

if starting_player_actions is None:
starting_player_actions = {
player: 0
for player in range(polymg.N)
}
else:
valid_start = (
len(starting_player_actions) == polymg.N and
all(a < max_a for a, max_a in zip(
starting_player_actions, polymg.nums_actions))
)
assert valid_start, "Invalid starting pure actions."

for player in range(polymg.N):
row = sum(polymg.nums_actions) + player
col = n + sum(polymg.nums_actions[:player]) + \
starting_player_actions[player]
_pivoting(tableau, col, row)
# These pivots do not count as iters
basis[row] = col

num_iter = 0
converging = True

# Array to store row indices in lex_min_ratio_test
argmins = np.empty(n + polymg.N, dtype=np.int_)
p = 0
retro = False
while p < polymg.N and converging:
finishing_v = sum(polymg.nums_actions) + n + p
finishing_x = n + \
sum(polymg.nums_actions[:p]) + starting_player_actions[p]
finishing_y = finishing_x - n

pivcol = (
finishing_v if not retro
else finishing_x if finishing_y in basis
else finishing_y
)

retro = False

while True:

if num_iter == max_iter:
converging = False
break
num_iter += 1

_, pivrow = _lex_min_ratio_test(
tableau, pivcol, 0, argmins,
)

_pivoting(tableau, pivcol, pivrow)
basis[pivrow], leaving_var = pivcol, basis[pivrow]

if leaving_var == finishing_x or leaving_var == finishing_y:
p += 1
break
elif leaving_var == finishing_v:
# print("entering the backtracking case")
p -= 1
retro = True
break
elif leaving_var < n:
pivcol = leaving_var + n
else:
pivcol = leaving_var - n

combined_solution = _get_solution(tableau, basis, z)

# might not actually be Nash Equilibrium if we hit max iter
NE = tuple(
combined_solution[
sum(polymg.nums_actions[:player]):
sum(polymg.nums_actions[:player + 1])
]
for player in range(polymg.N)
)

if not full_output:
return NE

res = NashResult(NE=NE,
converged=converging,
num_iter=num_iter,
max_iter=max_iter,
init=starting_player_actions)

return NE, res
Loading

0 comments on commit 54d71fb

Please sign in to comment.