]> 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 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
 from . import options
 
 printing.options['dformat'] = options.FLOAT_FORMAT
@@ -345,6 +346,205 @@ class SymmetricLinearGame:
                           self.condition())
 
 
                           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``.
     def _zero(self):
         """
         Return a column of zeros that fits ``K``.
@@ -360,7 +560,7 @@ class SymmetricLinearGame:
         -------
 
         matrix
         -------
 
         matrix
-            A ``K.dimension()``-by-``1`` column vector of zeros.
+            A ``self.dimension()``-by-``1`` column vector of zeros.
 
         Examples
         --------
 
         Examples
         --------
@@ -378,7 +578,7 @@ class SymmetricLinearGame:
             <BLANKLINE>
 
         """
             <BLANKLINE>
 
         """
-        return matrix(0, (self._K.dimension(), 1), tc='d')
+        return matrix(0, (self.dimension(), 1), tc='d')
 
 
     def _A(self):
 
 
     def _A(self):
@@ -397,7 +597,7 @@ class SymmetricLinearGame:
         -------
 
         matrix
         -------
 
         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
             entry is zero, and the rest are the entries of ``e2``.
 
         Examples
@@ -414,7 +614,7 @@ class SymmetricLinearGame:
             <BLANKLINE>
 
         """
             <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
         -------
 
         matrix
-            A ``2*K.dimension()``-by-``1 + K.dimension()`` matrix.
+            A ``2*self.dimension()``-by-``(1 + self.dimension())`` matrix.
 
         Examples
         --------
 
         Examples
         --------
@@ -455,8 +655,8 @@ class SymmetricLinearGame:
             <BLANKLINE>
 
         """
             <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))
 
 
                           append_col(self._e1, -self._L))
 
 
@@ -476,7 +676,7 @@ class SymmetricLinearGame:
         -------
 
         matrix
         -------
 
         matrix
-            A ``K.dimension()``-by-``1`` column vector.
+            A ``self.dimension()``-by-``1`` column vector.
 
         Examples
         --------
 
         Examples
         --------
@@ -544,7 +744,7 @@ class SymmetricLinearGame:
         -------
 
         matrix
         -------
 
         matrix
-            A ``2*K.dimension()``-by-``1`` column vector of zeros.
+            A ``2*self.dimension()``-by-``1`` column vector of zeros.
 
         Examples
         --------
 
         Examples
         --------
@@ -568,13 +768,19 @@ class SymmetricLinearGame:
 
         return matrix([self._zero(), self._zero()])
 
 
         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.
 
         """
         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
         .. 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:]
         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
 
         # 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()
 
     """
     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.
 
     # 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()
 
     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
 
 
     return G
 
@@ -445,16 +445,16 @@ def random_ll_icecream_game():
 
     """
     G = random_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.
 
     # 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()
 
     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
 
 
     return G
 
@@ -485,16 +485,16 @@ def random_positive_orthant_game():
     """
 
     G = random_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.
 
     # 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()
 
     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
 
 
     return G
 
@@ -526,21 +526,21 @@ def random_nn_scaling(G):
         >>> (alpha, H) = random_nn_scaling(G)
         >>> alpha >= 0
         True
         >>> (alpha, H) = random_nn_scaling(G)
         >>> alpha >= 0
         True
-        >>> G._K == H._K
+        >>> G.K() == H.K()
         True
         True
-        >>> norm(G._e1 - H._e1) < ABS_TOL
+        >>> norm(G.e1() - H.e1()) < ABS_TOL
         True
         True
-        >>> norm(G._e2 - H._e2) < ABS_TOL
+        >>> norm(G.e2() - H.e2()) < ABS_TOL
         True
 
     """
     alpha = random_nn_scalar()
         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()
 
     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)
 
 
     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)
         >>> from dunshire.options import ABS_TOL
         >>> G = random_orthant_game()
         >>> (alpha, H) = random_translation(G)
-        >>> G._K == H._K
+        >>> G.K() == H.K()
         True
         True
-        >>> norm(G._e1 - H._e1) < ABS_TOL
+        >>> norm(G.e1() - H.e1()) < ABS_TOL
         True
         True
-        >>> norm(G._e2 - H._e2) < ABS_TOL
+        >>> norm(G.e2() - H.e2()) < ABS_TOL
         True
 
     """
     alpha = random_scalar()
         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()
     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)
 
     return (alpha, H)
index 936a7e869283b4fd9ee2e82ddccefa5ef7253658..bba2f7ccfcb48c38312e5d5849aae892d40eac31 100644 (file)
@@ -66,8 +66,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
         """
         soln = G.solution()
 
         """
         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())
 
 
         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,
 
         # 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())
 
 
                                H.condition())
 
 
@@ -200,11 +199,11 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
         """
         # This is the "correct" representation of ``M``, but
         # COLUMN indexed...
         """
         # 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``.
 
         # 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()
 
         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(),
 
         # 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())
 
 
                                H.condition())
 
 
@@ -249,10 +248,10 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
         y_bar = soln.player2_optimal()
         value = soln.game_value()
 
         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())
 
         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())
 
 
         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.
         #
         # 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
 
         if soln.game_value() > EPSILON:
             # L should be positive stable