quadlib 0.1.0__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.
Files changed (43) hide show
  1. quadlib/__init__.py +39 -0
  2. quadlib/basis/__init__.py +60 -0
  3. quadlib/basis/boson.py +61 -0
  4. quadlib/basis/discrete.py +71 -0
  5. quadlib/basis/expdvr.py +80 -0
  6. quadlib/basis/hodvr.py +51 -0
  7. quadlib/basis/jbasis.py +87 -0
  8. quadlib/basis/sindvr.py +97 -0
  9. quadlib/basis/spin.py +64 -0
  10. quadlib/cli/__init__.py +0 -0
  11. quadlib/cli/qlb_offset_oper_dof.py +34 -0
  12. quadlib/experimental/__init__.py +17 -0
  13. quadlib/experimental/cpd_eom.py +95 -0
  14. quadlib/experimental/mctdh.py +32 -0
  15. quadlib/experimental/node.py +259 -0
  16. quadlib/experimental/pdof.py +9 -0
  17. quadlib/experimental/tree.py +138 -0
  18. quadlib/hdmctdh/__init__.py +0 -0
  19. quadlib/hdmctdh/rundirectory.py +74 -0
  20. quadlib/legacy/__init__.py +14 -0
  21. quadlib/legacy/math.py +1337 -0
  22. quadlib/operator/sop.py +250 -0
  23. quadlib/propagation/__init__.py +17 -0
  24. quadlib/propagation/cpd.py +35 -0
  25. quadlib/propagation/exact.py +139 -0
  26. quadlib/propagation/integrators.py +108 -0
  27. quadlib/tools/__init__.py +0 -0
  28. quadlib/tools/bath.py +84 -0
  29. quadlib/tools/constants.py +289 -0
  30. quadlib/tools/iterators.py +276 -0
  31. quadlib/tools/linalg.py +215 -0
  32. quadlib/tools/pulses.py +187 -0
  33. quadlib/tools/spectra.py +179 -0
  34. quadlib/tools/tensor.py +227 -0
  35. quadlib/wavefunction/__init__.py +0 -0
  36. quadlib/wavefunction/wfn_cpd.py +266 -0
  37. quadlib/wavefunction/wfn_exact.py +147 -0
  38. quadlib-0.1.0.dist-info/METADATA +135 -0
  39. quadlib-0.1.0.dist-info/RECORD +43 -0
  40. quadlib-0.1.0.dist-info/WHEEL +5 -0
  41. quadlib-0.1.0.dist-info/entry_points.txt +2 -0
  42. quadlib-0.1.0.dist-info/licenses/LICENSE +19 -0
  43. quadlib-0.1.0.dist-info/top_level.txt +1 -0
quadlib/legacy/math.py ADDED
@@ -0,0 +1,1337 @@
1
+ # QuaDLib: QUAntum Dynamics LIBrary for research and methods development
2
+ # Copyright (C) 2018-2026, The Theoretical Chemistry Group, Heidelberg University
3
+ #
4
+ # This software is licensed under the MIT License (https://spdx.org/licenses/MIT.html)
5
+
6
+ """
7
+ Legacy utilities (formerly quadlib.tools.math); not used by the library.
8
+
9
+ invert_reg, random_orthogonal_matrix, random_normal_columns_matrix moved
10
+ to quadlib.tools.linalg; corrspec and fourierTransform to
11
+ quadlib.tools.spectra. distributeMatrix/distributeMatrixList stay here;
12
+ quadlib.tools.linalg.kron_list computes the same product with np.kron.
13
+
14
+ SECTIONS
15
+
16
+ - LINEAR ALGEBRA
17
+ - TENSOR ALGEBRA
18
+ - VECTORS in 3D SPACE
19
+ - INTERPOLATION
20
+ - NUMERICAL
21
+ - MONTE-CARLO
22
+ - OTHER
23
+ - internal functions
24
+ """
25
+
26
+
27
+ import os
28
+ import sys
29
+ import random
30
+ import math
31
+ from math import sqrt, pi, sin, cos, asin, acos, exp
32
+
33
+ import numpy as np
34
+ try:
35
+ from scipy.integrate import simps
36
+ except:
37
+ from scipy.integrate import simpson as simps
38
+
39
+ import quadlib.tools.iterators as itr
40
+
41
+ #import CDTK
42
+ #from CDTK.Tools.Utils import ReverseArray, ShiftArray
43
+
44
+ au2am = 0.00054857989586762
45
+
46
+ ##############################################################################
47
+ # --- LINEAR ALGEBRA: Matrix and vector related utilities ---
48
+ ##############################################################################
49
+
50
+ def krylov(func,v,**opts):
51
+ """Construct a Krylov space
52
+
53
+ func -- takes one vector as argument and returns the action of
54
+ the Hamiltonian matrix on that vector
55
+ v -- Start vector for the construction of the Krylov space
56
+ argdict -- dictionary:
57
+ order: order (dimension) of the Krylov space to be constructed
58
+ kvecs : np.array, space to store the krylov vectors
59
+ kmat(order x order) : space to store the krylov matrix
60
+ work : scratch array of length len(v)
61
+
62
+ Return the tridiagonal matrix, kmat, and a list of the vectors
63
+ spanning the Krylov space kvecs
64
+
65
+ Test: The condition K^* A K = t has been tested for this routine
66
+ using an Hermitian matrix 40x40 and a Krylov space 5x5
67
+ Orthonormality of the Kvecs was also tested
68
+ 19.01.2007
69
+ """
70
+ n = len(v)
71
+ order = opts.get("order",10)
72
+ kvecs = opts.get("kvecs",None)
73
+ kmat = opts.get("kmat",None)
74
+ t = opts.get("work",None)
75
+ if not kvecs:
76
+ kvecs = np.zeros((order,n),type(v[0]))
77
+ if not kmat:
78
+ kmat = np.zeros((order,order),type(v[0]))
79
+ if not t:
80
+ t = np.zeros(n,type(v[0]))
81
+
82
+ # Normalize starting vector
83
+ kvecs[0] = v/(np.dot(v.conjugate(),v)**0.5)
84
+ a = 0.0
85
+ b = 0.0
86
+ for j in range(order):
87
+ # Calculate H(v) on the current vector
88
+ t[:] = func(kvecs[j])
89
+ if j > 0:
90
+ # Schmidt orthogonalize with respect to prev. prev. vector
91
+ t[:] = t - b*kvecs[j-1]
92
+ # Calculate diagonal term
93
+ a = np.dot(kvecs[j].conjugate(),t)
94
+ # Schmidt orthogonalize with respect to prev. vector
95
+ t[:] = t - a*kvecs[j]
96
+ # Calculate non-diagonal term
97
+ b = np.dot(t.conjugate(),t)**0.5
98
+ kmat[j,j] = a
99
+ if j < order-1:
100
+ kvecs[j+1] = (1./b)*t
101
+ kmat[j+1,j] = b
102
+ kmat[j,j+1] = b
103
+ return kmat,kvecs
104
+
105
+
106
+ def ovrMatrix(vecs1,vecs2,nvecs,veclen,matrix):
107
+ """
108
+ Compute the overlap matrix between two lists of vectors
109
+
110
+ Parameters
111
+ ----------
112
+ vecs1 : array_like
113
+ must be conformable to shape=(nvecs,veclen)
114
+ vecs2 : array_like
115
+ must be conformable to shape=(nvecs,veclen)
116
+ nvecs : integer, number of vectors
117
+ veclen: integer, length of each vector
118
+ matrix: 2D array, output, overlap matrix
119
+ """
120
+ saveshape = vecs1.shape
121
+ vecs1.shape = (nvecs,veclen)
122
+ vecs2.shape = (nvecs,veclen)
123
+ for i,v2 in enumerate(vecs2):
124
+ for j,v1 in enumerate(vecs1):
125
+ matrix[j,i] = np.vdot(v1,v2)
126
+ vecs1.shape = saveshape
127
+ vecs2.shape = saveshape
128
+ return matrix
129
+
130
+ def ovrMatrix2(vecs1,vecs2,nvecs1,nvecs2,veclen,matrix):
131
+ """
132
+ Compute the overlap matrix between two lists of vectors
133
+
134
+ Parameters
135
+ ----------
136
+ vecs1 : array_like
137
+ must be conformable to shape=(nvecs1,veclen1)
138
+ vecs2 : array_like
139
+ must be conformable to shape=(nvecs2,veclen2)
140
+ nvecs1 : integer, number of vectors in vecs1
141
+ nvecs2 : integer, number of vectors in vecs2
142
+ veclen: integer, length of each vector
143
+ matrix: 2D array, output, overlap matrix
144
+ """
145
+ saveshape1 = vecs1.shape
146
+ saveshape2 = vecs2.shape
147
+ vecs1.shape = (nvecs1,veclen)
148
+ vecs2.shape = (nvecs2,veclen)
149
+ for j,v1 in enumerate(vecs1):
150
+ for i,v2 in enumerate(vecs2):
151
+ matrix[j,i] = np.vdot(v1,v2)
152
+ vecs1.shape = saveshape1
153
+ vecs2.shape = saveshape2
154
+ return matrix
155
+
156
+
157
+ # sort_eig, simul_diag and the Jacobi-sweep helpers now live in
158
+ # quadlib.tools.linalg. Phase 1.4 will move the rest of this module's
159
+ # linear-algebra surface there too.
160
+
161
+
162
+ def distributeMatrix(mat1, mat2, oper='p'):
163
+ """
164
+ Given two square matrices that operate on spaces S1 and S2,
165
+ return a single matrix that operates on the direct product
166
+ space S = S1 x S2
167
+
168
+ mat1 -- square matrix n*n
169
+ mat2 -- square matrix m*m
170
+ oper -- 's' for summ or 'p' for product
171
+ whether the resulting matrices are summed or
172
+ matrix multiplied
173
+
174
+ Example:
175
+ Given the matrices:
176
+
177
+
178
+ | 1 2 3 4| |a b c|
179
+ mat1 = | 5 6 7 8| mat2 = |d e f|
180
+ | 9 10 11 12| |g h i|
181
+ |13 14 15 16|
182
+
183
+
184
+ The matrix of dimension 12 that operates on the product space is
185
+ generated from matrices A and B where
186
+
187
+ | 1 2 3 4 | |a b c |
188
+ | 1 2 3 4 | |d e f |
189
+ | 1 2 3 4 | |g h i |
190
+ | 5 6 7 8 | | a b c |
191
+ | 5 6 7 8 | | d e f |
192
+ A = | 5 6 7 8 | B = | g h i |
193
+ | 9 10 11 12 | | a b c |
194
+ | 9 10 11 12 | | d e f |
195
+ | 9 10 11 12 | | g h i |
196
+ |13 14 15 16 | | a b c|
197
+ | 13 14 15 16 | | d e f|
198
+ | 13 14 15 16 | | g h i|
199
+ """
200
+ n = len(mat1)
201
+ m = len(mat2)
202
+ nm = n*m
203
+ nmat1 = np.zeros((nm,nm),type(mat1[0,0]))
204
+ nmat2 = np.zeros((nm,nm),type(mat2[0,0]))
205
+ for k in range(nm):
206
+ for l in range(nm):
207
+ i = k%m
208
+ j = l%m
209
+ ii = int(k/m)
210
+ jj = int(l/m)
211
+ if i == j:
212
+ nmat1[k,l] = mat1[ii,jj]
213
+ if ii == jj:
214
+ nmat2[k,l] = mat2[i,j]
215
+ if oper == 's': return nmat1 + nmat2
216
+ if oper == 'p': return nmat1 @ nmat2
217
+
218
+ def distributeMatrixList(ml, oper='p'):
219
+ M = ml[0]
220
+ for m in ml[1:]:
221
+ M = distributeMatrix(M, m, oper=oper)
222
+ return M
223
+
224
+ def isPositiveDefinite(m):
225
+ """Return True if m is a positive-definite 2D matrix"""
226
+ if not isinstance(m, np.ndarray) or m.ndim != 2:
227
+ return False
228
+ try:
229
+ np.linalg.cholesky(m)
230
+ except np.linalg.LinAlgError:
231
+ return False
232
+ return True
233
+
234
+
235
+ # invert_reg moved to quadlib.tools.linalg.
236
+
237
+
238
+ def schroedinger_to_interaction(VS,H0,t):
239
+ """
240
+ Return interaction picture matrix VI using diagonal matrix H0
241
+
242
+ VI = exp(i*H0*t) VS exp(-i*H0*t) Sakurai 5.5.7
243
+ """
244
+ exp_p = np.diag( np.exp( 1j*np.diag(H0)*t ) )
245
+ exp_m = np.diag( np.exp(-1j*np.diag(H0)*t ) )
246
+ return np.dot( exp_p, np.dot(VS,exp_m) )
247
+
248
+
249
+
250
+ def svd(M, pad=False):
251
+ """
252
+ Return the SVD decomposition of matrix M
253
+
254
+ This is a thin wrap around numpy.linalg.svd. It pads the array of singular
255
+ values returned by svd, such that M is reproduced as
256
+ M = np.(np.dot(U, S), Vt)
257
+ """
258
+ u, s, vh = np.linalg.svd(M)
259
+ if pad:
260
+ dim = M.shape
261
+ padC = dim[1] - dim[0]
262
+ padR = -padC
263
+ if padC < 0: padC = 0
264
+ if padR < 0: padR = 0
265
+ S = np.pad(np.diag(s),((0,padR),(0,padC)),'constant',constant_values=((0,0),(0,0)))
266
+ return u, S, vh
267
+ else:
268
+ return u, s, vh
269
+
270
+
271
+ # simul_diag, _sweep, _sum_diag2, _rotation_angles, _rank2_diagonalization
272
+ # moved to quadlib.tools.linalg.
273
+
274
+
275
+ # random_orthogonal_matrix moved to quadlib.tools.linalg.
276
+
277
+
278
+ # random_normal_columns_matrix moved to quadlib.tools.linalg.
279
+
280
+
281
+ ##############################################################################
282
+ # --- VECTORS in 3D SPACE: vectors in cartesian space ---
283
+ ##############################################################################
284
+ def eulerMatrix(a,b,c,inv=False):
285
+ """Return a 3x3 rotation matrix
286
+
287
+ xyz is the body fixed frame
288
+ XYZ is the space fixed frame
289
+ The normal convention (default) is the rotation XYZ -> xyz
290
+ First rotate around Z by an ammount of xhi,
291
+ then around Y by an ammount of theta
292
+ and finally around Z by an ammount of phi.
293
+ The theta and phi angles can also be seen as the two spherical angles
294
+ of a vector with respect to the Z vector.
295
+ phi azimutal spherical angle (0,2pi)
296
+ theta polar spherical angle (0,pi)
297
+ phi -- rotation around Z axis
298
+ theta -- rotation around Y axis
299
+ xhi -- rotation around Z axis
300
+
301
+ The inverse convention corresponds to xyz -> XYZ
302
+ The vector is rotated by the specified Euler angles that align it
303
+ with the space fixed frame.
304
+
305
+ Normal and Inverse conventions are inverse operations with respect to
306
+ each other
307
+ """
308
+ A=np.zeros((3,3),float)
309
+ B=np.zeros((3,3),float)
310
+ C=np.zeros((3,3),float)
311
+
312
+ A[0,0] = cos(a)
313
+ A[1,1] = cos(a)
314
+ A[2,2] = 1.0
315
+ B[0,0] = cos(b)
316
+ B[1,1] = 1.0
317
+ B[2,2] = cos(b)
318
+ C[0,0] = cos(c)
319
+ C[1,1] = cos(c)
320
+ C[2,2] = 1.0
321
+ if not inv:
322
+ A[0,1] = -sin(a)
323
+ A[1,0] = sin(a)
324
+ B[0,2] = sin(b)
325
+ B[2,0] = -sin(b)
326
+ C[0,1] = -sin(c)
327
+ C[1,0] = sin(c)
328
+ r=np.dot(np.dot(A,B),C)
329
+ else:
330
+ A[0,1] = sin(a)
331
+ A[1,0] = -sin(a)
332
+ B[0,2] = -sin(b)
333
+ B[2,0] = sin(b)
334
+ C[0,1] = sin(c)
335
+ C[1,0] = -sin(c)
336
+ r=np.dot(np.dot(C,B),A)
337
+ return r
338
+
339
+ def alignMatrix(v,axis='z'):
340
+ """Return a 3x3 rotation matrix
341
+
342
+ Given a 3D vector, the routine returns a matrix that,
343
+ when multiplied by v, rotates it to the specified axis.
344
+ The routine computes the rotation axis around which the
345
+ rotation has to be performed, the angle that has to be
346
+ rotated, and then use the Rodrigues formula obtain the
347
+ desired rotation matrix.
348
+ """
349
+ if axis == 'x':
350
+ v1=np.array([1.,0.,0.])
351
+ elif axis == 'y':
352
+ v1=np.array([0.,1.,0.])
353
+ elif axis == 'z':
354
+ v1=np.array([0.,0.,1.])
355
+ else:
356
+ return np.zeros((3,3),float)
357
+ v2 = np.cross(v,v1)
358
+ if sqrt(np.dot(v2,v2)) < 1.e-8:
359
+ return np.identity(3,float)
360
+ cosphi = np.dot(v,v1)/sqrt(np.dot(v,v))
361
+ phi = acos(cosphi)
362
+ return rodriguesMatrix(v2,phi)
363
+
364
+ def rodriguesMatrix(v,phi):
365
+ """Return a 3x3 rotation matrix
366
+
367
+ v -- 3D vector, the rotation axis (a copy will be
368
+ normalized in the routine)
369
+ phi -- ammount of rotation
370
+
371
+ Given the vector v=[v0,v1,v2] and an angle phi, it returns the
372
+ rotation matrix around that axis by the ammount phi.
373
+ The routine uses the Rodrigues Formula:
374
+
375
+ M = I + sin(phi)*A + (1-cos(phi))*A^2
376
+
377
+ where I is the unit matrix and A is given by:
378
+ | 0 -v2 v1|
379
+ A = | v2 0 -v0|
380
+ |-v1 v0 0|
381
+ """
382
+ vn = v/sqrt(np.dot(v,v))
383
+ id=np.identity(3,float)
384
+ a=np.zeros((3,3),float)
385
+ a[0,1]=-vn[2]
386
+ a[0,2]= vn[1]
387
+ a[1,0]= vn[2]
388
+ a[1,2]=-vn[0]
389
+ a[2,0]=-vn[1]
390
+ a[2,1]= vn[0]
391
+ aa=np.dot(a,a)
392
+ m = id + sin(phi)*a + (1-cos(phi))*aa
393
+ return m
394
+
395
+ def pointsDistance(p,q):
396
+ """
397
+ Return the distance (Euclidean norm of connecting vector) between two points
398
+ p - vector or list
399
+ q - vector or list
400
+ """
401
+ d=0.0
402
+ for i in range(len(p)):
403
+ d = d + (p[i]-q[i])**2
404
+ return d**0.5
405
+
406
+ def pointsVector(p,q):
407
+ """
408
+ Return the vector q ---> p (p-q) between points p and q
409
+ p - vector or list
410
+ q - vector or list
411
+
412
+ on return v is a list
413
+ """
414
+ v = []
415
+ for i in range(len(p)):
416
+ v.append(p[i]-q[i])
417
+ return v
418
+
419
+ def sphericalAngles(v):
420
+ """
421
+ return the spherical angles theta(polar) and phi(azimutal) of v
422
+
423
+ v - 3D vector (x,y,z)
424
+ """
425
+ r=pointsDistance(v,[0.0,0.0,0.0])
426
+ theta = acos(v[2]/r)
427
+ rxy = r*sin(theta)
428
+ phi = acos(v[0]/rxy)
429
+ if v[1] < 0.0:
430
+ phi=2*pi-phi
431
+ return theta,phi
432
+
433
+ ##############################################################################
434
+ # --- INTERPOLATION on a regular grid ---
435
+ ##############################################################################
436
+ def ndimIntGrid(xgrid1,ygrid1,xgrid2,intfunctions):
437
+ """Interpolate xgrid2 into the grid defined by xgrid1 and ygrid1
438
+
439
+ xgrid1 -- 2D list containing arrays or lists of the original coordinates
440
+ ygrid1 -- ND list or array containing the values of the function at
441
+ each point.
442
+ ygrid1[i1,i2,i3,...,in] = y( xgrid1[1,i1],xgrid1[2,i2],...,xgrid1[n,in] )
443
+
444
+ xgrid2 -- 2D list or array containing the arrays of the coordinates
445
+ to be interpolated.
446
+ intfunctions -- list containing the pointers to the functions that will handle
447
+ the interpolation of each degree of freedom
448
+ Each element of the list is also a list, the first
449
+ element being the pointer and the rest the arguments
450
+ expected by the interpolation routine, i.e.
451
+ ((f1,op1a,op1b),(f2,op2a,op2b,op2c),...)
452
+ """
453
+ # declare the ygrid2 output array
454
+ # l contains the number of points in each dimension
455
+ l = []
456
+ for xvec in xgrid2:
457
+ l.append(len(xvec))
458
+ ygrid2 = np.zeros(l,float)
459
+
460
+ # loop over all the points in xgrid2 and interpolate
461
+ ygrid1a = np.array(ygrid1)
462
+ p = itr.MultiCounter()
463
+ p.setPointersN(0,len(l))
464
+ p.setMaxIter(l)
465
+ for ic in p:
466
+ i=0
467
+ xpoint = []
468
+ for xvec in xgrid2:
469
+ xpoint.append(xvec[ic[i]])
470
+ i = i+1
471
+ ygrid2[ic] = ndimIntPoint(xgrid1,ygrid1a,xpoint,intfunctions)
472
+ return ygrid2
473
+
474
+
475
+ def ndimIntPoint(xgrid1,ygrid1,xpoint,intfunctions):
476
+ """Iterpolate xpoint into the grid defined by xgrid1 and ygrid1
477
+
478
+ xgrid1 -- 2D list containing arrays or lists of the original coordinates
479
+ ygrid1 -- ND np.array with the function values
480
+ xpoint -- list or array of the coordinates of the point to be
481
+ interpolated
482
+ intfunctions -- list with pointers to the functions that will handle
483
+ the interpolation of each degree of freedom
484
+ Each element of the list is also a list, the first
485
+ element being the pointer and the rest the arguments
486
+ expected by the interpolation routine
487
+
488
+ Example:
489
+ We have a 3D grid, each coordinate (q1,q2,q3) is defined between
490
+ 0.0 and 5.0. Each coordinate is sampled at 11 equispaced points.
491
+ We have a mapping f(q1,q2,q3) from the grid points to the real numbers.
492
+ The specific form of the mapping is of course irrelevant here, we only
493
+ are interested on the value corresponding to each grid point.
494
+ Thus xgrid1 contains:
495
+ xgrid1 = [ [0.0, 0.5, 1.0, 1.5, ..., 5.0],
496
+ [0.0, 0.5, 1.0, 1.5, ..., 5.0],
497
+ [0.0, 0.5, 1.0, 1.5, ..., 5.0]
498
+ ]
499
+ And ygrid1 contains:
500
+ ygrid1[i][j][k] = f(xgrid1[0][i],xgrid1[1][j],xgrid1[2][k])
501
+
502
+ And we provide also an interpolation point from which we want to
503
+ know the interpolation f_intp(xpoint):
504
+ xpoint = [1.3,2.2,3.7]
505
+
506
+ """
507
+ ygridtmpA = np.array(ygrid1)
508
+ ndim = len(xpoint)
509
+ l = ygrid1.shape
510
+ # at each step we get a new grid of 1 dimension less until we reach
511
+ # the final interpolated number
512
+ for d in range(ndim):
513
+ xg = np.array(xgrid1[d])
514
+ xp = xpoint[d]
515
+ if l[d+1:]:
516
+ ygridtmpB = np.zeros(l[d+1:],float)
517
+ p = itr.MultiCounter()
518
+ p.setPointersN(0,len(l[d+1:]))
519
+ p.setMaxIter(l[d+1:])
520
+ # loop over the (ndim-d+1) dimensional grid
521
+ for ic in p:
522
+ yg = np.zeros(l[d],float)
523
+ # assign y values for the 1D interpolation
524
+ for j in range(l[d]):
525
+ k = (j,)
526
+ yg[j] = ygridtmpA[k+ic]
527
+ ygridtmpB[ic] = onedimInt(xg,yg,xp,intfunctions[d])
528
+ ygridtmpA = ygridtmpB
529
+ else: # last iteration
530
+ ygridtmpB = 0.0
531
+ yg = ygridtmpA
532
+ ygridtmpB = onedimInt(xg,yg,xp,intfunctions[d])
533
+ return ygridtmpB
534
+ return None
535
+
536
+ def scattered2grid2D(points,xgrid,ygrid,kernel,**args):
537
+ """
538
+ Interpolate scattered 2D data into a regular grid
539
+
540
+ Arguments:
541
+ kernel -- w(d)
542
+ z = Sum_i w(d_i)
543
+ """
544
+ griddata = np.zeros((len(xgrid),len(ygrid)),float)
545
+ for i,x in enumerate(xgrid):
546
+ for j,y in enumerate(ygrid):
547
+ z = 0.0
548
+ for p in points:
549
+ d = (p[0]-x)**2 + (p[1]-y)**2
550
+ d = d**0.5
551
+ z = z + kernel(d)
552
+ griddata[i,j] = z
553
+ return griddata
554
+
555
+
556
+
557
+ ##############################################################################
558
+ # 1D interpolators
559
+ ##############################################################################
560
+
561
+ def onedimInt(xg,yg,xp,intfunc):
562
+ """Interpolate point xp using the yg[xg] function
563
+
564
+ this routine is a thin wrapper over 1D interpolation routines
565
+ xg -- array with tabulated x points
566
+ yg -- array with tabulated y points
567
+ xp -- point where the interpolation is desired
568
+ func -- list of one or more elements, the first is the pointer
569
+ to the interpolation function, the rest are possible
570
+ arguments expected by such function
571
+ If the last argument is "periodic", a periodic interpolation
572
+ will be atempted enlarging the xg segment, usually up to 4
573
+ more points per side to be safe with respect to the cubic
574
+ spline interpolation
575
+ """
576
+ f = intfunc[0]
577
+ ops = intfunc[1:]
578
+ if ops:
579
+ if ops[len(ops)-1] == 'periodic':
580
+ xg,yg = _toPeriodic(xg,yg)
581
+ return f(xg,yg,xp,ops[:-1])
582
+ return f(xg,yg,xp,ops)
583
+ else:
584
+ return f(xg,yg,xp)
585
+
586
+
587
+ def linealInterpolation(xg,yg,xp,args=None):
588
+ """1D lineal interpolation of x
589
+
590
+ xg -- array with tabulated x points
591
+ yg -- array with tabulated y points
592
+ xp -- point where the interpolation is desired
593
+ args -- arguments, it is ignored and does not need to be set,
594
+ only keeps the interface consistency of 1D interpolators
595
+ """
596
+ imin = _segment(xg,xp)
597
+ y = yg[imin] + ((yg[imin+1]-yg[imin])/(xg[imin+1]-xg[imin]))*(xp-xg[imin])
598
+ return y
599
+
600
+
601
+ # Cubic-spline and ENO interpolators removed: they depended on Fortran
602
+ # helpers (splinenm/spline1d/enoi) that were never part of this package.
603
+ # Use scipy.interpolate.CubicSpline / PchipInterpolator instead.
604
+
605
+
606
+ # A dictionary is defined to translate from the input file
607
+ # code of an interpolation method (usually 3 characters)
608
+ # and the pointer to the real function, add entries always
609
+ # when a new 1D interpolator is implemented
610
+ keyToFunction = {
611
+ "default": linealInterpolation,
612
+ "lin": linealInterpolation,
613
+ }
614
+
615
+
616
+ ##############################################################################
617
+ # --- NUMERICAL functions ---
618
+ ##############################################################################
619
+
620
+ def dkron(i,j):
621
+ """Return the delta of Kroneker operation of integers i and j
622
+
623
+ i,j - integers, in case they are not integers the function
624
+ silently returns 0
625
+ """
626
+ if isinstance(i,int) and isinstance(j,int):
627
+ if i==j:
628
+ return 1
629
+ else:
630
+ return 0
631
+ else:
632
+ return 0
633
+
634
+
635
+ def richardson(f,hi,con=1.4,niter=10,safe=2.0,fast=False):
636
+ """
637
+ Implements Richardson extrapolation to calculate numerical estimates
638
+ of values obtained by finite difference formulas.
639
+ f -- function depending on 1 variable, the finite difference, what f is,
640
+ a derivative, a numerical integral... does not play any role here
641
+ hi -- initial estimate for the finite difference
642
+ con -- constant factor by which hi is divided at each step
643
+ niter -- maximum number of iterations
644
+ safe -- when error grows by a factor more than safe, terminate and return
645
+ """
646
+ if fast:
647
+ if hi > 0.05:
648
+ return f(0.05),0.0,hi
649
+ else:
650
+ return f(hi),0.0,hi
651
+ con2 = con*con
652
+ big = 1.e30
653
+ a=np.zeros((niter,niter),float)
654
+ hh=hi
655
+ a[0,0] = f(hh)
656
+ err = big
657
+ for i in range(1,niter):
658
+ hh = hh/con
659
+ a[0,i] = f(hh)
660
+ fac = con2
661
+ for j in range(1,i+1):
662
+ a[j,i] = (a[j-1,i]*fac - a[j-1,i-1])/(fac-1.0)
663
+ fac = con2*fac
664
+ errt = max(abs(a[j,i]-a[j-1,i]),abs(a[j,i]-a[j-1,i-1]))
665
+ if errt <= err:
666
+ err = errt
667
+ res = a[j,i]
668
+ hf = hh
669
+ if abs(a[i,i]-a[i-1,i-1]) >= safe*err:
670
+ return res,err,hf
671
+ return res,err,hf
672
+
673
+
674
+
675
+ ##############################################################################
676
+ # --- Sampling & Monte-Carlo functions ---
677
+ ##############################################################################
678
+
679
+ def randomWalkStep(xvec,min=None,max=None,stepnorm=1.0):
680
+ """
681
+ Make a random movement on a vector of real (float) coordinates
682
+
683
+ xvec - current state of the system in coordinate space
684
+ min - minimum value for each coordinate
685
+ max - maximum value for each coordinate
686
+ stepnorm - length of random steps to be performed
687
+
688
+ return
689
+ - next random position
690
+ """
691
+ lx = len(xvec)
692
+ rstep = np.random.random(lx) - 0.5
693
+ norm = (np.dot(rstep,rstep))**0.5
694
+ rstep = (stepnorm/norm)*rstep
695
+ xvecnew = xvec + rstep
696
+ if not max is None:
697
+ delta = max - xvecnew
698
+ hit = (delta < 0)
699
+ if hit.any():
700
+ for i in range(lx):
701
+ if hit[i]: xvecnew[i] = max[i] + delta[i]
702
+ if not min is None:
703
+ delta = min - xvecnew
704
+ hit = (delta > 0)
705
+ if hit.any():
706
+ for i in range(lx):
707
+ if hit[i]: xvecnew[i] = min[i] + delta[i]
708
+ return xvecnew
709
+
710
+
711
+ def metropolisStep(xvec,pval,pfunc,pfuncargs=None,
712
+ min=None,max=None,stepnorm=1.0):
713
+ """
714
+ Make a random movement, accept or reject accordint to metropolis alg.
715
+
716
+ xvec - xvec state of the vector of coordinates (list, tuple or array)
717
+ pval - probability value associated to the xvec position
718
+ pfunc - maps the xvec position in N-Dim space to a probability
719
+ pfuncargs - passed to pfunc, put pfunc arguments there
720
+
721
+ return
722
+ 1. True if accepted, False if rejected
723
+ 2. accepted or old xvec position
724
+ 3. probability accepted point, pval retuned if not acc.
725
+ """
726
+ xnext = randomWalkStep(xvec,min=min,max=max,stepnorm=stepnorm)
727
+ if pfuncargs is None:
728
+ pnext = pfunc(xnext)
729
+ else:
730
+ pnext = pfunc(xnext,pfuncargs)
731
+ if pnext > pval:
732
+ return True,xnext,pnext
733
+ else:
734
+ p = pnext/pval
735
+ r = random.random()
736
+ if r < p:
737
+ return True,xnext,pnext
738
+ else:
739
+ return False,xvec,pval
740
+
741
+
742
+ def randomWalkIntStep(xvec,xvecsize):
743
+ """
744
+ Make a random movement on a vector of integer coordinates
745
+
746
+ xvec - current state of the vector of integers (list, tuple or array)
747
+ xvecsize - vector with the maximum allowed value per each coordinate
748
+
749
+ return
750
+ 1. list with the next position
751
+ 2. index of the changed coordinate
752
+ 3. +1 if changed in positive direction, -1 if negative
753
+ """
754
+ next = list(xvec)
755
+ n = len(xvec) - 1
756
+ i = random.randint(0,n)
757
+ s = 2*random.randint(1,2) - 3
758
+ next[i] = next[i] + s
759
+ if next[i] > xvecsize[i] or next[i] < 0:
760
+ next,i,s = randomWalkStep(xvec,xvecsize)
761
+ return next,i,s
762
+
763
+ def metropolisIntStep(xvec,xvecsize,xvecVal,pfunc,pfuncargs=None):
764
+ """
765
+ Make a random movement, accept or reject accordint to metropolis alg.
766
+
767
+ xvec - xvec state of the vector of integers (list, tuple or array)
768
+ xvecsize - vector with the maximum allowed value per each coordinate
769
+ xvecVal - probability value associated to the xvec position
770
+ pfunc - maps the xvec position in N-Dim space to a probability
771
+ pfunc must accept 2 arguments, xvec and pfuncargs
772
+ pfuncargs - passed to pfunc, put pfunc arguments there
773
+
774
+ return
775
+ 1. True if accepted, False if rejected
776
+ 2. list with the accepted or old position
777
+ 3. index of the changed coordinate, (-1 if no change)
778
+ 4. +1 if changed in positive direction, -1 if negative, 0 if no change
779
+ 5. function value of the accepted point, xvecVal retuned if not acc.
780
+ """
781
+ xnext,i,s = randomWalkStep(xvec,xvecsize)
782
+ xnextVal = pfunc(xnext,pfuncargs)
783
+ if xnextVal > xvecVal:
784
+ return True,xnext,i,s,xnextVal
785
+ else:
786
+ p = xnextVal/xvecVal
787
+ r = random.random()
788
+ if r < p:
789
+ return True,xnext,i,s,xnextVal
790
+ else:
791
+ return False,xvec,-1,0,xvecVal
792
+
793
+
794
+ def WignerWalkQuantumStep(qnum,M_amu,w,radius2,min=None,max=None):
795
+ """
796
+ Make a random movement on a vector of real (float) coordinates
797
+ bound on a 1D sphere of radius [radius]
798
+
799
+ radius2 - radius squared of the sphere
800
+ min - minimum value for each coordinate
801
+ max - maximum value for each coordinate
802
+
803
+ return
804
+ - next random position
805
+
806
+ """
807
+ M = M_amu * au2am
808
+ radius = sqrt(radius2 / 2.0)
809
+ accepted = False
810
+ if qnum == 0:
811
+ qref = 0
812
+ else:
813
+ qref = radius # [T]=[V] for HO
814
+ pref = qref
815
+ while not accepted:
816
+ pos = random.random() - 0.5
817
+ q1D = radius * sin(pi*pos)
818
+ p1D = radius * cos(pi*pos) * np.sign(pos)
819
+ # von Neumann scheme
820
+ if qnum == 0:
821
+ ratio = exp( - q1D**2 - p1D**2 )
822
+ else:
823
+ ratio = exp( - ( q1D - qref )**2 - (p1D - pref)**2 )
824
+ if ratio > random.random():
825
+ accepted = True
826
+
827
+ xvecnext1D = np.array([q1D, p1D], dtype=float)
828
+ return xvecnext1D
829
+
830
+
831
+ def WignerQuantumStep(M_nmode,w_nmode,qnum_nmode,min=None,max=None):
832
+ """
833
+ Make a random movement for generating Wigner distribution
834
+
835
+ M_nmode - N masses of normal modes
836
+ w_nmode - N energies of normal modes
837
+ qnum_nmode - N quantum numbers of normal modes
838
+
839
+ return
840
+ xnext - coordinates and momenta
841
+ """
842
+ hbar = 1.0
843
+ ncoords = len(w_nmode)
844
+ xnext = np.zeros(2*ncoords, dtype=float) # [q,p] 2N vector
845
+ for i in range(ncoords):
846
+ # energy of the normal modes
847
+ ET_nmode = hbar * (float(qnum_nmode[i]) + 0.5)
848
+ # generate a 2-unit vector on sphere of radius squared [ET_nmode]
849
+ # [q,p]
850
+ xnext1D = WignerWalkQuantumStep(qnum_nmode[i],M_nmode[i],w_nmode[i],ET_nmode,min=min,max=max)
851
+ # scale unit of q and p backwards
852
+ xnext[i] = xnext1D[0] * sqrt(2.0) # q
853
+ xnext[i+ncoords] = xnext1D[1] * sqrt(2.0) # p
854
+ #
855
+ return xnext
856
+
857
+
858
+ ##############################################################################
859
+ # --- Statistical Functions ---
860
+ ##############################################################################
861
+
862
+ # rmsError() removed: it referred to project-specific globals
863
+ # (parametersS0, parlist, table, potevb) that were never part of this package.
864
+
865
+ ##############################################################################
866
+ # --- Orthonormal functions ---
867
+ ##############################################################################
868
+
869
+ def generateOrthonormalSpace(vs,n):
870
+ """
871
+ Given an initial vector generate n-1 additional orthonormal vectors
872
+
873
+ Discrete orthonormality is preserved:
874
+ dot(v_n,v_m) = delta(n,m)
875
+
876
+ v0 - initial vector
877
+ n - size of orthonormal space generated
878
+ """
879
+ m = len(vs)
880
+ if n > m:
881
+ raise ValueError("n > m, maximum m orthonormal vectors can be generated")
882
+ v0 = np.asarray(vs)
883
+ vspace = np.zeros((n,m),type(v0[0]))
884
+ vspace[0] = v0
885
+ x = np.linspace(0.0,1.0,m+2)[1:-1]
886
+ for i in range(1,n):
887
+ vspace[i] = np.sin((i+1)*np.pi*x) # particle in a box excited states
888
+ gramSchmidt(vspace)
889
+ return vspace
890
+
891
+
892
+ def gramSchmidt(vecs):
893
+ """
894
+ Perform a Gram-Schmidt orthonormalization on a given list of arrays
895
+
896
+ vecs -- list of arrays to orthonormalize
897
+ """
898
+ vecs[0] = vecs[0] / sqrt(np.vdot(vecs[0],vecs[0]))
899
+ if len(vecs) > 1:
900
+ for i in range(1,len(vecs)):
901
+ for j in range(0,i):
902
+ vecs[i] = vecs[i] - np.vdot(vecs[j],vecs[i])*vecs[j]
903
+ vecs[i] = vecs[i] / np.sqrt(np.vdot(vecs[i],vecs[i]))
904
+
905
+
906
+ ##############################################################################
907
+ # --- Function analysis ---
908
+ ##############################################################################
909
+
910
+ def convolutionFilter(input,fwhm,step=None,lbound=None,hbound=None,
911
+ filter='gaussian',externalFilter=None):
912
+ """
913
+ Filter the x,y input array with a gaussian filter
914
+
915
+ input -- x,y input array
916
+ fwhm -- full width at half maximum of the filter
917
+
918
+ step -- discretization interval in the abcisas axis
919
+ lbound -- lower bound of for the output data
920
+ hbound -- higher bound for the output data
921
+ filter -- string: type of function used for filtering, gaussian
922
+ and lorentzian shapes are implemented
923
+ externalFilter -- externally provided function for filtering,
924
+ must take two parameters:
925
+ x - value of the abcisa
926
+ b - center of the filter
927
+
928
+ return: an x,y array with the convolved function
929
+ """
930
+ if externalFilter:
931
+ f=externalFilter
932
+ else:
933
+ if filter == 'gaussian':
934
+ def f(x,b):
935
+ k = 4.0*math.log(2.0)
936
+ N = 4.0*math.log(2.0)/math.pi
937
+ N = N/fwhm
938
+ return N*math.exp(-k*((x-b)/fwhm)**2)
939
+ elif filter == 'lorentzian':
940
+ def f(x,b):
941
+ k=0.5*fwhm
942
+ k=k*k
943
+ return k/((x-b)**2 + k)
944
+
945
+ if lbound is None: lbound=input[0][0]
946
+ if hbound is None: hbound=input[0][-1]
947
+ if step is None: step=fwhm*0.05
948
+
949
+ xvec = np.arange(lbound,hbound+step,step)
950
+ nstep = len(xvec)
951
+
952
+ yinp = np.zeros(nstep,float)
953
+
954
+ # Map input data to the inp array
955
+ for i,x in enumerate(input[0]):
956
+ xi = (x-lbound)/step
957
+ ii = int(np.round(xi))
958
+ yinp[ii] = yinp[ii] + input[1][i]
959
+
960
+ # I(w) = Sum_t Func(t)*Filt(w-t)
961
+ yfltMat = [[f(t,w) for t in xvec] for w in xvec]
962
+ yfltMat = np.array(yfltMat)
963
+ yout = np.dot(yfltMat,yinp)
964
+ return np.array([xvec,yout])
965
+
966
+
967
+ # convolution() removed: it referenced an undefined `si` module and was
968
+ # strictly slower than numpy/scipy alternatives. Use scipy.signal.fftconvolve
969
+ # (or numpy.convolve for short kernels).
970
+
971
+
972
+ def rectangularConvolve(X,N,**kwargs):
973
+ Xpad = np.pad(X,N,'edge')
974
+ Xcon = np.convolve(np.ones(N,float)/N,Xpad,'same')
975
+ return Xcon[N:-N]
976
+
977
+
978
+ # fourierTransform moved to quadlib.tools.spectra.
979
+
980
+
981
+ ##############################################################################
982
+ # --- OTHER functions ---
983
+ ##############################################################################
984
+
985
+ def wignerHarmonic1D(q,p,m,w,hbar=1.0,q0=0.0,p0=0.0):
986
+ """
987
+ Compute a probability density in phase space for the harmonic case.
988
+
989
+ Given a point in phase space compute the probability density using
990
+ the wigner transform of the vibrational ground state in harmonic
991
+ approximation. The wigner transform of a gaussian is another gaussian.
992
+
993
+ If hbar is not specified atomic units are assumed.
994
+
995
+ q -- position in phase space
996
+ p -- momentum in phase space
997
+ m -- mass
998
+ w -- frequency of the harmonic oscillator
999
+ """
1000
+ qexp = math.exp(-(m*w/hbar)*(q-q0)**2)
1001
+ pexp = math.exp(-(1.0/(m*w*hbar))*(p-p0)**2)
1002
+ return (1.0/(hbar*math.pi))*qexp*pexp
1003
+
1004
+ def wignerHarmonicND(q,p,m,w,hbar=1.0):
1005
+ """
1006
+ Compute a probability density in phase space for the N-Dim harmonic case.
1007
+
1008
+ Given a point in phase space compute the probability density using
1009
+ the wigner transform of the vibrational ground state in harmonic
1010
+ approximation. The wigner transform of a gaussian is another gaussian.
1011
+
1012
+ If hbar is not specified atomic units are assumed.
1013
+
1014
+ q -- list of positions in phase space
1015
+ p -- list of momenta in phase space
1016
+ m -- list of masses
1017
+ w -- list of harmonic frequencies
1018
+ """
1019
+ wprob = 1.0
1020
+ for i in range(len(q)):
1021
+ wprob = wprob*wignerHarmonic1D(q[i],p[i],m[i],w[i],hbar)
1022
+ return wprob
1023
+
1024
+
1025
+ # wignerHarmonicFT1D / wignerHarmonicFTND removed: they referenced an
1026
+ # undefined KBAU constant (Boltzmann constant in atomic units) and the
1027
+ # N-dim variant called the 1D form with the wrong argument order.
1028
+
1029
+
1030
+ # corrspec moved to quadlib.tools.spectra.
1031
+
1032
+
1033
+ def makeMesh(f,grid,ctype=float):
1034
+ """
1035
+ make a mesh of f at the positions of a product grid
1036
+
1037
+ f -- function depending on N variables
1038
+ grid -- tuple or list of N 1D arrays which define the primitive grid
1039
+
1040
+ on return: a N-D array with the values of f at the grid points.
1041
+ """
1042
+ dim = len(grid)
1043
+ indexes = []
1044
+ for i in range(dim):
1045
+ indexes.append(len(grid[i]))
1046
+ z=np.zeros(indexes,ctype)
1047
+ x=np.zeros(dim,float)
1048
+ mc = itr.MultiCounter()
1049
+ mc.setPointersN(0,dim)
1050
+ mc.setMaxIter(indexes)
1051
+ for p in mc:
1052
+ for i in range(dim):
1053
+ x[i]=grid[i][p[i]]
1054
+ z[p]=f(x)
1055
+ return z
1056
+
1057
+
1058
+ def bssrSumOfStates(freqs,emax,m,E0=0.0):
1059
+ """
1060
+ Beyer-Swinehart-Stein-Rabinovitch (BSSR) sum of states for harmonic systems
1061
+
1062
+ freqs: list of vibrational frequencies (hbar * omega)
1063
+ emax: maximum energy related to the ZPE
1064
+ m: discretization for the sum
1065
+ E0: energy offset (can be a reaction barrier if dealing with a TS)
1066
+
1067
+ On return four lists containing:
1068
+ E : binned energy units
1069
+ N(E) : number of states at energy E (+- dE)
1070
+ rho(E) : density of states at energy E
1071
+ S(E) : sum of states up to energy E
1072
+ """
1073
+ t = np.zeros(m+1,float)
1074
+ ta = np.zeros(m+1,float)
1075
+ e = np.zeros(m+1,float)
1076
+ f = np.array(freqs)
1077
+ fzpe = f/2.0 # zero point energies of the modes
1078
+ zpe = fzpe.sum() # zero point energy of the system
1079
+ EMAX = emax # Absolute maximum energy
1080
+ dE = emax/m
1081
+ # E0 = E0 + zpe
1082
+
1083
+ R0 = int(divmod(E0,dE)[0])
1084
+ t[R0] = 1.0
1085
+ ta[R0] = 1.0
1086
+ for i in range(m+1):
1087
+ e[i] = i*dE
1088
+ for l,fmode in enumerate(f):
1089
+ k = 1
1090
+ Ek = fmode*k
1091
+ Rk = int(divmod(Ek,dE)[0])
1092
+ while Ek < emax:
1093
+ for j in range(m-Rk+1):
1094
+ ta[Rk+j] = ta[Rk+j] + t[j]
1095
+ k += 1
1096
+ Ek = fmode*k
1097
+ Rk = int(divmod(Ek,dE)[0])
1098
+ t[:] = ta
1099
+ for i in range(1,m+1):
1100
+ ta[i] = ta[i] + ta[i-1]
1101
+ return e,t,t/dE,ta
1102
+
1103
+
1104
+ def classicalSumOfStates(freqs,emax,m,E0=0.0):
1105
+ """
1106
+ Classical approximation to the sum of states for harmonic systems
1107
+
1108
+ See: W.H. Miller, JACS 101, 6810 (1979)
1109
+
1110
+ freqs: list of vibrational frequencies (hbar * omega)
1111
+ emax: maximum energy related to the ZPE
1112
+ m: discretization for the sum
1113
+ E0: energy offset (can be a reaction barrier if dealing with a TS)
1114
+
1115
+ On return four lists containing:
1116
+ E : binned energy units
1117
+ N(E) : number of states at energy E (+- dE)
1118
+ rho(E) : density of states at energy E
1119
+ S(E) : sum of states up to energy E
1120
+ """
1121
+ from math import factorial
1122
+ E = np.zeros(m+1,float)
1123
+ f = np.array(freqs)
1124
+ fzpe = f/2.0 # zero point energies of the modes
1125
+ zpe = fzpe.sum() # zero point energy of the system
1126
+ EMAX = emax # Absolute maximum energy
1127
+ dE = emax/m # discretization
1128
+ s = len(f)
1129
+ for i in range(m+1):
1130
+ E[i] = i*dE
1131
+ E[0] = 1e-8
1132
+ sfac = float(factorial(s))
1133
+ den = sfac*f.prod()
1134
+ N = (E-E0)**s/den
1135
+ for i in range(m*1):
1136
+ if E[i] >= E0: continue
1137
+ if E[i] < E0: N[i] = 0.0
1138
+ RhoE = s*N/E
1139
+ nE = RhoE*dE
1140
+ return E,nE,RhoE,N
1141
+
1142
+ def GaussianFunction(x, **option):
1143
+ """
1144
+ Gaussian function
1145
+
1146
+ Parameters:
1147
+ -----------
1148
+ x : 1D array_like
1149
+ variable domain.
1150
+ xbar : float, default: 0.0, optional
1151
+ mean value
1152
+ std : float, default: 1.0, optional
1153
+ standard deviation
1154
+ fwhm : float, default: None, optional
1155
+ full width at half maximum. If fwhm is None (or non-positive number),
1156
+ the std (standard deviation) will be used. If fwhm is present, then it
1157
+ is used in to calculate std. fwhm = 2.0 * std * sqrt(2.0 * ln 2.0)
1158
+ norm : bool, default: True, optional
1159
+ if norm == True, the gaussian function is normalized
1160
+ else, its pre-factor is 1.0
1161
+
1162
+ Returns:
1163
+ --------
1164
+ Gaussian function
1165
+ """
1166
+ xbar = option.get('xbar', 0.0)
1167
+ std = option.get('std', 1.0)
1168
+ norm = option.get('norm', True)
1169
+ fwhm = option.get('fwhm', None)
1170
+ if fwhm != None and fwhm > 0.0:
1171
+ std = fwhm / (2.0 * np.sqrt(2.0 * np.log(2.0)))
1172
+
1173
+ A = 1.0
1174
+ if norm:
1175
+ A = 1.0 / (std * np.sqrt(2.0 * np.pi))
1176
+
1177
+ gaussian = A * np.exp(-0.5 * ((x - xbar)/std)**2)
1178
+
1179
+ return gaussian
1180
+
1181
+
1182
+ #################################################################
1183
+ # Array manipulation
1184
+ #################################################################
1185
+ def ReverseArray(array):
1186
+ """
1187
+ Reverse array.
1188
+
1189
+ Parameters:
1190
+ -----------
1191
+ array : 1D array_like
1192
+ array to be reversed
1193
+
1194
+ Returns
1195
+ -------
1196
+ Reversed array
1197
+ """
1198
+ reverse_array = np.zeros(len(array))
1199
+ for i, x in enumerate(array):
1200
+ reverse_array[-i-1] = x
1201
+ return reverse_array
1202
+
1203
+ def ShiftArray(array, shift, **option):
1204
+ """
1205
+ Shift array.
1206
+
1207
+ Parameters:
1208
+ -----------
1209
+ array : 1D array_like
1210
+ array to be shifted
1211
+ shift : int
1212
+ amount of shifting,
1213
+ positive integer will shift the array to the right,
1214
+ negative integer will shift the array to the left.
1215
+ periodic : Bool, optional, default: True
1216
+ if True, use the periodic boudary condition on the array
1217
+ edge (the right most array element which is right shifted
1218
+ will appear on the left most, and vice versa). If False,
1219
+ the new array element which enter the array as the right
1220
+ most or the left most elements will be set 0.0.
1221
+
1222
+ Returns:
1223
+ --------
1224
+ Shifted array
1225
+ """
1226
+ periodic = option.get('periodic', True)
1227
+
1228
+ N = len(array)
1229
+ shifted_array = np.zeros(N)
1230
+ if periodic:
1231
+ for i in range(N):
1232
+ shifted_array[(i + shift) % N] = array[i]
1233
+ else:
1234
+ for i in range(N):
1235
+ l = i + shift
1236
+ if l < 0 or l >= N:
1237
+ shifted_array[l % N] = 0.0
1238
+ else:
1239
+ shifted_array[l] = array[i]
1240
+
1241
+ return shifted_array
1242
+
1243
+ ##############################################################################
1244
+ # --- INTERNAL functions ---
1245
+ ##############################################################################
1246
+
1247
+ def _segment(vect,point):
1248
+ """Given a list of ordered values and a value, what is the index
1249
+ of the point at the left of the given point?
1250
+
1251
+ vect -- list of ordered values
1252
+ point -- value
1253
+ on return, an index (integer). The returned index is always
1254
+ at least 0 and at maximum len(vect)-2
1255
+ """
1256
+ if point < vect[0]: return 0
1257
+ if point > vect[len(vect)-1]: return len(vect)-2
1258
+ for i in range(len(vect)-1):
1259
+ if point > vect[i] and point < vect[i+1]:
1260
+ return i
1261
+ return 0
1262
+
1263
+
1264
+ def _toPeriodic(xg,yg,order=4):
1265
+ """Modify xg and yg vectors for a periodic interpolation of order "order"
1266
+
1267
+ xg -- list of x values
1268
+ yg -- list of y(x) values
1269
+ order -- maximum order of the interpolation
1270
+ """
1271
+ if len(xg) < order:
1272
+ error = "error in periodic interpolation, too few points"
1273
+ raise ValueError(error)
1274
+ xgnew = []
1275
+ ygnew = []
1276
+ xlast = xg[len(xg)-1]
1277
+ dx = xg[1]-xg[0]
1278
+ for i in range(len(xg)):
1279
+ xgnew.append(xg[i])
1280
+ ygnew.append(yg[i])
1281
+ for i in range(order):
1282
+ xgnew.append(xlast+dx*(i+1))
1283
+ xgnew.insert(0,xg[0]-dx*(i+1))
1284
+ ygnew.append(yg[i])
1285
+ ygnew.insert(0,yg[len(yg)-1-i])
1286
+ return xgnew,ygnew
1287
+
1288
+
1289
+ def lag(x, n):
1290
+ """
1291
+ Compute the hypergeometric Laguerre polynomial Ln(x)
1292
+
1293
+ """
1294
+ # Lm(x) [0]-[n] ord.
1295
+ poly = np.zeros(n+1, dtype=float)
1296
+ poly[0] = 1
1297
+ poly[1] = 1 - x
1298
+ m = 2
1299
+ while m < (n + 1):
1300
+ mf = float(m)
1301
+ poly[m] = ((2*mf-1-x) * poly[m-1] - (mf-1) * poly[m-2]) / mf
1302
+ m += 1
1303
+
1304
+ return poly[n]
1305
+
1306
+
1307
+ def distributionFromList(l,fwhm,nstep=100,lbound=None,hbound=None):
1308
+ """
1309
+ Generate a distribution from a 1D list of values using a Gaussian kernel
1310
+
1311
+ l - 1D array with data
1312
+ fwhm - full width at half maximum of the Gaussian kernel
1313
+ nstep - number of discrete output points
1314
+ lbound - lower bound of output;
1315
+ hbound - higher bound of output
1316
+ """
1317
+ lrange = l.max() - l.min()
1318
+ if lbound is None:
1319
+ lbound = l.min() - 0.1*lrange
1320
+ if hbound is None:
1321
+ hbound = l.max() + 0.1*lrange
1322
+ x = np.linspace(lbound,hbound,nstep)
1323
+ y = np.zeros(len(x),float)
1324
+ for i,xp in enumerate(x):
1325
+ for xl in l:
1326
+ y[i] = y[i] + _gaussian(xl-xp,fwhm)
1327
+ return x,y
1328
+
1329
+
1330
+ __k = 4.0*math.log(2.0)
1331
+ __N = 4.0*math.log(2.0)/math.pi
1332
+ def _gaussian(x,fwhm=1.0,norm=True):
1333
+ N = __N/fwhm
1334
+ if norm: return N*np.exp(-__k*((x)/fwhm)**2)
1335
+ else: return np.exp(-__k*((x)/fwhm)**2)
1336
+
1337
+