diff --git a/open_spiel/python/algorithms/mip_nash.py b/open_spiel/python/algorithms/mip_nash.py index 4068a69130..61fb3690ba 100644 --- a/open_spiel/python/algorithms/mip_nash.py +++ b/open_spiel/python/algorithms/mip_nash.py @@ -11,11 +11,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -'''MIP-Nash. +"""MIP-Nash. -Based on the first formulation of https://dl.acm.org/doi/10.5555/1619410.1619413. -Compute optimal Nash equilibrium of two-player general-sum games by solving a mixed-integer programming problem. -''' +Based on the first formulation of + https://dl.acm.org/doi/10.5555/1619410.1619413. +Compute optimal Nash equilibrium of two-player general-sum games +by solving a mixed-integer programming problem. +""" import numpy as np @@ -46,8 +48,10 @@ def mip_nash(game, objective, solver='GLPK_MI'): for all n, b0[n] \in {0, 1}, for all m, b1[m] \in {0, 1}, U0, U1 are the maximum payoff differences of player 0 and 1. - This formulation is a basic one that may only work well for simple objective function or low-dimensional inputs. - To handle more complex cases, It is possible to extend this by using advanced internal solvers or piecewise linear approximation of the objective. + This formulation is a basic one that may only work well + for simple objective function or low-dimensional inputs. + To handle more complex cases, It is possible to extend this by + using advanced internal solvers or piecewise linear approximation of the objective. Args: game: a pyspiel matrix game object objective: a string representing the objective (e.g., MAX_SOCIAL_WELFARE) @@ -94,24 +98,28 @@ def mip_nash(game, objective, solver='GLPK_MI'): return _simplex_projection(x.value.reshape(-1)), _simplex_projection(y.value.reshape(-1)) - def max_social_welfare_two_player(variables): + """Max social welfare objective.""" return cp.Maximize(variables['u0'] + variables['u1']) def min_social_welfare_two_player(variables): + """Min social welfare objective.""" return cp.Minimize(variables['u0'] + variables['u1']) def max_support_two_player(variables): + """Max support objective.""" return cp.Minimize(cp.sum(variables['b0']) + cp.sum(variables['b1'])) def min_support_two_player(variables): + """Min support objective.""" return cp.Maximize(cp.sum(variables['b0']) + cp.sum(variables['b1'])) def max_gini_two_player(variables): + """Max gini objective.""" return cp.Minimize(cp.sum(cp.square(variables['x'])) + cp.sum(cp.square(variables['y']))) @@ -121,4 +129,4 @@ def max_gini_two_player(variables): 'MAX_SUPPORT': max_support_two_player, 'MIN_SUPPORT': min_support_two_player, 'MAX_GINI': max_gini_two_player, -} \ No newline at end of file +} diff --git a/open_spiel/python/algorithms/mip_nash_test.py b/open_spiel/python/algorithms/mip_nash_test.py index df504e04d2..ede45c8199 100644 --- a/open_spiel/python/algorithms/mip_nash_test.py +++ b/open_spiel/python/algorithms/mip_nash_test.py @@ -14,39 +14,38 @@ """Tests for open_spiel.python.algorithms.mip_nash.""" from absl.testing import absltest -from absl.testing import parameterized import numpy as np from open_spiel.python.algorithms.mip_nash import mip_nash import pyspiel -# prisoners' dilemma -pd_game = pyspiel.create_matrix_game( - [[-2.0, -10.0], [0.0, -5.0]], - [[-2.0, 0.0], [-10.0, -5.0]]) +class MIPNash(absltest.TestCase): + def test_simple_games(self): + # prisoners' dilemma + pd_game = pyspiel.create_matrix_game( + [[-2.0, -10.0], [0.0, -5.0]], + [[-2.0, 0.0], [-10.0, -5.0]]) -pd_eq = (np.array([0, 1]), np.array([0, 1])) + pd_eq = (np.array([0, 1]), np.array([0, 1])) -# stag hunt -sh_game = pyspiel.create_matrix_game( - [[10.0, 1.0], [8.0, 5.0]], - [[10.0, 8.0], [1.0, 5.0]]) + computed_eq = mip_nash(pd_game, objective="MAX_SOCIAL_WELFARE") + with self.subTest("pd"): + np.testing.assert_array_almost_equal(computed_eq[0], pd_eq[0]) + np.testing.assert_array_almost_equal(computed_eq[1], pd_eq[1]) + # stag hunt + sh_game = pyspiel.create_matrix_game( + [[10.0, 1.0], [8.0, 5.0]], + [[10.0, 8.0], [1.0, 5.0]]) -sh_eq = (np.array([1, 0]), np.array([1, 0])) + sh_eq = (np.array([1, 0]), np.array([1, 0])) -class MIPNash(parameterized.TestCase): - @parameterized.named_parameters( - ("pd", pd_game, pd_eq), - ("sh", sh_game, sh_eq), - ) - def test_simple_games(self, game, eq): - computed_eq = mip_nash(game, objective='MAX_SOCIAL_WELFARE') - with self.subTest("probability"): - np.testing.assert_array_almost_equal(computed_eq[0], eq[0]) - np.testing.assert_array_almost_equal(computed_eq[1], eq[1]) + computed_eq = mip_nash(sh_game, objective="MAX_SOCIAL_WELFARE") + with self.subTest("sh"): + np.testing.assert_array_almost_equal(computed_eq[0], sh_eq[0]) + np.testing.assert_array_almost_equal(computed_eq[1], sh_eq[1]) if __name__ == "__main__": - absltest.main() \ No newline at end of file + absltest.main()