]> gitweb.michael.orlitzky.com - dunshire.git/blob - dunshire/errors.py
Remove the condition number from the game string representation.
[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 CVXOPT returned:
95 dual infeasibility: None
96 dual objective: 1.0
97 dual slack: 8.779496368228267e-10
98 gap: None
99 iterations: 5
100 primal infeasibility: None
101 primal objective: None
102 primal slack: None
103 relative gap: None
104 residual as dual infeasibility certificate: None
105 residual as primal infeasibility certificate: 3.986246886102996e-09
106 s: None
107 status: primal infeasible
108 x: None
109 y:
110 [ 1]
111 [ 1]
112 z:
113 [ 1]
114 [ 1]
115 [ 0]
116 [ 0]
117 """
118 def __init__(self, game, solution_dict):
119 """
120 Create a new :class:`GameUnsolvableException` object.
121 """
122 super().__init__()
123 self._game = game
124 self._solution_dict = solution_dict
125
126
127 def __str__(self):
128 """
129 Return a string representation of this exception.
130
131 The returned representation highlights the "status" field of the
132 CVXOPT dictionary, since that should explain what went
133 wrong. The game details and full CVXOPT solution dictionary are
134 included after the status.
135 """
136 tpl = 'Solution failed with result "{:s}."\n' \
137 '{!s}\n' \
138 'CVXOPT returned:\n {!s}'
139 cvx_lines = _pretty_format_dict(self._solution_dict).splitlines()
140 # Indent the whole dict by two spaces.
141 cvx_str = '\n '.join(cvx_lines)
142 return tpl.format(self._solution_dict['status'], self._game, cvx_str)
143
144
145 class PoorScalingException(Exception):
146 """
147 An exception raised when poor scaling leads to solution errors.
148
149 Under certain circumstances, a problem that should be solvable can
150 trigger errors in CVXOPT. The end result is the following
151 :class:`ValueError`::
152
153 Traceback (most recent call last):
154 ...
155 return math.sqrt(x[offset] - a) * math.sqrt(x[offset] + a)
156 ValueError: math domain error
157
158 This happens when one of the arguments to :func:`math.sqrt` is
159 negative, but the underlying cause is elusive. We're blaming it on
160 "poor scaling," whatever that means.
161
162 Similar issues have been discussed a few times on the CVXOPT mailing
163 list; for example,
164
165 1. https://groups.google.com/forum/#!msg/cvxopt/TeQGdc2b4Xc/j5_mQME_rvUJ
166 2. https://groups.google.com/forum/#!topic/cvxopt/HZrRfaoM0pk
167 3. https://groups.google.com/forum/#!topic/cvxopt/riFSxB31zU4
168
169 Parameters
170 ----------
171
172 game : SymmetricLinearGame
173 A copy of the game whose solution failed.
174
175 Examples
176 --------
177
178 >>> from dunshire import *
179 >>> K = IceCream(2)
180 >>> L = [[1,2],[3,4]]
181 >>> e1 = [1, 0.1]
182 >>> e2 = [3, 0.1]
183 >>> G = SymmetricLinearGame(L,K,e1,e2)
184 >>> print(PoorScalingException(G))
185 Solution failed due to poor scaling.
186 The linear game (L, K, e1, e2) where
187 L = [ 1 2]
188 [ 3 4],
189 K = Lorentz "ice cream" cone in the real 2-space,
190 e1 = [1.0000000]
191 [0.1000000],
192 e2 = [3.0000000]
193 [0.1000000]
194 <BLANKLINE>
195 """
196 def __init__(self, game):
197 """
198 Create a new :class:`PoorScalingException` object.
199 """
200 super().__init__()
201 self._game = game
202
203
204 def __str__(self):
205 """
206 Return a string representation of this exception.
207
208 Pretty much all we can say is that there was poor scaling; that
209 is, that CVXOPT failed. The game details are included after
210 that.
211 """
212 tpl = 'Solution failed due to poor scaling.\n' \
213 '{!s}\n'
214 return tpl.format(self._game)