From edb2575f68414f97408d876368172d1862cedc5e Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sun, 30 Oct 2016 16:40:58 -0400 Subject: [PATCH] Add a PoorScalingException for the "math domain errors" that haunt me. --- dunshire/errors.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++ dunshire/games.py | 17 +++++++++-- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/dunshire/errors.py b/dunshire/errors.py index c58a57b..1ecf3dc 100644 --- a/dunshire/errors.py +++ b/dunshire/errors.py @@ -140,3 +140,75 @@ class GameUnsolvableException(Exception): # Indent the whole dict by two spaces. cvx_str = '\n '.join(cvx_lines) return tpl.format(self._solution_dict['status'], self._game, cvx_str) + + +class PoorScalingException(Exception): + """ + An exception raised when poor scaling leads to solution errors. + + Under certain circumstances, a problem that should be solvable can + trigger errors in CVXOPT. The end result is the following + :class:`ValueError`:: + + Traceback (most recent call last): + ... + return math.sqrt(x[offset] - a) * math.sqrt(x[offset] + a) + ValueError: math domain error + + This happens when one of the arguments to :func:`math.sqrt` is + negative, but the underlying cause is elusive. We're blaming it on + "poor scaling," whatever that means. + + Similar issues have been discussed a few times on the CVXOPT mailing + list; for example, + + 1. https://groups.google.com/forum/#!msg/cvxopt/TeQGdc2b4Xc/j5_mQME_rvUJ + 2. https://groups.google.com/forum/#!topic/cvxopt/HZrRfaoM0pk + 3. https://groups.google.com/forum/#!topic/cvxopt/riFSxB31zU4 + + Parameters + ---------- + + game : SymmetricLinearGame + A copy of the game whose solution failed. + + Examples + -------- + + >>> from dunshire import * + >>> K = IceCream(2) + >>> L = [[1,2],[3,4]] + >>> e1 = [1, 0.1] + >>> e2 = [3, 0.1] + >>> G = SymmetricLinearGame(L,K,e1,e2) + >>> print(PoorScalingException(G)) + Solution failed due to poor scaling. + The linear game (L, K, e1, e2) where + L = [ 1 2] + [ 3 4], + K = Lorentz "ice cream" cone in the real 2-space, + e1 = [1.0000000] + [0.1000000], + e2 = [3.0000000] + [0.1000000]. + + """ + def __init__(self, game): + """ + Create a new :class:`PoorScalingException` object. + """ + super().__init__() + self._game = game + + + def __str__(self): + """ + Return a string representation of this exception. + + Pretty much all we can say is that there was poor scaling; that + is, that CVXOPT failed. The game details are included after + that. + """ + tpl = 'Solution failed due to poor scaling.\n' \ + '{!s}\n' + return tpl.format(self._game) diff --git a/dunshire/games.py b/dunshire/games.py index 80f5f8a..9610802 100644 --- a/dunshire/games.py +++ b/dunshire/games.py @@ -7,7 +7,7 @@ knows how to solve a linear game. from cvxopt import matrix, printing, solvers from .cones import CartesianProduct -from .errors import GameUnsolvableException +from .errors import GameUnsolvableException, PoorScalingException from .matrices import append_col, append_row, identity from . import options @@ -351,6 +351,10 @@ class SymmetricLinearGame: 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 -------- @@ -425,7 +429,16 @@ class SymmetricLinearGame: # Actually solve the thing and obtain a dictionary describing # what happened. - soln_dict = solvers.conelp(c, G, h, C.cvxopt_dims(), A, b) + try: + soln_dict = solvers.conelp(c, G, h, C.cvxopt_dims(), A, 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 -- 2.43.2