]> gitweb.michael.orlitzky.com - dunshire.git/blob - dunshire/errors.py
7cd8342ece4f659bfdc3c2378ce6c7774848e2ba
[dunshire.git] / dunshire / errors.py
1 """
2 Errors that can occur when solving a linear game.
3 """
4
5 from cvxopt import matrix
6
7
8 def _pretty_format_dict(dictionary):
9 """
10 Return a pretty-formatted string representation of a dictionary
11 containing CVXOPT matrices.
12
13 The dictionary is also sorted so that it can be tested repeatably.
14
15 Examples
16 --------
17
18 >>> d = {'foo': 1.234, 'bar': matrix([1,2,3])}
19 >>> print(_pretty_format_dict(d))
20 bar:
21 [ 1]
22 [ 2]
23 [ 3]
24 foo: 1.234
25
26 """
27 result = ''
28 for (key, value) in sorted(dictionary.items()):
29 if isinstance(value, matrix):
30 # Display matrices on their own lines, indented.
31 result += '{:s}:'.format(key)
32 colvec = '\n{!s}'.format(value)
33 result += '\n '.join(colvec.splitlines())
34 result += '\n'
35 else:
36 result += '{:s}: {!s}\n'.format(key, value)
37
38 return result.rstrip('\n') # Kills trailing newlines on matrices.
39
40
41 class GameUnsolvableException(Exception):
42 """
43 An exception raised when a game cannot be solved.
44
45 Every linear game has a solution. If we can't solve the conic
46 program associated with a linear game, then something is wrong with
47 either the model or the input, and this exception should be raised.
48
49 Parameters
50 ----------
51
52 game : SymmetricLinearGame
53 A copy of the game whose solution failed.
54
55 solution_dict : dict
56 The solution dictionary returned from the failed cone program.
57
58 Examples
59 --------
60
61 >>> from dunshire import *
62 >>> K = IceCream(2)
63 >>> L = [[1,2],[3,4]]
64 >>> e1 = [1, 0.1]
65 >>> e2 = [3, 0.1]
66 >>> G = SymmetricLinearGame(L,K,e1,e2)
67 >>> d = {'residual as dual infeasibility certificate': None,
68 ... 'y': matrix([1,1]),
69 ... 'dual slack': 8.779496368228267e-10,
70 ... 'z': matrix([1,1,0,0]),
71 ... 's': None,
72 ... 'primal infeasibility': None,
73 ... 'status': 'primal infeasible',
74 ... 'dual infeasibility': None,
75 ... 'relative gap': None,
76 ... 'iterations': 5,
77 ... 'primal slack': None,
78 ... 'x': None,
79 ... 'dual objective': 1.0,
80 ... 'primal objective': None,
81 ... 'gap': None,
82 ... 'residual as primal infeasibility certificate':
83 ... 3.986246886102996e-09}
84 >>> print(GameUnsolvableException(G,d))
85 Solution failed with result "primal infeasible."
86 The linear game (L, K, e1, e2) where
87 L = [ 1 2]
88 [ 3 4],
89 K = Lorentz "ice cream" cone in the real 2-space,
90 e1 = [1.0000000]
91 [0.1000000],
92 e2 = [3.0000000]
93 [0.1000000],
94 Condition((L, K, e1, e2)) = 4.155638.
95 CVXOPT returned:
96 dual infeasibility: None
97 dual objective: 1.0
98 dual slack: 8.779496368228267e-10
99 gap: None
100 iterations: 5
101 primal infeasibility: None
102 primal objective: None
103 primal slack: None
104 relative gap: None
105 residual as dual infeasibility certificate: None
106 residual as primal infeasibility certificate: 3.986246886102996e-09
107 s: None
108 status: primal infeasible
109 x: None
110 y:
111 [ 1]
112 [ 1]
113 z:
114 [ 1]
115 [ 1]
116 [ 0]
117 [ 0]
118 """
119 def __init__(self, game, solution_dict):
120 """
121 Create a new :class:`GameUnsolvableException` object.
122 """
123 super().__init__()
124 self._game = game
125 self._solution_dict = solution_dict
126
127
128 def __str__(self):
129 """
130 Return a string representation of this exception.
131
132 The returned representation highlights the "status" field of the
133 CVXOPT dictionary, since that should explain what went
134 wrong. The game details and full CVXOPT solution dictionary are
135 included after the status.
136 """
137 tpl = 'Solution failed with result "{:s}."\n' \
138 '{!s}\n' \
139 'CVXOPT returned:\n {!s}'
140 cvx_lines = _pretty_format_dict(self._solution_dict).splitlines()
141 # Indent the whole dict by two spaces.
142 cvx_str = '\n '.join(cvx_lines)
143 return tpl.format(self._solution_dict['status'], self._game, cvx_str)
144
145
146 class PoorScalingException(Exception):
147 """
148 An exception raised when poor scaling leads to solution errors.
149
150 Under certain circumstances, a problem that should be solvable can
151 trigger errors in CVXOPT. The end result is the following
152 :class:`ValueError`::
153
154 Traceback (most recent call last):
155 ...
156 return math.sqrt(x[offset] - a) * math.sqrt(x[offset] + a)
157 ValueError: math domain error
158
159 This happens when one of the arguments to :func:`math.sqrt` is
160 negative, but the underlying cause is elusive. We're blaming it on
161 "poor scaling," whatever that means.
162
163 Similar issues have been discussed a few times on the CVXOPT mailing
164 list; for example,
165
166 1. https://groups.google.com/forum/#!msg/cvxopt/TeQGdc2b4Xc/j5_mQME_rvUJ
167 2. https://groups.google.com/forum/#!topic/cvxopt/HZrRfaoM0pk
168 3. https://groups.google.com/forum/#!topic/cvxopt/riFSxB31zU4
169
170 Parameters
171 ----------
172
173 game : SymmetricLinearGame
174 A copy of the game whose solution failed.
175
176 Examples
177 --------
178
179 >>> from dunshire import *
180 >>> K = IceCream(2)
181 >>> L = [[1,2],[3,4]]
182 >>> e1 = [1, 0.1]
183 >>> e2 = [3, 0.1]
184 >>> G = SymmetricLinearGame(L,K,e1,e2)
185 >>> print(PoorScalingException(G))
186 Solution failed due to poor scaling.
187 The linear game (L, K, e1, e2) where
188 L = [ 1 2]
189 [ 3 4],
190 K = Lorentz "ice cream" cone in the real 2-space,
191 e1 = [1.0000000]
192 [0.1000000],
193 e2 = [3.0000000]
194 [0.1000000],
195 Condition((L, K, e1, e2)) = 4.155638.
196 <BLANKLINE>
197 """
198 def __init__(self, game):
199 """
200 Create a new :class:`PoorScalingException` object.
201 """
202 super().__init__()
203 self._game = game
204
205
206 def __str__(self):
207 """
208 Return a string representation of this exception.
209
210 Pretty much all we can say is that there was poor scaling; that
211 is, that CVXOPT failed. The game details are included after
212 that.
213 """
214 tpl = 'Solution failed due to poor scaling.\n' \
215 '{!s}\n'
216 return tpl.format(self._game)