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 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
@@ -0,0 +1,14 @@
1
+ from time import time
2
+
3
+ _tstart_stack = []
4
+
5
+
6
+ def tic():
7
+ _tstart_stack.append(time())
8
+
9
+
10
+ def toc(fmt="Elapsed: %s s"):
11
+ tt = time() - _tstart_stack.pop()
12
+ if fmt:
13
+ print(fmt % tt)
14
+ return tt
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any