]> gitweb.michael.orlitzky.com - dunshire.git/blobdiff - dunshire/games.py
A bunch more doc fixes.
[dunshire.git] / dunshire / games.py
index a0c52279869510090f9d051a92b479bfefa3fbdd..ea7a64f6b8e6451a808b464494c11e9be9f0de78 100644 (file)
@@ -179,11 +179,15 @@ class SymmetricLinearGame:
     ----------
 
     L : list of list of float
-        A matrix represented as a list of ROWS. This representation
-        agrees with (for example) SageMath and NumPy, but not with CVXOPT
-        (whose matrix constructor accepts a list of columns).
-
-    K : :class:`SymmetricCone`
+        A matrix represented as a list of **rows**. This representation
+        agrees with (for example) `SageMath <http://www.sagemath.org/>`_
+        and `NumPy <http://www.numpy.org/>`_, but not with CVXOPT (whose
+        matrix constructor accepts a list of columns). In reality, ``L``
+        can be any iterable type of the correct length; however, you
+        should be extremely wary of the way we interpret anything other
+        than a list of rows.
+
+    K : dunshire.cones.SymmetricCone
         The symmetric cone instance over which the game is played.
 
     e1 : iterable float
@@ -220,8 +224,7 @@ class SymmetricLinearGame:
                [ 1],
           e2 = [ 1]
                [ 2]
-               [ 3],
-          Condition((L, K, e1, e2)) = 31.834...
+               [ 3]
 
     Lists can (and probably should) be used for every argument::
 
@@ -239,8 +242,7 @@ class SymmetricLinearGame:
           e1 = [ 1]
                [ 1],
           e2 = [ 1]
-               [ 1],
-          Condition((L, K, e1, e2)) = 1.707...
+               [ 1]
 
     The points ``e1`` and ``e2`` can also be passed as some other
     enumerable type (of the correct length) without much harm, since
@@ -262,8 +264,7 @@ class SymmetricLinearGame:
           e1 = [ 1]
                [ 1],
           e2 = [ 1]
-               [ 1],
-          Condition((L, K, e1, e2)) = 1.707...
+               [ 1]
 
     However, ``L`` will always be intepreted as a list of rows, even
     if it is passed as a :class:`cvxopt.base.matrix` which is
@@ -284,8 +285,7 @@ class SymmetricLinearGame:
           e1 = [ 1]
                [ 1],
           e2 = [ 1]
-               [ 1],
-          Condition((L, K, e1, e2)) = 6.073...
+               [ 1]
         >>> L = cvxopt.matrix(L)
         >>> print(L)
         [ 1  3]
@@ -300,8 +300,7 @@ class SymmetricLinearGame:
           e1 = [ 1]
                [ 1],
           e2 = [ 1]
-               [ 1],
-          Condition((L, K, e1, e2)) = 6.073...
+               [ 1]
 
     """
     def __init__(self, L, K, e1, e2):
@@ -335,8 +334,7 @@ class SymmetricLinearGame:
               '  L = {:s},\n' \
               '  K = {!s},\n' \
               '  e1 = {:s},\n' \
-              '  e2 = {:s},\n' \
-              '  Condition((L, K, e1, e2)) = {:f}.'
+              '  e2 = {:s}'
         indented_L = '\n      '.join(str(self.L()).splitlines())
         indented_e1 = '\n       '.join(str(self.e1()).splitlines())
         indented_e2 = '\n       '.join(str(self.e2()).splitlines())
@@ -344,8 +342,7 @@ class SymmetricLinearGame:
         return tpl.format(indented_L,
                           str(self.K()),
                           indented_e1,
-                          indented_e2,
-                          self.condition())
+                          indented_e2)
 
 
     def L(self):
@@ -468,8 +465,8 @@ class SymmetricLinearGame:
         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
+        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.
 
@@ -496,7 +493,6 @@ class SymmetricLinearGame:
         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]
@@ -505,7 +501,7 @@ class SymmetricLinearGame:
             >>> 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
+            >>> SLG.payoff(x_bar, y_bar) == soln.game_value()
             True
 
         """
@@ -584,11 +580,12 @@ class SymmetricLinearGame:
 
 
     def A(self):
-        """
+        r"""
         Return the matrix ``A`` used in our CVXOPT construction.
 
-        This matrix ``A``  appears on the right-hand side of ``Ax = b``
-        in the statement of the CVXOPT conelp program.
+        This matrix :math:`A` appears on the right-hand side of
+        :math:`Ax = b` in the `statement of the CVXOPT conelp program
+        <http://cvxopt.org/userguide/coneprog.html#linear-cone-programs>`_.
 
         .. warning::
 
@@ -600,7 +597,7 @@ class SymmetricLinearGame:
 
         matrix
             A ``1``-by-``(1 + self.dimension())`` row vector. Its first
-            entry is zero, and the rest are the entries of ``e2``.
+            entry is zero, and the rest are the entries of :meth:`e2`.
 
         Examples
         --------
@@ -620,12 +617,13 @@ class SymmetricLinearGame:
 
 
 
-    def _G(self):
+    def G(self):
         r"""
         Return the matrix ``G`` used in our CVXOPT construction.
 
-        Thus matrix ``G`` appears on the left-hand side of ``Gx + s = h``
-        in the statement of the CVXOPT conelp program.
+        Thus matrix :math:`G` appears on the left-hand side of :math:`Gx
+        + s = h` in the `statement of the CVXOPT conelp program
+        <http://cvxopt.org/userguide/coneprog.html#linear-cone-programs>`_.
 
         .. warning::
 
@@ -647,7 +645,7 @@ class SymmetricLinearGame:
             >>> e1 = [1,2,3]
             >>> e2 = [1,1,1]
             >>> SLG = SymmetricLinearGame(L, K, e1, e2)
-            >>> print(SLG._G())
+            >>> print(SLG.G())
             [  0.0000000  -1.0000000   0.0000000   0.0000000]
             [  0.0000000   0.0000000  -1.0000000   0.0000000]
             [  0.0000000   0.0000000   0.0000000  -1.0000000]
@@ -662,12 +660,14 @@ class SymmetricLinearGame:
                           append_col(self.e1(), -self.L()))
 
 
-    def _c(self):
-        """
+    def c(self):
+        r"""
         Return the vector ``c`` used in our CVXOPT construction.
 
-        The column vector ``c``  appears in the objective function
-        value ``<c,x>`` in the statement of the CVXOPT conelp program.
+        The column vector :math:`c` appears in the objective function
+        value :math:`\left\langle c,x \right\rangle` in the `statement
+        of the CVXOPT conelp program
+        <http://cvxopt.org/userguide/coneprog.html#linear-cone-programs>`_.
 
         .. warning::
 
@@ -678,7 +678,7 @@ class SymmetricLinearGame:
         -------
 
         matrix
-            A ``self.dimension()``-by-``1`` column vector.
+            A :meth:`dimension`-by-``1`` column vector.
 
         Examples
         --------
@@ -689,7 +689,7 @@ class SymmetricLinearGame:
             >>> e1 = [1,2,3]
             >>> e2 = [1,1,1]
             >>> SLG = SymmetricLinearGame(L, K, e1, e2)
-            >>> print(SLG._c())
+            >>> print(SLG.c())
             [-1.0000000]
             [ 0.0000000]
             [ 0.0000000]
@@ -704,8 +704,9 @@ class SymmetricLinearGame:
         """
         Return the cone ``C`` used in our CVXOPT construction.
 
-        The cone ``C`` is the cone over which the conelp program takes
-        place.
+        This is the cone over which the `CVXOPT conelp program
+        <http://cvxopt.org/userguide/coneprog.html#linear-cone-programs>`_
+        takes place.
 
         Returns
         -------
@@ -730,12 +731,13 @@ class SymmetricLinearGame:
         """
         return CartesianProduct(self._K, self._K)
 
-    def _h(self):
+    def h(self):
         r"""
         Return the ``h`` vector used in our CVXOPT construction.
 
-        The ``h`` vector appears on the right-hand side of :math:`Gx + s
-        = h` in the statement of the CVXOPT conelp program.
+        The :math:`h` vector appears on the right-hand side of :math:`Gx
+        + s = h` in the `statement of the CVXOPT conelp program
+        <http://cvxopt.org/userguide/coneprog.html#linear-cone-programs>`_.
 
         .. warning::
 
@@ -757,7 +759,7 @@ class SymmetricLinearGame:
             >>> e1 = [1,2,3]
             >>> e2 = [1,1,1]
             >>> SLG = SymmetricLinearGame(L, K, e1, e2)
-            >>> print(SLG._h())
+            >>> print(SLG.h())
             [0.0000000]
             [0.0000000]
             [0.0000000]
@@ -776,8 +778,9 @@ class SymmetricLinearGame:
         r"""
         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.
+        The vector :math:`b` appears on the right-hand side of :math:`Ax
+        = b` in the `statement of the CVXOPT conelp program
+        <http://cvxopt.org/userguide/coneprog.html#linear-cone-programs>`_.
 
         This method is static because the dimensions and entries of
         ``b`` are known beforehand, and don't depend on any other
@@ -816,17 +819,32 @@ class SymmetricLinearGame:
         Return a feasible starting point for player one.
 
         This starting point is for the CVXOPT formulation and not for
-        the original game. The basic premise is that if you normalize
-        :meth:`e2`, then you get a point in :meth:`K` that makes a unit
-        inner product with :meth:`e2`. We then get to choose the primal
-        objective function value such that the constraint involving
-        :meth:`L` is satisfied.
+        the original game. The basic premise is that if you scale
+        :meth:`e2` by the reciprocal of its squared norm, then you get a
+        point in :meth:`K` that makes a unit inner product with
+        :meth:`e2`. We then get to choose the primal objective function
+        value such that the constraint involving :meth:`L` is satisfied.
+
+        Returns
+        -------
+
+        dict
+            A dictionary with two keys, ``'x'`` and ``'s'``, which
+            contain the vectors of the same name in the CVXOPT primal
+            problem formulation.
+
+            The vector ``x`` consists of the primal objective function
+            value concatenated with the strategy (for player one) that
+            achieves it. The vector ``s`` is essentially a dummy
+            variable, and is computed from the equality constraing in
+            the CVXOPT primal problem.
+
         """
         p = self.e2() / (norm(self.e2()) ** 2)
         dist = self.K().ball_radius(self.e1())
         nu = - self._L_specnorm()/(dist*norm(self.e2()))
         x = matrix([nu, p], (self.dimension() + 1, 1))
-        s = - self._G()*x
+        s = - self.G()*x
 
         return {'x': x, 's': s}
 
@@ -834,6 +852,29 @@ class SymmetricLinearGame:
     def player2_start(self):
         """
         Return a feasible starting point for player two.
+
+        This starting point is for the CVXOPT formulation and not for
+        the original game. The basic premise is that if you scale
+        :meth:`e1` by the reciprocal of its squared norm, then you get a
+        point in :meth:`K` that makes a unit inner product with
+        :meth:`e1`. We then get to choose the dual objective function
+        value such that the constraint involving :meth:`L` is satisfied.
+
+        Returns
+        -------
+
+        dict
+            A dictionary with two keys, ``'y'`` and ``'z'``, which
+            contain the vectors of the same name in the CVXOPT dual
+            problem formulation.
+
+            The ``1``-by-``1`` vector ``y`` consists of the dual
+            objective function value. The last :meth:`dimension` entries
+            of the vector ``z`` contain the strategy (for player two)
+            that achieves it. The remaining entries of ``z`` are
+            essentially dummy variables, computed from the equality
+            constraint in the CVXOPT dual problem.
+
         """
         q = self.e1() / (norm(self.e1()) ** 2)
         dist = self.K().ball_radius(self.e2())
@@ -862,6 +903,19 @@ class SymmetricLinearGame:
             A nonnegative real number; the largest singular value of
             the matrix :meth:`L`.
 
+        Examples
+        --------
+
+            >>> from dunshire import *
+            >>> from dunshire.matrices import specnorm
+            >>> L = [[1,2],[3,4]]
+            >>> K = NonnegativeOrthant(2)
+            >>> e1 = [1,1]
+            >>> e2 = e1
+            >>> SLG = SymmetricLinearGame(L,K,e1,e2)
+            >>> specnorm(SLG.L()) == SLG._L_specnorm()
+            True
+
         """
         if self._L_specnorm_value is None:
             self._L_specnorm_value = specnorm(self.L())
@@ -870,19 +924,21 @@ class SymmetricLinearGame:
 
     def tolerance_scale(self, solution):
         r"""
-        Return a scaling factor that should be applied to ``ABS_TOL``
-        for this game.
-
-        When performing certain comparisons, the default tolernace
-        ``ABS_TOL`` may not be appropriate. For example, if we expect
-        ``x`` and ``y`` to be within ``ABS_TOL`` of each other, than the
-        inner product of ``L*x`` and ``y`` can be as far apart as the
-        spectral norm of ``L`` times the sum of the norms of ``x`` and
+
+        Return a scaling factor that should be applied to
+        :const:`dunshire.options.ABS_TOL` for this game.
+
+        When performing certain comparisons, the default tolerance
+        :const:`dunshire.options.ABS_TOL` may not be appropriate. For
+        example, if we expect ``x`` and ``y`` to be within
+        :const:`dunshire.options.ABS_TOL` of each other, than the inner
+        product of ``L*x`` and ``y`` can be as far apart as the spectral
+        norm of ``L`` times the sum of the norms of ``x`` and
         ``y``. Such a comparison is made in :meth:`solution`, and in
         many of our unit tests.
 
-        The returned scaling factor found from the inner product mentioned
-        above is
+        The returned scaling factor found from the inner product
+        mentioned above is
 
         .. math::
 
@@ -913,9 +969,27 @@ class SymmetricLinearGame:
         -------
 
         float
-            A scaling factor to be multiplied by ``ABS_TOL`` when
+            A scaling factor to be multiplied by
+            :const:`dunshire.options.ABS_TOL` when
             making comparisons involving solutions of this game.
 
+        Examples
+        --------
+
+        The spectral norm of ``L`` in this case is around ``5.464``, and
+        the optimal strategies both have norm one, so we expect the
+        tolerance scale to be somewhere around ``2 * 5.464``, or
+        ``10.929``::
+
+            >>> from dunshire import *
+            >>> L = [[1,2],[3,4]]
+            >>> K = NonnegativeOrthant(2)
+            >>> e1 = [1,1]
+            >>> e2 = e1
+            >>> SLG = SymmetricLinearGame(L,K,e1,e2)
+            >>> SLG.tolerance_scale(SLG.solution())
+            10.929...
+
         """
         norm_p1_opt = norm(solution.player1_optimal())
         norm_p2_opt = norm(solution.player2_optimal())
@@ -933,7 +1007,7 @@ class SymmetricLinearGame:
         Returns
         -------
 
-        :class:`Solution`
+        Solution
             A :class:`Solution` object describing the game's value and
             the optimal strategies of both players.
 
@@ -1081,9 +1155,9 @@ class SymmetricLinearGame:
         """
         try:
             opts = {'show_progress': False}
-            soln_dict = solvers.conelp(self._c(),
-                                       self._G(),
-                                       self._h(),
+            soln_dict = solvers.conelp(self.c(),
+                                       self.G(),
+                                       self.h(),
                                        self.C().cvxopt_dims(),
                                        self.A(),
                                        self.b(),
@@ -1141,12 +1215,14 @@ class SymmetricLinearGame:
         # same. Even if CVXOPT bails out due to numerical difficulty,
         # it will have some candidate points in mind. If those
         # candidates are good enough, we take them. We do the same
-        # check (perhaps pointlessly so) for "optimal" results.
+        # check for "optimal" results.
         #
         # First we check that the primal/dual objective values are
-        # close enough (one could be low by ABS_TOL, the other high by
-        # it) because otherwise CVXOPT might return "unknown" and give
-        # us two points in the cone that are nowhere near optimal.
+        # close enough because otherwise CVXOPT might return "unknown"
+        # and give us two points in the cone that are nowhere near
+        # optimal. And in fact, we need to ensure that they're close
+        # for "optimal" results, too, because we need to know how
+        # lenient to be in our testing.
         #
         if abs(p1_value - p2_value) > self.tolerance_scale(soln)*ABS_TOL:
             printing.options['dformat'] = DEBUG_FLOAT_FORMAT
@@ -1170,8 +1246,12 @@ class SymmetricLinearGame:
         can show up. We define the condition number of this game to be
         the average of the condition numbers of ``G`` and ``A`` in the
         CVXOPT construction. If the condition number of this game is
-        high, then you can expect numerical difficulty (such as
-        :class:`PoorScalingException`).
+        high, you can problems like :class:`PoorScalingException`.
+
+        Random testing shows that a condition number of around ``125``
+        is about the best that we can solve reliably. However, the
+        failures are intermittent, and you may get lucky with an
+        ill-conditioned game.
 
         Returns
         -------
@@ -1193,7 +1273,7 @@ class SymmetricLinearGame:
         1.809...
 
         """
-        return (condition_number(self._G()) + condition_number(self.A()))/2
+        return (condition_number(self.G()) + condition_number(self.A()))/2
 
 
     def dual(self):
@@ -1225,8 +1305,7 @@ class SymmetricLinearGame:
                    [ 3],
               e2 = [ 1]
                    [ 1]
-                   [ 1],
-              Condition((L, K, e1, e2)) = 44.476...
+                   [ 1]
 
         """
         # We pass ``self.L()`` right back into the constructor, because