]>
gitweb.michael.orlitzky.com - dunshire.git/blob - dunshire/games.py
a1ac0f54c2982cd59b25cc479293c4e3dc558e44
2 Symmetric linear games and their solutions.
4 This module contains the main :class:`SymmetricLinearGame` class that
5 knows how to solve a linear game.
7 from cvxopt
import matrix
, printing
, solvers
8 from .cones
import CartesianProduct
9 from .errors
import GameUnsolvableException
, PoorScalingException
10 from .matrices
import (append_col
, append_row
, condition_number
, identity
,
11 inner_product
, norm
, specnorm
)
14 printing
.options
['dformat'] = options
.FLOAT_FORMAT
18 A representation of the solution of a linear game. It should contain
19 the value of the game, and both players' strategies.
24 >>> print(Solution(10, matrix([1,2]), matrix([3,4])))
34 def __init__(self
, game_value
, p1_optimal
, p2_optimal
):
36 Create a new Solution object from a game value and two optimal
37 strategies for the players.
39 self
._game
_value
= game_value
40 self
._player
1_optimal
= p1_optimal
41 self
._player
2_optimal
= p2_optimal
45 Return a string describing the solution of a linear game.
47 The three data that are described are,
49 * The value of the game.
50 * The optimal strategy of player one.
51 * The optimal strategy of player two.
53 The two optimal strategy vectors are indented by two spaces.
55 tpl
= 'Game value: {:.7f}\n' \
56 'Player 1 optimal:{:s}\n' \
57 'Player 2 optimal:{:s}'
59 p1_str
= '\n{!s}'.format(self
.player1_optimal())
60 p1_str
= '\n '.join(p1_str
.splitlines())
61 p2_str
= '\n{!s}'.format(self
.player2_optimal())
62 p2_str
= '\n '.join(p2_str
.splitlines())
64 return tpl
.format(self
.game_value(), p1_str
, p2_str
)
69 Return the game value for this solution.
74 >>> s = Solution(10, matrix([1,2]), matrix([3,4]))
79 return self
._game
_value
82 def player1_optimal(self
):
84 Return player one's optimal strategy in this solution.
89 >>> s = Solution(10, matrix([1,2]), matrix([3,4]))
90 >>> print(s.player1_optimal())
96 return self
._player
1_optimal
99 def player2_optimal(self
):
101 Return player two's optimal strategy in this solution.
106 >>> s = Solution(10, matrix([1,2]), matrix([3,4]))
107 >>> print(s.player2_optimal())
113 return self
._player
2_optimal
116 class SymmetricLinearGame
:
118 A representation of a symmetric linear game.
120 The data for a symmetric linear game are,
122 * A "payoff" operator ``L``.
123 * A symmetric cone ``K``.
124 * Two points ``e1`` and ``e2`` in the interior of ``K``.
126 The ambient space is assumed to be the span of ``K``.
128 With those data understood, the game is played as follows. Players
129 one and two choose points :math:`x` and :math:`y` respectively, from
130 their respective strategy sets,
137 x \in K \ \middle|\ \left\langle x, e_{2} \right\rangle = 1
142 y \in K \ \middle|\ \left\langle y, e_{1} \right\rangle = 1
146 Afterwards, a "payout" is computed as :math:`\left\langle
147 L\left(x\right), y \right\rangle` and is paid to player one out of
148 player two's pocket. The game is therefore zero sum, and we suppose
149 that player one would like to guarantee himself the largest minimum
150 payout possible. That is, player one wishes to,
155 &\underset{y \in \Delta_{2}}{\min}\left(
156 \left\langle L\left(x\right), y \right\rangle
158 \text{subject to } & x \in \Delta_{1}.
161 Player two has the simultaneous goal to,
166 &\underset{x \in \Delta_{1}}{\max}\left(
167 \left\langle L\left(x\right), y \right\rangle
169 \text{subject to } & y \in \Delta_{2}.
172 These goals obviously conflict (the game is zero sum), but an
173 existence theorem guarantees at least one optimal min-max solution
174 from which neither player would like to deviate. This class is
175 able to find such a solution.
180 L : list of list of float
181 A matrix represented as a list of ROWS. This representation
182 agrees with (for example) SageMath and NumPy, but not with CVXOPT
183 (whose matrix constructor accepts a list of columns).
185 K : :class:`SymmetricCone`
186 The symmetric cone instance over which the game is played.
189 The interior point of ``K`` belonging to player one; it
190 can be of any iterable type having the correct length.
193 The interior point of ``K`` belonging to player two; it
194 can be of any enumerable type having the correct length.
200 If either ``e1`` or ``e2`` lie outside of the cone ``K``.
205 >>> from dunshire import *
206 >>> K = NonnegativeOrthant(3)
207 >>> L = [[1,-5,-15],[-1,2,-3],[-12,-15,1]]
210 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
212 The linear game (L, K, e1, e2) where
216 K = Nonnegative orthant in the real 3-space,
223 Condition((L, K, e1, e2)) = 31.834...
225 Lists can (and probably should) be used for every argument::
227 >>> from dunshire import *
228 >>> K = NonnegativeOrthant(2)
229 >>> L = [[1,0],[0,1]]
232 >>> G = SymmetricLinearGame(L, K, e1, e2)
234 The linear game (L, K, e1, e2) where
237 K = Nonnegative orthant in the real 2-space,
242 Condition((L, K, e1, e2)) = 1.707...
244 The points ``e1`` and ``e2`` can also be passed as some other
245 enumerable type (of the correct length) without much harm, since
246 there is no row/column ambiguity::
250 >>> from dunshire import *
251 >>> K = NonnegativeOrthant(2)
252 >>> L = [[1,0],[0,1]]
253 >>> e1 = cvxopt.matrix([1,1])
254 >>> e2 = numpy.matrix([1,1])
255 >>> G = SymmetricLinearGame(L, K, e1, e2)
257 The linear game (L, K, e1, e2) where
260 K = Nonnegative orthant in the real 2-space,
265 Condition((L, K, e1, e2)) = 1.707...
267 However, ``L`` will always be intepreted as a list of rows, even
268 if it is passed as a :class:`cvxopt.base.matrix` which is
269 otherwise indexed by columns::
272 >>> from dunshire import *
273 >>> K = NonnegativeOrthant(2)
274 >>> L = [[1,2],[3,4]]
277 >>> G = SymmetricLinearGame(L, K, e1, e2)
279 The linear game (L, K, e1, e2) where
282 K = Nonnegative orthant in the real 2-space,
287 Condition((L, K, e1, e2)) = 6.073...
288 >>> L = cvxopt.matrix(L)
293 >>> G = SymmetricLinearGame(L, K, e1, e2)
295 The linear game (L, K, e1, e2) where
298 K = Nonnegative orthant in the real 2-space,
303 Condition((L, K, e1, e2)) = 6.073...
306 def __init__(self
, L
, K
, e1
, e2
):
308 Create a new SymmetricLinearGame object.
311 self
._e
1 = matrix(e1
, (K
.dimension(), 1))
312 self
._e
2 = matrix(e2
, (K
.dimension(), 1))
314 # Our input ``L`` is indexed by rows but CVXOPT matrices are
315 # indexed by columns, so we need to transpose the input before
316 # feeding it to CVXOPT.
317 self
._L = matrix(L
, (K
.dimension(), K
.dimension())).trans()
319 if not self
._e
1 in K
:
320 raise ValueError('the point e1 must lie in the interior of K')
322 if not self
._e
2 in K
:
323 raise ValueError('the point e2 must lie in the interior of K')
325 # Initial value of cached method.
326 self
._L_specnorm
_value
= None
331 Return a string representation of this game.
333 tpl
= 'The linear game (L, K, e1, e2) where\n' \
338 ' Condition((L, K, e1, e2)) = {:f}.'
339 indented_L
= '\n '.join(str(self
.L()).splitlines())
340 indented_e1
= '\n '.join(str(self
.e1()).splitlines())
341 indented_e2
= '\n '.join(str(self
.e2()).splitlines())
343 return tpl
.format(indented_L
,
352 Return the matrix ``L`` passed to the constructor.
358 The matrix that defines this game's :meth:`payoff` operator.
363 >>> from dunshire import *
364 >>> K = NonnegativeOrthant(3)
365 >>> L = [[1,-5,-15],[-1,2,-3],[-12,-15,1]]
368 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
381 Return the cone over which this game is played.
387 The :class:`SymmetricCone` over which this game is played.
392 >>> from dunshire import *
393 >>> K = NonnegativeOrthant(3)
394 >>> L = [[1,-5,-15],[-1,2,-3],[-12,-15,1]]
397 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
399 Nonnegative orthant in the real 3-space
407 Return player one's interior point.
413 The point interior to :meth:`K` affiliated with player one.
418 >>> from dunshire import *
419 >>> K = NonnegativeOrthant(3)
420 >>> L = [[1,-5,-15],[-1,2,-3],[-12,-15,1]]
423 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
436 Return player two's interior point.
442 The point interior to :meth:`K` affiliated with player one.
447 >>> from dunshire import *
448 >>> K = NonnegativeOrthant(3)
449 >>> L = [[1,-5,-15],[-1,2,-3],[-12,-15,1]]
452 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
463 def payoff(self
, strategy1
, strategy2
):
465 Return the payoff associated with ``strategy1`` and ``strategy2``.
467 The payoff operator takes pairs of strategies to a real
468 number. For example, if player one's strategy is :math:`x` and
469 player two's strategy is :math:`y`, then the associated payoff
470 is :math:`\left\langle L\left(x\right),y \right\rangle` \in
471 \mathbb{R}. Here, :math:`L` denotes the same linear operator as
472 :meth:`L`. This method computes the payoff given the two
479 Player one's strategy.
482 Player two's strategy.
488 The payoff for the game when player one plays ``strategy1``
489 and player two plays ``strategy2``.
494 The value of the game should be the payoff at the optimal
497 >>> from dunshire import *
498 >>> from dunshire.options import ABS_TOL
499 >>> K = NonnegativeOrthant(3)
500 >>> L = [[1,-5,-15],[-1,2,-3],[-12,-15,1]]
503 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
504 >>> soln = SLG.solution()
505 >>> x_bar = soln.player1_optimal()
506 >>> y_bar = soln.player2_optimal()
507 >>> abs(SLG.payoff(x_bar, y_bar) - soln.game_value()) < ABS_TOL
511 return inner_product(self
.L()*strategy1
, strategy2
)
516 Return the dimension of this game.
518 The dimension of a game is not needed for the theory, but it is
519 useful for the implementation. We define the dimension of a game
520 to be the dimension of its underlying cone. Or what is the same,
521 the dimension of the space from which the strategies are chosen.
527 The dimension of the cone :meth:`K`, or of the space where
533 The dimension of a game over the nonnegative quadrant in the
534 plane should be two (the dimension of the plane)::
536 >>> from dunshire import *
537 >>> K = NonnegativeOrthant(2)
538 >>> L = [[1,-5],[-1,2]]
541 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
546 return self
.K().dimension()
551 Return a column of zeros that fits ``K``.
553 This is used in our CVXOPT construction.
557 It is not safe to cache any of the matrices passed to
558 CVXOPT, because it can clobber them.
564 A ``self.dimension()``-by-``1`` column vector of zeros.
569 >>> from dunshire import *
570 >>> K = NonnegativeOrthant(3)
574 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
575 >>> print(SLG._zero())
582 return matrix(0, (self
.dimension(), 1), tc
='d')
587 Return the matrix ``A`` used in our CVXOPT construction.
589 This matrix ``A`` appears on the right-hand side of ``Ax = b``
590 in the statement of the CVXOPT conelp program.
594 It is not safe to cache any of the matrices passed to
595 CVXOPT, because it can clobber them.
601 A ``1``-by-``(1 + self.dimension())`` row vector. Its first
602 entry is zero, and the rest are the entries of ``e2``.
607 >>> from dunshire import *
608 >>> K = NonnegativeOrthant(3)
609 >>> L = [[1,1,1],[1,1,1],[1,1,1]]
612 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
614 [0.0000000 1.0000000 2.0000000 3.0000000]
618 return matrix([0, self
.e2()], (1, self
.dimension() + 1), 'd')
624 Return the matrix ``G`` used in our CVXOPT construction.
626 Thus matrix ``G`` appears on the left-hand side of ``Gx + s = h``
627 in the statement of the CVXOPT conelp program.
631 It is not safe to cache any of the matrices passed to
632 CVXOPT, because it can clobber them.
638 A ``2*self.dimension()``-by-``(1 + self.dimension())`` matrix.
643 >>> from dunshire import *
644 >>> K = NonnegativeOrthant(3)
645 >>> L = [[4,5,6],[7,8,9],[10,11,12]]
648 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
650 [ 0.0000000 -1.0000000 0.0000000 0.0000000]
651 [ 0.0000000 0.0000000 -1.0000000 0.0000000]
652 [ 0.0000000 0.0000000 0.0000000 -1.0000000]
653 [ 1.0000000 -4.0000000 -5.0000000 -6.0000000]
654 [ 2.0000000 -7.0000000 -8.0000000 -9.0000000]
655 [ 3.0000000 -10.0000000 -11.0000000 -12.0000000]
659 identity_matrix
= identity(self
.dimension())
660 return append_row(append_col(self
._zero
(), -identity_matrix
),
661 append_col(self
.e1(), -self
.L()))
666 Return the vector ``c`` used in our CVXOPT construction.
668 The column vector ``c`` appears in the objective function
669 value ``<c,x>`` in the statement of the CVXOPT conelp program.
673 It is not safe to cache any of the matrices passed to
674 CVXOPT, because it can clobber them.
680 A ``self.dimension()``-by-``1`` column vector.
685 >>> from dunshire import *
686 >>> K = NonnegativeOrthant(3)
687 >>> L = [[4,5,6],[7,8,9],[10,11,12]]
690 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
699 return matrix([-1, self
._zero
()])
704 Return the cone ``C`` used in our CVXOPT construction.
706 The cone ``C`` is the cone over which the conelp program takes
713 The cartesian product of ``K`` with itself.
718 >>> from dunshire import *
719 >>> K = NonnegativeOrthant(3)
720 >>> L = [[4,5,6],[7,8,9],[10,11,12]]
723 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
725 Cartesian product of dimension 6 with 2 factors:
726 * Nonnegative orthant in the real 3-space
727 * Nonnegative orthant in the real 3-space
730 return CartesianProduct(self
._K
, self
._K
)
734 Return the ``h`` vector used in our CVXOPT construction.
736 The ``h`` vector appears on the right-hand side of :math:`Gx + s
737 = h` in the statement of the CVXOPT conelp program.
741 It is not safe to cache any of the matrices passed to
742 CVXOPT, because it can clobber them.
748 A ``2*self.dimension()``-by-``1`` column vector of zeros.
753 >>> from dunshire import *
754 >>> K = NonnegativeOrthant(3)
755 >>> L = [[4,5,6],[7,8,9],[10,11,12]]
758 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
770 return matrix([self
._zero
(), self
._zero
()])
776 Return the ``b`` vector used in our CVXOPT construction.
778 The vector ``b`` appears on the right-hand side of :math:`Ax =
779 b` in the statement of the CVXOPT conelp program.
781 This method is static because the dimensions and entries of
782 ``b`` are known beforehand, and don't depend on any other
783 properties of the game.
787 It is not safe to cache any of the matrices passed to
788 CVXOPT, because it can clobber them.
794 A ``1``-by-``1`` matrix containing a single entry ``1``.
799 >>> from dunshire import *
800 >>> K = NonnegativeOrthant(3)
801 >>> L = [[4,5,6],[7,8,9],[10,11,12]]
804 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
810 return matrix([1], tc
='d')
813 def player1_start(self
):
815 Return a feasible starting point for player one.
817 This starting point is for the CVXOPT formulation and not for
818 the original game. The basic premise is that if you normalize
819 :meth:`e2`, then you get a point in :meth:`K` that makes a unit
820 inner product with :meth:`e2`. We then get to choose the primal
821 objective function value such that the constraint involving
822 :meth:`L` is satisfied.
824 p
= self
.e2() / (norm(self
.e2()) ** 2)
825 dist
= self
.K().ball_radius(self
.e1())
826 nu
= - self
._L_specnorm
()/(dist
*norm(self
.e2()))
827 x
= matrix([nu
, p
], (self
.dimension() + 1, 1))
830 return {'x': x, 's': s}
833 def player2_start(self
):
835 Return a feasible starting point for player two.
837 q
= self
.e1() / (norm(self
.e1()) ** 2)
838 dist
= self
.K().ball_radius(self
.e2())
839 omega
= self
._L_specnorm
()/(dist
*norm(self
.e1()))
842 z1
= y
*self
.e2() - self
.L().trans()*z2
843 z
= matrix([z1
, z2
], (self
.dimension()*2, 1))
845 return {'y': y, 'z': z}
848 def _L_specnorm(self
):
850 Compute the spectral norm of ``L`` and cache it.
852 if self
._L_specnorm
_value
is None:
853 self
._L_specnorm
_value
= specnorm(self
.L())
854 return self
._L_specnorm
_value
858 Solve this linear game and return a :class:`Solution`.
864 A :class:`Solution` object describing the game's value and
865 the optimal strategies of both players.
869 GameUnsolvableException
870 If the game could not be solved (if an optimal solution to its
871 associated cone program was not found).
874 If the game could not be solved because CVXOPT crashed while
875 trying to take the square root of a negative number.
880 This example is computed in Gowda and Ravindran in the section
881 "The value of a Z-transformation"::
883 >>> from dunshire import *
884 >>> K = NonnegativeOrthant(3)
885 >>> L = [[1,-5,-15],[-1,2,-3],[-12,-15,1]]
888 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
889 >>> print(SLG.solution())
890 Game value: -6.172...
900 The value of the following game can be computed using the fact
901 that the identity is invertible::
903 >>> from dunshire import *
904 >>> K = NonnegativeOrthant(3)
905 >>> L = [[1,0,0],[0,1,0],[0,0,1]]
908 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
909 >>> print(SLG.solution())
920 This is another Gowda/Ravindran example that is supposed to have
921 a negative game value::
923 >>> from dunshire import *
924 >>> from dunshire.options import ABS_TOL
925 >>> L = [[1, -2], [-2, 1]]
926 >>> K = NonnegativeOrthant(2)
929 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
930 >>> SLG.solution().game_value() < -ABS_TOL
933 The following two games are problematic numerically, but we
934 should be able to solve them::
936 >>> from dunshire import *
937 >>> L = [[-0.95237953890954685221, 1.83474556206462535712],
938 ... [ 1.30481749924621448500, 1.65278664543326403447]]
939 >>> K = NonnegativeOrthant(2)
940 >>> e1 = [0.95477167524644313001, 0.63270781756540095397]
941 >>> e2 = [0.39633793037154141370, 0.10239281495640320530]
942 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
943 >>> print(SLG.solution())
944 Game value: 18.767...
954 >>> from dunshire import *
955 >>> L = [[1.54159395026049472754, 2.21344728574316684799],
956 ... [1.33147433507846657541, 1.17913616272988108769]]
957 >>> K = NonnegativeOrthant(2)
958 >>> e1 = [0.39903040089404784307, 0.12377403622479113410]
959 >>> e2 = [0.15695181142215544612, 0.85527381344651265405]
960 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
961 >>> print(SLG.solution())
962 Game value: 24.614...
972 opts
= {'show_progress': False}
973 soln_dict
= solvers
.conelp(self
._c
(),
976 self
.C().cvxopt_dims(),
979 primalstart
=self
.player1_start(),
981 except ValueError as error
:
982 if str(error
) == 'math domain error':
983 # Oops, CVXOPT tried to take the square root of a
984 # negative number. Report some details about the game
985 # rather than just the underlying CVXOPT crash.
986 printing
.options
['dformat'] = options
.DEBUG_FLOAT_FORMAT
987 raise PoorScalingException(self
)
991 # The optimal strategies are named ``p`` and ``q`` in the
992 # background documentation, and we need to extract them from
993 # the CVXOPT ``x`` and ``z`` variables. The objective values
994 # :math:`nu` and :math:`omega` can also be found in the CVXOPT
995 # ``x`` and ``y`` variables; however, they're stored
996 # conveniently as separate entries in the solution dictionary.
997 p1_value
= -soln_dict
['primal objective']
998 p2_value
= -soln_dict
['dual objective']
999 p1_optimal
= soln_dict
['x'][1:]
1000 p2_optimal
= soln_dict
['z'][self
.dimension():]
1002 # The "status" field contains "optimal" if everything went
1003 # according to plan. Other possible values are "primal
1004 # infeasible", "dual infeasible", "unknown", all of which mean
1005 # we didn't get a solution.
1007 # The "infeasible" ones are the worst, since they indicate
1008 # that CVXOPT is convinced the problem is infeasible (and that
1010 if soln_dict
['status'] in ['primal infeasible', 'dual infeasible']:
1011 printing
.options
['dformat'] = options
.DEBUG_FLOAT_FORMAT
1012 raise GameUnsolvableException(self
, soln_dict
)
1014 # The "optimal" and "unknown" results, we actually treat the
1015 # same. Even if CVXOPT bails out due to numerical difficulty,
1016 # it will have some candidate points in mind. If those
1017 # candidates are good enough, we take them. We do the same
1018 # check (perhaps pointlessly so) for "optimal" results.
1020 # First we check that the primal/dual objective values are
1021 # close enough (one could be low by ABS_TOL, the other high by
1022 # it) because otherwise CVXOPT might return "unknown" and give
1023 # us two points in the cone that are nowhere near optimal.
1024 if abs(p1_value
- p2_value
) > 2*options
.ABS_TOL
:
1025 printing
.options
['dformat'] = options
.DEBUG_FLOAT_FORMAT
1026 raise GameUnsolvableException(self
, soln_dict
)
1028 # And we also check that the points it gave us belong to the
1029 # cone, just in case...
1030 if (p1_optimal
not in self
._K
) or (p2_optimal
not in self
._K
):
1031 printing
.options
['dformat'] = options
.DEBUG_FLOAT_FORMAT
1032 raise GameUnsolvableException(self
, soln_dict
)
1034 # For the game value, we could use any of:
1038 # * (p1_value + p2_value)/2
1041 # We want the game value to be the payoff, however, so it
1042 # makes the most sense to just use that, even if it means we
1043 # can't test the fact that p1_value/p2_value are close to the
1045 payoff
= self
.payoff(p1_optimal
, p2_optimal
)
1046 return Solution(payoff
, p1_optimal
, p2_optimal
)
1049 def condition(self
):
1051 Return the condition number of this game.
1053 In the CVXOPT construction of this game, two matrices ``G`` and
1054 ``A`` appear. When those matrices are nasty, numerical problems
1055 can show up. We define the condition number of this game to be
1056 the average of the condition numbers of ``G`` and ``A`` in the
1057 CVXOPT construction. If the condition number of this game is
1058 high, then you can expect numerical difficulty (such as
1059 :class:`PoorScalingException`).
1065 A real number greater than or equal to one that measures how
1066 bad this game is numerically.
1071 >>> from dunshire import *
1072 >>> K = NonnegativeOrthant(1)
1076 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
1077 >>> actual = SLG.condition()
1078 >>> expected = 1.8090169943749477
1079 >>> abs(actual - expected) < options.ABS_TOL
1083 return (condition_number(self
._G
()) + condition_number(self
.A()))/2
1088 Return the dual game to this game.
1090 If :math:`G = \left(L,K,e_{1},e_{2}\right)` is a linear game,
1091 then its dual is :math:`G^{*} =
1092 \left(L^{*},K^{*},e_{2},e_{1}\right)`. However, since this cone
1093 is symmetric, :math:`K^{*} = K`.
1098 >>> from dunshire import *
1099 >>> K = NonnegativeOrthant(3)
1100 >>> L = [[1,-5,-15],[-1,2,-3],[-12,-15,1]]
1103 >>> SLG = SymmetricLinearGame(L, K, e1, e2)
1104 >>> print(SLG.dual())
1105 The linear game (L, K, e1, e2) where
1109 K = Nonnegative orthant in the real 3-space,
1116 Condition((L, K, e1, e2)) = 44.476...
1119 # We pass ``self.L()`` right back into the constructor, because
1120 # it will be transposed there. And keep in mind that ``self._K``
1122 return SymmetricLinearGame(self
.L(),