- 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.
-
- 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 game can be solved easily, so the first attempt in
- :meth:`solution` should succeed::
-
- >>> 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)
- >>> 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
-
- """
- try:
- solvers.options['show_progress'] = options.VERBOSE
- solvers.options['abs_tol'] = tolerance
- soln_dict = solvers.conelp(self._c(),
- self._G(),
- self._h(),
- self._C().cvxopt_dims(),
- self._A(),
- self._b())
- except ValueError as e:
- if str(e) == '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.
- raise PoorScalingException(self)
- else:
- raise e
-
- # The optimal strategies are named ``p`` and ``q`` in the
- # background documentation, and we need to extract them from
- # the CVXOPT ``x`` and ``z`` variables. The objective values
- # :math:`nu` and :math:`omega` can also be found in the CVXOPT
- # ``x`` and ``y`` variables; however, they're stored
- # conveniently as separate entries in the solution dictionary.
- 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():]
-
- # 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).
- if soln_dict['status'] in ['primal infeasible', 'dual infeasible']:
- 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)
-