]> gitweb.michael.orlitzky.com - dunshire.git/blobdiff - dunshire/errors.py
Add setup.py and reorganize everything to make its "test" command happy.
[dunshire.git] / dunshire / errors.py
diff --git a/dunshire/errors.py b/dunshire/errors.py
new file mode 100644 (file)
index 0000000..b63344a
--- /dev/null
@@ -0,0 +1,139 @@
+"""
+Errors that can occur when solving a linear game.
+"""
+
+from cvxopt import matrix
+
+
+def _pretty_format_dict(dictionary):
+    """
+    Return a pretty-formatted string representation of a dictionary
+    containing CVXOPT matrices.
+
+    The dictionary is also sorted so that it can be tested repeatably.
+
+    Examples
+    --------
+
+        >>> d = {'foo': 1.234, 'bar': matrix([1,2,3])}
+        >>> print(_pretty_format_dict(d))
+        bar:
+          [ 1]
+          [ 2]
+          [ 3]
+        foo: 1.234
+
+    """
+    result = ''
+    for (key, value) in sorted(dictionary.items()):
+        if isinstance(value, matrix):
+            # Display matrices on their own lines, indented.
+            result += '{:s}:'.format(key)
+            colvec = '\n{!s}'.format(value)
+            result += '\n  '.join(colvec.splitlines())
+            result += '\n'
+        else:
+            result += '{:s}: {!s}\n'.format(key, value)
+
+    return result.rstrip('\n') # Kills trailing newlines on matrices.
+
+
+class GameUnsolvableException(Exception):
+    """
+    An exception raised when a game cannot be solved.
+
+    Every linear game has a solution. If we can't solve the conic
+    program associated with a linear game, then something is wrong with
+    either the model or the input, and this exception should be raised.
+
+    Parameters
+    ----------
+
+    solution_dict : dict
+        The solution dictionary returned from the failed cone program.
+
+    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)
+       >>> d = {'residual as dual infeasibility certificate': None,
+       ...      'y': matrix([1,1]),
+       ...      'dual slack': 8.779496368228267e-10,
+       ...      'z': matrix([1,1,0,0]),
+       ...      's': None,
+       ...      'primal infeasibility': None,
+       ...      'status': 'primal infeasible',
+       ...      'dual infeasibility': None,
+       ...      'relative gap': None,
+       ...      'iterations': 5,
+       ...      'primal slack': None,
+       ...      'x': None,
+       ...      'dual objective': 1.0,
+       ...      'primal objective': None,
+       ...      'gap': None,
+       ...      'residual as primal infeasibility certificate':
+       ...          3.986246886102996e-09}
+       >>> print(GameUnsolvableException(G,d))
+       Solution failed with result "primal infeasible."
+       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].
+       CVXOPT returned:
+         dual infeasibility: None
+         dual objective: 1.0
+         dual slack: 8.779496368228267e-10
+         gap: None
+         iterations: 5
+         primal infeasibility: None
+         primal objective: None
+         primal slack: None
+         relative gap: None
+         residual as dual infeasibility certificate: None
+         residual as primal infeasibility certificate: 3.986246886102996e-09
+         s: None
+         status: primal infeasible
+         x: None
+         y:
+           [ 1]
+           [ 1]
+         z:
+           [ 1]
+           [ 1]
+           [ 0]
+           [ 0]
+    """
+    def __init__(self, game, solution_dict):
+        """
+        Create a new GameUnsolvableException object.
+        """
+        super().__init__()
+        self._game = game
+        self._solution_dict = solution_dict
+
+
+    def __str__(self):
+        """
+        Return a string representation of this exception.
+
+        The returned representation highlights the "status" field of the
+        CVXOPT dictionary, since that should explain what went
+        wrong. The game details and full CVXOPT solution dictionary is
+        included after the status.
+        """
+        tpl = 'Solution failed with result "{:s}."\n' \
+              '{!s}\n' \
+              'CVXOPT returned:\n  {!s}'
+        cvx_lines = _pretty_format_dict(self._solution_dict).splitlines()
+        # Indent the whole dict by two spaces.
+        cvx_str = '\n  '.join(cvx_lines)
+        return tpl.format(self._solution_dict['status'], self._game, cvx_str)