]> gitweb.michael.orlitzky.com - dunshire.git/blobdiff - dunshire/games.py
Make the _C(), _A(), and _b() methods for games public.
[dunshire.git] / dunshire / games.py
index 08a6d6f342b59f93d26fbdeae320034f95004321..672810de8094df7c37005cd5106fe5b8175888c4 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
@@ -334,17 +335,216 @@ class SymmetricLinearGame:
               '  e1 = {:s},\n' \
               '  e2 = {:s},\n' \
               '  Condition((L, K, e1, e2)) = {:f}.'
               '  e1 = {:s},\n' \
               '  e2 = {:s},\n' \
               '  Condition((L, K, e1, e2)) = {:f}.'
-        indented_L = '\n      '.join(str(self._L).splitlines())
-        indented_e1 = '\n       '.join(str(self._e1).splitlines())
-        indented_e2 = '\n       '.join(str(self._e2).splitlines())
+        indented_L = '\n      '.join(str(self.L()).splitlines())
+        indented_e1 = '\n       '.join(str(self.e1()).splitlines())
+        indented_e2 = '\n       '.join(str(self.e2()).splitlines())
 
         return tpl.format(indented_L,
 
         return tpl.format(indented_L,
-                          str(self._K),
+                          str(self.K()),
                           indented_e1,
                           indented_e2,
                           self.condition())
 
 
                           indented_e1,
                           indented_e2,
                           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,10 +578,10 @@ 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):
         """
         Return the matrix ``A`` used in our CVXOPT construction.
 
         """
         Return the matrix ``A`` used in our CVXOPT construction.
 
@@ -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
@@ -409,12 +609,12 @@ class SymmetricLinearGame:
             >>> e1 = [1,1,1]
             >>> e2 = [1,2,3]
             >>> SLG = SymmetricLinearGame(L, K, e1, e2)
             >>> e1 = [1,1,1]
             >>> e2 = [1,2,3]
             >>> SLG = SymmetricLinearGame(L, K, e1, e2)
-            >>> print(SLG._A())
+            >>> print(SLG.A())
             [0.0000000 1.0000000 2.0000000 3.0000000]
             <BLANKLINE>
 
         """
             [0.0000000 1.0000000 2.0000000 3.0000000]
             <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,9 +655,9 @@ class SymmetricLinearGame:
             <BLANKLINE>
 
         """
             <BLANKLINE>
 
         """
-        I = identity(self._K.dimension())
-        return append_row(append_col(self._zero(), -I),
-                          append_col(self._e1, -self._L))
+        identity_matrix = identity(self.dimension())
+        return append_row(append_col(self._zero(), -identity_matrix),
+                          append_col(self.e1(), -self.L()))
 
 
     def _c(self):
 
 
     def _c(self):
@@ -476,7 +676,7 @@ class SymmetricLinearGame:
         -------
 
         matrix
         -------
 
         matrix
-            A ``K.dimension()``-by-``1`` column vector.
+            A ``self.dimension()``-by-``1`` column vector.
 
         Examples
         --------
 
         Examples
         --------
@@ -498,7 +698,7 @@ class SymmetricLinearGame:
         return matrix([-1, self._zero()])
 
 
         return matrix([-1, self._zero()])
 
 
-    def _C(self):
+    def C(self):
         """
         Return the cone ``C`` used in our CVXOPT construction.
 
         """
         Return the cone ``C`` used in our CVXOPT construction.
 
@@ -520,7 +720,7 @@ class SymmetricLinearGame:
             >>> e1 = [1,2,3]
             >>> e2 = [1,1,1]
             >>> SLG = SymmetricLinearGame(L, K, e1, e2)
             >>> e1 = [1,2,3]
             >>> e2 = [1,1,1]
             >>> SLG = SymmetricLinearGame(L, K, e1, e2)
-            >>> print(SLG._C())
+            >>> print(SLG.C())
             Cartesian product of dimension 6 with 2 factors:
               * Nonnegative orthant in the real 3-space
               * Nonnegative orthant in the real 3-space
             Cartesian product of dimension 6 with 2 factors:
               * Nonnegative orthant in the real 3-space
               * Nonnegative orthant in the real 3-space
@@ -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
@@ -595,7 +801,7 @@ class SymmetricLinearGame:
             >>> e1 = [1,2,3]
             >>> e2 = [1,1,1]
             >>> SLG = SymmetricLinearGame(L, K, e1, e2)
             >>> e1 = [1,2,3]
             >>> e2 = [1,1,1]
             >>> SLG = SymmetricLinearGame(L, K, e1, e2)
-            >>> print(SLG._b())
+            >>> print(SLG.b())
             [1.0000000]
             <BLANKLINE>
 
             [1.0000000]
             <BLANKLINE>
 
@@ -603,29 +809,10 @@ class SymmetricLinearGame:
         return matrix([1], tc='d')
 
 
         return matrix([1], tc='d')
 
 
-    def _try_solution(self, tolerance):
-        """
-        Solve this linear game within ``tolerance``, if possible.
-
-        This private function is the one that does all of the actual
-        work for :meth:`solution`. This method accepts a ``tolerance``,
-        and what :meth:`solution` does is call this method twice with
-        two different tolerances. First it tries a strict tolerance, and
-        then it tries a looser one.
-
-        .. warning::
-
-            If you try to be smart and precompute the matrices used by
-            this function (the ones passed to ``conelp``), then you're
-            going to shoot yourself in the foot. CVXOPT can and will
-            clobber some (but not all) of its input matrices. This isn't
-            performance sensitive, so play it safe.
 
 
-        Parameters
-        ----------
-
-        tolerance : float
-            The absolute tolerance to pass to the CVXOPT solver.
+    def solution(self):
+        """
+        Solve this linear game and return a :class:`Solution`.
 
         Returns
         -------
 
         Returns
         -------
@@ -647,65 +834,112 @@ class SymmetricLinearGame:
         Examples
         --------
 
         Examples
         --------
 
-        This game can be solved easily, so the first attempt in
-        :meth:`solution` should succeed::
+        This example is computed in Gowda and Ravindran in the section
+        "The value of a Z-transformation"::
 
             >>> from dunshire import *
 
             >>> from dunshire import *
-            >>> from dunshire.matrices import norm
-            >>> 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)
             >>> 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)
-            >>> s1 = SLG.solution()
-            >>> s2 = SLG._try_solution(options.ABS_TOL)
-            >>> abs(s1.game_value() - s2.game_value()) < ABS_TOL
-            True
-            >>> norm(s1.player1_optimal() - s2.player1_optimal()) < ABS_TOL
-            True
-            >>> norm(s1.player2_optimal() - s2.player2_optimal()) < ABS_TOL
-            True
+            >>> print(SLG.solution())
+            Game value: -6.1724138
+            Player 1 optimal:
+              [ 0.551...]
+              [-0.000...]
+              [ 0.448...]
+            Player 2 optimal:
+              [0.448...]
+              [0.000...]
+              [0.551...]
+
+        The value of the following game can be computed using the fact
+        that the identity is invertible::
+
+            >>> from dunshire import *
+            >>> K = NonnegativeOrthant(3)
+            >>> L = [[1,0,0],[0,1,0],[0,0,1]]
+            >>> e1 = [1,2,3]
+            >>> e2 = [4,5,6]
+            >>> SLG = SymmetricLinearGame(L, K, e1, e2)
+            >>> print(SLG.solution())
+            Game value: 0.0312500
+            Player 1 optimal:
+              [0.031...]
+              [0.062...]
+              [0.093...]
+            Player 2 optimal:
+              [0.125...]
+              [0.156...]
+              [0.187...]
 
 
-        This game cannot be solved with the default tolerance, but it
-        can be solved with a weaker one::
+        This is another Gowda/Ravindran example that is supposed to have
+        a negative game value::
 
             >>> from dunshire import *
             >>> from dunshire.options import ABS_TOL
 
             >>> from dunshire import *
             >>> from dunshire.options import ABS_TOL
-            >>> L = [[ 0.58538005706658102767,  1.53764301129883040886],
-            ...      [-1.34901059721452210027,  1.50121179114155500756]]
+            >>> L = [[1, -2], [-2, 1]]
+            >>> K = NonnegativeOrthant(2)
+            >>> e1 = [1, 1]
+            >>> e2 = e1
+            >>> SLG = SymmetricLinearGame(L, K, e1, e2)
+            >>> SLG.solution().game_value() < -ABS_TOL
+            True
+
+        The following two games are problematic numerically, but we
+        should be able to solve them::
+
+            >>> from dunshire import *
+            >>> L = [[-0.95237953890954685221, 1.83474556206462535712],
+            ...      [ 1.30481749924621448500, 1.65278664543326403447]]
             >>> K = NonnegativeOrthant(2)
             >>> K = NonnegativeOrthant(2)
-            >>> e1 = [1.04537193228494995623, 1.39699624965841895374]
-            >>> e2 = [0.35326554172108337593, 0.11795703527854853321]
-            >>> SLG = SymmetricLinearGame(L,K,e1,e2)
-            >>> print(SLG._try_solution(ABS_TOL / 10))
-            Traceback (most recent call last):
-            ...
-            dunshire.errors.GameUnsolvableException: Solution failed...
-            >>> print(SLG._try_solution(ABS_TOL))
-            Game value: 9.1100945
+            >>> e1 = [0.95477167524644313001, 0.63270781756540095397]
+            >>> e2 = [0.39633793037154141370, 0.10239281495640320530]
+            >>> SLG = SymmetricLinearGame(L, K, e1, e2)
+            >>> print(SLG.solution())
+            Game value: 18.767...
             Player 1 optimal:
             Player 1 optimal:
-              [-0.0000000]
-              [ 8.4776631]
+              [-0.000...]
+              [ 9.766...]
             Player 2 optimal:
             Player 2 optimal:
-              [0.0000000]
-              [0.7158216]
+              [1.047...]
+              [0.000...]
+
+        ::
+
+            >>> from dunshire import *
+            >>> L = [[1.54159395026049472754, 2.21344728574316684799],
+            ...      [1.33147433507846657541, 1.17913616272988108769]]
+            >>> K = NonnegativeOrthant(2)
+            >>> e1 = [0.39903040089404784307, 0.12377403622479113410]
+            >>> e2 = [0.15695181142215544612, 0.85527381344651265405]
+            >>> SLG = SymmetricLinearGame(L, K, e1, e2)
+            >>> print(SLG.solution())
+            Game value: 24.614...
+            Player 1 optimal:
+              [ 6.371...]
+              [-0.000...]
+            Player 2 optimal:
+              [2.506...]
+              [0.000...]
 
         """
         try:
 
         """
         try:
-            opts = {'show_progress': options.VERBOSE, 'abstol': tolerance}
+            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(),
+                                       self.C().cvxopt_dims(),
+                                       self.A(),
+                                       self.b(),
                                        options=opts)
         except ValueError as error:
             if str(error) == 'math domain error':
                 # Oops, CVXOPT tried to take the square root of a
                 # negative number. Report some details about the game
                 # rather than just the underlying CVXOPT crash.
                                        options=opts)
         except ValueError as error:
             if str(error) == 'math domain error':
                 # Oops, CVXOPT tried to take the square root of a
                 # negative number. Report some details about the game
                 # rather than just the underlying CVXOPT crash.
+                printing.options['dformat'] = options.DEBUG_FLOAT_FORMAT
                 raise PoorScalingException(self)
             else:
                 raise error
                 raise PoorScalingException(self)
             else:
                 raise error
@@ -719,112 +953,53 @@ 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
         # infeasible", "dual infeasible", "unknown", all of which mean
 
         # 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. The "infeasible" ones are the
-        # worst, since they indicate that CVXOPT is convinced the
-        # problem is infeasible (and that cannot happen).
+        # we didn't get a solution.
+        #
+        # The "infeasible" ones are the worst, since they indicate
+        # that CVXOPT is convinced the problem is infeasible (and that
+        # cannot happen).
         if soln_dict['status'] in ['primal infeasible', 'dual infeasible']:
         if soln_dict['status'] in ['primal infeasible', 'dual infeasible']:
+            printing.options['dformat'] = options.DEBUG_FLOAT_FORMAT
             raise GameUnsolvableException(self, soln_dict)
             raise GameUnsolvableException(self, soln_dict)
-        elif soln_dict['status'] == 'unknown':
-            # When we get a status of "unknown", we may still be able
-            # to salvage a solution out of the returned
-            # dictionary. Often this is the result of numerical
-            # difficulty and we can simply check that the primal/dual
-            # objectives match (within a tolerance) and that the
-            # primal/dual optimal solutions are within the cone (to a
-            # tolerance as well).
-            #
-            # The fudge factor of two is basically unjustified, but
-            # makes intuitive sense when you imagine that the primal
-            # value could be under the true optimal by ``ABS_TOL``
-            # and the dual value could be over by the same amount.
-            #
-            if abs(p1_value - p2_value) > tolerance:
-                raise GameUnsolvableException(self, soln_dict)
-            if (p1_optimal not in self._K) or (p2_optimal not in self._K):
-                raise GameUnsolvableException(self, soln_dict)
-
-        return Solution(p1_value, p1_optimal, p2_optimal)
 
 
+        # The "optimal" and "unknown" results, we actually treat the
+        # 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.
+        #
+        # 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.
+        if abs(p1_value - p2_value) > 2*options.ABS_TOL:
+            printing.options['dformat'] = options.DEBUG_FLOAT_FORMAT
+            raise GameUnsolvableException(self, soln_dict)
 
 
-    def solution(self):
-        """
-        Solve this linear game and return a :class:`Solution`.
-
-        Returns
-        -------
-
-        :class:`Solution`
-            A :class:`Solution` object describing the game's value and
-            the optimal strategies of both players.
-
-        Raises
-        ------
-        GameUnsolvableException
-            If the game could not be solved (if an optimal solution to its
-            associated cone program was not found).
-
-        PoorScalingException
-            If the game could not be solved because CVXOPT crashed while
-            trying to take the square root of a negative number.
-
-        Examples
-        --------
-
-        This example is computed in Gowda and Ravindran in the section
-        "The value of a Z-transformation"::
-
-            >>> from dunshire import *
-            >>> 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)
-            >>> print(SLG.solution())
-            Game value: -6.1724138
-            Player 1 optimal:
-              [ 0.551...]
-              [-0.000...]
-              [ 0.448...]
-            Player 2 optimal:
-              [0.448...]
-              [0.000...]
-              [0.551...]
-
-        The value of the following game can be computed using the fact
-        that the identity is invertible::
-
-            >>> from dunshire import *
-            >>> K = NonnegativeOrthant(3)
-            >>> L = [[1,0,0],[0,1,0],[0,0,1]]
-            >>> e1 = [1,2,3]
-            >>> e2 = [4,5,6]
-            >>> SLG = SymmetricLinearGame(L, K, e1, e2)
-            >>> print(SLG.solution())
-            Game value: 0.0312500
-            Player 1 optimal:
-              [0.031...]
-              [0.062...]
-              [0.093...]
-            Player 2 optimal:
-              [0.125...]
-              [0.156...]
-              [0.187...]
-
-        """
-        try:
-            # First try with a stricter tolerance. Who knows, it might
-            # work. If it does, we prefer that solution.
-            return self._try_solution(options.ABS_TOL / 10)
+        # And we also check that the points it gave us belong to the
+        # cone, just in case...
+        if (p1_optimal not in self._K) or (p2_optimal not in self._K):
+            printing.options['dformat'] = options.DEBUG_FLOAT_FORMAT
+            raise GameUnsolvableException(self, soln_dict)
 
 
-        except (PoorScalingException, GameUnsolvableException):
-            # Ok, that didn't work. Let's try it with the default
-            # tolerance, and whatever happens, happens.
-            return self._try_solution(options.ABS_TOL)
+        # For the game value, we could use any of:
+        #
+        #   * p1_value
+        #   * p2_value
+        #   * (p1_value + p2_value)/2
+        #   * the game payoff
+        #
+        # We want the game value to be the payoff, however, so it
+        # makes the most sense to just use that, even if it means we
+        # can't test the fact that p1_value/p2_value are close to the
+        # payoff.
+        payoff = self.payoff(p1_optimal, p2_optimal)
+        return Solution(payoff, p1_optimal, p2_optimal)
 
 
     def condition(self):
 
 
     def condition(self):
@@ -861,7 +1036,7 @@ class SymmetricLinearGame:
         True
 
         """
         True
 
         """
-        return (condition_number(self._G()) + condition_number(self._A()))/2
+        return (condition_number(self._G()) + condition_number(self.A()))/2
 
 
     def dual(self):
 
 
     def dual(self):
@@ -897,10 +1072,10 @@ class SymmetricLinearGame:
               Condition((L, K, e1, e2)) = 44.476...
 
         """
               Condition((L, K, e1, e2)) = 44.476...
 
         """
-        # We pass ``self._L`` right back into the constructor, because
+        # We pass ``self.L()`` right back into the constructor, because
         # it will be transposed there. And keep in mind that ``self._K``
         # is its own dual.
         # it will be transposed there. And keep in mind that ``self._K``
         # is its own dual.
-        return SymmetricLinearGame(self._L,
-                                   self._K,
-                                   self._e2,
-                                   self._e1)
+        return SymmetricLinearGame(self.L(),
+                                   self.K(),
+                                   self.e2(),
+                                   self.e1())