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.
- quadlib/__init__.py +39 -0
- quadlib/basis/__init__.py +60 -0
- quadlib/basis/boson.py +61 -0
- quadlib/basis/discrete.py +71 -0
- quadlib/basis/expdvr.py +80 -0
- quadlib/basis/hodvr.py +51 -0
- quadlib/basis/jbasis.py +87 -0
- quadlib/basis/sindvr.py +97 -0
- quadlib/basis/spin.py +64 -0
- quadlib/cli/__init__.py +0 -0
- quadlib/cli/qlb_offset_oper_dof.py +34 -0
- quadlib/experimental/__init__.py +17 -0
- quadlib/experimental/cpd_eom.py +95 -0
- quadlib/experimental/mctdh.py +32 -0
- quadlib/experimental/node.py +259 -0
- quadlib/experimental/pdof.py +9 -0
- quadlib/experimental/tree.py +138 -0
- quadlib/hdmctdh/__init__.py +0 -0
- quadlib/hdmctdh/rundirectory.py +74 -0
- quadlib/legacy/__init__.py +14 -0
- quadlib/legacy/math.py +1337 -0
- quadlib/operator/sop.py +250 -0
- quadlib/propagation/__init__.py +17 -0
- quadlib/propagation/cpd.py +35 -0
- quadlib/propagation/exact.py +139 -0
- quadlib/propagation/integrators.py +108 -0
- quadlib/tools/__init__.py +0 -0
- quadlib/tools/bath.py +84 -0
- quadlib/tools/constants.py +289 -0
- quadlib/tools/iterators.py +276 -0
- quadlib/tools/linalg.py +215 -0
- quadlib/tools/pulses.py +187 -0
- quadlib/tools/spectra.py +179 -0
- quadlib/tools/tensor.py +227 -0
- quadlib/wavefunction/__init__.py +0 -0
- quadlib/wavefunction/wfn_cpd.py +266 -0
- quadlib/wavefunction/wfn_exact.py +147 -0
- quadlib-0.1.0.dist-info/METADATA +135 -0
- quadlib-0.1.0.dist-info/RECORD +43 -0
- quadlib-0.1.0.dist-info/WHEEL +5 -0
- quadlib-0.1.0.dist-info/entry_points.txt +2 -0
- quadlib-0.1.0.dist-info/licenses/LICENSE +19 -0
- 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
|
+
|