]> 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)
+
+
+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 .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