diff --git a/quantecon/game_theory/tests/test_vertex_enumeration.py b/quantecon/game_theory/tests/test_vertex_enumeration.py index 6e5e7d49f..31d77ef1b 100644 --- a/quantecon/game_theory/tests/test_vertex_enumeration.py +++ b/quantecon/game_theory/tests/test_vertex_enumeration.py @@ -44,6 +44,25 @@ def test_vertex_enumeration(self): assert_allclose(action_computed, action) +def test_vertex_enumeration_qhull_options(): + # Degenerate game, player 0's actions reordered + bimatrix = [[(0, 3), (6, 1)], + [(2, 2), (5, 6)], + [(3, 3), (3, 3)]] + g = NormalFormGame(bimatrix) + NEs_expected = [([0, 0, 1], [1, 0]), + ([0, 0, 1], [2/3, 1/3]), + ([2/3, 1/3, 0], [1/3, 2/3])] + qhull_options = 'QJ' + NEs_computed = vertex_enumeration(g, qhull_options=qhull_options) + eq_(len(NEs_computed), len(NEs_expected)) + for NEs in (NEs_computed, NEs_expected): + NEs.sort(key=lambda x: (list(x[1]), list(x[0]))) + for actions_computed, actions in zip(NEs_computed, NEs_expected): + for action_computed, action in zip(actions_computed, actions): + assert_allclose(action_computed, action, atol=1e-10) + + @raises(TypeError) def test_vertex_enumeration_invalid_g(): bimatrix = [[(3, 3), (3, 2)], diff --git a/quantecon/game_theory/vertex_enumeration.py b/quantecon/game_theory/vertex_enumeration.py index 7e573f1d8..481cdb08a 100644 --- a/quantecon/game_theory/vertex_enumeration.py +++ b/quantecon/game_theory/vertex_enumeration.py @@ -14,7 +14,7 @@ from numba import jit, guvectorize -def vertex_enumeration(g): +def vertex_enumeration(g, qhull_options=None): """ Compute mixed-action Nash equilibria of a 2-player normal form game by enumeration and matching of vertices of the best response @@ -32,16 +32,20 @@ def vertex_enumeration(g): g : NormalFormGame NormalFormGame instance with 2 players. + qhull_options : str, optional(default=None) + Options to pass to `scipy.spatial.ConvexHull`. See the `Qhull + manual `_ for details. + Returns ------- list(tuple(ndarray(float, ndim=1))) List containing tuples of Nash equilibrium mixed actions. """ - return list(vertex_enumeration_gen(g)) + return list(vertex_enumeration_gen(g, qhull_options=qhull_options)) -def vertex_enumeration_gen(g): +def vertex_enumeration_gen(g, qhull_options=None): """ Generator version of `vertex_enumeration`. @@ -50,6 +54,10 @@ def vertex_enumeration_gen(g): g : NormalFormGame NormalFormGame instance with 2 players. + qhull_options : str, optional(default=None) + Options to pass to `scipy.spatial.ConvexHull`. See the `Qhull + manual `_ for details. + Yields ------- tuple(ndarray(float, ndim=1)) @@ -63,7 +71,9 @@ def vertex_enumeration_gen(g): if N != 2: raise NotImplementedError('Implemented only for 2-player games') - brps = [_BestResponsePolytope(g.players[1-i], idx=i) for i in range(N)] + brps = [_BestResponsePolytope( + g.players[1-i], idx=i, qhull_options=qhull_options + ) for i in range(N)] labelings_bits_tup = \ tuple(_ints_arr_to_bits(brps[i].labelings) for i in range(N)) @@ -166,6 +176,10 @@ class _BestResponsePolytope: idx : scalar(int), optional(default=0) Player index in the normal form game, either 0 or 1. + qhull_options : str, optional(default=None) + Options to pass to `scipy.spatial.ConvexHull`. See the `Qhull + manual `_ for details. + Attributes ---------- ndim : scalar(int) @@ -191,7 +205,7 @@ class _BestResponsePolytope: `-equations[k, :-1]/equations[k, -1] + 1/trans_recip`. """ - def __init__(self, opponent_player, idx=0): + def __init__(self, opponent_player, idx=0, qhull_options=None): try: num_opponents = opponent_player.num_opponents except AttributeError: @@ -232,7 +246,7 @@ def __init__(self, opponent_player, idx=0): ) # Create scipy.spatial.ConvexHull - self.hull = scipy.spatial.ConvexHull(D) + self.hull = scipy.spatial.ConvexHull(D, qhull_options=qhull_options) self.equations = self.hull.equations self.labelings = self.hull.simplices