Skip to content

Commit

Permalink
game_theory.Player: Add is_dominated
Browse files Browse the repository at this point in the history
  • Loading branch information
oyamad committed Aug 18, 2017
1 parent 188e5ba commit 19b1a38
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 1 deletion.
73 changes: 72 additions & 1 deletion quantecon/game_theory/normal_form_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ def best_response(self, opponents_actions, tie_breaking='smallest',
array of floats (mixed action).
tie_breaking : str, optional(default='smallest')
str in {'smallest', 'random', False}. Control how, or
str in {'smallest', 'random', False}. Control how, or
whether, to break a tie (see Returns for details).
payoff_perturbation : array_like(float), optional(default=None)
Expand Down Expand Up @@ -380,6 +380,77 @@ def random_choice(self, actions=None, random_state=None):
else:
return idx

def is_dominated(self, action, tol=None, method=None):
"""
Determine whether `action` is strictly dominated by some mixed
action.
Parameters
----------
action : scalar(int)
Integer representing a pure action.
tol : scalar(float), optional(default=None)
Tolerance level used in determining domination. If None,
default to the value of the `tol` attribute.
method : str, optional(default=None)
If None, `lemke_howson` from `quantecon.game_theory` is used
to solve for a Nash equilibrium of an auxiliary zero-sum
game. If `method='simplex'`, `scipy.optimize.linprog` is
used with `method='simplex'`.
Returns
-------
bool
True if `action` is strictly dominated by some mixed action;
False otherwise.
"""
if tol is None:
tol = self.tol

payoff_array = self.payoff_array

if self.num_opponents == 0:
return payoff_array.max() > payoff_array[action] + tol

ind = np.ones(self.num_actions, dtype=bool)
ind[action] = False
D = payoff_array[ind]
D -= payoff_array[action]
if self.num_opponents >= 2:
D.shape = (D.shape[0], np.prod(D.shape[1:]))

if method is None:
from .lemke_howson import lemke_howson
g_zero_sum = NormalFormGame([Player(D), Player(-D.T)])
NE = lemke_howson(g_zero_sum)
return NE[0] @ D @ NE[1] > tol
elif method in ['simplex']:
from scipy.optimize import linprog
m, n = D.shape
A = np.empty((n+2, m+1))
A[:n, :m] = -D.T
A[:n, -1] = 1 # Slack variable
A[n, :m], A[n+1, :m] = 1, -1 # Equality constraint
A[n:, -1] = 0
b = np.empty(n+2)
b[:n] = 0
b[n], b[n+1] = 1, -1
c = np.zeros(m+1)
c[-1] = -1
res = linprog(c, A_ub=A, b_ub=b, method=method)
if res.success:
return res.x[-1] > tol
elif res.status == 2: # infeasible
return False
else: # pragma: no cover
msg = 'scipy.optimize.linprog returned {0}'.format(res.status)
raise RuntimeError(msg)
else:
raise ValueError('Unknown method {0}'.format(method))


class NormalFormGame:
"""
Expand Down
33 changes: 33 additions & 0 deletions quantecon/game_theory/tests/test_normal_form_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ def test_is_best_response_against_pure(self):
def test_is_best_response_against_mixed(self):
ok_(self.player.is_best_response([1/2, 1/2], [2/3, 1/3]))

def test_is_dominated(self):
for action in range(self.player.num_actions):
for method in [None, 'simplex']:
eq_(self.player.is_dominated(action, method=method), False)


class TestPlayer_2opponents:
"""Test the methods of Player with two opponent players"""
Expand Down Expand Up @@ -101,6 +106,11 @@ def test_best_response_list_when_tie(self):
sorted([0, 1])
)

def test_is_dominated(self):
for action in range(self.player.num_actions):
for method in [None, 'simplex']:
eq_(self.player.is_dominated(action, method=method), False)


def test_random_choice():
n, m = 5, 4
Expand All @@ -113,6 +123,24 @@ def test_random_choice():
ok_(player.random_choice() in actions)


def test_player_corner_cases():
n, m = 3, 4
player = Player(np.zeros((n, m)))
for action in range(n):
eq_(player.is_best_response(action, [1/m]*m), True)
for method in [None, 'simplex']:
eq_(player.is_dominated(action, method=method), False)

e = 1e-8
player = Player([[-e, -e], [1, -1], [-1, 1]])
action = 0
eq_(player.is_best_response(action, [1/2, 1/2], tol=e), True)
eq_(player.is_best_response(action, [1/2, 1/2], tol=e/2), False)
for method in [None, 'simplex']:
eq_(player.is_dominated(action, tol=e, method=method), False)
eq_(player.is_dominated(action, tol=e/2, method=method), True)


# NormalFormGame #

class TestNormalFormGame_Sym2p:
Expand Down Expand Up @@ -268,6 +296,11 @@ def test_best_response(self):
"""Trivial player: best_response"""
eq_(self.player.best_response(None), 1)

def test_is_dominated(self):
"""Trivial player: is_dominated"""
eq_(self.player.is_dominated(0), True)
eq_(self.player.is_dominated(1), False)


class TestNormalFormGame_1p:
"""Test for trivial NormalFormGame with a single player"""
Expand Down

0 comments on commit 19b1a38

Please sign in to comment.