]> gitweb.michael.orlitzky.com - dunshire.git/commitdiff
Add a PoorScalingException for the "math domain errors" that haunt me.
authorMichael Orlitzky <michael@orlitzky.com>
Sun, 30 Oct 2016 20:40:58 +0000 (16:40 -0400)
committerMichael Orlitzky <michael@orlitzky.com>
Sun, 30 Oct 2016 20:40:58 +0000 (16:40 -0400)
dunshire/errors.py
dunshire/games.py

index c58a57bdbfdc7c599164684af8590c1308426287..1ecf3dc7ffad232bf3fd540e119778f2eb42be95 100644 (file)
@@ -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)
         # 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].
+       <BLANKLINE>
+    """
+    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)
index 80f5f8ae2a46d09f15e34546c511edeca286f4fa..9610802e4ffd4108d2c244b2c36a9e44084427cd 100644 (file)
@@ -7,7 +7,7 @@ knows how to solve a linear game.
 
 from cvxopt import matrix, printing, solvers
 from .cones import CartesianProduct
 
 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
 
 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).
 
             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
         --------
 
         Examples
         --------
 
@@ -425,7 +429,16 @@ class SymmetricLinearGame:
 
         # Actually solve the thing and obtain a dictionary describing
         # what happened.
 
         # 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
 
         # The optimal strategies are named ``p`` and ``q`` in the
         # background documentation, and we need to extract them from