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
@@ -0,0 +1,215 @@
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
+ Linear-algebra helpers used by the library.
8
+ """
9
+
10
+ from functools import reduce
11
+
12
+ import numpy as np
13
+
14
+
15
+ def sort_eig(evals, evecs, absval=False, order='ascending'):
16
+ """Sort an (eigvals, eigvecs) pair in place.
17
+
18
+ Parameters
19
+ ----------
20
+ evals : 1D ndarray, modified in place
21
+ evecs : 2D ndarray, eigenvectors stored in columns (axis=1), modified in place
22
+ absval : bool, optional
23
+ If True, sort by ``|evals|`` rather than the signed value.
24
+ order : {'ascending', 'descending'}, optional
25
+ Sort direction. Ignored when ``absval`` is True.
26
+ """
27
+ if absval:
28
+ s = abs(evals).argsort()
29
+ else:
30
+ if order == 'ascending':
31
+ s = evals.argsort()
32
+ elif order == 'descending':
33
+ s = (-evals).argsort()
34
+ else:
35
+ raise ValueError("order must be 'ascending' or 'descending'")
36
+ evecs[:] = evecs.take(s, axis=1)
37
+ evals[:] = evals.take(s)
38
+
39
+
40
+ def simul_diag(matrices, **kwargs):
41
+ """Simultaneous diagonalisation of a collection of real symmetric matrices.
42
+
43
+ Finds an orthogonal Us that makes every Us^T A Us as diagonal as
44
+ possible (exactly diagonal if the matrices commute), by Jacobi
45
+ rotations. Based on: "Jacobi angles for simultaneous
46
+ diagonalization", J.-F. Cardoso and A. Souloumiac,
47
+ SIAM J. Matrix Anal. Appl. 17, 161 (1996).
48
+
49
+ Note: kept largely for legacy reasons (not used by the library), but
50
+ it may be useful later, e.g. for DVRs of several coordinates. Only
51
+ real symmetric input is supported (ValueError otherwise): the real
52
+ Jacobi rotations used here do not apply to non-symmetric or complex
53
+ Hermitian matrices; the latter would need complex rotations.
54
+
55
+ Parameters
56
+ ----------
57
+ matrices : sequence of real symmetric (n, n) arrays
58
+ maxiter : int, maximum number of sweeps (default 100)
59
+ tol : float, stop when the change of sum_k |diag(A_k)|^2 between
60
+ sweeps falls below tol (default 1e-4)
61
+ prediag : bool, first diagonalize matrices[matdiag] exactly (default True)
62
+ matdiag : int, index of that matrix (default 0)
63
+
64
+ Returns
65
+ -------
66
+ Us : (n, n) orthogonal matrix; Us^T A Us is (nearly) diagonal
67
+ k : index of the last sweep
68
+ l_eps : array of the convergence measure per sweep
69
+ """
70
+ for A in matrices:
71
+ A = np.asarray(A)
72
+ if np.iscomplexobj(A):
73
+ raise ValueError('simul_diag: only real symmetric matrices are supported')
74
+ if not np.allclose(A, A.T, rtol=0, atol=1e-12 * max(1.0, np.abs(A).max())):
75
+ raise ValueError('simul_diag: input matrices must be symmetric')
76
+ maxiter = kwargs.get('maxiter', 100)
77
+ tol = kwargs.get('tol', 1.e-4)
78
+ prediag = kwargs.get('prediag', True)
79
+ matdiag = kwargs.get('matdiag', 0) # matrix to fully diagonalise 1st step
80
+ l_eps = np.zeros(maxiter, float)
81
+ rank = len(matrices[0])
82
+ mat0 = []
83
+ sdiag0 = 0.0
84
+ if prediag:
85
+ ev, U = np.linalg.eigh(matrices[matdiag])
86
+ for M in matrices:
87
+ mat0.append(reduce(np.dot, (U.T, M, U)))
88
+ U = U.T
89
+ else:
90
+ U = np.identity(rank, float)
91
+ mat0 = matrices
92
+ for k in range(maxiter):
93
+ mat1, U1, sdiag1 = _sweep(mat0)
94
+ U = np.dot(U1, U)
95
+ mat0 = mat1
96
+ epsilon = abs(sdiag1 - sdiag0)
97
+ sdiag0 = sdiag1
98
+ l_eps[k] = epsilon
99
+ if epsilon < tol:
100
+ break
101
+ return U.T, k, l_eps
102
+
103
+
104
+ def _sweep(matrices):
105
+ """Single sweep of the simultaneous-diagonalisation algorithm."""
106
+ rank = len(matrices[0])
107
+ dmatr = []
108
+ R = np.identity(rank, float)
109
+ for i in range(rank):
110
+ for j in range(i + 1, rank):
111
+ c, s = _rotation_angles(matrices, i, j)
112
+ Rij = np.identity(rank, float)
113
+ Rij[i, i] = c
114
+ Rij[j, j] = c
115
+ Rij[i, j] = s
116
+ Rij[j, i] = -s
117
+ R = np.dot(Rij, R)
118
+ for A in matrices:
119
+ dA = reduce(np.dot, (R, A, R.T))
120
+ dmatr.append(dA)
121
+ return dmatr, R, _sum_diag2(dmatr)
122
+
123
+
124
+ def _sum_diag2(matrices):
125
+ """Return the sum of ``dot(diag(A), diag(A))`` over a list of matrices."""
126
+ d2 = 0.0
127
+ for A in matrices:
128
+ da = np.diag(A)
129
+ d2 = d2 + np.dot(da, da)
130
+ return d2
131
+
132
+
133
+ def _rotation_angles(matrices, i, j):
134
+ if i == j:
135
+ raise ValueError('indices must be different')
136
+ G = np.zeros((2, 2), float)
137
+ for A in matrices:
138
+ h = np.array([A[i, i] - A[j, j], A[i, j] + A[j, i]])
139
+ G += np.outer(h, h)
140
+ E, U = np.linalg.eigh(G) # ascending: column 1 is the top eigenvector
141
+ x = U[0, 1]
142
+ y = U[1, 1]
143
+ if x < 0: # either sign is a valid rotation; x >= 0 avoids x + r ~ 0
144
+ x, y = -x, -y
145
+ r = np.dot(U[:, 1], U[:, 1])**0.5
146
+ c = ((x + r) / (2 * r))**0.5
147
+ s = y / (2 * r * (x + r))**0.5
148
+ return c, s
149
+
150
+
151
+ def _rank2_diagonalization(M):
152
+ """Closed-form diagonalisation of a 2x2 matrix.
153
+
154
+ Not used; ``_rotation_angles`` uses :func:`numpy.linalg.eigh`
155
+ for the same job.
156
+ """
157
+ trace = M[0, 0] + M[1, 1]
158
+ detrm = M[0, 0] * M[1, 1] - M[0, 1] * M[1, 0]
159
+ ev0 = trace / 2 + (trace**2 / 4 - detrm)**0.5
160
+ ev1 = trace / 2 - (trace**2 / 4 - detrm)**0.5
161
+ E = np.array([ev0, ev1], float)
162
+ U = np.zeros((2, 2), float)
163
+ U[0, 0] = ev0 - M[1, 1]
164
+ U[0, 1] = ev1 - M[1, 1]
165
+ U[1, 0] = M[1, 0]
166
+ U[1, 1] = M[1, 0]
167
+ U[:, 0] = U[:, 0] / np.dot(U[:, 0], U[:, 0])**0.5
168
+ U[:, 1] = U[:, 1] / np.dot(U[:, 1], U[:, 1])**0.5
169
+ return E, U
170
+
171
+
172
+ def kron_list(matrices):
173
+ """Kronecker product of a list of matrices, first factor slowest."""
174
+ return reduce(np.kron, matrices)
175
+
176
+
177
+ def invert_reg(mat, tol=1.0e-8):
178
+ """
179
+ Invert a Hermitian matrix, flooring its eigenvalues at tol
180
+
181
+ Eigenvalues below tol (including negative ones) are replaced by tol
182
+ before inverting.
183
+
184
+ Returns (inverse, eigenvalues, floored eigenvalues, eigenvectors).
185
+ """
186
+ evals, evecs = np.linalg.eigh(mat)
187
+ evalsreg = np.fmax(evals, tol)
188
+ inv = evecs @ np.diag(evalsreg**-1) @ evecs.conj().T
189
+ return inv, evals, evalsreg, evecs
190
+
191
+
192
+ def random_orthogonal_matrix(dim, nvec, istype=float):
193
+ """(dim, nvec) matrix with orthonormal columns (QR of a random matrix)."""
194
+ T = np.random.random((dim, nvec))
195
+ if istype is complex:
196
+ T = T + 1.0j * np.random.random((dim, nvec))
197
+ elif istype is not float:
198
+ raise ValueError('istype = [float|complex] only')
199
+ Q, R = np.linalg.qr(T)
200
+ return Q
201
+
202
+
203
+ def random_normal_columns_matrix(ndim, nvec, istype=float):
204
+ """(ndim, nvec) matrix of random columns, each normalized to 1.
205
+
206
+ The columns are not orthogonal; entries are uniform in [0, 1) before
207
+ normalization ("normal" means normalized, not Gaussian).
208
+ """
209
+ T = np.random.random((nvec, ndim))
210
+ if istype is complex:
211
+ T = T + 1.0j * np.random.random((nvec, ndim))
212
+ elif istype is not float:
213
+ raise ValueError('istype = [float|complex] only')
214
+ T = T / np.linalg.norm(T, axis=1)[:, None]
215
+ return T.T
@@ -0,0 +1,187 @@
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
+ import math
7
+
8
+ import numpy as np
9
+
10
+ import quadlib.tools.constants as cv
11
+
12
+
13
+ class EField:
14
+ """
15
+ Represents an oscillating field characterized by
16
+
17
+ E(t) = P A(t)
18
+
19
+ P: unit 3-vector, polarizarion axis of the field
20
+ A(t): function, field amplitude as a function of time
21
+
22
+ P and A must have been defined at execution time.
23
+
24
+ Units for A(t) must be au both for time and E-field.
25
+ """
26
+ def __init__(self,**opts):
27
+ self.P = np.array([0.0,0.0,1.0],float)
28
+
29
+
30
+ def A(self,t):
31
+ return 0.0
32
+
33
+
34
+ def E(self,t):
35
+ return self.A(t) * np.asarray(self.P)
36
+
37
+
38
+ def set_gaussian_pulse(self, omega, fwhm, amp, t0=0, phase=0):
39
+ """
40
+ Gaussian shaped pulse with single photon energy
41
+
42
+ A(t) = amp * G(fwhm,t0) * cos( omega t + phase)
43
+
44
+ All input in au
45
+
46
+ omega -- photon energy
47
+ fwhm -- full width at half maximum
48
+ amp -- maximum field amplitude
49
+ t0 -- center of the pulse
50
+ phase -- phase of the cosine carrier, units of Pi
51
+
52
+ No output, modifies self.A
53
+ """
54
+ g = gaussian_envelope(fwhm,t0)
55
+ p = phase * math.pi
56
+ def a(t):
57
+ return g(t) * amp * np.cos( omega*t + p )
58
+ self.A = a
59
+
60
+
61
+ def init_from_input_options(self, iopts):
62
+ opt_env = iopts.get('envelope',None)
63
+ opt_amp = iopts.get('amplitude',None)
64
+ opt_omega = iopts.get('omega',None)
65
+ opt_center = iopts.get('center',None)
66
+ opt_fwhm = iopts.get('fwhm',None)
67
+ opt_phase = iopts.get('phase',None)
68
+ opt_pol = iopts.get('polarization',None)
69
+
70
+ if opt_env:
71
+ env = opt_env[0]
72
+ else:
73
+ env = 'gaussian' # Default envelope
74
+
75
+ if opt_amp:
76
+ amp = cv.numval(opt_amp, 'efield')
77
+ else:
78
+ amp = 0.001 # Default peak field amplitude
79
+
80
+ if opt_omega:
81
+ omega = cv.numval(opt_omega, 'energy')
82
+ else:
83
+ omega = 0.0 # Default photon energy
84
+
85
+ if opt_center:
86
+ center = cv.numval(opt_center, 'time')
87
+ else:
88
+ center = 0.0 # Default center
89
+
90
+ if opt_fwhm:
91
+ fwhm = cv.numval(opt_fwhm, 'time')
92
+ else:
93
+ fwhm = 200.0 # Default fwhm in au
94
+
95
+ if opt_phase:
96
+ phase = float( opt_phase[0] )
97
+ else:
98
+ phase = 0.0
99
+
100
+ if opt_pol:
101
+ pol = np.array( list( map(float,opt_pol) ) )
102
+ pol = pol/np.dot(pol,pol)**0.5
103
+ else:
104
+ pol = np.array([0,0,1],float)
105
+
106
+ self.P = pol
107
+ if env == 'gaussian':
108
+ self.set_gaussian_pulse(omega,fwhm,amp,center,phase)
109
+
110
+
111
+
112
+ def plot(self, ti=0.0, tf=10.0, **opts):
113
+ """
114
+ Use matplotlib to plot the time-profile of the pulse
115
+
116
+ ti -- float, intial time
117
+ tf -- float, final time
118
+ """
119
+ import matplotlib.pyplot as plt
120
+ tunit = opts.get('tunit','au') # time units (field is plotted in au)
121
+ npoints = opts.get('npoints',500) # number of points used for plotting
122
+ color = opts.get('color','r')
123
+ t = np.linspace(ti,tf,npoints)
124
+ if tunit == 'fs':
125
+ au_t = t*cv.fs2au
126
+ else:
127
+ au_t = t
128
+ amp = list( map(self.A,au_t) )
129
+ plt.plot(t,amp,color)
130
+ # plt.show()
131
+
132
+
133
+
134
+
135
+ __k = 4.0*math.log(2.0)
136
+ __N = __k/math.pi
137
+ def gaussian_envelope(fwhm, t0=0.0, norm=False):
138
+ """
139
+ Return Gaussian function f(t)
140
+
141
+ fwhm -- float; full width at half maximum
142
+ t0 -- float; center of envelope; Def t0 = 0.0
143
+ norm -- bool; True: integral = 1, False: maximum = 1. Def. norm=False
144
+ """
145
+ N = __N/fwhm
146
+ if norm:
147
+ def g(t):
148
+ return N*np.exp(-__k*((t-t0)/fwhm)**2)
149
+ else:
150
+ def g(t):
151
+ return np.exp(-__k*((t-t0)/fwhm)**2)
152
+ return g
153
+
154
+
155
+ def gaussian_pulse(w, F, E0, t0=0.0, phi=0.0):
156
+ """
157
+ Return a Gaussian pulse f(t)
158
+
159
+ The pulsed is derived from the vector potential
160
+ A[t] = G[t]cos(wt + phi)
161
+ as E[t] = -dA[t]/dt
162
+ thus ensuring that the pulse is fully propagating
163
+
164
+ w - photon energy
165
+ F - full with at half maximum (duration) of the INTENSITY profile
166
+ E0 - maximum field amplitude in atomic units
167
+ t0 - center of the pulse
168
+ phi - additional phase
169
+ """
170
+ ln5 = np.log(0.5)
171
+ def g(Time):
172
+ ENVg = np.exp(2*ln5*(Time-t0)**2/F**2)
173
+ dtENVg = ENVg*4*ln5*(Time-t0)/F**2
174
+ CARCOS = np.cos(w*Time - phi)
175
+ CARSIN = np.sin(w*Time - phi)
176
+ return -E0*CARSIN*ENVg + E0/w*CARCOS*dtENVg
177
+ return g
178
+
179
+
180
+ if __name__ == '__main__':
181
+ ef = EField()
182
+ ef.P = np.array([0,0,1],float)
183
+ ef.set_gaussian_pulse(100*cv.ic2au, 200.0*cv.fs2au, 1.0, 250.0*cv.fs2au, 0.5)
184
+ ef.plot(ti=0,tf=1000,tunit='fs')
185
+
186
+
187
+
@@ -0,0 +1,179 @@
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
+ Spectra from time signals: correlation-function spectra and Fourier
8
+ transforms.
9
+ """
10
+
11
+ import numpy as np
12
+ from scipy.integrate import simpson
13
+
14
+
15
+ def corrspec(corr,dt,**opts):
16
+ """
17
+ Compute spectrum of a correlation function
18
+
19
+ Parameters
20
+ ----------
21
+ corr : array_like
22
+ 1D complex array with correlation function.
23
+ dt : float
24
+ time interval between correlation values; [au]
25
+
26
+ Options
27
+ -------
28
+ eref : float; 0.0
29
+ energy shift, e.g. zero point energy; [au]
30
+ emin : float; 0.0
31
+ start of spectrum counting from eref; [au]
32
+ emax : float; 0.0 (calculated from dt if not given)
33
+ end of spectrum counting from eref; [au]
34
+ npoints : int; 500
35
+ number of discrete points where spectrum is evaluated
36
+ between emin and emax
37
+ EP : bool; False
38
+ if True, multiplication of spectrum with energy factor
39
+ ncos : int; 1
40
+ exponent of the cosine damping function
41
+ """
42
+ eref = opts.get('eref',0.0)
43
+ emin = opts.get('emin',0.0)
44
+ emax = opts.get('emax',0.0)
45
+ npoints = opts.get('npoints',500)
46
+ EP = opts.get('EP',False)
47
+ ncos = opts.get('ncos',1)
48
+
49
+ a = np.asarray(corr)
50
+ maxwidth = 2.0*np.pi/dt
51
+ eminspec = eref + emin
52
+ emaxspec = eref + emax
53
+ if emax <= emin:
54
+ emaxspec = eminspec + maxwidth
55
+ T = (len(a) - 1)*dt
56
+ tlist = np.linspace(0.0,T,len(a))
57
+ elist = np.linspace(eminspec,emaxspec,npoints)
58
+ spect = np.zeros(len(elist), complex)
59
+
60
+ cos_damp = np.cos(0.5*np.pi*tlist/T)**ncos
61
+ adamp = a*cos_damp
62
+
63
+ for i,energy in enumerate(elist):
64
+ tmp = energy*tlist*1.0j
65
+ tmp = np.exp(tmp)
66
+ tmp = tmp*adamp
67
+ spect[i] = tmp.sum()*dt
68
+ #spect[i] = spect[i].real
69
+
70
+ if EP:
71
+ spect = spect*(elist-eref)
72
+
73
+ return elist-eref,spect
74
+
75
+
76
+ def fourierTransform(y, dt, **option):
77
+ """
78
+ Compute Fourier transform
79
+
80
+ Parameters:
81
+ -----------
82
+ y : 1D array_like
83
+ function to be Fourier transformed.
84
+ dt : float
85
+ interval of the domain function.
86
+ method : string, default: 'simp', optional
87
+ integration method to be used.
88
+ 'trapz' for trapezoidal rule,
89
+ 'simp' for Simpson rule
90
+ t_init : float, default: 0.0, optional
91
+ initial point of the domain function.
92
+ odamp : int, default: 1, optional
93
+ cosine damping function order (to damp the signal function at its end) [as the cosine power]
94
+ usage : {'phys', 'math'}, default: 'phys', optional
95
+ usage of the Fourier transform:
96
+ 'phys': mostly for physics application, it means using angular frequency, (2pi*f),
97
+ thus the inverse is asymmetric.
98
+ 'math': mostly for mathematics application, it means using normal frequency,
99
+ thus the inverse is symmetric.
100
+ f_range : tuple of float, default: if usage='phys' (-pi/dt, pi/dt), if usage='math' (-0.5/dt, 0.5/dt), optional
101
+ minimum frequency and maximum frequency.
102
+ npoints : int, default: len(y), optional
103
+ number of points of transformed domain function.
104
+ symmetry : {'gen', 'even', 'odd'}, default: 'gen', optional
105
+ symmetry of the function y to be transformed.
106
+ 'gen': general, the exponential function will be used.
107
+ 'even': even function, cosine transform will be used.
108
+ 'odd': odd function, sine transform will be used.
109
+ if symmetry is 'even' or 'odd', the t_init will be set to 0.0
110
+
111
+ Returns
112
+ -------
113
+ fline, FT_func : (list)
114
+ fline : 1D numpy.ndarray
115
+ Domain of transformed function (frequency domain)
116
+ FT_func : 1D numpy.ndarray
117
+ Fourier transform of y function.
118
+ """
119
+ func = np.asarray(y, dtype=complex)
120
+ # evaluating options
121
+ method = option.get('method', 'simp')
122
+ t_init = option.get('t_init', 0.0)
123
+ odamp = option.get('odamp', 1)
124
+ usage = option.get('usage', 'phys')
125
+ npoints = option.get('npoints', len(func))
126
+ symmetry = option.get('symmetry', 'gen')
127
+ if usage == 'math':
128
+ f_range = option.get('f_range', (-0.5/dt, 0.5/dt))
129
+ else:
130
+ f_range = option.get('f_range', (-np.pi/dt, np.pi/dt))
131
+ fmin = f_range[0]
132
+ fmax = f_range[1]
133
+
134
+ Nsegments = len(func) - 1
135
+ spanT = dt * (len(func) - 1)
136
+ if symmetry == 'even' or symmetry == 'odd':
137
+ t_init = 0.0
138
+ t_end = t_init + spanT
139
+ tline = np.linspace(t_init, t_end, Nsegments + 1)
140
+
141
+ if usage == 'phys':
142
+ #fline = np.linspace(0.0, 2.0 * np.pi / dt, npoints)
143
+ factor = 1.0
144
+ else:
145
+ #fline = np.linspace(0.0, 1.0 / dt, npoints)
146
+ factor = 2.0 * np.pi
147
+ fline = np.linspace(fmin, fmax, npoints)
148
+
149
+ # integration method
150
+ int_method = {'simp': simpson, 'trapz': np.trapezoid}
151
+ if method != 'simp' and method != 'trapz':
152
+ method = 'simp'
153
+
154
+ FT_func = np.zeros(npoints, dtype=complex)
155
+
156
+ # damped input function
157
+ if odamp == 0:
158
+ damp_func = func
159
+ else:
160
+ fn_damp = np.cos(0.5 * np.pi * (tline - t_init) / spanT)**odamp
161
+ damp_func = func * fn_damp
162
+
163
+ if symmetry == 'even':
164
+ for i, alpha in enumerate(fline):
165
+ integrand = alpha * tline * factor
166
+ integrand = damp_func * np.cos(integrand)
167
+ FT_func[i] = 2.0 * int_method[method](integrand, dx = dt)
168
+ elif symmetry == 'odd':
169
+ for i, alpha in enumerate(fline):
170
+ integrand = alpha * tline * factor
171
+ integrand = damp_func * np.sin(integrand)
172
+ FT_func[i] = 2.0 * 1.0j * int_method[method](integrand, dx = dt)
173
+ else:
174
+ for i, alpha in enumerate(fline):
175
+ integrand = -alpha * tline * factor
176
+ integrand = damp_func * np.exp(1.0j * integrand)
177
+ FT_func[i] = int_method[method](integrand, dx = dt)
178
+
179
+ return fline, FT_func