Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Project: topocm
Views: 386
1
"""A package for computing Pfaffians"""
2
3
4
import numpy as np
5
import scipy.linalg as la
6
import scipy.sparse as sp
7
import math, cmath
8
9
def householder_real(x):
10
"""(v, tau, alpha) = householder_real(x)
11
12
Compute a Householder transformation such that
13
(1-tau v v^T) x = alpha e_1
14
where x and v a real vectors, tau is 0 or 2, and
15
alpha a real number (e_1 is the first unit vector)
16
"""
17
18
assert x.shape[0]>0
19
20
sigma=np.dot(x[1:],x[1:])
21
22
if sigma==0:
23
return (np.zeros(x.shape[0]), 0, x[0])
24
else:
25
norm_x=math.sqrt(x[0]**2+sigma)
26
27
v=x.copy();
28
29
#depending on whether x[0] is positive or negatvie
30
#choose the sign
31
if x[0]<=0:
32
v[0]-=norm_x
33
alpha=+norm_x
34
else:
35
v[0]+=norm_x
36
alpha=-norm_x
37
38
v/=np.linalg.norm(v)
39
40
return (v, 2, alpha)
41
42
def householder_complex(x):
43
"""(v, tau, alpha) = householder_real(x)
44
45
Compute a Householder transformation such that
46
(1-tau v v^T) x = alpha e_1
47
where x and v a complex vectors, tau is 0 or 2, and
48
alpha a complex number (e_1 is the first unit vector)
49
"""
50
assert x.shape[0]>0
51
52
sigma=np.dot(np.conj(x[1:]), x[1:])
53
54
if sigma==0:
55
return (np.zeros(x.shape[0]), 0, x[0])
56
else:
57
norm_x=cmath.sqrt(x[0].conjugate()*x[0]+sigma)
58
59
v=x.copy();
60
61
phase=cmath.exp(1j*math.atan2(x[0].imag, x[0].real))
62
63
v[0]+=phase*norm_x
64
65
v/=np.linalg.norm(v)
66
67
return (v, 2, -phase*norm_x)
68
69
def skew_tridiagonalize(A, overwrite_a=False, calc_q=True):
70
""" T, Q = skew_tridiagonalize(A, overwrite_a, calc_q=True)
71
72
or
73
74
T = skew_tridiagonalize(A, overwrite_a, calc_q=False)
75
76
Bring a real or complex skew-symmetric matrix (A=-A^T) into
77
tridiagonal form T (with zero diagonal) with a orthogonal
78
(real case) or unitary (complex case) matrix U such that
79
A = Q T Q^T
80
(Note that Q^T and *not* Q^dagger also in the complex case)
81
82
A is overwritten if overwrite_a=True (default: False), and
83
Q only calculated if calc_q=True (default: True)
84
"""
85
86
#Check if matrix is square
87
assert A.shape[0] == A.shape[1] > 0
88
#Check if it's skew-symmetric
89
assert abs((A+A.T).max())<1e-14
90
91
n = A.shape[0]
92
A = np.asarray(A) #the slice views work only properly for arrays
93
94
#Check if we have a complex data type
95
if np.issubdtype(A.dtype, np.complexfloating):
96
householder = householder_complex
97
elif not np.issubdtype(A.dtype, np.number):
98
raise TypeError("pfaffian() can only work on numeric input")
99
else:
100
householder = householder_real
101
102
if not overwrite_a:
103
A = A.copy()
104
105
if calc_q:
106
Q = np.eye(A.shape[0], dtype=A.dtype)
107
108
for i in range(A.shape[0]-2):
109
#Find a Householder vector to eliminate the i-th column
110
v, tau, alpha = householder(A[i+1:,i])
111
A[i+1, i] = alpha
112
A[i, i+1] = -alpha
113
A[i+2:, i] = 0
114
A[i, i+2:] = 0
115
116
#Update the matrix block A(i+1:N,i+1:N)
117
w = tau*np.dot(A[i+1:, i+1:], v.conj());
118
A[i+1:,i+1:]+=np.outer(v,w)-np.outer(w,v)
119
120
if calc_q:
121
#Accumulate the individual Householder reflections
122
#Accumulate them in the form P_1*P_2*..., which is
123
# (..*P_2*P_1)^dagger
124
y = tau*np.dot(Q[:, i+1:], v)
125
Q[:, i+1:]-=np.outer(y,v.conj())
126
127
if calc_q:
128
return (np.asmatrix(A), np.asmatrix(Q))
129
else:
130
return np.asmatrix(A)
131
132
def skew_LTL(A, overwrite_a=False, calc_L=True, calc_P=True):
133
""" T, L, P = skew_LTL(A, overwrite_a, calc_q=True)
134
135
Bring a real or complex skew-symmetric matrix (A=-A^T) into
136
tridiagonal form T (with zero diagonal) with a lower unit
137
triangular matrix L such that
138
P A P^T= L T L^T
139
140
A is overwritten if overwrite_a=True (default: False),
141
L and P only calculated if calc_L=True or calc_P=True,
142
respectively (default: True).
143
"""
144
145
#Check if matrix is square
146
assert A.shape[0] == A.shape[1] > 0
147
#Check if it's skew-symmetric
148
assert abs((A+A.T).max())<1e-14
149
150
n = A.shape[0]
151
A = np.asarray(A) #the slice views work only properly for arrays
152
153
if not overwrite_a:
154
A = A.copy()
155
156
if calc_L:
157
L = np.eye(n, dtype=A.dtype)
158
159
if calc_P:
160
Pv = np.arange(n)
161
162
for k in range(n-2):
163
#First, find the largest entry in A[k+1:,k] and
164
#permute it to A[k+1,k]
165
kp = k+1+np.abs(A[k+1:,k]).argmax()
166
167
#Check if we need to pivot
168
if kp != k+1:
169
#interchange rows k+1 and kp
170
temp = A[k+1,k:].copy()
171
A[k+1,k:] = A[kp,k:]
172
A[kp,k:] = temp
173
174
#Then interchange columns k+1 and kp
175
temp = A[k:,k+1].copy()
176
A[k:,k+1] = A[k:,kp]
177
A[k:,kp] = temp
178
179
if calc_L:
180
#permute L accordingly
181
temp = L[k+1,1:k+1].copy()
182
L[k+1,1:k+1] = L[kp,1:k+1]
183
L[kp,1:k+1] = temp
184
185
if calc_P:
186
#accumulate the permutation matrix
187
temp = Pv[k+1]
188
Pv[k+1] = Pv[kp]
189
Pv[kp] = temp
190
191
#Now form the Gauss vector
192
if A[k+1,k] != 0.0:
193
tau = A[k+2:,k].copy()
194
tau /= A[k+1,k]
195
196
#clear eliminated row and column
197
A[k+2:,k] = 0.0
198
A[k,k+2:] = 0.0
199
200
#Update the matrix block A(k+2:,k+2)
201
A[k+2:,k+2:] += np.outer(tau, A[k+2:,k+1])
202
A[k+2:,k+2:] -= np.outer(A[k+2:,k+1], tau)
203
204
if calc_L:
205
L[k+2:,k+1] = tau
206
207
if calc_P:
208
#form the permutation matrix as a sparse matrix
209
P = sp.csr_matrix( (np.ones(n), (np.arange(n), Pv)) )
210
211
if calc_L:
212
if calc_P:
213
return (np.asmatrix(A), np.asmatrix(L), P)
214
else:
215
return (np.asmatrix(A), np.asmatrix(L))
216
else:
217
if calc_P:
218
return (np.asmatrix(A), P)
219
else:
220
return np.asmatrix(A)
221
222
def pfaffian(A, overwrite_a=False, method='P'):
223
""" pfaffian(A, overwrite_a=False, method='P')
224
225
Compute the Pfaffian of a real or complex skew-symmetric
226
matrix A (A=-A^T). If overwrite_a=True, the matrix A
227
is overwritten in the process. This function uses
228
either the Parlett-Reid algorithm (method='P', default),
229
or the Householder tridiagonalization (method='H')
230
"""
231
# Check if matrix is square
232
assert A.shape[0] == A.shape[1] > 0
233
# Check if it's skew-symmetric
234
assert abs((A+A.T).max()) < 1e-14, abs((A+A.T).max())
235
# Check that the method variable is appropriately set
236
assert method == 'P' or method == 'H'
237
238
if method == 'P':
239
return pfaffian_LTL(A, overwrite_a)
240
else:
241
return pfaffian_householder(A, overwrite_a)
242
243
def pfaffian_LTL(A, overwrite_a=False):
244
""" pfaffian_LTL(A, overwrite_a=False)
245
246
Compute the Pfaffian of a real or complex skew-symmetric
247
matrix A (A=-A^T). If overwrite_a=True, the matrix A
248
is overwritten in the process. This function uses
249
the Parlett-Reid algorithm.
250
"""
251
#Check if matrix is square
252
assert A.shape[0] == A.shape[1] > 0
253
#Check if it's skew-symmetric
254
assert abs((A+A.T).max())<1e-14
255
256
n = A.shape[0]
257
A = np.asarray(A) #the slice views work only properly for arrays
258
259
#Quick return if possible
260
if n%2==1:
261
return 0
262
263
if not overwrite_a:
264
A = A.copy()
265
266
pfaffian_val = 1.0
267
268
for k in range(0, n-1, 2):
269
#First, find the largest entry in A[k+1:,k] and
270
#permute it to A[k+1,k]
271
kp = k+1+np.abs(A[k+1:,k]).argmax()
272
273
#Check if we need to pivot
274
if kp != k+1:
275
#interchange rows k+1 and kp
276
temp = A[k+1,k:].copy()
277
A[k+1,k:] = A[kp,k:]
278
A[kp,k:] = temp
279
280
#Then interchange columns k+1 and kp
281
temp = A[k:,k+1].copy()
282
A[k:,k+1] = A[k:,kp]
283
A[k:,kp] = temp
284
285
#every interchange corresponds to a "-" in det(P)
286
pfaffian_val *= -1
287
288
#Now form the Gauss vector
289
if A[k+1,k] != 0.0:
290
tau = A[k,k+2:].copy()
291
tau /= A[k,k+1]
292
293
pfaffian_val *= A[k,k+1]
294
295
if k+2<n:
296
#Update the matrix block A(k+2:,k+2)
297
A[k+2:,k+2:] += np.outer(tau, A[k+2:,k+1])
298
A[k+2:,k+2:] -= np.outer(A[k+2:,k+1], tau)
299
else:
300
#if we encounter a zero on the super/subdiagonal, the
301
#Pfaffian is 0
302
return 0.0
303
304
return pfaffian_val
305
306
307
def pfaffian_householder(A, overwrite_a=False):
308
""" pfaffian(A, overwrite_a=False)
309
310
Compute the Pfaffian of a real or complex skew-symmetric
311
matrix A (A=-A^T). If overwrite_a=True, the matrix A
312
is overwritten in the process. This function uses the
313
Householder tridiagonalization.
314
315
Note that the function pfaffian_schur() can also be used in the
316
real case. That function does not make use of the skew-symmetry
317
and is only slightly slower than pfaffian_householder().
318
"""
319
320
#Check if matrix is square
321
assert A.shape[0] == A.shape[1] > 0
322
#Check if it's skew-symmetric
323
assert abs((A+A.T).max())<1e-14
324
325
n = A.shape[0]
326
327
#Quick return if possible
328
if n%2==1:
329
return 0
330
331
#Check if we have a complex data type
332
if np.issubdtype(A.dtype, np.complexfloating):
333
householder=householder_complex
334
elif not np.issubdtype(A.dtype, np.number):
335
raise TypeError("pfaffian() can only work on numeric input")
336
else:
337
householder=householder_real
338
339
A = np.asarray(A) #the slice views work only properly for arrays
340
341
if not overwrite_a:
342
A = A.copy()
343
344
pfaffian_val = 1.
345
346
for i in range(A.shape[0]-2):
347
#Find a Householder vector to eliminate the i-th column
348
v, tau, alpha = householder(A[i+1:,i])
349
A[i+1, i] = alpha
350
A[i, i+1] = -alpha
351
A[i+2:, i] = 0
352
A[i, i+2:] = 0
353
354
#Update the matrix block A(i+1:N,i+1:N)
355
w = tau*np.dot(A[i+1:, i+1:], v.conj());
356
A[i+1:,i+1:]+=np.outer(v,w)-np.outer(w,v)
357
358
if tau!=0:
359
pfaffian_val *= 1-tau
360
if i%2==0:
361
pfaffian_val *= -alpha
362
363
pfaffian_val *= A[n-2,n-1]
364
365
return pfaffian_val
366
367
def pfaffian_schur(A, overwrite_a=False):
368
"""Calculate Pfaffian of a real antisymmetric matrix using
369
the Schur decomposition. (Hessenberg would in principle be faster,
370
but scipy-0.8 messed up the performance for scipy.linalg.hessenberg()).
371
372
This function does not make use of the skew-symmetry of the matrix A,
373
but uses a LAPACK routine that is coded in FORTRAN and hence faster
374
than python. As a consequence, pfaffian_schur is only slightly slower
375
than pfaffian().
376
"""
377
378
assert np.issubdtype(A.dtype, np.number) and not np.issubdtype(A.dtype, np.complexfloating)
379
380
assert A.shape[0] == A.shape[1] > 0
381
382
assert abs(A + A.T).max() < 1e-14
383
384
#Quick return if possible
385
if A.shape[0]%2 == 1:
386
return 0
387
388
(t, z) = la.schur(A, output='real', overwrite_a=overwrite_a)
389
l = np.diag(t, 1)
390
return np.prod(l[::2]) * la.det(z)
391
392