Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions quantecon/game_theory/lemke_howson.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,12 @@ def _initialize_tableaux(payoff_matrices, tableaux, bases):
`abs(payoff_matrices[i].min()) + 1` (to ensure for the tableau not
to have a negative entry or a column identically zero).

The shifted payoffs of each player are additionally divided by their
maximum, so that the payoff block of each tableau lies in a fixed
range and the absolute tolerances of the pivoting routine act as
tolerances relative to the payoff scale. Nash equilibria are
invariant to such per-player positive affine transformations.

Suppose that the players 0 and 1 have m and n actions, respectively.

* `tableaux[0]` has n rows and m+n+1 columns, where columns 0, ...,
Expand Down Expand Up @@ -294,30 +300,38 @@ def _initialize_tableaux(payoff_matrices, tableaux, bases):
>>> bases = (np.empty(n, dtype=int), np.empty(m, dtype=int))
>>> tableaux, bases = _initialize_tableaux((A, B), tableaux, bases)
>>> tableaux[0]
array([[3., 2., 3., 1., 0., 1.],
[2., 6., 1., 0., 1., 1.]])
array([[0.5 , 0.33333333, 0.5 , 1. , 0. ,
1. ],
[0.33333333, 1. , 0.16666667, 0. , 1. ,
1. ]])
>>> tableaux[1]
array([[1., 0., 0., 4., 4., 1.],
[0., 1., 0., 3., 6., 1.],
[0., 0., 1., 1., 7., 1.]])
array([[1. , 0. , 0. , 0.57142857, 0.57142857,
1. ],
[0. , 1. , 0. , 0.42857143, 0.85714286,
1. ],
[0. , 0. , 1. , 0.14285714, 1. ,
1. ]])
>>> bases
(array([3, 4]), array([0, 1, 2]))

"""
nums_actions = payoff_matrices[0].shape

consts = np.zeros(2) # To be added to payoffs if min <= 0
scales = np.ones(2) # To divide the shifted payoffs by their maximum
for pl in range(2):
min_ = payoff_matrices[pl].min()
if min_ <= 0:
consts[pl] = min_ * (-1) + 1
scales[pl] = (payoff_matrices[pl] + consts[pl]).max()

for pl, (py_start, sl_start) in enumerate(zip((0, nums_actions[0]),
(nums_actions[0], 0))):
for i in range(nums_actions[1-pl]):
for j in range(nums_actions[pl]):
tableaux[pl][i, py_start+j] = \
payoff_matrices[1-pl][i, j] + consts[1-pl]
(payoff_matrices[1-pl][i, j] + consts[1-pl]) / \
scales[1-pl]
for j in range(nums_actions[1-pl]):
if j == i:
tableaux[pl][i, sl_start+j] = 1
Expand Down Expand Up @@ -381,12 +395,12 @@ def _lemke_howson_tbl(tableaux, bases, init_pivot, max_iter):
>>> _lemke_howson_tbl(tableaux, bases, 1, 10)
(True, 4, False)
>>> tableaux[0]
array([[ 0.875 , 0. , 1. , 0.375 , -0.125 , 0.25 ],
[ 0.1875, 1. , 0. , -0.0625, 0.1875, 0.125 ]])
array([[ 0.875 , 0. , 1. , 2.25 , -0.75 , 1.5 ],
[ 0.1875, 1. , 0. , -0.375 , 1.125 , 0.75 ]])
>>> tableaux[1]
array([[ 1. , -1.6 , 0.8 , 0. , 0. , 0.2 ],
[ 0. , 0.4667, -0.4 , 1. , 0. , 0.0667],
[ 0. , -0.0667, 0.2 , 0. , 1. , 0.1333]])
[ 0. , 3.2667, -2.8 , 1. , 0. , 0.4667],
[ 0. , -0.4667, 1.4 , 0. , 1. , 0.9333]])
>>> bases
(array([2, 1]), array([0, 3, 4]))

Expand Down
34 changes: 25 additions & 9 deletions quantecon/game_theory/tests/test_lemke_howson.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,21 +134,37 @@ def test_lemke_howson_invalid_init_pivot_float():


@pytest.mark.parametrize('scale', [1e15, 1e16])
def test_lemke_howson_large_payoffs_report_breakdown(scale):
# With payoffs of this size, the ratios in the lexico-minimum ratio
# test all tie within the (absolute) `tol_ratio_diff`, and the tie
# breaking fails. The routine must then report non-convergence
# rather than return a wrong "equilibrium" with `converged=True`.
def test_lemke_howson_large_payoffs_converge(scale):
# gh-951: with the payoff blocks normalized in `_initialize_tableaux`,
# the absolute pivoting tolerances act relative to the payoff scale,
# so the routine converges to a valid equilibrium instead of
# reporting a breakdown.
A = scale * np.array([[3., 1.],
[1., 3.]])
B = scale * np.array([[1., 3.],
[3., 1.]])
g = NormalFormGame((Player(A), Player(B)))
NE, res = lemke_howson(g, full_output=True)
assert_(not res.converged)
# Without capping, no other initial pivot is tried on breakdown
assert_(res.init == 0)
assert_(res.num_iter == 0)
assert_(res.converged)
assert_(g.is_nash(NE))


@pytest.mark.parametrize('scale', [1e-12, 1e-10, 1e12, 1e15])
def test_lemke_howson_payoff_scale_invariance(scale):
# gh-951: Nash equilibria are invariant to per-player positive affine
# transformations of the payoffs. With the payoff blocks normalized
# in `_initialize_tableaux`, the routine must converge to an
# equilibrium that is valid for the unit-scale game at any scale.
# Uniform payoffs are used as in the issue: all values are positive,
# so only the rescaling (not the positivity shift) is exercised.
rng = np.random.default_rng(0)
for _ in range(10):
base = rng.random(size=(4, 4, 2))
g_unit = NormalFormGame(base)
NE, res = lemke_howson(NormalFormGame(base * scale),
full_output=True)
assert_(res.converged)
assert_(g_unit.is_nash(tuple(NE)))


def test_lemke_howson_tbl_breakdown():
Expand Down