jftools 0.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jftools/__init__.py +14 -0
- jftools/fedvr.py +512 -0
- jftools/interpolate.py +32 -0
- jftools/ipynbimport.py +140 -0
- jftools/myjit.py +14 -0
- jftools/plotting.py +19 -0
- jftools/shade_color.py +29 -0
- jftools/short_iterative_lanczos.py +232 -0
- jftools/tictoc.py +14 -0
- jftools/unroll_phase.py +31 -0
- jftools-0.4.2.dist-info/LICENSE +21 -0
- jftools-0.4.2.dist-info/METADATA +20 -0
- jftools-0.4.2.dist-info/RECORD +14 -0
- jftools-0.4.2.dist-info/WHEEL +4 -0
jftools/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Collection of small useful helper tools for Python by Johannes Feist."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.4.2"
|
|
4
|
+
|
|
5
|
+
__all__ = ["shade_color", "tic", "toc", "ipynbimport_install", "unroll_phase", "interp_cmplx", "plotcolored", "fedvr", "short_iterative_lanczos"]
|
|
6
|
+
|
|
7
|
+
from .shade_color import shade_color
|
|
8
|
+
from .tictoc import tic, toc
|
|
9
|
+
from .ipynbimport import install as ipynbimport_install
|
|
10
|
+
from .unroll_phase import unroll_phase
|
|
11
|
+
from .interpolate import interp_cmplx
|
|
12
|
+
from .plotting import plotcolored
|
|
13
|
+
from . import fedvr
|
|
14
|
+
from . import short_iterative_lanczos
|
jftools/fedvr.py
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import scipy.linalg
|
|
4
|
+
import numpy as np
|
|
5
|
+
import math
|
|
6
|
+
|
|
7
|
+
from .myjit import jit
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@jit(nopython=True)
|
|
11
|
+
def simpsrule(n):
|
|
12
|
+
if n % 2 == 0:
|
|
13
|
+
raise ValueError("n must be odd for simpson rule")
|
|
14
|
+
if n <= 1:
|
|
15
|
+
t = np.zeros(1)
|
|
16
|
+
w = 2.0 * np.ones(1)
|
|
17
|
+
else:
|
|
18
|
+
t = np.linspace(-1, 1, n)
|
|
19
|
+
h = t[1] - t[0]
|
|
20
|
+
w = np.ones(n)
|
|
21
|
+
w[1:-1:2] = 4
|
|
22
|
+
w[2:-2:2] = 2
|
|
23
|
+
w *= h / 3
|
|
24
|
+
return t, w
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@jit(nopython=True)
|
|
28
|
+
def classpol(ikind, n, alpha=0.0, beta=0.0):
|
|
29
|
+
# this procedure supplies the coefficients a(j), b(j) of the
|
|
30
|
+
# recurrence relation
|
|
31
|
+
# b p (x) = (x - a ) p (x) - b p (x)
|
|
32
|
+
# j j j j-1 j-1 j-2
|
|
33
|
+
# for the various classical (normalized) orthogonal polynomials,
|
|
34
|
+
# and the zero-th moment
|
|
35
|
+
# muzero = integral w(x) dx
|
|
36
|
+
# of the given polynomial weight function w(x). since the
|
|
37
|
+
# polynomials are orthonormalized, the tridiagonal matrix is
|
|
38
|
+
# guaranteed to be symmetric.
|
|
39
|
+
# the input parameter alpha is used only for laguerre and
|
|
40
|
+
# jacobi polynomials, and the parameter beta is used only for
|
|
41
|
+
# jacobi polynomials. the laguerre and jacobi polynomials
|
|
42
|
+
# require the gamma function.
|
|
43
|
+
if ikind == 1:
|
|
44
|
+
# ikind = 1= legendre polynomials p(x)
|
|
45
|
+
# on (-1, +1), w(x) = 1.
|
|
46
|
+
muzero = 2.0
|
|
47
|
+
a = np.zeros(n)
|
|
48
|
+
iis = np.arange(1, n)
|
|
49
|
+
b = iis / np.sqrt(4 * iis**2 - 1)
|
|
50
|
+
elif ikind == 2:
|
|
51
|
+
# ikind = 2= chebyshev polynomials of the first ikind t(x)
|
|
52
|
+
# on (-1, +1), w(x) = 1 / sqrt(1 - x*x)
|
|
53
|
+
muzero = np.pi
|
|
54
|
+
a = np.zeros(n)
|
|
55
|
+
b = 0.5 * np.ones(n - 1)
|
|
56
|
+
b[0] = np.sqrt(0.5)
|
|
57
|
+
elif ikind == 3:
|
|
58
|
+
# ikind = 3= chebyshev polynomials of the second ikind u(x)
|
|
59
|
+
# on (-1, +1), w(x) = sqrt(1 - x*x)
|
|
60
|
+
muzero = np.pi / 2.0
|
|
61
|
+
a = np.zeros(n)
|
|
62
|
+
b = 0.5 * np.ones(n - 1)
|
|
63
|
+
elif ikind == 4:
|
|
64
|
+
# ikind = 4= hermite polynomials h(x)
|
|
65
|
+
# on (-infinity,+infinity), w(x) = exp(-x**2)
|
|
66
|
+
muzero = np.sqrt(np.pi)
|
|
67
|
+
a = np.zeros(n)
|
|
68
|
+
b = np.sqrt(np.arange(1, n) / 2)
|
|
69
|
+
elif ikind == 5:
|
|
70
|
+
# ikind = 5= jacobi polynomials p(alpha, beta)(x)
|
|
71
|
+
# on (-1, +1), w(x) = (1-x)**alpha + (1+x)**beta,
|
|
72
|
+
# alpha and beta greater than -1
|
|
73
|
+
alpha = float(alpha)
|
|
74
|
+
beta = float(beta)
|
|
75
|
+
ab = alpha + beta
|
|
76
|
+
abi = 2.0 + ab
|
|
77
|
+
muzero = 2.0 ** (ab + 1) * math.gamma(alpha + 1) * math.gamma(beta + 1.0) / math.gamma(abi)
|
|
78
|
+
a = np.empty(n)
|
|
79
|
+
b = np.empty(n - 1)
|
|
80
|
+
a[0] = (beta - alpha) / abi
|
|
81
|
+
b[0] = np.sqrt(4.0 * (1.0 + alpha) * (1.0 + beta) / ((abi + 1.0) * abi**2))
|
|
82
|
+
a2b2 = beta**2 - alpha**2
|
|
83
|
+
for ii in range(1, n - 1):
|
|
84
|
+
jj = ii + 1
|
|
85
|
+
abi = 2.0 * jj + ab
|
|
86
|
+
a[ii] = a2b2 / ((abi - 2.0) * abi)
|
|
87
|
+
b[ii] = np.sqrt(4.0 * jj * (jj + alpha) * (jj + beta) * (jj + ab) / ((abi**2 - 1) * abi**2))
|
|
88
|
+
abi = 2.0 * n + ab
|
|
89
|
+
a[-1] = a2b2 / ((abi - 2.0) * abi)
|
|
90
|
+
elif ikind == 6:
|
|
91
|
+
# ikind = 6= laguerre polynomials l(alpha)(x)
|
|
92
|
+
# on (0, +infinity), w(x) = exp(-x) * x**alpha, alpha greater than -1.
|
|
93
|
+
alpha = float(alpha)
|
|
94
|
+
muzero = math.gamma(alpha + 1.0)
|
|
95
|
+
iis = np.arange(1, n + 1)
|
|
96
|
+
a = 2 * iis - 1 + alpha
|
|
97
|
+
b = np.sqrt(iis[:-1] * (iis[:-1] + alpha))
|
|
98
|
+
return b, a, muzero
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@jit(nopython=True)
|
|
102
|
+
def gbslve(shift, a, b):
|
|
103
|
+
"""this procedure performs elimination to solve for the
|
|
104
|
+
n-th component of the solution delta to the equation
|
|
105
|
+
(jn - shift*identity) * delta = en,
|
|
106
|
+
where en is the vector of all zeroes except for 1 in
|
|
107
|
+
the n-th position.
|
|
108
|
+
the matrix jn is symmetric tridiagonal, with diagonal
|
|
109
|
+
elements a(i), off-diagonal elements b(i). this equation
|
|
110
|
+
must be solved to obtain the appropriate changes in the lower
|
|
111
|
+
2 by 2 submatrix of coefficients for orthogonal polynomials."""
|
|
112
|
+
alpha = a[0] - shift
|
|
113
|
+
for ii in range(1, len(a) - 1):
|
|
114
|
+
alpha = a[ii] - shift - b[ii - 1] ** 2 / alpha
|
|
115
|
+
return 1.0 / alpha
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def gaussq(kind, n, endpts, alpha=0.0, beta=0.0):
|
|
119
|
+
# this set of routines computes the nodes x(i) and weights
|
|
120
|
+
# c(i) for gaussian-type quadrature rules with pre-assigned
|
|
121
|
+
# nodes. these are used when one wishes to approximate
|
|
122
|
+
|
|
123
|
+
# integral (from a to b) f(x) w(x) dx
|
|
124
|
+
|
|
125
|
+
# n
|
|
126
|
+
# by sum c f(x )
|
|
127
|
+
# i=1 i i
|
|
128
|
+
|
|
129
|
+
# here w(x) is one of six possible non-negative weight
|
|
130
|
+
# functions (listed below), and f(x) is the
|
|
131
|
+
# function to be integrated. gaussian quadrature is particularly
|
|
132
|
+
# useful on infinite intervals (with appropriate weight
|
|
133
|
+
# functions), since then other techniques often fail.
|
|
134
|
+
|
|
135
|
+
# associated with each weight function w(x) is a set of
|
|
136
|
+
# orthogonal polynomials. the nodes x(i) are just the zeroes
|
|
137
|
+
# of the proper n-th degree polynomial.
|
|
138
|
+
|
|
139
|
+
# input parameters
|
|
140
|
+
|
|
141
|
+
# ikind an integer between 0 and 6 giving the type of
|
|
142
|
+
# quadrature rule
|
|
143
|
+
|
|
144
|
+
# ikind = 0= simpson's rule w(x) = 1 on (-1, 1) n must be odd.
|
|
145
|
+
# ikind = 1= legendre quadrature, w(x) = 1 on (-1, 1)
|
|
146
|
+
# ikind = 2= chebyshev quadrature of the first kind
|
|
147
|
+
# w(x) = 1/dsqrt(1 - x*x) on (-1, +1)
|
|
148
|
+
# ikind = 3= chebyshev quadrature of the second kind
|
|
149
|
+
# w(x) = dsqrt(1 - x*x) on (-1, 1)
|
|
150
|
+
# ikind = 4= hermite quadrature, w(x) = exp(-x*x) on
|
|
151
|
+
# (-infinity, +infinity)
|
|
152
|
+
# ikind = 5= jacobi quadrature, w(x) = (1-x)**alpha * (1+x)**
|
|
153
|
+
# beta on (-1, 1), alpha, beta .gt. -1.
|
|
154
|
+
# note= ikind=2 and 3 are a special case of this.
|
|
155
|
+
# ikind = 6= generalized laguerre quadrature, w(x) = exp(-x)*
|
|
156
|
+
# x**alpha on (0, +infinity), alpha .gt. -1
|
|
157
|
+
|
|
158
|
+
# n the number of points used for the quadrature rule
|
|
159
|
+
# alpha real(doub_prec) parameter used only for gauss-jacobi and gauss-
|
|
160
|
+
# laguerre quadrature (otherwise use 0.).
|
|
161
|
+
# beta real(doub_prec) parameter used only for gauss-jacobi quadrature--
|
|
162
|
+
# (otherwise use 0.).
|
|
163
|
+
# kpts (integer) normally 0, unless the left or right end-
|
|
164
|
+
# point (or both) of the interval is required to be a
|
|
165
|
+
# node (this is called gauss-radau or gauss-lobatto
|
|
166
|
+
# quadrature). then kpts is the number of fixed
|
|
167
|
+
# endpoints (1 or 2).
|
|
168
|
+
# endpts real(doub_prec) array of length 2. contains the values of
|
|
169
|
+
# any fixed endpoints, if kpts = 1 or 2.
|
|
170
|
+
# b real(doub_prec) scratch array of length n
|
|
171
|
+
|
|
172
|
+
# output parameters (both arrays of length n)
|
|
173
|
+
|
|
174
|
+
# t will contain the desired nodes x(1),,,x(n)
|
|
175
|
+
# w will contain the desired weights c(1),,,c(n)
|
|
176
|
+
|
|
177
|
+
# subroutines required
|
|
178
|
+
|
|
179
|
+
# gbslve, class, and gbtql2 are provided. underflow may sometimes
|
|
180
|
+
# occur, but it is harmless if the underflow interrupts are
|
|
181
|
+
# turned off as they are on this machine.
|
|
182
|
+
|
|
183
|
+
# accuracy
|
|
184
|
+
|
|
185
|
+
# the routine was tested up to n = 512 for legendre quadrature,
|
|
186
|
+
# up to n = 136 for hermite, up to n = 68 for laguerre, and up
|
|
187
|
+
# to n = 10 or 20 in other cases. in all but two instances,
|
|
188
|
+
# comparison with tables in ref. 3 showed 12 or more significant
|
|
189
|
+
# digits of accuracy. the two exceptions were the weights for
|
|
190
|
+
# hermite and laguerre quadrature, where underflow caused some
|
|
191
|
+
# very small weights to be set to zero. this is, of course,
|
|
192
|
+
# completely harmless.
|
|
193
|
+
|
|
194
|
+
# method
|
|
195
|
+
|
|
196
|
+
# the coefficients of the three-term recurrence relation
|
|
197
|
+
# for the corresponding set of orthogonal polynomials are
|
|
198
|
+
# used to form a symmetric tridiagonal matrix, whose
|
|
199
|
+
# eigenvalues (determined by the implicit ql-method with
|
|
200
|
+
# shifts) are just the desired nodes. the first components of
|
|
201
|
+
# the orthonormalized eigenvectors, when properly scaled,
|
|
202
|
+
# yield the weights. this technique is much faster than using a
|
|
203
|
+
# root-finder to locate the zeroes of the orthogonal polynomial.
|
|
204
|
+
# for further details, see ref. 1. ref. 2 contains details of
|
|
205
|
+
# gauss-radau and gauss-lobatto quadrature only.
|
|
206
|
+
|
|
207
|
+
# references
|
|
208
|
+
|
|
209
|
+
# 1. golub, g. h., and welsch, j. h., calculation of gaussian
|
|
210
|
+
# quadrature rules, mathematics of computation 23 (april,
|
|
211
|
+
# 1969), pp. 221-230.
|
|
212
|
+
# 2. golub, g. h., some modified matrix eigenvalue problems,
|
|
213
|
+
# siam review 15 (april, 1973), pp. 318-334 (section 7).
|
|
214
|
+
# 3. stroud and secrest, gaussian quadrature formulas, prentice-
|
|
215
|
+
# hall, englewood cliffs, n.j., 1966.
|
|
216
|
+
|
|
217
|
+
# ..................................................................
|
|
218
|
+
kinds = ("simpson", "legendre", "chebyshev-1", "chebyshev-2", "hermite", "jacobi", "laguerre")
|
|
219
|
+
ikind = kinds.index(kind)
|
|
220
|
+
|
|
221
|
+
if ikind == 0:
|
|
222
|
+
return simpsrule(n)
|
|
223
|
+
|
|
224
|
+
b, t, muzero = classpol(ikind, n, alpha, beta)
|
|
225
|
+
# the matrix of coefficients is assumed to be symmetric.
|
|
226
|
+
# the array t contains the diagonal elements, the array
|
|
227
|
+
# b the off-diagonal elements.
|
|
228
|
+
# make appropriate changes in the lower right 2 by 2
|
|
229
|
+
# submatrix.
|
|
230
|
+
|
|
231
|
+
if len(endpts) == 1:
|
|
232
|
+
# if kpts=1, only t(n) must be changed
|
|
233
|
+
t[-1] = gbslve(endpts[0], t, b) * b[-1] ** 2 + endpts[0]
|
|
234
|
+
elif len(endpts) == 2:
|
|
235
|
+
# if kpts=2, t(n) and b(n-1) must be recomputed
|
|
236
|
+
gam = gbslve(endpts[0], t, b)
|
|
237
|
+
t1 = (endpts[0] - endpts[1]) / (gbslve(endpts[1], t, b) - gam)
|
|
238
|
+
b[-1] = np.sqrt(t1)
|
|
239
|
+
t[-1] = endpts[0] + gam * t1
|
|
240
|
+
|
|
241
|
+
# now compute the eigenvalues of the symmetric tridiagonal
|
|
242
|
+
# matrix, which has been modified as necessary.
|
|
243
|
+
|
|
244
|
+
# upper form:
|
|
245
|
+
# * * a02 a13 a24 a35
|
|
246
|
+
# * a01 a12 a23 a34 a45
|
|
247
|
+
# a00 a11 a22 a33 a44 a55
|
|
248
|
+
A = np.empty((2, n))
|
|
249
|
+
A[0, 1:] = b
|
|
250
|
+
A[1, :] = t
|
|
251
|
+
t, w = scipy.linalg.eig_banded(A)
|
|
252
|
+
w = muzero * w[0, :] ** 2
|
|
253
|
+
return t, w
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@jit(nopython=True)
|
|
257
|
+
def lgngr(x, y):
|
|
258
|
+
"""Finds Lagrange interpolating polynomials of function of x
|
|
259
|
+
and their first and second derivatives on an arbitrary grid y."""
|
|
260
|
+
nx, ny = len(x), len(y)
|
|
261
|
+
p = np.empty((ny, nx))
|
|
262
|
+
dp = np.empty_like(p)
|
|
263
|
+
ddp = np.empty_like(p)
|
|
264
|
+
# generate polynomials and derivatives with respect to x
|
|
265
|
+
for i in range(ny):
|
|
266
|
+
zerfac = -1
|
|
267
|
+
for j in range(nx):
|
|
268
|
+
if abs(y[i] - x[j]) <= 1e-10:
|
|
269
|
+
zerfac = j
|
|
270
|
+
for j in range(nx):
|
|
271
|
+
p[i, j] = 1.0
|
|
272
|
+
for k in range(nx):
|
|
273
|
+
if k == j:
|
|
274
|
+
continue
|
|
275
|
+
p[i, j] *= (y[i] - x[k]) / (x[j] - x[k])
|
|
276
|
+
if abs(p[i, j]) > 1e-10:
|
|
277
|
+
sn = 0.0
|
|
278
|
+
ssn = 0.0
|
|
279
|
+
for k in range(nx):
|
|
280
|
+
if k == j:
|
|
281
|
+
continue
|
|
282
|
+
fac = 1.0 / (y[i] - x[k])
|
|
283
|
+
sn += fac
|
|
284
|
+
ssn += fac**2
|
|
285
|
+
dp[i, j] = sn * p[i, j]
|
|
286
|
+
ddp[i, j] = sn * dp[i, j] - ssn * p[i, j]
|
|
287
|
+
else:
|
|
288
|
+
sn = 1.0
|
|
289
|
+
ssn = 0.0
|
|
290
|
+
for k in range(nx):
|
|
291
|
+
if k in (j, zerfac):
|
|
292
|
+
continue
|
|
293
|
+
fac = 1.0 / (x[j] - x[k])
|
|
294
|
+
sn *= fac * (y[i] - x[k])
|
|
295
|
+
ssn += 1.0 / (y[i] - x[k])
|
|
296
|
+
dp[i, j] = sn / (x[j] - x[zerfac])
|
|
297
|
+
ddp[i, j] = 2.0 * ssn * dp[i, j]
|
|
298
|
+
return p, dp, ddp
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class fedvr_region:
|
|
302
|
+
# NB: the weight factors wt in each region
|
|
303
|
+
# do NOT include the sum of the two weights
|
|
304
|
+
# wt_n^i + wt_1^i+1 for the bridge functions!!
|
|
305
|
+
def __init__(self, nfun, bounds):
|
|
306
|
+
self.nfun = nfun
|
|
307
|
+
|
|
308
|
+
# Find zeros of nfun'th order Legendre quadrature (Gauss-Lobatto)
|
|
309
|
+
self.x, self.wt = gaussq("legendre", nfun, [-1, 1])
|
|
310
|
+
|
|
311
|
+
# Set up rescaled position grid
|
|
312
|
+
xmin, xmax = bounds
|
|
313
|
+
A = abs(xmax - xmin) / 2.0
|
|
314
|
+
B = (xmax + xmin) / 2.0
|
|
315
|
+
self.x = A * self.x + B
|
|
316
|
+
self.wt = A * self.wt
|
|
317
|
+
|
|
318
|
+
# fix that the ends sometimes do not come out as exactly the given bounds
|
|
319
|
+
self.x[[0, -1]] = bounds
|
|
320
|
+
|
|
321
|
+
# Generate Lagrange interpolating polynomials
|
|
322
|
+
# and their first and second derivatives
|
|
323
|
+
f, self.dx, self.dx2 = lgngr(self.x, self.x)
|
|
324
|
+
|
|
325
|
+
# Set up kinetic energy matrix
|
|
326
|
+
# ke[function index, point index]
|
|
327
|
+
self.ke = self.dx2 * self.wt[:, None]
|
|
328
|
+
# add the bloch contributions
|
|
329
|
+
self.ke[0, :] += f[0, 0] * self.dx[0, :]
|
|
330
|
+
self.ke[-1, :] += -f[-1, -1] * self.dx[-1, :]
|
|
331
|
+
self.ke *= -0.5
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class fedvr_grid:
|
|
335
|
+
def __init__(self, nfun, xels):
|
|
336
|
+
from scipy.sparse import csr_matrix
|
|
337
|
+
|
|
338
|
+
self.nfun = nfun
|
|
339
|
+
self.Nreg = len(xels) - 1
|
|
340
|
+
self.regs = []
|
|
341
|
+
nx = self.Nreg * (nfun - 1) + 1
|
|
342
|
+
self.x = np.empty(nx)
|
|
343
|
+
# self.wt has to be initialized to zeros!
|
|
344
|
+
self.wt = np.zeros_like(self.x)
|
|
345
|
+
istart = 0
|
|
346
|
+
for ii in range(self.Nreg):
|
|
347
|
+
reg = fedvr_region(nfun, xels[ii : ii + 2])
|
|
348
|
+
iend = istart + reg.nfun
|
|
349
|
+
self.regs.append(reg)
|
|
350
|
+
self.x[istart:iend] = reg.x
|
|
351
|
+
# the weights are additive (so bridge functions have sum of weights from the two elements)
|
|
352
|
+
self.wt[istart:iend] += reg.wt
|
|
353
|
+
# one grid point overlap
|
|
354
|
+
istart = iend - 1
|
|
355
|
+
|
|
356
|
+
dx = np.zeros([nx, nx])
|
|
357
|
+
dx2 = np.zeros_like(dx)
|
|
358
|
+
istart = 0
|
|
359
|
+
for reg in self.regs:
|
|
360
|
+
iend = istart + reg.nfun
|
|
361
|
+
# we have to multiply by the weights here to take into account that
|
|
362
|
+
# the bridge function weight is actually different from the element weight
|
|
363
|
+
wtcorr = 1.0 / np.sqrt(self.wt[istart:iend, None] * self.wt[None, istart:iend])
|
|
364
|
+
dx[istart:iend, istart:iend] += reg.dx * reg.wt[:, None] * wtcorr
|
|
365
|
+
# we use the "ke" (kinetic energy) matrix, which treats the second derivatives correctly at the boundaries
|
|
366
|
+
dx2[istart:iend, istart:iend] += -2 * reg.ke * wtcorr
|
|
367
|
+
istart = iend - 1
|
|
368
|
+
# make dx explicitly anti-hermitian (this also set the diagonal to zero)
|
|
369
|
+
self.dx = csr_matrix(0.5 * (dx - dx.T))
|
|
370
|
+
# make dx2 explicitly hermitian
|
|
371
|
+
self.dx2 = csr_matrix(0.5 * (dx2 + dx2.T))
|
|
372
|
+
|
|
373
|
+
def __repr__(self):
|
|
374
|
+
return "FEDVR basis: Rmin=%s, Rmax=%s, nfun=%s, nreg=%s, NR=%s" % (self.x[0], self.x[-1], self.nfun, self.Nreg, len(self.x))
|
|
375
|
+
|
|
376
|
+
def project_function(self, f):
|
|
377
|
+
"""Takes a function f(x) and returns the coefficients c_n representing it in the FEDVR basis, f̃ = Σ c_n ϕ_n(x).
|
|
378
|
+
f must be callable with an array."""
|
|
379
|
+
return f(self.x) * np.sqrt(self.wt)
|
|
380
|
+
|
|
381
|
+
def evaluate_basis(self, cn, xs):
|
|
382
|
+
"""Takes FEDVR basis coefficients c_n and returns the function values at points xs."""
|
|
383
|
+
fvals = self.get_basis_function_values(xs)
|
|
384
|
+
return cn.dot(fvals)
|
|
385
|
+
|
|
386
|
+
def get_basis_function_values(self, xs):
|
|
387
|
+
"""Returns an array F_ni=f_n(x_i), where f_n(x) are the orthonormalized basis functions we use."""
|
|
388
|
+
# only interior points
|
|
389
|
+
xels = [reg.x[0] for reg in self.regs[1:]]
|
|
390
|
+
# searchsorted returns the index at which to insert into a sorted array to keep the order
|
|
391
|
+
# this is the same as the finite element number containing the point for us
|
|
392
|
+
reginds = np.searchsorted(xels, xs)
|
|
393
|
+
fvals = np.zeros([len(self.x), len(xs)])
|
|
394
|
+
istart = 0
|
|
395
|
+
for ireg, reg in enumerate(self.regs):
|
|
396
|
+
# find xs that are in this region
|
|
397
|
+
regixs = np.where(ireg == reginds)
|
|
398
|
+
if len(regixs) == 0:
|
|
399
|
+
continue
|
|
400
|
+
# construct basis functions = Lagrange interpolating polynomials with weight,
|
|
401
|
+
# f_i(x_j) = δ_ij/sqrt(w_i)
|
|
402
|
+
# f_i(x) = 1/sqrt(w_i) Π_{j≠i} (x-x_j)/(x_i-x_j)
|
|
403
|
+
for ibas, xbas in enumerate(reg.x, start=istart):
|
|
404
|
+
# we have to use the _global_ weight self.wt here, which treats the bridge functions correctly
|
|
405
|
+
fvals[ibas, regixs] = 1.0 / np.sqrt(self.wt[ibas])
|
|
406
|
+
for ix, xp in enumerate(reg.x, start=istart):
|
|
407
|
+
if ibas != ix:
|
|
408
|
+
fvals[ibas, regixs] *= (xs[regixs] - xp) / (xbas - xp)
|
|
409
|
+
# next element starts nfun-1 basis functions later (-1 because of bridge function)
|
|
410
|
+
istart += reg.nfun - 1
|
|
411
|
+
return fvals
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class fedvr_grid_ecs:
|
|
415
|
+
def __init__(self, nfun, xels, xels_ecs, ecs_angle):
|
|
416
|
+
from scipy.sparse import csr_matrix
|
|
417
|
+
|
|
418
|
+
assert xels[-1] == xels_ecs[0]
|
|
419
|
+
self.nfun = nfun
|
|
420
|
+
self.ecs_angle = ecs_angle
|
|
421
|
+
self.Nreg_real = len(xels) - 1
|
|
422
|
+
self.Nreg_ecs = len(xels_ecs) - 1
|
|
423
|
+
self.Nreg = self.Nreg_real + self.Nreg_ecs
|
|
424
|
+
self.regs = []
|
|
425
|
+
nx = self.Nreg * (nfun - 1) + 1
|
|
426
|
+
self.x = np.empty(nx)
|
|
427
|
+
# self.wt has to be initialized to zeros!
|
|
428
|
+
self.wt = np.zeros(nx, dtype=complex)
|
|
429
|
+
istart = 0
|
|
430
|
+
for ii in range(self.Nreg_real):
|
|
431
|
+
reg = fedvr_region(nfun, xels[ii : ii + 2])
|
|
432
|
+
iend = istart + reg.nfun
|
|
433
|
+
self.regs.append(reg)
|
|
434
|
+
self.x[istart:iend] = reg.x
|
|
435
|
+
# the weights are additive (so bridge functions have sum of weights from the two elements)
|
|
436
|
+
self.wt[istart:iend] += reg.wt
|
|
437
|
+
# one grid point overlap
|
|
438
|
+
istart = iend - 1
|
|
439
|
+
|
|
440
|
+
for ii in range(self.Nreg_ecs):
|
|
441
|
+
reg = fedvr_region(nfun, xels_ecs[ii : ii + 2])
|
|
442
|
+
# do complex rotation
|
|
443
|
+
reg.ke = reg.ke * np.exp(-1j * ecs_angle)
|
|
444
|
+
# reg.first_deriv does not get a factor from complex rotation
|
|
445
|
+
reg.wt = reg.wt * np.exp(1j * ecs_angle)
|
|
446
|
+
self.regs.append(reg)
|
|
447
|
+
|
|
448
|
+
iend = istart + reg.nfun
|
|
449
|
+
self.x[istart:iend] = reg.x
|
|
450
|
+
# the weights are additive (so bridge functions have sum of weights from the two elements)
|
|
451
|
+
self.wt[istart:iend] += reg.wt
|
|
452
|
+
# one grid point overlap
|
|
453
|
+
istart = iend - 1
|
|
454
|
+
|
|
455
|
+
dx = np.zeros([nx, nx], dtype=complex)
|
|
456
|
+
dx2 = np.zeros_like(dx)
|
|
457
|
+
istart = 0
|
|
458
|
+
for reg in self.regs:
|
|
459
|
+
iend = istart + reg.nfun
|
|
460
|
+
# we have to multiply by the weights here to take into account that
|
|
461
|
+
# the bridge function weight is actually different from the element weight
|
|
462
|
+
wtcorr = 1.0 / np.sqrt(self.wt[istart:iend, None] * self.wt[None, istart:iend])
|
|
463
|
+
dx[istart:iend, istart:iend] += reg.dx * reg.wt[:, None] * wtcorr
|
|
464
|
+
# we use the "ke" (kinetic energy) matrix, which treats the second derivatives correctly at the boundaries
|
|
465
|
+
dx2[istart:iend, istart:iend] += -2 * reg.ke * wtcorr
|
|
466
|
+
istart = iend - 1
|
|
467
|
+
# make dx explicitly anti-hermitian (this also set the diagonal to zero)
|
|
468
|
+
# self.dx = csr_matrix(0.5*(dx-dx.T))
|
|
469
|
+
# make dx2 explicitly hermitian
|
|
470
|
+
# self.dx2 = csr_matrix(0.5*(dx2+dx2.T))
|
|
471
|
+
self.dx = csr_matrix(dx)
|
|
472
|
+
self.dx2 = csr_matrix(dx2)
|
|
473
|
+
|
|
474
|
+
def __repr__(self):
|
|
475
|
+
return f"FEDVR basis: Rmin={self.x[0]}, Rmax={self.x[-1]}, nfun={self.nfun}, nreg={self.Nreg}, NR={len(self.x)}, Recs={self.reg[self.Nreg_real-1].x[-1]}, θecs={self.ecs_angle}"
|
|
476
|
+
|
|
477
|
+
def project_function(self, f):
|
|
478
|
+
"""Takes a function f(x) and returns the coefficients c_n representing it in the FEDVR basis, f̃ = Σ c_n ϕ_n(x).
|
|
479
|
+
f must be callable with an array."""
|
|
480
|
+
return f(self.x) * np.sqrt(self.wt)
|
|
481
|
+
|
|
482
|
+
def evaluate_basis(self, cn, xs):
|
|
483
|
+
"""Takes FEDVR basis coefficients c_n and returns the function values at points xs."""
|
|
484
|
+
fvals = self.get_basis_function_values(xs)
|
|
485
|
+
return cn.dot(fvals)
|
|
486
|
+
|
|
487
|
+
def get_basis_function_values(self, xs):
|
|
488
|
+
"""Returns an array F_ni=f_n(x_i), where f_n(x) are the orthonormalized basis functions we use."""
|
|
489
|
+
# only interior points
|
|
490
|
+
xels = [reg.x[0] for reg in self.regs[1:]]
|
|
491
|
+
# searchsorted returns the index at which to insert into a sorted array to keep the order
|
|
492
|
+
# this is the same as the finite element number containing the point for us
|
|
493
|
+
reginds = np.searchsorted(xels, xs)
|
|
494
|
+
fvals = np.zeros([len(self.x), len(xs)])
|
|
495
|
+
istart = 0
|
|
496
|
+
for ireg, reg in enumerate(self.regs):
|
|
497
|
+
# find xs that are in this region
|
|
498
|
+
regixs = np.where(ireg == reginds)
|
|
499
|
+
if len(regixs) == 0:
|
|
500
|
+
continue
|
|
501
|
+
# construct basis functions = Lagrange interpolating polynomials with weight,
|
|
502
|
+
# f_i(x_j) = δ_ij/sqrt(w_i)
|
|
503
|
+
# f_i(x) = 1/sqrt(w_i) Π_{j≠i} (x-x_j)/(x_i-x_j)
|
|
504
|
+
for ibas, xbas in enumerate(reg.x, start=istart):
|
|
505
|
+
# we have to use the _global_ weight self.wt here, which treats the bridge functions correctly
|
|
506
|
+
fvals[ibas, regixs] = 1.0 / np.sqrt(self.wt[ibas])
|
|
507
|
+
for ix, xp in enumerate(reg.x, start=istart):
|
|
508
|
+
if ibas != ix:
|
|
509
|
+
fvals[ibas, regixs] *= (xs[regixs] - xp) / (xbas - xp)
|
|
510
|
+
# next element starts nfun-1 basis functions later (-1 because of bridge function)
|
|
511
|
+
istart += reg.nfun - 1
|
|
512
|
+
return fvals
|
jftools/interpolate.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from scipy.interpolate import InterpolatedUnivariateSpline
|
|
2
|
+
from numpy import exp, angle
|
|
3
|
+
from . import unroll_phase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def arg(x):
|
|
7
|
+
return unroll_phase(angle(x))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def interp_cmplx(x, y, *args, absarg=True, interpolator=InterpolatedUnivariateSpline, **kwargs):
|
|
11
|
+
if absarg:
|
|
12
|
+
return interp_cmplx_absarg(interpolator, x, y, *args, **kwargs)
|
|
13
|
+
else:
|
|
14
|
+
return interp_cmplx_reim(interpolator, x, y, *args, **kwargs)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class interp_cmplx_absarg:
|
|
18
|
+
def __init__(self, interpolator, x, y, *args, **kwargs):
|
|
19
|
+
self.abs = interpolator(x, abs(y), *args, **kwargs)
|
|
20
|
+
self.arg = interpolator(x, arg(y), *args, **kwargs)
|
|
21
|
+
|
|
22
|
+
def __call__(self, *args, **kwargs):
|
|
23
|
+
return self.abs(*args, **kwargs) * exp(1j * self.arg(*args, **kwargs))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class interp_cmplx_reim:
|
|
27
|
+
def __init__(self, interpolator, x, y, *args, **kwargs):
|
|
28
|
+
self.real = interpolator(x, y.real, *args, **kwargs)
|
|
29
|
+
self.imag = interpolator(x, y.imag, *args, **kwargs)
|
|
30
|
+
|
|
31
|
+
def __call__(self, *args, **kwargs):
|
|
32
|
+
return self.real(*args, **kwargs) + 1j * self.imag(*args, **kwargs)
|
jftools/ipynbimport.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
## Importing IPython Notebooks as Modules
|
|
4
|
+
# It is a common problem that people want to import code from IPython Notebooks.
|
|
5
|
+
# This is made difficult by the fact that Notebooks are not plain Python files,
|
|
6
|
+
# and thus cannot be imported by the regular Python machinery.
|
|
7
|
+
#
|
|
8
|
+
# Fortunately, Python provides some fairly sophisticated [hooks](http://www.python.org/dev/peps/pep-0302/) into the import machinery,
|
|
9
|
+
# so we can actually make IPython notebooks importable without much difficulty,
|
|
10
|
+
# and only using public APIs.
|
|
11
|
+
|
|
12
|
+
import io
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import types
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import nbformat
|
|
19
|
+
except ImportError:
|
|
20
|
+
from IPython import nbformat
|
|
21
|
+
from IPython.core.interactiveshell import InteractiveShell
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Import hooks typically take the form of two objects:
|
|
25
|
+
#
|
|
26
|
+
# 1. a Module **Loader**, which takes a module name (e.g. `'IPython.display'`), and returns a Module
|
|
27
|
+
# 2. a Module **Finder**, which figures out whether a module might exist, and tells Python what **Loader** to use
|
|
28
|
+
def find_notebook(fullname, path=None):
|
|
29
|
+
"""find a notebook, given its fully qualified name and an optional path
|
|
30
|
+
|
|
31
|
+
This turns "foo.bar" into "foo/bar.ipynb"
|
|
32
|
+
and tries turning "Foo_Bar" into "Foo Bar" if Foo_Bar
|
|
33
|
+
does not exist.
|
|
34
|
+
"""
|
|
35
|
+
name = fullname.rsplit(".", 1)[-1]
|
|
36
|
+
if not path:
|
|
37
|
+
path = [""]
|
|
38
|
+
for d in path:
|
|
39
|
+
nb_path = os.path.join(d, name + ".ipynb")
|
|
40
|
+
if os.path.isfile(nb_path):
|
|
41
|
+
return nb_path
|
|
42
|
+
# let import Notebook_Name find "Notebook Name.ipynb"
|
|
43
|
+
nb_path = nb_path.replace("_", " ")
|
|
44
|
+
if os.path.isfile(nb_path):
|
|
45
|
+
return nb_path
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
### Notebook Loader
|
|
49
|
+
# Here we have our Notebook Loader.
|
|
50
|
+
# It's actually quite simple - once we figure out the filename of the module,
|
|
51
|
+
# all it does is:
|
|
52
|
+
#
|
|
53
|
+
# 1. load the notebook document into memory
|
|
54
|
+
# 2. create an empty Module
|
|
55
|
+
# 3. execute every cell in the Module namespace
|
|
56
|
+
#
|
|
57
|
+
# Since IPython cells can have extended syntax,
|
|
58
|
+
# the IPython transform is applied to turn each of these cells into their pure-Python counterparts before executing them.
|
|
59
|
+
# If all of your notebook cells are pure-Python,
|
|
60
|
+
# this step is unnecessary.
|
|
61
|
+
class NotebookLoader(object):
|
|
62
|
+
"""Module Loader for IPython Notebooks"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, path=None):
|
|
65
|
+
self.shell = InteractiveShell.instance()
|
|
66
|
+
self.path = path
|
|
67
|
+
|
|
68
|
+
def load_module(self, fullname):
|
|
69
|
+
"""import a notebook as a module"""
|
|
70
|
+
path = find_notebook(fullname, self.path)
|
|
71
|
+
|
|
72
|
+
print("importing IPython notebook from %s" % path)
|
|
73
|
+
|
|
74
|
+
# load the notebook object
|
|
75
|
+
with io.open(path, "r", encoding="utf-8") as f:
|
|
76
|
+
nb = nbformat.read(f, 4)
|
|
77
|
+
|
|
78
|
+
# create the module and add it to sys.modules
|
|
79
|
+
# if name in sys.modules:
|
|
80
|
+
# return sys.modules[name]
|
|
81
|
+
mod = types.ModuleType(fullname)
|
|
82
|
+
mod.__file__ = path
|
|
83
|
+
mod.__loader__ = self
|
|
84
|
+
sys.modules[fullname] = mod
|
|
85
|
+
|
|
86
|
+
# extra work to ensure that magics that would affect the user_ns
|
|
87
|
+
# actually affect the notebook module's ns
|
|
88
|
+
save_user_ns = self.shell.user_ns
|
|
89
|
+
self.shell.user_ns = mod.__dict__
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
for cell in nb.cells:
|
|
93
|
+
if cell.cell_type == "code":
|
|
94
|
+
# transform the input to executable Python
|
|
95
|
+
code = self.shell.input_transformer_manager.transform_cell(cell.source)
|
|
96
|
+
# run the code in themodule
|
|
97
|
+
exec(code, mod.__dict__)
|
|
98
|
+
finally:
|
|
99
|
+
self.shell.user_ns = save_user_ns
|
|
100
|
+
return mod
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
### The Module Finder
|
|
104
|
+
# The finder is a simple object that tells you whether a name can be imported,
|
|
105
|
+
# and returns the appropriate loader.
|
|
106
|
+
# All this one does is check, when you do:
|
|
107
|
+
#
|
|
108
|
+
# ```python
|
|
109
|
+
# import mynotebook
|
|
110
|
+
# ```
|
|
111
|
+
#
|
|
112
|
+
# it checks whether `mynotebook.ipynb` exists.
|
|
113
|
+
# If a notebook is found, then it returns a NotebookLoader.
|
|
114
|
+
#
|
|
115
|
+
# Any extra logic is just for resolving paths within packages.
|
|
116
|
+
class NotebookFinder(object):
|
|
117
|
+
"""Module finder that locates IPython Notebooks"""
|
|
118
|
+
|
|
119
|
+
def __init__(self):
|
|
120
|
+
self.loaders = {}
|
|
121
|
+
|
|
122
|
+
def find_module(self, fullname, path=None):
|
|
123
|
+
nb_path = find_notebook(fullname, path)
|
|
124
|
+
if not nb_path:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
key = path
|
|
128
|
+
if path:
|
|
129
|
+
# lists aren't hashable
|
|
130
|
+
key = os.path.sep.join(path)
|
|
131
|
+
|
|
132
|
+
if key not in self.loaders:
|
|
133
|
+
self.loaders[key] = NotebookLoader(path)
|
|
134
|
+
return self.loaders[key]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
### Register the hook
|
|
138
|
+
# Now we register the `NotebookFinder` with `sys.meta_path`
|
|
139
|
+
def install():
|
|
140
|
+
sys.meta_path.append(NotebookFinder())
|
jftools/myjit.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from numba import jit
|
|
3
|
+
except ImportError:
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
warnings.warn("jftools: not using numba - can accelerate some computations!")
|
|
7
|
+
|
|
8
|
+
# in principle, jit can be used without options as well, which would make it trickier to deal with
|
|
9
|
+
# but we only use the second form, so no problem
|
|
10
|
+
def jit(*args, **kwargs):
|
|
11
|
+
def g(f):
|
|
12
|
+
return f
|
|
13
|
+
|
|
14
|
+
return g
|
jftools/plotting.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib as mpl
|
|
3
|
+
from matplotlib.collections import LineCollection
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def plotcolored(ax, x, y, c, *, lw=None, cmap=None, norm=None):
|
|
7
|
+
if lw is None:
|
|
8
|
+
lw = mpl.rcParams["lines.linewidth"]
|
|
9
|
+
if cmap is None:
|
|
10
|
+
cmap = mpl.rcParams["image.cmap"]
|
|
11
|
+
if norm is None:
|
|
12
|
+
norm = mpl.colors.Normalize(vmin=0, vmax=1)
|
|
13
|
+
points = np.array([x, y]).T.reshape(-1, 1, 2)
|
|
14
|
+
segments = np.concatenate([points[:-1], points[1:]], axis=1)
|
|
15
|
+
lc = LineCollection(segments, cmap=cmap, norm=norm)
|
|
16
|
+
lc.set_array(c)
|
|
17
|
+
lc.set_linewidth(lw)
|
|
18
|
+
ax.add_collection(lc)
|
|
19
|
+
return lc
|
jftools/shade_color.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# taken from https://github.com/matplotlib/matplotlib/pull/2745
|
|
2
|
+
# Copyright (c) 2012- Matplotlib Development Team; All Rights Reserved
|
|
3
|
+
def shade_color(color, percent):
|
|
4
|
+
"""
|
|
5
|
+
A color helper utility to either darken or lighten given color.
|
|
6
|
+
This color utility function allows the user to easily darken or lighten a color for
|
|
7
|
+
plotting purposes. This function first converts the given color to RGB using
|
|
8
|
+
ColorConverter and then to HSL. The saturation is modified according to the given
|
|
9
|
+
percentage and converted back to RGB.
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
color : string, list, hexvalue
|
|
13
|
+
Any acceptable Matplotlib color value, such as 'red', 'slategrey', '#FFEE11', (1,0,0)
|
|
14
|
+
percent : the amount by which to brighten or darken the color.
|
|
15
|
+
Returns
|
|
16
|
+
-------
|
|
17
|
+
color : tuple of floats
|
|
18
|
+
tuple representing converted rgb values
|
|
19
|
+
"""
|
|
20
|
+
from matplotlib.colors import colorConverter
|
|
21
|
+
from colorsys import rgb_to_hls, hls_to_rgb
|
|
22
|
+
import numpy as np
|
|
23
|
+
|
|
24
|
+
R, G, B = colorConverter.to_rgb(color)
|
|
25
|
+
H, L, S = rgb_to_hls(R, G, B)
|
|
26
|
+
L *= 1 + float(percent) / 100
|
|
27
|
+
L = np.clip(L, 0, 1)
|
|
28
|
+
R, G, B = hls_to_rgb(H, L, S)
|
|
29
|
+
return R, G, B
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy import einsum, empty, zeros, vdot, log10, exp
|
|
3
|
+
from numpy.linalg import norm
|
|
4
|
+
from scipy.linalg import eig_banded
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import qutip
|
|
9
|
+
|
|
10
|
+
have_qutip = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
have_qutip = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class normdotndarray(np.ndarray):
|
|
16
|
+
"""extension of numpy array that supports interface for lanczos_timeprop"""
|
|
17
|
+
|
|
18
|
+
def norm(self):
|
|
19
|
+
return norm(self)
|
|
20
|
+
|
|
21
|
+
def dot(self, other):
|
|
22
|
+
return vdot(self, other)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def calc_coeff(step, a_band, HT, coeff):
|
|
26
|
+
vals, vecs = eig_banded(a_band[:, :step], lower=False, overwrite_a_band=False)
|
|
27
|
+
# expH(j,k) = vecs(j,i) * exp(vals(i)) * delta(i,l) * transpose(vecs(l,k))
|
|
28
|
+
# expH(j,k) = vecs(j,i) * exp(vals(i)) * vecs(k,i)
|
|
29
|
+
coeff[:step] = einsum("ji,i,i->j", vecs, vecs[0], exp(-1j * HT * vals))
|
|
30
|
+
coeff[step:] = 0.0
|
|
31
|
+
return coeff
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class lanczos_timeprop:
|
|
35
|
+
def __init__(self, H, maxsteps, target_convg, debug=0, do_full_order=False):
|
|
36
|
+
if have_qutip and isinstance(H, qutip.Qobj):
|
|
37
|
+
H = H.data
|
|
38
|
+
|
|
39
|
+
if not callable(H):
|
|
40
|
+
# time-independent operator
|
|
41
|
+
# assume it supports .dot for matrix-vector multiplication
|
|
42
|
+
def Hfun(t, phi, Hphi):
|
|
43
|
+
Hphi[:] = H.dot(phi)
|
|
44
|
+
return Hphi
|
|
45
|
+
|
|
46
|
+
self.Hfun = Hfun
|
|
47
|
+
else:
|
|
48
|
+
self.Hfun = H
|
|
49
|
+
|
|
50
|
+
self.maxsteps = maxsteps
|
|
51
|
+
self.target_convg = target_convg
|
|
52
|
+
self.prefacs = empty(maxsteps + 1)
|
|
53
|
+
# this is the array that will hold the banded matrix
|
|
54
|
+
self.a_band = empty((2, maxsteps + 1))
|
|
55
|
+
self.debug = debug
|
|
56
|
+
self.do_full_order = do_full_order
|
|
57
|
+
|
|
58
|
+
self.curr_coeff = zeros(maxsteps + 1, dtype=complex)
|
|
59
|
+
self.prev_coeff = self.curr_coeff.copy()
|
|
60
|
+
|
|
61
|
+
def propagate(self, phi0, ts, maxHT=None):
|
|
62
|
+
ts = np.asarray(ts)
|
|
63
|
+
assert ts.ndim == 1, "ts must be a 1d array"
|
|
64
|
+
|
|
65
|
+
def get_phi_out(x):
|
|
66
|
+
return x.copy()
|
|
67
|
+
|
|
68
|
+
if isinstance(phi0, np.ndarray):
|
|
69
|
+
|
|
70
|
+
def get_phi_out(x):
|
|
71
|
+
return x.view(np.ndarray).copy()
|
|
72
|
+
|
|
73
|
+
phi0 = phi0.view(normdotndarray)
|
|
74
|
+
elif have_qutip and isinstance(phi0, qutip.Qobj):
|
|
75
|
+
outdims = phi0.dims.copy()
|
|
76
|
+
outtype = phi0.type
|
|
77
|
+
|
|
78
|
+
def get_phi_out(x):
|
|
79
|
+
return qutip.Qobj(x, dims=outdims, type=outtype)
|
|
80
|
+
|
|
81
|
+
phi0 = phi0.full().view(normdotndarray)
|
|
82
|
+
|
|
83
|
+
self.phia = [phi0.copy() for _ in range(self.maxsteps + 1)]
|
|
84
|
+
|
|
85
|
+
ids = np.array([id(x) for x in self.phia])
|
|
86
|
+
|
|
87
|
+
tt = ts[0]
|
|
88
|
+
phis = [get_phi_out(phi0)]
|
|
89
|
+
for tf in ts[1:]:
|
|
90
|
+
while tt < tf:
|
|
91
|
+
HT = tf - tt
|
|
92
|
+
if maxHT is not None:
|
|
93
|
+
HT = min(HT, maxHT)
|
|
94
|
+
HT_done = self._step(tt, HT)
|
|
95
|
+
tt += HT_done
|
|
96
|
+
phis.append(get_phi_out(self.phia[0]))
|
|
97
|
+
|
|
98
|
+
if not np.all(ids == np.array([id(x) for x in self.phia])):
|
|
99
|
+
warnings.warn("self.phia have not been updated in-place!")
|
|
100
|
+
|
|
101
|
+
return phis
|
|
102
|
+
|
|
103
|
+
def _step(self, t, HT):
|
|
104
|
+
# create local variables that use the class storage locations
|
|
105
|
+
beta, alpha = self.a_band
|
|
106
|
+
phia = self.phia
|
|
107
|
+
prefacs = self.prefacs
|
|
108
|
+
curr_coeff = self.curr_coeff
|
|
109
|
+
prev_coeff = self.prev_coeff
|
|
110
|
+
debug = self.debug
|
|
111
|
+
Hfun = self.Hfun
|
|
112
|
+
|
|
113
|
+
HT_done = HT
|
|
114
|
+
|
|
115
|
+
# initialize norm of starting vector
|
|
116
|
+
phinorm = phia[0].norm()
|
|
117
|
+
prefacs[0] = 1.0 / phinorm
|
|
118
|
+
|
|
119
|
+
# set current solution vector to zero so that it
|
|
120
|
+
# doesn't converge at first step
|
|
121
|
+
curr_coeff[:] = 0.0
|
|
122
|
+
|
|
123
|
+
for step in range(1, self.maxsteps + 1):
|
|
124
|
+
# set |phia(step)> to H|phia(step-1)>
|
|
125
|
+
phia[step] = Hfun(t, phia[step - 1], phia[step])
|
|
126
|
+
prefacs[step] = prefacs[step - 1]
|
|
127
|
+
phinorm = prefacs[step] * phia[step].norm()
|
|
128
|
+
# phinorm = sqrt(<q(step-1)|H H|q(step-1)>)
|
|
129
|
+
# build lanczos-matrix
|
|
130
|
+
# it's tridiagonal, so we only need two steps in the loop
|
|
131
|
+
# start with step-1 for numerical reasons - this should ensure better orthogonality by
|
|
132
|
+
# removing the potentially largest part (by a significant amount) first
|
|
133
|
+
dotpr = prefacs[step - 1] * prefacs[step] * phia[step - 1].dot(phia[step])
|
|
134
|
+
# don't use too stringent a criterion here, as the dot product does not contain squares
|
|
135
|
+
if abs(dotpr.imag) > 1e-9 and debug > 3:
|
|
136
|
+
print("imaginary part of dotpr !=0:", dotpr.imag)
|
|
137
|
+
alpha[step - 1] = dotpr.real
|
|
138
|
+
|
|
139
|
+
phia[step] -= alpha[step - 1] * prefacs[step - 1] / prefacs[step] * phia[step - 1]
|
|
140
|
+
|
|
141
|
+
# phinorm = sqrt(<q(step-1)|H H|q(step-1)>)
|
|
142
|
+
# alpha(step) = <q(step-1)| H |q(step-1)>
|
|
143
|
+
# abs(phinorm**2 - alpha(step)**2) is deltaH**2, i.e. a measure of how close q(step-1) is
|
|
144
|
+
# to being an eigenvector of H. if this is a small number, we take another
|
|
145
|
+
# gram-schmidt step to ensure that we have good orthogonality
|
|
146
|
+
if abs(phinorm**2 - alpha[step - 1] ** 2) < 0.1:
|
|
147
|
+
dotpr = prefacs[step - 1] * prefacs[step] * phia[step - 1].dot(phia[step])
|
|
148
|
+
phia[step] -= dotpr * prefacs[step - 1] / prefacs[step] * phia[step - 1]
|
|
149
|
+
if step >= 2:
|
|
150
|
+
phia[step] -= beta[step - 1] * prefacs[step - 2] / prefacs[step] * phia[step - 2]
|
|
151
|
+
|
|
152
|
+
# ************ normalize phia(step) to get q_step ***************
|
|
153
|
+
# be careful here: beta should be the norm of the
|
|
154
|
+
# current q_step == prefac * |phi>,
|
|
155
|
+
# i.e. beta = prefac * sqrt(<phi|phi>)
|
|
156
|
+
# after that, we set prefac to _normalize_ the vector |phi>,
|
|
157
|
+
# i.e. to prefac = 1.d0 / sqrt(<phi|phi>)
|
|
158
|
+
phinorm = phia[step].norm()
|
|
159
|
+
beta[step] = prefacs[step] * phinorm
|
|
160
|
+
prefacs[step] = 1.0 / phinorm
|
|
161
|
+
if abs(log10(prefacs[step])) > 4.0:
|
|
162
|
+
phia[step] *= prefacs[step]
|
|
163
|
+
prefacs[step] = 1.0
|
|
164
|
+
if abs(beta[step]) < 1e-2 and debug > 2:
|
|
165
|
+
print("WARNING! beta[%d]=%g is very small - there seems to be a linearly dependent vector!" % (step, beta[step]))
|
|
166
|
+
if debug > 1:
|
|
167
|
+
# check if new vector is orthogonal to all others
|
|
168
|
+
for ii in range(step):
|
|
169
|
+
dotpr = prefacs[ii] * prefacs[step] * phia[ii].dot(phia[step])
|
|
170
|
+
if abs(dotpr) > 1e-12:
|
|
171
|
+
print("WARNING! vectors not orthogonal. dotpr(%d,%d) = %g" % (ii, step, dotpr))
|
|
172
|
+
|
|
173
|
+
# check convergence
|
|
174
|
+
prev_coeff[:] = curr_coeff[:]
|
|
175
|
+
calc_coeff(step, self.a_band, HT_done, curr_coeff)
|
|
176
|
+
convg = norm(curr_coeff - prev_coeff)
|
|
177
|
+
if debug > 6:
|
|
178
|
+
print("prev_coeff:", prev_coeff)
|
|
179
|
+
if debug > 6:
|
|
180
|
+
print("curr_coeff:", curr_coeff)
|
|
181
|
+
if debug > 5:
|
|
182
|
+
print("convg:", convg)
|
|
183
|
+
|
|
184
|
+
if not self.do_full_order and convg < self.target_convg:
|
|
185
|
+
break
|
|
186
|
+
|
|
187
|
+
if debug > 8:
|
|
188
|
+
print(alpha[0:step])
|
|
189
|
+
print(beta[1:step])
|
|
190
|
+
|
|
191
|
+
# if convergence was reached in lanczos_loop, convg < target_convg, and this loop is never entered
|
|
192
|
+
while convg > self.target_convg:
|
|
193
|
+
# error (~convg) should be O(HT**maxsteps)
|
|
194
|
+
# convg = a * HT**maxsteps
|
|
195
|
+
# target_convg = a * HT_new**maxsteps
|
|
196
|
+
# target_convg/convg = (HT_new/HT)**maxsteps
|
|
197
|
+
# -> HT_new = HT * (target_convg/convg)**(1/maxsteps)
|
|
198
|
+
scale = 0.95 * (self.target_convg / convg) ** (1.0 / step)
|
|
199
|
+
# the 0.95d0 is to get convergence when we're very close
|
|
200
|
+
# and scale would be almost unity
|
|
201
|
+
# to prevent going to much too small steps when very far from convergence, decrease
|
|
202
|
+
# step size by at most one half
|
|
203
|
+
scale = max(0.5, scale)
|
|
204
|
+
if debug > 3:
|
|
205
|
+
print("scales HT with scale =", scale)
|
|
206
|
+
HT_done = HT_done * scale
|
|
207
|
+
calc_coeff(step - 1, self.a_band, HT_done, prev_coeff)
|
|
208
|
+
calc_coeff(step, self.a_band, HT_done, curr_coeff)
|
|
209
|
+
convg = norm(curr_coeff - prev_coeff)
|
|
210
|
+
if debug > 6:
|
|
211
|
+
print("prev_coeff:", abs(prev_coeff) ** 2)
|
|
212
|
+
if debug > 6:
|
|
213
|
+
print("curr_coeff:", abs(curr_coeff) ** 2)
|
|
214
|
+
if debug > 5:
|
|
215
|
+
print("convg:", convg)
|
|
216
|
+
|
|
217
|
+
if debug > 6 and HT_done != HT:
|
|
218
|
+
print("did not converge in %d iterations, step size decreased from %g to %g" % (self.maxsteps, HT, HT_done))
|
|
219
|
+
|
|
220
|
+
# build the new vector
|
|
221
|
+
# do NOT include the prefactor prefac[0] into phi - we do not want to normalize it
|
|
222
|
+
phia[0] *= curr_coeff[0]
|
|
223
|
+
cc = curr_coeff * prefacs / prefacs[0]
|
|
224
|
+
for ii in range(1, step + 1):
|
|
225
|
+
phia[0] += cc[ii] * phia[ii]
|
|
226
|
+
|
|
227
|
+
return HT_done
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def sesolve_lanczos(H, phi0, ts, maxsteps, target_convg, maxHT=None, debug=0, do_full_order=False):
|
|
231
|
+
prop = lanczos_timeprop(H, maxsteps, target_convg, debug, do_full_order)
|
|
232
|
+
return prop.propagate(phi0, ts, maxHT)
|
jftools/tictoc.py
ADDED
jftools/unroll_phase.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from .myjit import jit
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def unroll_phase(phases):
|
|
6
|
+
# first convert to numpy array
|
|
7
|
+
phases = np.asarray(phases)
|
|
8
|
+
# then make sure it is at least float64
|
|
9
|
+
phases = np.asarray(phases, dtype=np.promote_types(phases.dtype, np.float64))
|
|
10
|
+
return _unroll_phase(phases)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@jit(nopython=True)
|
|
14
|
+
def _unroll_phase(phases):
|
|
15
|
+
TWOPI = 2 * np.pi
|
|
16
|
+
# acc_phase keeps track of how many phase jumps by TWOPI we have accumulated
|
|
17
|
+
acc_phase = 0.0
|
|
18
|
+
for ii in range(1, len(phases)):
|
|
19
|
+
# the phase difference between the previous and current step,
|
|
20
|
+
# including a possible phase jump of +-TWOPI
|
|
21
|
+
phase_diff = phases[ii] - phases[ii - 1] - acc_phase
|
|
22
|
+
if abs(phase_diff - TWOPI) < abs(phase_diff):
|
|
23
|
+
# if +TWOPI
|
|
24
|
+
acc_phase += TWOPI
|
|
25
|
+
phase_diff -= TWOPI
|
|
26
|
+
elif abs(phase_diff + TWOPI) < abs(phase_diff):
|
|
27
|
+
# if -TWOPI
|
|
28
|
+
acc_phase -= TWOPI
|
|
29
|
+
phase_diff += TWOPI
|
|
30
|
+
phases[ii] = phases[ii - 1] + phase_diff
|
|
31
|
+
return phases
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Johannes Feist
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: jftools
|
|
3
|
+
Version: 0.4.2
|
|
4
|
+
Summary: Collection of small useful helper tools for Python by Johannes Feist.
|
|
5
|
+
Author-email: Johannes Feist <johannes.feist@gmail.com>
|
|
6
|
+
Requires-Python: >=3.7
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Requires-Dist: numpy >=1.15
|
|
10
|
+
Requires-Dist: scipy >=1.0
|
|
11
|
+
Requires-Dist: numba >=0.49
|
|
12
|
+
Requires-Dist: nbformat >= 5.0
|
|
13
|
+
Requires-Dist: IPython >= 7.0
|
|
14
|
+
Requires-Dist: matplotlib >= 3.0
|
|
15
|
+
Project-URL: Home, https://github.com/jfeist/jftools
|
|
16
|
+
|
|
17
|
+
# JF Tools
|
|
18
|
+
|
|
19
|
+
Collection of small useful helper tools for Python by Johannes Feist.
|
|
20
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
jftools/__init__.py,sha256=80bNjbw1H9jEQAemD2nVhmMSQV2illkRtMggjCXCGwM,538
|
|
2
|
+
jftools/fedvr.py,sha256=dXC7a35NnJAQMi6VTJ73QvoB7B0HWzKWO3ug2B1GLIk,21675
|
|
3
|
+
jftools/interpolate.py,sha256=sZPvXrp3m62eNIggArRER68wfWL7osK5k5DNYr8Z69s,1088
|
|
4
|
+
jftools/ipynbimport.py,sha256=r2qIVzbeE04MeRZrFFykNJW7Im9lTEdSQN6lLcE8KwE,4592
|
|
5
|
+
jftools/myjit.py,sha256=AgLmTaIflJq-K8U8SOV-GTIpmYH9dCPS6hUWPN0QC48,398
|
|
6
|
+
jftools/plotting.py,sha256=HrcZuW_qedib1bC1GOqf5KoSeZEbbBRTAm4KhhDgCHw,616
|
|
7
|
+
jftools/shade_color.py,sha256=n0n5YSypCMxgBbp5TjhPEN11YsyiVnqxey0iAjQspq0,1188
|
|
8
|
+
jftools/short_iterative_lanczos.py,sha256=iTlR_gQC0J7QvE34lSKzB6huGN1HoGWsRh1k54Drq-E,9128
|
|
9
|
+
jftools/tictoc.py,sha256=836wCGtFcqlv3iTAIwsNSbSNhuVrHwo1r4uKjfrtLRE,208
|
|
10
|
+
jftools/unroll_phase.py,sha256=nxxiDHTlgmpLFHUcH8zd4jSP9tJN_wFkYps8fnUqimg,1040
|
|
11
|
+
jftools-0.4.2.dist-info/LICENSE,sha256=SpiNboJTsVr6tepRnUAsFaa7aFnIgVlNguSlX5WedXc,1081
|
|
12
|
+
jftools-0.4.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
|
13
|
+
jftools-0.4.2.dist-info/METADATA,sha256=ddsMRG6GMM-2q3fMN1pQzkhaQDvwhmgeCQ2Svl4xnCQ,614
|
|
14
|
+
jftools-0.4.2.dist-info/RECORD,,
|