]> gitweb.michael.orlitzky.com - dunshire.git/commitdiff
Add game accessor methods for its L, K, e1, e2, and dimension.
authorMichael Orlitzky <michael@orlitzky.com>
Fri, 4 Nov 2016 23:13:21 +0000 (19:13 -0400)
committerMichael Orlitzky <michael@orlitzky.com>
Fri, 4 Nov 2016 23:13:21 +0000 (19:13 -0400)
dunshire/games.py
test/randomgen.py
test/symmetric_linear_game_test.py

index 08a6d6f342b59f93d26fbdeae320034f95004321..3575da52b15c0b874a1ebb0fd346d46947d887d4 100644 (file)
@@ -8,7 +8,8 @@ knows how to solve a linear game.
 from cvxopt import matrix, printing, solvers
 from .cones import CartesianProduct
 from .errors import GameUnsolvableException, PoorScalingException
-from .matrices import append_col, append_row, condition_number, identity
+from .matrices import (append_col, append_row, condition_number, identity,
+                       inner_product)
 from . import options
 
 printing.options['dformat'] = options.FLOAT_FORMAT
@@ -345,6 +346,205 @@ class SymmetricLinearGame:
                           self.condition())
 
 
+    def L(self):
+        """
+        Return the matrix ``L`` passed to the constructor.
+
+        Returns
+        -------
+
+        matrix
+            The matrix that defines this game's :meth:`payoff` operator.
+
+        Examples
+        --------
+
+            >>> from dunshire import *
+            >>> K = NonnegativeOrthant(3)
+            >>> L = [[1,-5,-15],[-1,2,-3],[-12,-15,1]]
+            >>> e1 = [1,1,1]
+            >>> e2 = [1,2,3]
+            >>> SLG = SymmetricLinearGame(L, K, e1, e2)
+            >>> print(SLG.L())
+            [  1  -5 -15]
+            [ -1   2  -3]
+            [-12 -15   1]
+            <BLANKLINE>
+
+        """
+        return self._L
+
+
+    def K(self):
+        """
+        Return the cone over which this game is played.
+
+        Returns
+        -------
+
+        SymmetricCone
+            The :class:`SymmetricCone` over which this game is played.
+
+        Examples
+        --------
+
+            >>> from dunshire import *
+            >>> K = NonnegativeOrthant(3)
+            >>> L = [[1,-5,-15],[-1,2,-3],[-12,-15,1]]
+            >>> e1 = [1,1,1]
+            >>> e2 = [1,2,3]
+            >>> SLG = SymmetricLinearGame(L, K, e1, e2)
+            >>> print(SLG.K())
+            Nonnegative orthant in the real 3-space
+
+        """
+        return self._K
+
+
+    def e1(self):
+        """
+        Return player one's interior point.
+
+        Returns
+        -------
+
+        matrix
+            The point interior to :meth:`K` affiliated with player one.
+
+        Examples
+        --------
+
+            >>> from dunshire import *
+            >>> K = NonnegativeOrthant(3)
+            >>> L = [[1,-5,-15],[-1,2,-3],[-12,-15,1]]
+            >>> e1 = [1,1,1]
+            >>> e2 = [1,2,3]
+            >>> SLG = SymmetricLinearGame(L, K, e1, e2)
+            >>> print(SLG.e1())
+            [ 1]
+            [ 1]
+            [ 1]
+            <BLANKLINE>
+
+        """
+        return self._e1
+
+
+    def e2(self):
+        """
+        Return player two's interior point.
+
+        Returns
+        -------
+
+        matrix
+            The point interior to :meth:`K` affiliated with player one.
+
+        Examples
+        --------
+
+            >>> from dunshire import *
+            >>> K = NonnegativeOrthant(3)
+            >>> L = [[1,-5,-15],[-1,2,-3],[-12,-15,1]]
+            >>> e1 = [1,1,1]
+            >>> e2 = [1,2,3]
+            >>> SLG = SymmetricLinearGame(L, K, e1, e2)
+            >>> print(SLG.e2())
+            [ 1]
+            [ 2]
+            [ 3]
+            <BLANKLINE>
+
+        """
+        return self._e2
+
+
+    def payoff(self, strategy1, strategy2):
+        r"""
+        Return the payoff associated with ``strategy1`` and ``strategy2``.
+
+        The payoff operator takes pairs of strategies to a real
+        number. For example, if player one's strategy is :math:`x` and
+        player two's strategy is :math:`y`, then the associated payoff
+        is :math:`\left\langle L\left(x\right),y \right\rangle` \in
+        \mathbb{R}. Here, :math:`L` denotes the same linear operator as
+        :meth:`L`. This method computes the payoff given the two
+        players' strategies.
+
+        Parameters
+        ----------
+
+        strategy1 : matrix
+            Player one's strategy.
+
+        strategy2 : matrix
+            Player two's strategy.
+
+        Returns
+        -------
+
+        float
+            The payoff for the game when player one plays ``strategy1``
+            and player two plays ``strategy2``.
+
+        Examples
+        --------
+
+        The value of the game should be the payoff at the optimal
+        strategies::
+
+            >>> from dunshire import *
+            >>> from dunshire.options import ABS_TOL
+            >>> K = NonnegativeOrthant(3)
+            >>> L = [[1,-5,-15],[-1,2,-3],[-12,-15,1]]
+            >>> e1 = [1,1,1]
+            >>> e2 = [1,1,1]
+            >>> SLG = SymmetricLinearGame(L, K, e1, e2)
+            >>> soln = SLG.solution()
+            >>> x_bar = soln.player1_optimal()
+            >>> y_bar = soln.player2_optimal()
+            >>> abs(SLG.payoff(x_bar, y_bar) - soln.game_value()) < ABS_TOL
+            True
+
+        """
+        return inner_product(self.L()*strategy1, strategy2)
+
+
+    def dimension(self):
+        """
+        Return the dimension of this game.
+
+        The dimension of a game is not needed for the theory, but it is
+        useful for the implementation. We define the dimension of a game
+        to be the dimension of its underlying cone. Or what is the same,
+        the dimension of the space from which the strategies are chosen.
+
+        Returns
+        -------
+
+        int
+            The dimension of the cone :meth:`K`, or of the space where
+            this game is played.
+
+        Examples
+        --------
+
+        The dimension of a game over the nonnegative quadrant in the
+        plane should be two (the dimension of the plane)::
+
+            >>> from dunshire import *
+            >>> K = NonnegativeOrthant(2)
+            >>> L = [[1,-5],[-1,2]]
+            >>> e1 = [1,1]
+            >>> e2 = [1,4]
+            >>> SLG = SymmetricLinearGame(L, K, e1, e2)
+            >>> SLG.dimension()
+            2
+
+        """
+        return self.K().dimension()
+
+
     def _zero(self):
         """
         Return a column of zeros that fits ``K``.
@@ -360,7 +560,7 @@ class SymmetricLinearGame:
         -------
 
         matrix
-            A ``K.dimension()``-by-``1`` column vector of zeros.
+            A ``self.dimension()``-by-``1`` column vector of zeros.
 
         Examples
         --------
@@ -378,7 +578,7 @@ class SymmetricLinearGame:
             <BLANKLINE>
 
         """
-        return matrix(0, (self._K.dimension(), 1), tc='d')
+        return matrix(0, (self.dimension(), 1), tc='d')
 
 
     def _A(self):
@@ -397,7 +597,7 @@ class SymmetricLinearGame:
         -------
 
         matrix
-            A ``1``-by-``(1 + K.dimension())`` row vector. Its first
+            A ``1``-by-``(1 + self.dimension())`` row vector. Its first
             entry is zero, and the rest are the entries of ``e2``.
 
         Examples
@@ -414,7 +614,7 @@ class SymmetricLinearGame:
             <BLANKLINE>
 
         """
-        return matrix([0, self._e2], (1, self._K.dimension() + 1), 'd')
+        return matrix([0, self._e2], (1, self.dimension() + 1), 'd')
 
 
 
@@ -434,7 +634,7 @@ class SymmetricLinearGame:
         -------
 
         matrix
-            A ``2*K.dimension()``-by-``1 + K.dimension()`` matrix.
+            A ``2*self.dimension()``-by-``(1 + self.dimension())`` matrix.
 
         Examples
         --------
@@ -455,8 +655,8 @@ class SymmetricLinearGame:
             <BLANKLINE>
 
         """
-        I = identity(self._K.dimension())
-        return append_row(append_col(self._zero(), -I),
+        identity_matrix = identity(self.dimension())
+        return append_row(append_col(self._zero(), -identity_matrix),
                           append_col(self._e1, -self._L))
 
 
@@ -476,7 +676,7 @@ class SymmetricLinearGame:
         -------
 
         matrix
-            A ``K.dimension()``-by-``1`` column vector.
+            A ``self.dimension()``-by-``1`` column vector.
 
         Examples
         --------
@@ -544,7 +744,7 @@ class SymmetricLinearGame:
         -------
 
         matrix
-            A ``2*K.dimension()``-by-``1`` column vector of zeros.
+            A ``2*self.dimension()``-by-``1`` column vector of zeros.
 
         Examples
         --------
@@ -568,13 +768,19 @@ class SymmetricLinearGame:
 
         return matrix([self._zero(), self._zero()])
 
-    def _b(self):
+
+    @staticmethod
+    def _b():
         """
         Return the ``b`` vector used in our CVXOPT construction.
 
         The vector ``b`` appears on the right-hand side of :math:`Ax =
         b` in the statement of the CVXOPT conelp program.
 
+        This method is static because the dimensions and entries of
+        ``b`` are known beforehand, and don't depend on any other
+        properties of the game.
+
         .. warning::
 
             It is not safe to cache any of the matrices passed to
@@ -719,7 +925,7 @@ class SymmetricLinearGame:
         p1_value = -soln_dict['primal objective']
         p2_value = -soln_dict['dual objective']
         p1_optimal = soln_dict['x'][1:]
-        p2_optimal = soln_dict['z'][self._K.dimension():]
+        p2_optimal = soln_dict['z'][self.dimension():]
 
         # The "status" field contains "optimal" if everything went
         # according to plan. Other possible values are "primal
index 9fee2f749cbcf8037f2c64d41fe42e17731381a4..8c5ebfcbbfa24dd4e99929940bb56424839fbf87 100644 (file)
@@ -407,16 +407,16 @@ def random_ll_orthant_game():
 
     """
     G = random_orthant_game()
-    L = random_diagonal_matrix(G._K.dimension())
+    L = random_diagonal_matrix(G.dimension())
 
     # Replace the totally-random ``L`` with random Lyapunov-like one.
-    G = SymmetricLinearGame(L, G._K, G._e1, G._e2)
+    G = SymmetricLinearGame(L, G.K(), G.e1(), G.e2())
 
     while G.condition() > MAX_COND:
         # Try again until the condition number is satisfactory.
         G = random_orthant_game()
-        L = random_diagonal_matrix(G._K.dimension())
-        G = SymmetricLinearGame(L, G._K, G._e1, G._e2)
+        L = random_diagonal_matrix(G.dimension())
+        G = SymmetricLinearGame(L, G.K(), G.e1(), G.e2())
 
     return G
 
@@ -445,16 +445,16 @@ def random_ll_icecream_game():
 
     """
     G = random_icecream_game()
-    L = random_lyapunov_like_icecream(G._K.dimension())
+    L = random_lyapunov_like_icecream(G.dimension())
 
     # Replace the totally-random ``L`` with random Lyapunov-like one.
-    G = SymmetricLinearGame(L, G._K, G._e1, G._e2)
+    G = SymmetricLinearGame(L, G.K(), G.e1(), G.e2())
 
     while G.condition() > MAX_COND:
         # Try again until the condition number is satisfactory.
         G = random_icecream_game()
-        L = random_lyapunov_like_icecream(G._K.dimension())
-        G = SymmetricLinearGame(L, G._K, G._e1, G._e2)
+        L = random_lyapunov_like_icecream(G.dimension())
+        G = SymmetricLinearGame(L, G.K(), G.e1(), G.e2())
 
     return G
 
@@ -485,16 +485,16 @@ def random_positive_orthant_game():
     """
 
     G = random_orthant_game()
-    L = random_nonnegative_matrix(G._K.dimension())
+    L = random_nonnegative_matrix(G.dimension())
 
     # Replace the totally-random ``L`` with the random nonnegative one.
-    G = SymmetricLinearGame(L, G._K, G._e1, G._e2)
+    G = SymmetricLinearGame(L, G.K(), G.e1(), G.e2())
 
     while G.condition() > MAX_COND:
         # Try again until the condition number is satisfactory.
         G = random_orthant_game()
-        L = random_nonnegative_matrix(G._K.dimension())
-        G = SymmetricLinearGame(L, G._K, G._e1, G._e2)
+        L = random_nonnegative_matrix(G.dimension())
+        G = SymmetricLinearGame(L, G.K(), G.e1(), G.e2())
 
     return G
 
@@ -526,21 +526,21 @@ def random_nn_scaling(G):
         >>> (alpha, H) = random_nn_scaling(G)
         >>> alpha >= 0
         True
-        >>> G._K == H._K
+        >>> G.K() == H.K()
         True
-        >>> norm(G._e1 - H._e1) < ABS_TOL
+        >>> norm(G.e1() - H.e1()) < ABS_TOL
         True
-        >>> norm(G._e2 - H._e2) < ABS_TOL
+        >>> norm(G.e2() - H.e2()) < ABS_TOL
         True
 
     """
     alpha = random_nn_scalar()
-    H = SymmetricLinearGame(alpha*G._L.trans(), G._K, G._e1, G._e2)
+    H = SymmetricLinearGame(alpha*G.L().trans(), G.K(), G.e1(), G.e2())
 
     while H.condition() > MAX_COND:
         # Loop until the condition number of H doesn't suck.
         alpha = random_nn_scalar()
-        H = SymmetricLinearGame(alpha*G._L.trans(), G._K, G._e1, G._e2)
+        H = SymmetricLinearGame(alpha*G.L().trans(), G.K(), G.e1(), G.e2())
 
     return (alpha, H)
 
@@ -571,23 +571,23 @@ def random_translation(G):
         >>> from dunshire.options import ABS_TOL
         >>> G = random_orthant_game()
         >>> (alpha, H) = random_translation(G)
-        >>> G._K == H._K
+        >>> G.K() == H.K()
         True
-        >>> norm(G._e1 - H._e1) < ABS_TOL
+        >>> norm(G.e1() - H.e1()) < ABS_TOL
         True
-        >>> norm(G._e2 - H._e2) < ABS_TOL
+        >>> norm(G.e2() - H.e2()) < ABS_TOL
         True
 
     """
     alpha = random_scalar()
-    tensor_prod = G._e1 * G._e2.trans()
-    M = G._L + alpha*tensor_prod
+    tensor_prod = G.e1() * G.e2().trans()
+    M = G.L() + alpha*tensor_prod
 
-    H = SymmetricLinearGame(M.trans(), G._K, G._e1, G._e2)
+    H = SymmetricLinearGame(M.trans(), G.K(), G.e1(), G.e2())
     while H.condition() > MAX_COND:
         # Loop until the condition number of H doesn't suck.
         alpha = random_scalar()
-        M = G._L + alpha*tensor_prod
-        H = SymmetricLinearGame(M.trans(), G._K, G._e1, G._e2)
+        M = G.L() + alpha*tensor_prod
+        H = SymmetricLinearGame(M.trans(), G.K(), G.e1(), G.e2())
 
     return (alpha, H)
index 936a7e869283b4fd9ee2e82ddccefa5ef7253658..bba2f7ccfcb48c38312e5d5849aae892d40eac31 100644 (file)
@@ -66,8 +66,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
         """
         soln = G.solution()
 
-        expected = inner_product(G._L*soln.player1_optimal(),
-                                 soln.player2_optimal())
+        expected = G.payoff(soln.player1_optimal(), soln.player2_optimal())
         self.assert_within_tol(soln.game_value(), expected, G.condition())
 
 
@@ -171,7 +170,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
 
         # Make sure the same optimal pair works.
         self.assert_within_tol(value2,
-                               inner_product(H._L*x_bar, y_bar),
+                               H.payoff(x_bar, y_bar),
                                H.condition())
 
 
@@ -200,11 +199,11 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
         """
         # This is the "correct" representation of ``M``, but
         # COLUMN indexed...
-        M = -G._L.trans()
+        M = -G.L().trans()
 
         # so we have to transpose it when we feed it to the constructor.
         # Note: the condition number of ``H`` should be comparable to ``G``.
-        H = SymmetricLinearGame(M.trans(), G._K, G._e2, G._e1)
+        H = SymmetricLinearGame(M.trans(), G.K(), G.e2(), G.e1())
 
         soln1 = G.solution()
         x_bar = soln1.player1_optimal()
@@ -217,7 +216,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
 
         # Make sure the switched optimal pair works.
         self.assert_within_tol(soln2.game_value(),
-                               inner_product(M*y_bar, x_bar),
+                               H.payoff(y_bar, x_bar),
                                H.condition())
 
 
@@ -249,10 +248,10 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
         y_bar = soln.player2_optimal()
         value = soln.game_value()
 
-        ip1 = inner_product(y_bar, G._L*x_bar - value*G._e1)
+        ip1 = inner_product(y_bar, G.L()*x_bar - value*G.e1())
         self.assert_within_tol(ip1, 0, G.condition())
 
-        ip2 = inner_product(value*G._e2 - G._L.trans()*y_bar, x_bar)
+        ip2 = inner_product(value*G.e2() - G.L().trans()*y_bar, x_bar)
         self.assert_within_tol(ip2, 0, G.condition())
 
 
@@ -298,7 +297,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
         #
         # See :meth:`assert_within_tol` for an explanation of the
         # fudge factors.
-        eigs = eigenvalues_re(G._L)
+        eigs = eigenvalues_re(G.L())
 
         if soln.game_value() > EPSILON:
             # L should be positive stable