1 """System of linear constraints for constrained optimization.
2
3 There are more informative comments in the source code.
4
5 Copyright 2008-2012 Research Foundation State University of New York
6 This file is part of QUB Express.
7
8 QUB Express is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 QUB Express is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License,
19 named LICENSE.txt, in the QUB Express program directory. If not, see
20 <http://www.gnu.org/licenses/>.
21
22 """
23
24 from numpy import *
25 import scipy
26 import scipy.linalg
27
28
29
30
31
32
34 """Returns all pairs (a,b) such that (0 <= a,b < N) and (a != b); in canonical order."""
35 ix = []
36 if N <= 0: return ix
37 for a in xrange(N):
38 for b in xrange(N):
39 if a != b:
40 ix.append((a,b))
41 return ix
42
44 """
45 Returns a flat array of the useful (non-diagonal) numbers in log(K0) and K1;
46 first all the K0s, then all the K1s, in canonical (L{K_index}) order.
47 """
48 k0, k1 = [], []
49 for a,b in K_index(K0.shape[1]):
50 if a != b:
51 x = K0[a,b]
52 k0.append((x > 0.0) and log(x) or 0.0)
53 k1.append(K1[a,b])
54 K = matrix(zeros(shape=(len(k0)+len(k1), 1)))
55 K[:,0] = matrix(array(k0 + k1)).T
56 return K
57
59 """
60 Returns a flat array of the useful (non-diagonal) numbers in log(K0) and K1;
61 first all the K0s, then all the K1s, in canonical (L{K_index}) order.
62 """
63 k0, k1, k2 = [], [], []
64 for a,b in K_index(K0.shape[1]):
65 if a != b:
66 x = K0[a,b]
67 k0.append((x > 0.0) and log(x) or 0.0)
68 k1.append(K1[a,b])
69 k2.append(K2[a,b])
70 K = matrix(zeros(shape=(len(k0)+len(k1)+len(k2), 1)))
71 K[:,0] = matrix(array(k0 + k1 + k2)).T
72 return K
73
74
75
76
78 """
79 Returns matrices K0, and K1 filled with numbers from K (see L{K01toK}).
80 You must provide last_K0 to disambiguate between K[i] == 0 == log(1) vs. K0[i] == 0 == unconnected.
81 """
82
83 N = int(round((1 + sqrt(1 + 2*len(K))) / 2.0))
84 ixs = K_index(N)
85 K0 = matrix(zeros(shape=(N,N)))
86 i = 0
87 for a,b in ixs:
88 if a != b:
89 if last_K0[a,b]:
90 K0[a,b] = exp(K[i,0])
91 i += 1
92 K1 = matrix(zeros(shape=(N,N)))
93 for a,b in ixs:
94 if a != b:
95 K1[a,b] = K[i]
96 i += 1
97 return K0, K1
98
100 """
101 Returns matrices K0, K1, K2 filled with numbers from K (see L{K01toK}).
102 You must provide last_K0 to disambiguate between K[i] == 0 == log(1) vs. K0[i] == 0 == unconnected.
103 """
104 N = last_K0.shape[0]
105 ixs = K_index(N)
106 K0 = matrix(zeros(shape=(N,N)))
107 i = 0
108 for a,b in ixs:
109 if a != b:
110 if last_K0[a,b]:
111 K0[a,b] = exp(K[i,0])
112 i += 1
113 K1 = matrix(zeros(shape=(N,N)))
114 for a,b in ixs:
115 if a != b:
116 K1[a,b] = K[i]
117 i += 1
118 K2 = matrix(zeros(shape=(N,N)))
119 for a,b in ixs:
120 if a != b:
121 K2[a,b] = K[i]
122 i += 1
123 return K0, K1, K2
124
125
126
127
128
129
130
131
133 """Returns the constraints (Ain, Bin) [11] which fix unconnected k0 and unsensitive k1."""
134 aa = []
135 bb = []
136 ixs = K_index(K0.shape[0])
137 for i, xx in enumerate(ixs):
138 a, b = xx
139 if not K0[a,b]:
140 coeffs = zeros(shape=(1,3*len(ixs)))
141 coeffs[0, i] = 1.0
142 aa.append(coeffs)
143 bb.append(0.0)
144 V[a,b] = 0
145 if not V[a,b]:
146 coeffs = zeros(shape=(1,3*len(ixs)))
147 coeffs[0, len(ixs)+i] = 1.0
148 aa.append(coeffs)
149 bb.append(0.0)
150 if not P[a,b]:
151 coeffs = zeros(shape=(1,3*len(ixs)))
152 coeffs[0, 2*len(ixs)+i] = 1.0
153 aa.append(coeffs)
154 bb.append(0.0)
155 if aa:
156 return vstack(aa), matrix(array(bb)).T
157 else:
158 return matrix(zeros(shape=(0,0))), matrix(zeros(shape=(0,1)))
159
160
162 """
163 Returns the constraints (Ain, Bin) [11]
164 corresponding to the tuples (name, states) in constraints
165 where name is in ["FixRate", "FixExp", "FixPress", "ScaleRate", "ScaleExp", "ScalePress", "BalanceLoop", "ImbalanceLoop"]
166 and states is a sequence of state indices. The first four names require 2,2,4,4 states respectively;
167 the last two require at least 3."""
168 aa = []
169 bb = []
170 ixs = K_index(K0.shape[0])
171 ixo = dict([(ab, i) for i, ab in enumerate(ixs)])
172 N = len(ixs)
173 for name, states in constraints:
174 if name == 'FixRate' and K0[states[0], states[1]]:
175 r = ixo[(states[0], states[1])]
176 coeffs = zeros(shape=(1,3*N))
177 coeffs[0, r] = 1.0
178 aa.append(coeffs)
179 bb.append(log(K0[states[0], states[1]]))
180 if name == 'FixExp' and K1[states[0], states[1]]:
181 r = ixo[(states[0], states[1])]
182 coeffs = zeros(shape=(1,3*N))
183 coeffs[0, N+r] = 1.0
184 aa.append(coeffs)
185 bb.append(K1[states[0], states[1]])
186 if name == 'FixPress' and K2[states[0], states[1]]:
187 r = ixo[(states[0], states[1])]
188 coeffs = zeros(shape=(1,3*N))
189 coeffs[0, 2*N+r] = 1.0
190 aa.append(coeffs)
191 bb.append(K2[states[0], states[1]])
192 if (name == 'ScaleRate') and K0[states[0], states[1]] and K0[states[2], states[3]]:
193 r1 = ixo[(states[0], states[1])]
194 r2 = ixo[(states[2], states[3])]
195 coeffs = zeros(shape=(1,3*N))
196 coeffs[0, r1] = 1.0
197 coeffs[0, r2] = -1.0
198 aa.append(coeffs)
199 bb.append(log(K0[states[0], states[1]]) - log(K0[states[2], states[3]]))
200 if (name == 'ScaleExp') and K1[states[0], states[1]] and K1[states[2], states[3]]:
201 r1 = ixo[(states[0], states[1])]
202 r2 = ixo[(states[2], states[3])]
203 coeffs = zeros(shape=(1,3*N))
204 coeffs[0, N+r1] = 1.0
205 coeffs[0, N+r2] = - K1[states[0], states[1]] / K1[states[2], states[3]]
206 aa.append(coeffs)
207 bb.append(0.0)
208 if (name == 'ScalePress') and K2[states[0], states[1]] and K2[states[2], states[3]]:
209 r1 = ixo[(states[0], states[1])]
210 r2 = ixo[(states[2], states[3])]
211 coeffs = zeros(shape=(1,3*N))
212 coeffs[0, 2*N+r1] = 1.0
213 coeffs[0, 2*N+r2] = - K2[states[0], states[1]] / K2[states[2], states[3]]
214 aa.append(coeffs)
215 bb.append(0.0)
216 if name == 'LoopBal':
217 v_depend = p_depend = False
218 loopst = list(states) + [states[0]]
219 coeffs = zeros(shape=(1,3*N))
220 for i in xrange(len(loopst)-1):
221 rf, rb = ixo[(loopst[i], loopst[i+1])], ixo[(loopst[i+1], loopst[i])]
222 coeffs[0,rf] = 1.0
223 coeffs[0,rb] = -1.0
224 v_depend = v_depend or V[(loopst[i], loopst[i+1])] or V[(loopst[i+1], loopst[i])]
225 p_depend = p_depend or P[(loopst[i], loopst[i+1])] or P[(loopst[i+1], loopst[i])]
226 aa.append(coeffs)
227 bb.append(0.0)
228 if v_depend:
229 coeffs = zeros(shape=(1,3*N))
230 for i in xrange(len(loopst)-1):
231 rf, rb = ixo[(loopst[i], loopst[i+1])], ixo[(loopst[i+1], loopst[i])]
232 coeffs[0,N+rf] = 1.0
233 coeffs[0,N+rb] = -1.0
234 aa.append(coeffs)
235 bb.append(0.0)
236 if p_depend:
237 coeffs = zeros(shape=(1,3*N))
238 for i in xrange(len(loopst)-1):
239 rf, rb = ixo[(loopst[i], loopst[i+1])], ixo[(loopst[i+1], loopst[i])]
240 coeffs[0,2*N+rf] = 1.0
241 coeffs[0,2*N+rb] = -1.0
242 aa.append(coeffs)
243 bb.append(0.0)
244 elif name == 'LoopImbal':
245 v_depend = p_depend = False
246 loopst = list(states) + [states[0]]
247 coeffs = zeros(shape=(1,3*N))
248 b = 0.0
249 for i in xrange(len(loopst)-1):
250 rf, rb = ixo[(loopst[i], loopst[i+1])], ixo[(loopst[i+1], loopst[i])]
251 coeffs[0,rf] = 1.0
252 coeffs[0,rb] = -1.0
253 b += log(K0[(loopst[i], loopst[i+1])]) - log(K0[(loopst[i+1], loopst[i])])
254 v_depend = v_depend or V[(loopst[i], loopst[i+1])] or V[(loopst[i+1], loopst[i])]
255 p_depend = p_depend or P[(loopst[i], loopst[i+1])] or P[(loopst[i+1], loopst[i])]
256 aa.append(coeffs)
257 bb.append(b)
258 if v_depend:
259 coeffs = zeros(shape=(1,3*N))
260 for i in xrange(len(loopst)-1):
261 rf, rb = ixo[(loopst[i], loopst[i+1])], ixo[(loopst[i+1], loopst[i])]
262 coeffs[0,N+rf] = 1.0
263 coeffs[0,N+rb] = -1.0
264 aa.append(coeffs)
265 bb.append(0.0)
266 if p_depend:
267 coeffs = zeros(shape=(1,3*N))
268 for i in xrange(len(loopst)-1):
269 rf, rb = ixo[(loopst[i], loopst[i+1])], ixo[(loopst[i+1], loopst[i])]
270 coeffs[0,2*N+rf] = 1.0
271 coeffs[0,2*N+rb] = -1.0
272 aa.append(coeffs)
273 bb.append(0.0)
274
275 if aa:
276 return vstack(aa), matrix(array(bb)).T
277 else:
278 return matrix(zeros(shape=(0,N))), matrix(zeros(shape=(0,1)))
279
280
281
283 """Returns combined AutoConstraints(...) and UserConstraints(...)"""
284 A, B = UserConstraints(K0, K1, K2, L, V, P, constraints)
285 Aauto, Bauto = AutoConstraints(K0, K1, K2, L, V, P)
286 if A.shape[0] == 0:
287 return Aauto, Bauto
288 elif Aauto.shape[0] == 0:
289 return A, B
290 else:
291 return vstack((A, Aauto)), vstack((B, Bauto))
292
293
294
295
296
297
298
299
300
301
302
303
304
305
307 """Returns U, W, V, Winv, Ainv, Nsv, such that A = U*W*V.T = Ainv.I, and A has rank Nsv."""
308 u,s,v = linalg.svd(A, full_matrices=True)
309 N = s.shape[0]
310 U = matrix(zeros(shape=(max(A.shape[0], u.shape[0]), max(A.shape[1], u.shape[1]))))
311 U[:u.shape[0],:u.shape[1]] = u
312 S = zeros(shape=(U.shape[1],))
313 S[:N] = s
314 N = len(s[s>=1e-10])
315 W = diag(S)
316 V = matrix(zeros(shape=(A.shape[1], W.shape[1])))
317 V[:v.shape[1],:v.shape[0]] = v.T
318 for M in U, W, V:
319 for i in xrange(M.shape[0]):
320 for j in xrange(M.shape[1]):
321 if abs(M[i,j]) < 1e-10:
322 M[i,j] = 0.0
323 for i,x in enumerate(S):
324 if abs(x) < 1e-10:
325 S[i] = 0.0
326 Sinv = array([x and 1/x or 0 for x in S])
327 Winv = diag(Sinv)
328
329 if linalg.norm(identity(U.shape[0]) - U*U.T) > 1e-8:
330 raise Exception("Bad SVD: U's columns are not orthonormal")
331 if linalg.norm(identity(V.shape[0]) - V*V.T) > 1e-8:
332 raise Exception("Bad SVD: V's columns are not orthonormal")
333 return U, W, V, Winv, V*(Winv*U.T), N
334
335
336
337
339 """Returns constraint system (A, B) with linearly dependent rows deleted."""
340 Csrc = matrix(hstack((A, B)).T)
341 N = max(Csrc.shape)
342 C = matrix(zeros(shape=(N,N)))
343 C[:Csrc.shape[0],:Csrc.shape[1]] = Csrc
344 Csub = C
345 cols = [C[:,i] for i in xrange(Csrc.shape[1])]
346 i = 0
347 rank = 0
348 while i < len(cols):
349 Csub = hstack(cols[:i+1])
350 U, W, V, Winv, Cinv, Nsv = adjusted_svd(Csub)
351 if Nsv == rank:
352 del cols[i]
353 else:
354 rank = Nsv
355 j = i+1
356 while j < len(cols):
357
358 image = Cinv * cols[j]
359 if 1e-5 > linalg.norm(cols[j] - (Csub*image)):
360 del cols[j]
361 else:
362 j += 1
363 i += 1
364 return matrix(Csub[:-1,:].T), matrix(Csub[-1,:].T)
365
366
367
368
369
370
371
372
373
375 """Returns A, B, A.I, P0 [12]."""
376 A, B = Ain, Bin
377 Nc = A.shape[0]
378 Nk = A.shape[1]
379 if (A.shape[0] == 0) or (A.shape[1] == 0):
380 return identity(len(K0)), matrix(zeros(shape=(len(K0),1))), matrix(array([x for x in K0])).T, K0
381 U, W, V, Winv, Ainv, rank = adjusted_svd(A)
382 if rank < Nc:
383 raise Exception('Bad SVD: Some constraints may be incompatible')
384 if rank != Nc:
385 raise Exception('Too many constraints. %i constraints may be linear combinations of the others.' % (Nc - rank))
386 for i in xrange(Nc):
387 for j in xrange(Nc,Nk):
388 if abs(U[i,j] > 1e-5):
389 raise Exception('Bad SVD: 0 != U[c,non] == %.6g' % U.A[i,j])
390 Acns = matrix(V[:,Nc:])
391 Bcns = Ainv * B
392 Out0 = Acns.T*K0
393 return Acns, Bcns, Acns.T, Out0
394
395
396
397
398
399
401 """Returns As, Bs, As.I, S0 [32]."""
402 scales = array([x or 1.0 for x in P0.A[:,0]])
403 reverses = array([x and 1/x or 1.0 for i,x in enumerate(P0.A[:,0])])
404 Ascal = matrix(diag(scales))
405 Bscal = matrix(zeros(shape=P0.shape))
406 for i,x in enumerate(P0.A[:,0]):
407 if not x:
408 Bscal[i] = -1.0
409 return Ascal, Bscal, matrix(diag(reverses)), matrix(ones(P0.shape))
410
412 k = matrix(zeros(shape=(prod(rates.shape),1)))
413 k[:,0] = matrix(rates.flatten()).T
414 return Ainv*k
415
416
418 pa = array(pars).flatten()
419 P = matrix(pa).T
420 K = ParsToK(P, A, Ainv, B)
421 return K
422
423
425 return A*P + B
426
427
428
429
430
431
432
433
434
436 """Returns array([error limit of k for k in K]).
437
438 @param Acns: the Nk x Np matrix from L{linear_constraints}; K = Acns*P + B
439 @param Ascal: the Np x Np matrix from L{start_at_ones}
440 @param HessInv: inverse Hessian matrix, as returned by qubx.optimize
441 """
442 hdiag = matrix(diag(HessInv).reshape((HessInv.shape[0],1)))
443 Asqr = matrix(array(Acns * Ascal)**2)
444 return array(sqrt(Asqr * hdiag)).flatten()
445
446
447
448
449