qflux 0.0.1__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.
Potentially problematic release.
This version of qflux might be problematic. Click here for more details.
- qflux/GQME/__init__.py +7 -0
- qflux/GQME/dynamics_GQME.py +438 -0
- qflux/GQME/params.py +62 -0
- qflux/GQME/readwrite.py +119 -0
- qflux/GQME/tdvp.py +233 -0
- qflux/GQME/tt_tfd.py +448 -0
- qflux/__init__.py +5 -0
- qflux/closed_systems/__init__.py +17 -0
- qflux/closed_systems/classical_methods.py +427 -0
- qflux/closed_systems/custom_execute.py +22 -0
- qflux/closed_systems/hamiltonians.py +88 -0
- qflux/closed_systems/qubit_methods.py +266 -0
- qflux/closed_systems/spin_dynamics_oo.py +371 -0
- qflux/closed_systems/spin_propagators.py +300 -0
- qflux/closed_systems/utils.py +205 -0
- qflux/open_systems/__init__.py +2 -0
- qflux/open_systems/dilation_circuit.py +183 -0
- qflux/open_systems/numerical_methods.py +303 -0
- qflux/open_systems/params.py +29 -0
- qflux/open_systems/quantum_simulation.py +360 -0
- qflux/open_systems/trans_basis.py +121 -0
- qflux/open_systems/walsh_gray_optimization.py +311 -0
- qflux/typing/__init__.py +0 -0
- qflux/typing/examples.py +24 -0
- qflux/utils/__init__.py +0 -0
- qflux/utils/io.py +16 -0
- qflux/utils/logging_config.py +61 -0
- qflux/variational_methods/__init__.py +1 -0
- qflux/variational_methods/qmad/__init__.py +0 -0
- qflux/variational_methods/qmad/ansatz.py +64 -0
- qflux/variational_methods/qmad/ansatzVect.py +61 -0
- qflux/variational_methods/qmad/effh.py +75 -0
- qflux/variational_methods/qmad/solver.py +356 -0
- qflux-0.0.1.dist-info/METADATA +144 -0
- qflux-0.0.1.dist-info/RECORD +38 -0
- qflux-0.0.1.dist-info/WHEEL +5 -0
- qflux-0.0.1.dist-info/licenses/LICENSE +674 -0
- qflux-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# Class for classical propagation methods
|
|
2
|
+
import numpy as np
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from matplotlib import axes
|
|
5
|
+
from tqdm.auto import trange
|
|
6
|
+
import qutip as qt
|
|
7
|
+
from typing import Callable
|
|
8
|
+
from .utils import *
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DynamicsCS:
|
|
12
|
+
"""
|
|
13
|
+
Class for closed-system dynamics. **All input parameters must be in
|
|
14
|
+
atomic units to ensure consistency. Please be sure to convert your
|
|
15
|
+
parameters to atomic units prior to instantiation.**
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, n_basis: int = 128, xo: float = 1.0, po: float = 0.0, mass: float = 1.0,
|
|
20
|
+
omega: float = 1.0) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Args:
|
|
23
|
+
n_basis (int): Number of states to include in the chosen representation. If basis
|
|
24
|
+
= 'ladder', this is the Fock cutoff and defines the number of states
|
|
25
|
+
used for representing the ladder operators. If basis = 'coordinate',
|
|
26
|
+
this defines the number of points for the position and momenta.
|
|
27
|
+
|
|
28
|
+
xo (float, optional): Defines the displacement of the initial state in the position
|
|
29
|
+
coordinate. Default is 1.0 Bohr.
|
|
30
|
+
|
|
31
|
+
po (float, optional): Defines the displacement of the initial state in the position
|
|
32
|
+
coordinate. Default is 1.0 au.
|
|
33
|
+
|
|
34
|
+
mass (float, optional): Defines the mass of the particle/system of interest.
|
|
35
|
+
Default is 1.0 au.
|
|
36
|
+
|
|
37
|
+
omega (float, optional): Frequency of harmonic oscillator.
|
|
38
|
+
Default is 1.0 au.
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
#--------- Required Attributes Populated During Execution ----------#
|
|
42
|
+
self.n_basis = n_basis
|
|
43
|
+
self.xo = xo
|
|
44
|
+
self.po = po
|
|
45
|
+
self.mass = mass
|
|
46
|
+
self.hbar = 1.0
|
|
47
|
+
self.omega = omega
|
|
48
|
+
#--------- Below are Attributes Populated During Execution ---------#
|
|
49
|
+
self.total_time = 0.
|
|
50
|
+
self.n_tsteps = 0.
|
|
51
|
+
self._KE_op = None
|
|
52
|
+
self._PE_op = None
|
|
53
|
+
self.H_op = None
|
|
54
|
+
self.prop_KE_op = None
|
|
55
|
+
self.prop_PE_op = None
|
|
56
|
+
self.prop_H_op = None
|
|
57
|
+
# Grid operators
|
|
58
|
+
self._KE_grid = None
|
|
59
|
+
self._PE_grid = None
|
|
60
|
+
self.H_grid = None
|
|
61
|
+
self.PE_prop_grid = None
|
|
62
|
+
self.KE_prop_grid = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _get_xgrid(self, x_min: float, x_max: float) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Populate the `self.x_grid` and `self.dx` attributes. This function
|
|
68
|
+
generates an array of `self.n_basis` evenly spaced values between
|
|
69
|
+
`x_min` and `x_max`.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
x_min (float): Minimum value of x-coordinates
|
|
73
|
+
x_max (float): Maximum value of x-coordinates
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
self.dx (float): Spacing between points in the x-coordinate grid.
|
|
77
|
+
self.xgrid (array_like): Array of grid points from x_min to x_max with spacing of dx
|
|
78
|
+
"""
|
|
79
|
+
dx = (x_max - x_min) / self.n_basis
|
|
80
|
+
x_grid = np.arange(-self.n_basis / 2, self.n_basis / 2) * dx
|
|
81
|
+
self.dx = dx
|
|
82
|
+
self.x_grid = x_grid
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _get_pgrid(self, x_min: float, x_max: float, reorder: bool = True) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Populate the `self.p_grid` and `self.dp` attributes. This function
|
|
89
|
+
generates an array of `self.n_basis` evenly spaced values.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
x_min (float): Minimum value of x-coordinates
|
|
93
|
+
x_max (float): Maximum value of x-coordinates
|
|
94
|
+
reorder (bool): Boolean flag to determine whether points should be reordered to be
|
|
95
|
+
compatible with the FFT routine or not.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
self.dp (float): Spacing between points in the p-coordinate grid.
|
|
99
|
+
self.pgrid (array_like): Array of momentum grid points
|
|
100
|
+
"""
|
|
101
|
+
dp = 2 * np.pi / (x_max - x_min)
|
|
102
|
+
pmin = -dp * self.n_basis / 2
|
|
103
|
+
pmax = dp * self.n_basis / 2
|
|
104
|
+
plus_pgrid = np.linspace(0, pmax, self.n_basis // 2 + 1)
|
|
105
|
+
minus_pgrid = - np.flip(np.copy(plus_pgrid))
|
|
106
|
+
if reorder:
|
|
107
|
+
p_grid = np.concatenate((plus_pgrid[:-1], minus_pgrid[:-1]))
|
|
108
|
+
else:
|
|
109
|
+
p_grid = np.concatenate((minus_pgrid, plus_pgrid))
|
|
110
|
+
self.p_grid = p_grid
|
|
111
|
+
self.dp = dp
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def set_coordinate_operators(self, x_min: float = -7., x_max: float = 7., reorder_p: bool = True) -> None:
|
|
116
|
+
"""
|
|
117
|
+
Populate the `self.x_grid`, `self.p_grid`, `self.dx`, and `self.dp`
|
|
118
|
+
attributes. This functions generates an array of `self.n_basis`
|
|
119
|
+
evenly spaced values.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
x_min : float
|
|
123
|
+
Minimum value of x-coordinates
|
|
124
|
+
x_max : float
|
|
125
|
+
Maximum value of x-coordinates
|
|
126
|
+
reorder_p : bool
|
|
127
|
+
Boolean flag to determine whether momentum values should be
|
|
128
|
+
reordered to be compatible with the FFT routine or not.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
self.dx : float
|
|
132
|
+
Spacing between points in the x-coordinate grid.
|
|
133
|
+
self.xgrid : array_like
|
|
134
|
+
Array of x-values
|
|
135
|
+
self.dp : float
|
|
136
|
+
Spacing between points in the p-coordinate grid.
|
|
137
|
+
self.pgrid : array_like
|
|
138
|
+
Array of p-values
|
|
139
|
+
"""
|
|
140
|
+
self._get_xgrid(x_min, x_max)
|
|
141
|
+
self._get_pgrid(x_min, x_max, reorder=reorder_p)
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def initialize_operators(self):
|
|
146
|
+
"""
|
|
147
|
+
Function to initialize core operators in the chosen basis.
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
self.a_op = qt.destroy(self.n_basis)
|
|
152
|
+
self.x_op = qt.position(self.n_basis)
|
|
153
|
+
self.p_op = qt.momentum(self.n_basis)
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _set_hamiltonian_grid(self, potential_type: str = 'harmonic', **kwargs):
|
|
158
|
+
if potential_type == 'harmonic':
|
|
159
|
+
|
|
160
|
+
# Set attributes for the coordinate basis
|
|
161
|
+
self._PE_grid = self.mass * self.omega ** 2 * self.x_grid ** 2 / 2.
|
|
162
|
+
self._KE_grid = self.p_grid ** 2 / (2. * self.mass)
|
|
163
|
+
self.H_grid = self._PE_grid + self._KE_grid
|
|
164
|
+
elif potential_type == 'quartic':
|
|
165
|
+
if kwargs:
|
|
166
|
+
if 'a0' in kwargs:
|
|
167
|
+
a0 = kwargs['a0']
|
|
168
|
+
if 'a1' in kwargs:
|
|
169
|
+
a1 = kwargs['a1']
|
|
170
|
+
if 'a2' in kwargs:
|
|
171
|
+
a2 = kwargs['a2']
|
|
172
|
+
if 'a3' in kwargs:
|
|
173
|
+
a3 = kwargs['a3']
|
|
174
|
+
if 'a4' in kwargs:
|
|
175
|
+
a4 = kwargs['a4']
|
|
176
|
+
if 'x0' in kwargs:
|
|
177
|
+
x0 = kwargs['x0']
|
|
178
|
+
# Assume that all inputs have the proper atomic units:
|
|
179
|
+
cf = 1.0
|
|
180
|
+
xi = self.x_op
|
|
181
|
+
|
|
182
|
+
else:
|
|
183
|
+
# Define relevant parameters
|
|
184
|
+
cf = convert_eV_to_au(1.)
|
|
185
|
+
x0 = 1.9592
|
|
186
|
+
a0 = 0.0
|
|
187
|
+
a1 = 0.429
|
|
188
|
+
a2 = -1.126
|
|
189
|
+
a3 = -0.143
|
|
190
|
+
a4 = 0.563
|
|
191
|
+
# Do calculation for ladder basis
|
|
192
|
+
xi = self.x_grid / x0
|
|
193
|
+
self._PE_grid = cf * (a0 + a1 * xi + a2 * xi ** 2 + a3 * xi ** 3 + a4 * xi ** 4)
|
|
194
|
+
self._KE_grid = self.p_grid ** 2 / (2. * self.mass)
|
|
195
|
+
self.H_grid = self._PE_grid + self._KE_grid
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _set_hamiltonian_qt(self, potential_type: str = 'harmonic', **kwargs):
|
|
200
|
+
if potential_type == 'harmonic':
|
|
201
|
+
# Set attributes for the ladder basis
|
|
202
|
+
self.H_op = self.hbar * self.omega * (self.a_op.dag() * self.a_op + 0.5)
|
|
203
|
+
self._KE_op = self.p_op ** 2 / (2. * self.mass)
|
|
204
|
+
self._PE_op = self.mass * self.omega ** 2 * self.x_op ** 2 / 0.5
|
|
205
|
+
self.H_xp_op = self._PE_op + self._KE_op
|
|
206
|
+
elif potential_type == 'quartic':
|
|
207
|
+
if kwargs:
|
|
208
|
+
if 'a0' in kwargs:
|
|
209
|
+
a0 = kwargs['a0']
|
|
210
|
+
if 'a1' in kwargs:
|
|
211
|
+
a1 = kwargs['a1']
|
|
212
|
+
if 'a2' in kwargs:
|
|
213
|
+
a2 = kwargs['a2']
|
|
214
|
+
if 'a3' in kwargs:
|
|
215
|
+
a3 = kwargs['a3']
|
|
216
|
+
if 'a4' in kwargs:
|
|
217
|
+
a4 = kwargs['a4']
|
|
218
|
+
if 'x0' in kwargs:
|
|
219
|
+
x0 = kwargs['x0']
|
|
220
|
+
# Assume that all inputs have the proper atomic units:
|
|
221
|
+
cf = 1.0
|
|
222
|
+
xi = self.x_op
|
|
223
|
+
|
|
224
|
+
else:
|
|
225
|
+
# Define relevant parameters
|
|
226
|
+
cf = convert_eV_to_au(1.)
|
|
227
|
+
x0 = 1.9592
|
|
228
|
+
a0 = 0.0
|
|
229
|
+
a1 = 0.429
|
|
230
|
+
a2 = -1.126
|
|
231
|
+
a3 = -0.143
|
|
232
|
+
a4 = 0.563
|
|
233
|
+
# Do calculation for ladder basis
|
|
234
|
+
xi = self.x_op / x0
|
|
235
|
+
self.x0 = x0
|
|
236
|
+
self._PE_op = cf * (a0 + a1 * xi + a2 * xi ** 2 + a3 * xi ** 3 + a4 * xi ** 4)
|
|
237
|
+
self._KE_op = self.p_op ** 2 / (2. * self.mass)
|
|
238
|
+
self.H_op = self._PE_op + self._KE_op
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
def set_hamiltonian(self, potential_type: str = 'harmonic', **kwargs):
|
|
242
|
+
"""
|
|
243
|
+
Function to define Hamiltonian.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
potential_type : str
|
|
247
|
+
String defining the type of potential energy surface.
|
|
248
|
+
Available options are: ('harmonic', 'quartic', ...)
|
|
249
|
+
|
|
250
|
+
Note: You can manually define your potential energy using the functions:
|
|
251
|
+
- set_H_grid_with_custom_potential
|
|
252
|
+
- set_H_op_with_custom_potential
|
|
253
|
+
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
if potential_type == 'harmonic':
|
|
257
|
+
self._set_hamiltonian_grid(potential_type=potential_type, **kwargs)
|
|
258
|
+
self._set_hamiltonian_qt(potential_type=potential_type, **kwargs)
|
|
259
|
+
elif potential_type == 'quartic':
|
|
260
|
+
self._set_hamiltonian_grid(potential_type=potential_type, **kwargs)
|
|
261
|
+
self._set_hamiltonian_qt(potential_type=potential_type, **kwargs)
|
|
262
|
+
else:
|
|
263
|
+
print('Error, this potential type has not yet been implemented!')
|
|
264
|
+
print('Set your parameters with the custom functions!')
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def set_H_grid_with_custom_potential(self, custom_function: Callable, **kwargs):
|
|
269
|
+
"""
|
|
270
|
+
Function to allow for user-defined potential defined by custom_function. Must be a function of qutip operators.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
custom_function (Callable): Function that defines the custom potential
|
|
274
|
+
energy. Must return an array
|
|
275
|
+
|
|
276
|
+
"""
|
|
277
|
+
potential = custom_function(**kwargs)
|
|
278
|
+
self._PE_grid = potential
|
|
279
|
+
self._KE_grid = self.p_grid ** 2 / (2. * self.mass)
|
|
280
|
+
self.H_grid = self._PE_grid + self._KE_grid
|
|
281
|
+
return
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def set_H_op_with_custom_potential(self, custom_function: Callable, **kwargs):
|
|
285
|
+
"""
|
|
286
|
+
Function to allow for user-defined potential defined by custom_function. Must be a function of qutip operators.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
custom_function (Callable): Function that defines the potential
|
|
290
|
+
energy in terms of qutip QObj operators. Must return a qutip.Qobj
|
|
291
|
+
"""
|
|
292
|
+
potential = custom_function(**kwargs)
|
|
293
|
+
self._PE_op = potential
|
|
294
|
+
self._KE_op = self.p_op ** 2 / (2. * self.mass)
|
|
295
|
+
self.H_op = self._PE_op + self._KE_op
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def set_initial_state(self, wfn_omega: float = 1.0):
|
|
300
|
+
"""
|
|
301
|
+
Function to define the initial state. By default, a coherent state is
|
|
302
|
+
used as the initial state defined in the basis chosen upon instantiation
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
wfn_omega (float, optional): Defines the frequency/width of the initial state.
|
|
306
|
+
Default is 1.0 au.
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
alpha_val = (self.xo + 1j * self.po) / np.sqrt(2)
|
|
310
|
+
psio = qt.coherent(self.n_basis, alpha=alpha_val)
|
|
311
|
+
# Now populate the initial state in the grid basis
|
|
312
|
+
normalization = (self.mass * wfn_omega / np.pi / self.hbar) ** (0.25)
|
|
313
|
+
exponential = np.exp(-1 * (self.mass * wfn_omega / self.hbar / 2) *
|
|
314
|
+
((self.x_grid - self.xo) ** 2)
|
|
315
|
+
+ 1j * self.po * self.x_grid / self.hbar
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
coherent_state = normalization * exponential
|
|
319
|
+
# Set the attributes
|
|
320
|
+
self.psio_grid = coherent_state
|
|
321
|
+
self.psio_op = psio
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def custom_grid_state_initialization(self, function_name: Callable, **kwargs):
|
|
327
|
+
"""
|
|
328
|
+
Function to allow for customized grid state initialization.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
function_name (Callable): name of user-defined function that returns
|
|
332
|
+
the initial state. Must return an array
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
self.psio_grid = function_name(**kwargs)
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
def custom_ladder_state_initialization(self, function_name: Callable, **kwargs):
|
|
339
|
+
"""
|
|
340
|
+
Function to allow for customized ladder state initialization.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
function_name (Callable): name of user-defined function that returns
|
|
344
|
+
the initial state. Must return a qutip.Qobj.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
self.psio_op = function_name(**kwargs)
|
|
348
|
+
return
|
|
349
|
+
|
|
350
|
+
def set_propagation_time(self, total_time: float, n_tsteps: int):
|
|
351
|
+
"""
|
|
352
|
+
Function to define the propagation time, an array of times from
|
|
353
|
+
t=0 to total_time, with n_tsteps equally-spaced steps.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
total_time : float
|
|
357
|
+
The total time for which we wish to compute the dynamics.
|
|
358
|
+
n_tsteps : int
|
|
359
|
+
The number of equally-spaced time steps used to compute the dynamics
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
self.tlist : array-like
|
|
363
|
+
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
self.tlist = np.linspace(0., total_time, n_tsteps+1)
|
|
367
|
+
self.dt = self.tlist[1] - self.tlist[0]
|
|
368
|
+
self.n_tsteps = n_tsteps
|
|
369
|
+
return
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def propagate_qt(self, solver_options : dict = None):
|
|
373
|
+
"""
|
|
374
|
+
Function used to propagate with qutip.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
solver_options (dict): A dictionary of arguments to pass to the qutip.sesolve function
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
dynamics_results (array-like): array containing the propagated state
|
|
381
|
+
|
|
382
|
+
"""
|
|
383
|
+
|
|
384
|
+
options = {'nsteps': len(self.tlist),
|
|
385
|
+
'progress_bar': True}
|
|
386
|
+
|
|
387
|
+
if solver_options:
|
|
388
|
+
for key in solver_options:
|
|
389
|
+
options[key] = solver_options[key]
|
|
390
|
+
|
|
391
|
+
results = qt.sesolve(self.H_op, self.psio_op, self.tlist,
|
|
392
|
+
options=options)
|
|
393
|
+
|
|
394
|
+
self.dynamics_results_op = results.states
|
|
395
|
+
return
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def propagate_SOFT(self):
|
|
399
|
+
"""
|
|
400
|
+
Function used to propagate with the 2nd-Order Trotter Expansion.
|
|
401
|
+
|
|
402
|
+
$$
|
|
403
|
+
e^{- \frac{i}{\\hbar} H t} \approx e^{- \frac{i}{\\hbar} V t/2} e^{- \frac{i}{\\hbar} T t} e^{- \frac{i}{\\hbar} V t/2} + \\mathcal{O}^{3}
|
|
404
|
+
$$
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
dynamics_results_grid (array-like): array containing the propagated state
|
|
408
|
+
shape (n_tsteps x self.n_basis)
|
|
409
|
+
|
|
410
|
+
"""
|
|
411
|
+
self.tau = self.tlist[1] - self.tlist[0]
|
|
412
|
+
PE_prop = np.exp(-1.0j * self._PE_grid / 2 * self.tau / self.hbar)
|
|
413
|
+
KE_prop = np.exp(-1.0j * self._KE_grid * self.tau / self.hbar)
|
|
414
|
+
|
|
415
|
+
self.PE_prop_grid = PE_prop
|
|
416
|
+
self.KE_prop_grid = KE_prop
|
|
417
|
+
|
|
418
|
+
propagated_states = [self.psio_grid]
|
|
419
|
+
psi_t = self.psio_grid
|
|
420
|
+
for ii in range(1, len(self.tlist)):
|
|
421
|
+
psi_t_position_grid = PE_prop * psi_t
|
|
422
|
+
psi_t_momentum_grid = KE_prop * np.fft.fft(psi_t_position_grid, norm="ortho")
|
|
423
|
+
psi_t = PE_prop * np.fft.ifft(psi_t_momentum_grid, norm="ortho")
|
|
424
|
+
propagated_states.append(psi_t)
|
|
425
|
+
|
|
426
|
+
self.dynamics_results_grid = np.asarray(propagated_states)
|
|
427
|
+
return
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Utility Functions/Patches:
|
|
2
|
+
from qiskit import transpile
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def execute(QCircuit, backend=None, shots=None):
|
|
6
|
+
'''
|
|
7
|
+
Function to replace the now-deprecated Qiskit
|
|
8
|
+
`QuantumCircuit.execute()` method.
|
|
9
|
+
|
|
10
|
+
Input:
|
|
11
|
+
- `QCircuit`: qiskit.QuantumCircuit object
|
|
12
|
+
- `Backend`: qiskit.Backend instance
|
|
13
|
+
- `shots`: int specifying the number of shots
|
|
14
|
+
'''
|
|
15
|
+
# Transpile circuit with statevector backend
|
|
16
|
+
tmp_circuit = transpile(QCircuit, backend)
|
|
17
|
+
# Run the transpiled circuit
|
|
18
|
+
if shots:
|
|
19
|
+
job = backend.run(tmp_circuit, n_shots=shots)
|
|
20
|
+
else:
|
|
21
|
+
job = backend.run(tmp_circuit)
|
|
22
|
+
return(job)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from qiskit.quantum_info import SparsePauliOp
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# -----------------------------------------------------------------
|
|
5
|
+
# Hamiltonian Functions
|
|
6
|
+
# -----------------------------------------------------------------
|
|
7
|
+
def get_hamiltonian_n_site_terms(n, coeff, n_qubits):
|
|
8
|
+
'''
|
|
9
|
+
Assembles each term in the Hamiltonian based on their Pauli string
|
|
10
|
+
representation and multiplying by the respective coefficient.
|
|
11
|
+
'''
|
|
12
|
+
XX_coeff = coeff[0]
|
|
13
|
+
YY_coeff = coeff[1]
|
|
14
|
+
ZZ_coeff = coeff[2]
|
|
15
|
+
Z_coeff = coeff[3]
|
|
16
|
+
|
|
17
|
+
XX_term = SparsePauliOp(("I" * n + "XX" + "I" * (n_qubits - 2 - n)))
|
|
18
|
+
XX_term *= XX_coeff
|
|
19
|
+
YY_term = SparsePauliOp(("I" * n + "YY" + "I" * (n_qubits - 2 - n)))
|
|
20
|
+
YY_term *= YY_coeff
|
|
21
|
+
ZZ_term = SparsePauliOp(("I" * n + "ZZ" + "I" * (n_qubits - 2 - n)))
|
|
22
|
+
ZZ_term *= ZZ_coeff
|
|
23
|
+
Z_term = SparsePauliOp(("I" * n + "Z" + "I" * (n_qubits - 1 - n)))
|
|
24
|
+
Z_term *= Z_coeff
|
|
25
|
+
|
|
26
|
+
return (XX_term + YY_term + ZZ_term + Z_term)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_heisenberg_hamiltonian(n_qubits, coeff=None):
|
|
30
|
+
r'''
|
|
31
|
+
Takes an integer number corresponding to number of spins/qubits
|
|
32
|
+
and a list of sublists containing the necessary coefficients
|
|
33
|
+
to assemble the complete Hamiltonian:
|
|
34
|
+
$$
|
|
35
|
+
H = \sum _i ^N h_z Z_i
|
|
36
|
+
+ \sum _i ^{N-1} (h_xx X_iX_{i+1}
|
|
37
|
+
+ h_yy Y_iY_{i+1}
|
|
38
|
+
+ h_zz Z_iZ_{i+1}
|
|
39
|
+
)
|
|
40
|
+
$$
|
|
41
|
+
Each sublist contains the [XX, YY, ZZ, Z] coefficients in this order.
|
|
42
|
+
The last sublist should have the same shape, but only the Z component
|
|
43
|
+
is used.
|
|
44
|
+
If no coefficient list is provided, all are set to 1.
|
|
45
|
+
'''
|
|
46
|
+
|
|
47
|
+
# Three qubits because for 2 we get H_O = 0
|
|
48
|
+
assert n_qubits >= 3
|
|
49
|
+
|
|
50
|
+
if coeff == None:
|
|
51
|
+
'Setting default values for the coefficients'
|
|
52
|
+
coeff = [[1.0, 1.0, 1.0, 1.0] for i in range(n_qubits)]
|
|
53
|
+
|
|
54
|
+
# Even terms of the Hamiltonian
|
|
55
|
+
# (summing over individual pair-wise elements)
|
|
56
|
+
H_E = sum((get_hamiltonian_n_site_terms(i, coeff[i], n_qubits)
|
|
57
|
+
for i in range(0, n_qubits-1, 2)))
|
|
58
|
+
|
|
59
|
+
# Odd terms of the Hamiltonian
|
|
60
|
+
# (summing over individual pair-wise elements)
|
|
61
|
+
H_O = sum((get_hamiltonian_n_site_terms(i, coeff[i], n_qubits)
|
|
62
|
+
for i in range(1, n_qubits-1, 2)))
|
|
63
|
+
|
|
64
|
+
# adding final Z term at the Nth site
|
|
65
|
+
final_term = SparsePauliOp("I" * (n_qubits - 1) + "Z")
|
|
66
|
+
final_term *= coeff[n_qubits-1][3]
|
|
67
|
+
if (n_qubits % 2) == 0:
|
|
68
|
+
H_E += final_term
|
|
69
|
+
else:
|
|
70
|
+
H_O += final_term
|
|
71
|
+
|
|
72
|
+
# Returns the list of the two sets of terms
|
|
73
|
+
return [H_E, H_O]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == '__main__':
|
|
77
|
+
num_q = 3
|
|
78
|
+
# XX YY ZZ, Z
|
|
79
|
+
ham_coeffs = ([[0.75/2, 0.75/2, 0.0, 0.65]]
|
|
80
|
+
+ [[0.5, 0.5, 0.0, 1.0]
|
|
81
|
+
for i in range(num_q-1)])
|
|
82
|
+
|
|
83
|
+
spin_chain_hamiltonian = get_heisenberg_hamiltonian(num_q,
|
|
84
|
+
ham_coeffs)
|
|
85
|
+
print('Hamiltonian separated into even and odd components:')
|
|
86
|
+
print(spin_chain_hamiltonian)
|
|
87
|
+
print('Hamiltonian combining even and odd components:')
|
|
88
|
+
print(sum(spin_chain_hamiltonian))
|