""" Symmetric linear games and their solutions. This module contains the main SymmetricLinearGame class that knows how to solve a linear game. """ from cvxopt import matrix, printing, solvers from cones import CartesianProduct from errors import GameUnsolvableException from matrices import append_col, append_row, identity printing.options['dformat'] = '%.7f' solvers.options['show_progress'] = False class Solution: """ A representation of the solution of a linear game. It should contain the value of the game, and both players' strategies. """ def __init__(self, game_value, p1_optimal, p2_optimal): """ Create a new Solution object from a game value and two optimal strategies for the players. """ self._game_value = game_value self._player1_optimal = p1_optimal self._player2_optimal = p2_optimal def __str__(self): """ Return a string describing the solution of a linear game. The three data that are described are, * The value of the game. * The optimal strategy of player one. * The optimal strategy of player two. """ tpl = 'Game value: {:.7f}\n' \ 'Player 1 optimal:{:s}\n' \ 'Player 2 optimal:{:s}\n' p1_str = '\n{!s}'.format(self.player1_optimal()) p1_str = '\n '.join(p1_str.splitlines()) p2_str = '\n{!s}'.format(self.player2_optimal()) p2_str = '\n '.join(p2_str.splitlines()) return tpl.format(self.game_value(), p1_str, p2_str) def game_value(self): """ Return the game value for this solution. """ return self._game_value def player1_optimal(self): """ Return player one's optimal strategy in this solution. """ return self._player1_optimal def player2_optimal(self): """ Return player two's optimal strategy in this solution. """ return self._player2_optimal class SymmetricLinearGame: """ A representation of a symmetric linear game. The data for a linear game are, * A "payoff" operator ``L``. * A cone ``K``. * A point ``e`` in the interior of ``K``. * A point ``f`` in the interior of the dual of ``K``. In a symmetric game, the cone ``K`` is be self-dual. We therefore name the two interior points ``e1`` and ``e2`` to indicate that they come from the same cone but are "chosen" by players one and two respectively. The ambient space is assumed to be the span of ``K``. """ def __init__(self, L, K, e1, e2): """ INPUT: - ``L`` -- an n-by-b matrix represented as a list of lists of real numbers. - ``K`` -- a SymmetricCone instance. - ``e1`` -- the interior point of ``K`` belonging to player one, as a column vector. - ``e2`` -- the interior point of ``K`` belonging to player two, as a column vector. """ self._K = K self._e1 = matrix(e1, (K.dimension(), 1)) self._e2 = matrix(e2, (K.dimension(), 1)) self._L = matrix(L, (K.dimension(), K.dimension())) if not K.contains_strict(self._e1): raise ValueError('the point e1 must lie in the interior of K') if not K.contains_strict(self._e2): raise ValueError('the point e2 must lie in the interior of K') def __str__(self): """ Return a string representatoin of this game. """ return "a game" def solution(self): """ Solve this linear game and return a Solution object. OUTPUT: If the cone program associated with this game could be successfully solved, then a Solution object containing the game's value and optimal strategies is returned. If the game could *not* be solved -- which should never happen -- then a GameUnsolvableException is raised. It can be printed to get the raw output from CVXOPT. """ # The cone "C" that appears in the statement of the CVXOPT # conelp program. C = CartesianProduct(self._K, self._K) # The column vector "b" that appears on the right-hand side of # Ax = b in the statement of the CVXOPT conelp program. b = matrix([1], tc='d') # A column of zeros that fits K. zero = matrix(0, (self._K.dimension(), 1), tc='d') # The column vector "h" that appears on the right-hand side of # Gx + s = h in the statement of the CVXOPT conelp program. h = matrix([zero, zero]) # The column vector "c" that appears in the objective function # value in the statement of the CVXOPT conelp program. c = matrix([-1, zero]) # The matrix "G" that appears on the left-hand side of Gx + s = h # in the statement of the CVXOPT conelp program. G = append_row(append_col(zero, -identity(self._K.dimension())), append_col(self._e1, -self._L)) # The matrix "A" that appears on the right-hand side of Ax = b # in the statement of the CVXOPT conelp program. A = matrix([0, self._e1], (1, self._K.dimension() + 1), 'd') # Actually solve the thing and obtain a dictionary describing # what happened. soln_dict = solvers.conelp(c, G, h, C.cvxopt_dims(), A, b) # The "status" field contains "optimal" if everything went # according to plan. Other possible values are "primal # infeasible", "dual infeasible", "unknown", all of which # mean we didn't get a solution. That should never happen, # because by construction our game has a solution, and thus # the cone program should too. if soln_dict['status'] != 'optimal': raise GameUnsolvableException(soln_dict) p1_value = soln_dict['x'][0] p1_optimal = soln_dict['x'][1:] p2_optimal = soln_dict['z'][self._K.dimension():] return Solution(p1_value, p1_optimal, p2_optimal) def dual(self): """ Return the dual game to this game. """ return SymmetricLinearGame(self._L.trans(), self._K, # Since "K" is symmetric. self._e2, self._e1)