forked from algorithm-archivists/algorithm-archive
-
Notifications
You must be signed in to change notification settings - Fork 0
/
split_op.py
127 lines (95 loc) · 3.77 KB
/
split_op.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# pylint: disable=invalid-name
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-few-public-methods
# pylint: disable=too-many-arguments
from math import pi
from math import sqrt
import numpy as np
class Param:
"""Container for holding all simulation parameters."""
def __init__(self,
xmax: float,
res: int,
dt: float,
timesteps: int,
im_time: bool) -> None:
self.xmax = xmax
self.res = res
self.dt = dt
self.timesteps = timesteps
self.im_time = im_time
self.dx = 2 * xmax / res
self.x = np.arange(-xmax + xmax / res, xmax, self.dx)
self.dk = pi / xmax
self.k = np.concatenate((np.arange(0, res / 2),
np.arange(-res / 2, 0))) * self.dk
class Operators:
"""Container for holding operators and wavefunction coefficients."""
def __init__(self, res: int) -> None:
self.V = np.empty(res, dtype=complex)
self.R = np.empty(res, dtype=complex)
self.K = np.empty(res, dtype=complex)
self.wfc = np.empty(res, dtype=complex)
def init(par: Param, voffset: float, wfcoffset: float) -> Operators:
"""Initialize the wavefunction coefficients and the potential."""
opr = Operators(len(par.x))
opr.V = 0.5 * (par.x - voffset) ** 2
opr.wfc = np.exp(-((par.x - wfcoffset) ** 2) / 2, dtype=complex)
if par.im_time:
opr.K = np.exp(-0.5 * (par.k ** 2) * par.dt)
opr.R = np.exp(-0.5 * opr.V * par.dt)
else:
opr.K = np.exp(-0.5 * (par.k ** 2) * par.dt * 1j)
opr.R = np.exp(-0.5 * opr.V * par.dt * 1j)
return opr
def split_op(par: Param, opr: Operators) -> None:
for i in range(par.timesteps):
# Half-step in real space
opr.wfc *= opr.R
# FFT to momentum space
opr.wfc = np.fft.fft(opr.wfc)
# Full step in momentum space
opr.wfc *= opr.K
# iFFT back
opr.wfc = np.fft.ifft(opr.wfc)
# Final half-step in real space
opr.wfc *= opr.R
# Density for plotting and potential
density = np.abs(opr.wfc) ** 2
# Renormalizing for imaginary time
if par.im_time:
renorm_factor = sum(density) * par.dx
opr.wfc /= sqrt(renorm_factor)
# Outputting data to file. Plotting can also be done in a
# similar way. This is set to output exactly 100 files, no
# matter how many timesteps were specified.
if i % (par.timesteps // 100) == 0:
filename = "output{}.dat".format(str(i).rjust(5, str(0)))
with open(filename, "w") as outfile:
# Outputting for gnuplot. Any plotter will do.
for j in range(len(density)):
template = "{}\t{}\t{}\n".format
line = template(par.x[j], density[j].real, opr.V[j].real)
outfile.write(line)
print("Outputting step: ", i + 1)
def calculate_energy(par: Param, opr: Operators) -> float:
"""Calculate the energy <Psi|H|Psi>."""
# Creating real, momentum, and conjugate wavefunctions.
wfc_r = opr.wfc
wfc_k = np.fft.fft(wfc_r)
wfc_c = np.conj(wfc_r)
# Finding the momentum and real-space energy terms
energy_k = 0.5 * wfc_c * np.fft.ifft((par.k ** 2) * wfc_k)
energy_r = wfc_c * opr.V * wfc_r
# Integrating over all space
energy_final = sum(energy_k + energy_r).real
return energy_final * par.dx
def main() -> None:
par = Param(5.0, 256, 0.05, 100, True)
# Starting wavefunction slightly offset so we can see it change
opr = init(par, 0.0, -1.00)
split_op(par, opr)
energy = calculate_energy(par, opr)
print("Energy is: ", energy)
if __name__ == "__main__":
main()