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,266 @@
|
|
|
1
|
+
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
|
|
2
|
+
from qiskit.compiler import transpile
|
|
3
|
+
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
|
|
4
|
+
from qiskit.quantum_info.operators import Operator
|
|
5
|
+
from qiskit.circuit.library import QFT, PauliEvolutionGate
|
|
6
|
+
from qiskit_aer import Aer
|
|
7
|
+
from qiskit.synthesis import LieTrotter
|
|
8
|
+
import qiskit_aer
|
|
9
|
+
import numpy as np
|
|
10
|
+
import scipy.linalg as spLA
|
|
11
|
+
from tqdm.auto import trange
|
|
12
|
+
from .classical_methods import DynamicsCS
|
|
13
|
+
from .utils import decompose, pauli_strings_2_pauli_sum
|
|
14
|
+
import numpy.typing as npt
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class QubitDynamicsCS(DynamicsCS):
|
|
18
|
+
"""
|
|
19
|
+
Class to extend `DynamicsCS` by adding qubit-based methods for dynamics.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
def __init__(self, **kwargs):
|
|
23
|
+
super().__init__(**kwargs)
|
|
24
|
+
self.n_qubits = int(np.log2(self.n_basis))
|
|
25
|
+
self.quantum_circuit = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _create_QSOFT_Circuit(self, psio: npt.ArrayLike=None):
|
|
29
|
+
"""
|
|
30
|
+
Function to construct the QSOFT Circuit.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
psio (npt.ArrayLike): initial state that we wish to propagate
|
|
34
|
+
"""
|
|
35
|
+
tgrid = self.tlist
|
|
36
|
+
time_step = self.dt
|
|
37
|
+
n_qubits = self.n_qubits
|
|
38
|
+
# Qubit-Basis Propagators
|
|
39
|
+
self.prop_PE_qubit = np.diag(np.exp(-1j*self._PE_grid/2*time_step))
|
|
40
|
+
self.prop_KE_qubit = np.diag(np.exp(-1j*self._KE_grid*time_step))
|
|
41
|
+
|
|
42
|
+
q_reg = QuantumRegister(n_qubits)
|
|
43
|
+
c_reg = ClassicalRegister(n_qubits)
|
|
44
|
+
qc = QuantumCircuit(q_reg)
|
|
45
|
+
if type(psio) == type(None):
|
|
46
|
+
qc.initialize(self._psio_grid, q_reg[:], normalize=True)
|
|
47
|
+
else:
|
|
48
|
+
qc.initialize(psio, q_reg[:], normalize=True)
|
|
49
|
+
# Define our PE and KE propagators in Qiskit-friendly manner
|
|
50
|
+
PE_cirq_op = Operator(self.prop_PE_qubit)
|
|
51
|
+
KE_cirq_op = Operator(self.prop_KE_qubit)
|
|
52
|
+
qc.append(PE_cirq_op, q_reg)
|
|
53
|
+
qc.append(QFT(self.n_qubits, do_swaps=True, inverse=False), q_reg)
|
|
54
|
+
qc.append(KE_cirq_op, q_reg)
|
|
55
|
+
qc.append(QFT(self.n_qubits, do_swaps=True, inverse=True), q_reg)
|
|
56
|
+
qc.append(PE_cirq_op, q_reg)
|
|
57
|
+
self.quantum_circuit = qc
|
|
58
|
+
return(qc)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _execute_circuit(self, QCircuit: QuantumCircuit, backend=None, shots: int = None, real_backend: bool = False):
|
|
62
|
+
"""
|
|
63
|
+
Function to replace the now-deprecated Qiskit
|
|
64
|
+
`QuantumCircuit.execute()` method.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
QCircuit (qiskit.QuantumCircuit): qiskit.QuantumCircuit object
|
|
68
|
+
backend (qiskit.Backend): qiskit backend instance
|
|
69
|
+
shots (int): the number of shots to use for circuit sampling
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
job: an executed quantum circuit job
|
|
73
|
+
"""
|
|
74
|
+
if shots:
|
|
75
|
+
n_shots = shots
|
|
76
|
+
else:
|
|
77
|
+
n_shots = 1024 # Use the qiskit default if not specified
|
|
78
|
+
backend_type = type(backend)
|
|
79
|
+
sv_type = qiskit_aer.backends.statevector_simulator.StatevectorSimulator
|
|
80
|
+
if backend_type == sv_type:
|
|
81
|
+
real_backend = False
|
|
82
|
+
else:
|
|
83
|
+
real_backend = True
|
|
84
|
+
|
|
85
|
+
if real_backend:
|
|
86
|
+
QCircuit.measure_all()
|
|
87
|
+
qc = transpile(QCircuit, backend=backend)
|
|
88
|
+
sampler = Sampler(backend)
|
|
89
|
+
job = sampler.run([qc], shots=n_shots)
|
|
90
|
+
else:
|
|
91
|
+
# Transpile circuit with statevector backend
|
|
92
|
+
tmp_circuit = transpile(QCircuit, backend)
|
|
93
|
+
# Run the transpiled circuit
|
|
94
|
+
job = backend.run(tmp_circuit, n_shots=shots)
|
|
95
|
+
return(job)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def propagate_qSOFT(self, backend=None, n_shots: int = 1024):
|
|
99
|
+
"""Function to propagate dynamics object with the qubit SOFT method.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
backend (qiskit.Backend): qiskit backend object
|
|
103
|
+
n_shots (int): specifies the number of shots to use when
|
|
104
|
+
executing the circuit
|
|
105
|
+
|
|
106
|
+
Example for using the Statevector Simulator backend:
|
|
107
|
+
>>> from qiskit_aer import Aer
|
|
108
|
+
>>> backend = Aer.get_backend('statevector_simulator')
|
|
109
|
+
>>> self.propagate_qSOFT(backend=backend)
|
|
110
|
+
"""
|
|
111
|
+
if backend is None:
|
|
112
|
+
print('A valid backend must be provided ')
|
|
113
|
+
backend_type = type(backend)
|
|
114
|
+
sv_type = qiskit_aer.backends.statevector_simulator.StatevectorSimulator
|
|
115
|
+
if backend_type != sv_type:
|
|
116
|
+
self._propagate_qSOFT_real(backend=backend, n_shots=n_shots)
|
|
117
|
+
return
|
|
118
|
+
else:
|
|
119
|
+
|
|
120
|
+
psi_in = self.psio_grid
|
|
121
|
+
# Get initial state from qiskit routine
|
|
122
|
+
q_reg = QuantumRegister(self.n_qubits)
|
|
123
|
+
c_reg = ClassicalRegister(self.n_qubits)
|
|
124
|
+
qc = QuantumCircuit(q_reg, c_reg)
|
|
125
|
+
qc.initialize(self.psio_grid, q_reg[:], normalize=True)
|
|
126
|
+
qc_result = self._execute_circuit(qc, backend=backend, shots=n_shots)
|
|
127
|
+
psio_cirq = qc_result.result().get_statevector().data
|
|
128
|
+
psi_in = psio_cirq
|
|
129
|
+
# Now do propagation loop
|
|
130
|
+
qubit_dynamics_results = [psio_cirq]
|
|
131
|
+
for ii in trange(1, len(self.tlist)):
|
|
132
|
+
circuit = self._create_QSOFT_Circuit(psio=psi_in)
|
|
133
|
+
executed_circuit = self._execute_circuit(circuit, backend=backend, shots=n_shots)
|
|
134
|
+
psi_out = executed_circuit.result().get_statevector().data
|
|
135
|
+
qubit_dynamics_results.append(psi_out)
|
|
136
|
+
psi_in = psi_out
|
|
137
|
+
|
|
138
|
+
self.dynamics_results_qSOFT = np.asarray(qubit_dynamics_results)
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def get_statevector_from_counts(self, counts, n_shots):
|
|
143
|
+
new_statevector = np.zeros_like(self.psio_grid)
|
|
144
|
+
|
|
145
|
+
for key in counts:
|
|
146
|
+
little_endian_int = int(key, 2)
|
|
147
|
+
new_statevector[little_endian_int] = counts[key]/n_shots
|
|
148
|
+
return(new_statevector)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _propagate_qSOFT_real(self, backend='statevector_simulator', n_shots=1024):
|
|
152
|
+
"""
|
|
153
|
+
Function to propagate dynamics object with the qubit SOFT method.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
backend (qiskit.Backend): qiskit backend object
|
|
157
|
+
n_shots (int): specifies the number of shots to use when
|
|
158
|
+
executing the circuit
|
|
159
|
+
|
|
160
|
+
Example for using the Statevector Simulator backend:
|
|
161
|
+
>>> from qiskit_aer import Aer
|
|
162
|
+
>>> backend = Aer.get_backend('statevector_simulator')
|
|
163
|
+
>>> self.propagate_qSOFT(backend=backend)
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
psi_in = self.psio_grid
|
|
168
|
+
# Get initial state from qiskit routine
|
|
169
|
+
q_reg = QuantumRegister(self.n_qubits)
|
|
170
|
+
c_reg = ClassicalRegister(self.n_qubits, name='c')
|
|
171
|
+
qc = QuantumCircuit(q_reg, c_reg)
|
|
172
|
+
qc.initialize(self.psio_grid, q_reg[:], normalize=True)
|
|
173
|
+
# Now do propagation loop
|
|
174
|
+
qubit_dynamics_results = []
|
|
175
|
+
for ii in trange(len(self.tlist)):
|
|
176
|
+
circuit = self._create_QSOFT_Circuit(psio=psi_in)
|
|
177
|
+
executed_circuit = self._execute_circuit(circuit, backend=backend, shots=n_shots)
|
|
178
|
+
circuit_result = executed_circuit.result()
|
|
179
|
+
measured_psi = circuit_result[0].data['meas'].get_counts()
|
|
180
|
+
self._last_measurement = measured_psi
|
|
181
|
+
psi_out = self.get_statevector_from_counts(measured_psi, n_shots)
|
|
182
|
+
psi_in = psi_out
|
|
183
|
+
qubit_dynamics_results.append(psi_out)
|
|
184
|
+
psi_in = psi_out
|
|
185
|
+
|
|
186
|
+
self.dynamics_results_qSOFT = np.asarray(qubit_dynamics_results)
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _construct_pauli_gate(self, hamiltonian_matrix=None):
|
|
191
|
+
"""
|
|
192
|
+
Function to construct a pauli evolution gate from Hamiltonian
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
hamiltonian_matrix (npt.ArrayLike): array-like matrix representing the hamiltonian of interest
|
|
196
|
+
If not provided, use the operator representation of the Hamiltonian by default.
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
if type(hamiltonian_matrix) == type(None):
|
|
201
|
+
decomposed_H = decompose(self.H_op.full())
|
|
202
|
+
else:
|
|
203
|
+
decomposed_H = decompose(hamiltonian_matrix)
|
|
204
|
+
H_pauli_sum = pauli_strings_2_pauli_sum(decomposed_H)
|
|
205
|
+
synthesizer = LieTrotter(reps=2)
|
|
206
|
+
prop_pauli_H = PauliEvolutionGate(operator=H_pauli_sum, time=self.dt, synthesis=synthesizer)
|
|
207
|
+
self.pauli_prop = prop_pauli_H
|
|
208
|
+
self._pauli_hamiltonian = decomposed_H
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _construct_pauli_cirq(self, psio=None):
|
|
213
|
+
q_reg = QuantumRegister(self.n_qubits)
|
|
214
|
+
c_reg = ClassicalRegister(self.n_qubits)
|
|
215
|
+
qc = QuantumCircuit(q_reg, c_reg)
|
|
216
|
+
qc.initialize(psio, q_reg[:], normalize=True)
|
|
217
|
+
qc.append(self.pauli_prop, q_reg)
|
|
218
|
+
self.quantum_circuit = qc
|
|
219
|
+
return(qc)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def propagate_qmatvec(self, backend=None, n_shots: int = 1024, hamiltonian_matrix=None, initial_state=None):
|
|
223
|
+
"""
|
|
224
|
+
Function to propagate dynamics object with the qubit matvec method.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
backend (qiskit.Backend): qiskit backend object
|
|
228
|
+
n_shots (int): specifies the number of shots to use when
|
|
229
|
+
executing the circuit
|
|
230
|
+
hamiltonian_matrix (npt.ArrayLike): array-like matrix representing the Hamiltonian
|
|
231
|
+
Used to construct the propagator:
|
|
232
|
+
|
|
233
|
+
$$ U(t) = e^{- i H t / \hbar} $$
|
|
234
|
+
|
|
235
|
+
By default, the operator representation of the hamiltonian `self.H_op` is used.
|
|
236
|
+
initial_state (npt.ArrayLike): array-like vector representing the initial state
|
|
237
|
+
Example for using the Statevector Simulator backend:
|
|
238
|
+
>>> from qiskit_aer import Aer
|
|
239
|
+
>>> backend = Aer.get_backend('statevector_simulator')
|
|
240
|
+
>>> self.propagate_qSOFT(backend=backend)
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
# Create the Pauli propagator:
|
|
244
|
+
self._construct_pauli_gate(hamiltonian_matrix=hamiltonian_matrix)
|
|
245
|
+
|
|
246
|
+
q_reg = QuantumRegister(self.n_qubits)
|
|
247
|
+
c_reg = ClassicalRegister(self.n_qubits)
|
|
248
|
+
qc = QuantumCircuit(q_reg, c_reg)
|
|
249
|
+
# Initialize State
|
|
250
|
+
if type(initial_state) == type(None):
|
|
251
|
+
qc.initialize(self.psio_op.full().flatten(), q_reg[:], normalize=True)
|
|
252
|
+
else:
|
|
253
|
+
qc.initialize(initial_state, q_reg[:], normalize=True)
|
|
254
|
+
qc_result = self._execute_circuit(qc, backend=backend, shots=n_shots)
|
|
255
|
+
psio_cirq = qc_result.result().get_statevector().data
|
|
256
|
+
psi_in = psio_cirq
|
|
257
|
+
new_qubit_dynamics_result = [psio_cirq]
|
|
258
|
+
for ii in trange(1, len(self.tlist)):
|
|
259
|
+
circuit = self._construct_pauli_cirq(psio=psi_in)
|
|
260
|
+
executed_circuit = self._execute_circuit(circuit, backend=backend, shots=n_shots)
|
|
261
|
+
psi_out = executed_circuit.result().get_statevector().data
|
|
262
|
+
new_qubit_dynamics_result.append(psi_out)
|
|
263
|
+
psi_in = psi_out
|
|
264
|
+
self.dynamics_results_qubit = np.asarray(new_qubit_dynamics_result)
|
|
265
|
+
return
|
|
266
|
+
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
from .utils import execute
|
|
4
|
+
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
|
|
5
|
+
from qiskit_aer import Aer
|
|
6
|
+
from .spin_propagators import get_time_evolution_operator
|
|
7
|
+
|
|
8
|
+
class SpinDynamicsS:
|
|
9
|
+
"""
|
|
10
|
+
A class to simulate the dynamics of a quantum system using a statevector approach.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
num_qubits (int): Number of qubits in the quantum system.
|
|
14
|
+
evolution_timestep (float): Time step for the evolution.
|
|
15
|
+
trotter_steps (int): Number of Trotter steps for the simulation.
|
|
16
|
+
hamiltonian_coefficients (list): Hamiltonian coefficients for the system.
|
|
17
|
+
initial_state (str or list): Initial state of the system, represented as a binary string or list.
|
|
18
|
+
time_evo_op (QuantumCircuit): Time evolution operator for the system.
|
|
19
|
+
psin0 (ndarray): Initial state vector.
|
|
20
|
+
psin_list (list): List of state vectors during the simulation.
|
|
21
|
+
correlation_list (list): List of correlations calculated during the simulation.
|
|
22
|
+
dpi (int): DPI setting for plot output.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
num_qubits,
|
|
28
|
+
evolution_timestep,
|
|
29
|
+
trotter_steps,
|
|
30
|
+
hamiltonian_coefficients,
|
|
31
|
+
):
|
|
32
|
+
"""
|
|
33
|
+
Initialize the QuantumDynamicsClassicalSimulation class.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
num_qubits (int): Number of qubits in the quantum system.
|
|
37
|
+
evolution_timestep (float): Time step for the evolution.
|
|
38
|
+
trotter_steps (int): Number of Trotter steps for the simulation.
|
|
39
|
+
hamiltonian_coefficients (list): Hamiltonian coefficients for the system.
|
|
40
|
+
initial_state (str or list): Initial state of the system, represented as a binary string or list.
|
|
41
|
+
"""
|
|
42
|
+
self.num_qubits = num_qubits
|
|
43
|
+
self.evolution_timestep = evolution_timestep
|
|
44
|
+
self.trotter_steps = trotter_steps
|
|
45
|
+
self.hamiltonian_coefficients = hamiltonian_coefficients
|
|
46
|
+
self.time_evo_op = get_time_evolution_operator(
|
|
47
|
+
num_qubits=self.num_qubits,
|
|
48
|
+
tau=self.evolution_timestep,
|
|
49
|
+
trotter_steps=self.trotter_steps,
|
|
50
|
+
coeff=self.hamiltonian_coefficients,
|
|
51
|
+
)
|
|
52
|
+
self.initial_state = ''
|
|
53
|
+
self.psin_list = []
|
|
54
|
+
self.correlation_list = []
|
|
55
|
+
|
|
56
|
+
# Plot settings
|
|
57
|
+
self.dpi = 300
|
|
58
|
+
plt.rcParams["axes.linewidth"] = 1.5
|
|
59
|
+
plt.rcParams["lines.markersize"] = 11
|
|
60
|
+
plt.rcParams["figure.figsize"] = (6.4, 3.6)
|
|
61
|
+
|
|
62
|
+
def prepare_initial_state(self, state_string):
|
|
63
|
+
"""
|
|
64
|
+
Prepare the initial state vector from the binary string or list.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
ndarray: Flattened initial state vector.
|
|
68
|
+
"""
|
|
69
|
+
zero_state = np.array([[1], [0]])
|
|
70
|
+
one_state = np.array([[0], [1]])
|
|
71
|
+
|
|
72
|
+
# Map binary string or list to quantum states
|
|
73
|
+
state_vectors = [
|
|
74
|
+
zero_state if str(bit) == "0" else one_state for bit in state_string
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
# Perform Kronecker product to construct the full initial state
|
|
78
|
+
psin = state_vectors[0]
|
|
79
|
+
for state in state_vectors[1:]:
|
|
80
|
+
psin = np.kron(psin, state)
|
|
81
|
+
return psin.flatten()
|
|
82
|
+
|
|
83
|
+
def qsolve_statevector(self, psin):
|
|
84
|
+
"""
|
|
85
|
+
Perform statevector propagation for the quantum circuit.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
psin (ndarray): Input statevector.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
ndarray: Final statevector after execution.
|
|
92
|
+
"""
|
|
93
|
+
d = int(np.log2(np.size(psin)))
|
|
94
|
+
qre = QuantumRegister(d)
|
|
95
|
+
circ = QuantumCircuit(qre)
|
|
96
|
+
circ.initialize(psin, qre)
|
|
97
|
+
circ.barrier()
|
|
98
|
+
circ.append(self.time_evo_op, qre)
|
|
99
|
+
circ.barrier()
|
|
100
|
+
|
|
101
|
+
device = Aer.get_backend("statevector_simulator")
|
|
102
|
+
result = execute(circ, backend=device).result()
|
|
103
|
+
return result.get_statevector()
|
|
104
|
+
|
|
105
|
+
def run_dynamics(self, nsteps, state_string):
|
|
106
|
+
"""
|
|
107
|
+
Simulate the dynamics of the quantum system over a number of steps.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
nsteps (int): Number of time steps for the simulation.
|
|
111
|
+
"""
|
|
112
|
+
self.psin0 = self.prepare_initial_state(state_string)
|
|
113
|
+
self.psin_list = [self.psin0]
|
|
114
|
+
for k in range(nsteps):
|
|
115
|
+
print(f"Running dynamics step {k}")
|
|
116
|
+
if k > 0:
|
|
117
|
+
psin = self.qsolve_statevector(self.psin_list[-1])
|
|
118
|
+
self.psin_list.pop()
|
|
119
|
+
self.psin_list.append(psin)
|
|
120
|
+
|
|
121
|
+
self.correlation_list.append(np.vdot(self.psin_list[-1], self.psin0))
|
|
122
|
+
|
|
123
|
+
def save_results(self, filename_prefix):
|
|
124
|
+
"""
|
|
125
|
+
Save the simulation results to files.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
filename_prefix (str): Prefix for the output filenames.
|
|
129
|
+
"""
|
|
130
|
+
time = np.arange(
|
|
131
|
+
0,
|
|
132
|
+
self.evolution_timestep * len(self.correlation_list),
|
|
133
|
+
self.evolution_timestep,
|
|
134
|
+
)
|
|
135
|
+
np.save(f"{filename_prefix}_time", time)
|
|
136
|
+
sa_observable = np.abs(self.correlation_list)
|
|
137
|
+
np.save(f"{filename_prefix}_SA_obs", sa_observable)
|
|
138
|
+
|
|
139
|
+
def plot_results(self, filename_prefix):
|
|
140
|
+
"""
|
|
141
|
+
Plot the simulation results and save the plots as files.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
filename_prefix (str): Prefix for the output filenames.
|
|
145
|
+
"""
|
|
146
|
+
time = np.arange(
|
|
147
|
+
0,
|
|
148
|
+
self.evolution_timestep * len(self.correlation_list),
|
|
149
|
+
self.evolution_timestep,
|
|
150
|
+
)
|
|
151
|
+
sa_observable = np.abs(self.correlation_list)
|
|
152
|
+
|
|
153
|
+
plt.plot(time, sa_observable, "-o")
|
|
154
|
+
plt.xlabel("Time")
|
|
155
|
+
plt.ylabel(r"$\left|\langle \psi | \psi (t) \rangle \right|$")
|
|
156
|
+
plt.xlim((min(time), max(time)))
|
|
157
|
+
plt.yscale("log")
|
|
158
|
+
#plt.legend()
|
|
159
|
+
plt.tight_layout()
|
|
160
|
+
plt.savefig(f"{filename_prefix}.pdf", format="pdf", dpi=self.dpi)
|
|
161
|
+
plt.savefig(f"{filename_prefix}.png", format="png", dpi=self.dpi)
|
|
162
|
+
plt.show()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class SpinDynamicsH:
|
|
166
|
+
"""
|
|
167
|
+
A class to simulate quantum dynamics using Hadamard tests and time evolution operators.
|
|
168
|
+
|
|
169
|
+
This class performs quantum circuit construction, execution, and data processing to calculate
|
|
170
|
+
survival amplitudes and probabilities for a spin chain system.
|
|
171
|
+
"""
|
|
172
|
+
def __init__(self, num_qubits, evolution_timestep, trotter_steps, hamiltonian_coefficients):
|
|
173
|
+
"""
|
|
174
|
+
Initializes the QuantumSimulation class with system parameters and sets up the simulator.
|
|
175
|
+
|
|
176
|
+
Parameters:
|
|
177
|
+
num_qubits (int): Number of qubits in the system.
|
|
178
|
+
hamiltonian_coefficients (list): Coefficients for the Hamiltonian terms.
|
|
179
|
+
evolution_timestep (float): Timestep for the time evolution operator.
|
|
180
|
+
"""
|
|
181
|
+
self.num_qubits = num_qubits
|
|
182
|
+
self.hamiltonian_coefficients = hamiltonian_coefficients
|
|
183
|
+
self.evolution_timestep = evolution_timestep
|
|
184
|
+
self.trotter_steps = trotter_steps,
|
|
185
|
+
self.simulator = Aer.get_backend('qasm_simulator')
|
|
186
|
+
self.real_amp_list = []
|
|
187
|
+
self.imag_amp_list = []
|
|
188
|
+
|
|
189
|
+
self.time_evo_op = get_time_evolution_operator(
|
|
190
|
+
num_qubits=num_qubits,
|
|
191
|
+
tau=evolution_timestep,
|
|
192
|
+
trotter_steps=trotter_steps,
|
|
193
|
+
coeff=hamiltonian_coefficients
|
|
194
|
+
)
|
|
195
|
+
self.controlled_time_evo_op = self.time_evo_op.control()
|
|
196
|
+
|
|
197
|
+
def get_hadamard_test(self, initial_state, control_repeats, imag_expectation=False):
|
|
198
|
+
"""
|
|
199
|
+
Constructs the Hadamard test circuit to evaluate the real or imaginary components of the operator.
|
|
200
|
+
|
|
201
|
+
Parameters:
|
|
202
|
+
initial_state (QuantumCircuit): Circuit for initializing the quantum state.
|
|
203
|
+
control_repeats (int): Number of times the controlled operation is applied.
|
|
204
|
+
imag_expectation (bool): Whether to evaluate the imaginary component (default: False).
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
QuantumCircuit: The constructed Hadamard test circuit.
|
|
208
|
+
"""
|
|
209
|
+
qr = QuantumRegister(self.num_qubits + 1)
|
|
210
|
+
cr = ClassicalRegister(1)
|
|
211
|
+
qc = QuantumCircuit(qr, cr)
|
|
212
|
+
qc.append(initial_state, qr[1:])
|
|
213
|
+
qc.barrier()
|
|
214
|
+
|
|
215
|
+
qc.h(0)
|
|
216
|
+
if imag_expectation:
|
|
217
|
+
qc.p(-np.pi / 2, 0)
|
|
218
|
+
|
|
219
|
+
for _ in range(control_repeats):
|
|
220
|
+
qc.append(self.controlled_time_evo_op, qr[:])
|
|
221
|
+
|
|
222
|
+
qc.h(0)
|
|
223
|
+
qc.barrier()
|
|
224
|
+
qc.measure(0, 0)
|
|
225
|
+
return qc
|
|
226
|
+
|
|
227
|
+
def execute_circuit(self, qc, num_shots=100):
|
|
228
|
+
"""
|
|
229
|
+
Executes a quantum circuit using the Qiskit simulator and retrieves measurement counts.
|
|
230
|
+
|
|
231
|
+
Parameters:
|
|
232
|
+
qc (QuantumCircuit): The quantum circuit to execute.
|
|
233
|
+
num_shots (int): Number of shots for circuit execution (default: 100).
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
dict: Measurement counts.
|
|
237
|
+
"""
|
|
238
|
+
job = execute(qc, self.simulator, shots=num_shots)
|
|
239
|
+
return job.result().get_counts()
|
|
240
|
+
|
|
241
|
+
@staticmethod
|
|
242
|
+
def calculate_spin_correlation(counts):
|
|
243
|
+
"""
|
|
244
|
+
Calculates the spin correlation based on measurement counts.
|
|
245
|
+
|
|
246
|
+
Parameters:
|
|
247
|
+
counts (dict): Measurement counts.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
float: Average spin correlation.
|
|
251
|
+
"""
|
|
252
|
+
qubit_to_spin_map = {'0': 1, '1': -1}
|
|
253
|
+
total_counts = sum(counts.values())
|
|
254
|
+
values = [qubit_to_spin_map[k] * v for k, v in counts.items()]
|
|
255
|
+
return sum(values) / total_counts
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def initialize_state(num_qubits, state_string):
|
|
259
|
+
"""
|
|
260
|
+
Creates a circuit to initialize the quantum state.
|
|
261
|
+
|
|
262
|
+
Parameters:
|
|
263
|
+
num_qubits (int): Number of qubits.
|
|
264
|
+
state_string (str): Binary string representing the initial state.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
QuantumCircuit: Circuit for initializing the state.
|
|
268
|
+
"""
|
|
269
|
+
state_string = ''.join('1' if char == '0' else '0' for char in state_string)
|
|
270
|
+
qr = QuantumRegister(num_qubits)
|
|
271
|
+
qc = QuantumCircuit(qr)
|
|
272
|
+
qc.initialize(state_string, qr[:])
|
|
273
|
+
return qc
|
|
274
|
+
|
|
275
|
+
def run_simulation(self, state_string, total_time, num_shots=100):
|
|
276
|
+
"""
|
|
277
|
+
Runs the Hadamard test simulation for the given initial state.
|
|
278
|
+
|
|
279
|
+
Parameters:
|
|
280
|
+
state_string (str): Binary string representing the initial state.
|
|
281
|
+
total_time (float): Total simulation time.
|
|
282
|
+
num_shots (int): Number of shots for circuit execution (default: 100).
|
|
283
|
+
"""
|
|
284
|
+
init_circuit = self.initialize_state(self.num_qubits, state_string)
|
|
285
|
+
self.time_range = np.arange(0, total_time + self.evolution_timestep,
|
|
286
|
+
self.evolution_timestep)
|
|
287
|
+
|
|
288
|
+
for idx, _ in enumerate(self.time_range):
|
|
289
|
+
print(f'Running dynamics step {idx}')
|
|
290
|
+
|
|
291
|
+
# Real component
|
|
292
|
+
qc_real = self.get_hadamard_test(init_circuit, idx, imag_expectation=False)
|
|
293
|
+
real_counts = self.execute_circuit(qc_real, num_shots)
|
|
294
|
+
real_amp = self.calculate_spin_correlation(real_counts)
|
|
295
|
+
self.real_amp_list.append(real_amp)
|
|
296
|
+
|
|
297
|
+
# Imaginary component
|
|
298
|
+
qc_imag = self.get_hadamard_test(init_circuit, idx, imag_expectation=True)
|
|
299
|
+
imag_counts = self.execute_circuit(qc_imag, num_shots)
|
|
300
|
+
imag_amp = self.calculate_spin_correlation(imag_counts)
|
|
301
|
+
self.imag_amp_list.append(imag_amp)
|
|
302
|
+
|
|
303
|
+
print(f'Finished step {idx}: Re = {real_amp:.3f}, Im = {imag_amp:.3f}')
|
|
304
|
+
|
|
305
|
+
def save_results(self, prefix):
|
|
306
|
+
"""
|
|
307
|
+
Saves the real and imaginary amplitudes, survival amplitude, and survival probability to files.
|
|
308
|
+
|
|
309
|
+
Parameters:
|
|
310
|
+
prefix (str): Prefix for the output file names.
|
|
311
|
+
"""
|
|
312
|
+
real_amp_array = np.array(self.real_amp_list)
|
|
313
|
+
imag_amp_array = np.array(self.imag_amp_list)
|
|
314
|
+
|
|
315
|
+
np.savetxt(f'{prefix}_real_amp.csv', real_amp_array, fmt='%.18e', delimiter=';')
|
|
316
|
+
np.savetxt(f'{prefix}_imag_amp.csv', imag_amp_array, fmt='%.18e', delimiter=';')
|
|
317
|
+
np.savetxt(f'{prefix}_abs_correlation.csv', np.abs(real_amp_array + 1j * imag_amp_array), fmt='%.18e', delimiter=';')
|
|
318
|
+
np.savetxt(f'{prefix}_sqrt_sum_squares.csv', np.sqrt(real_amp_array**2 + imag_amp_array**2), fmt='%.18e', delimiter=';')
|
|
319
|
+
|
|
320
|
+
def plot_results(self, prefix):
|
|
321
|
+
"""
|
|
322
|
+
Plots the survival amplitude and compares it with a reference.
|
|
323
|
+
|
|
324
|
+
Parameters:
|
|
325
|
+
prefix (str): Prefix for loading precomputed reference data.
|
|
326
|
+
"""
|
|
327
|
+
abs_corr = np.abs(np.array(self.real_amp_list) + 1j * np.array(self.imag_amp_list))
|
|
328
|
+
|
|
329
|
+
plt.plot(self.time_range, abs_corr, '.', label='Hadamard Test')
|
|
330
|
+
ref_sa = np.load(f'../../../data/{self.num_qubits}_spin_chain_SA_obs.npy')
|
|
331
|
+
ref_time = np.load(f'../../../data/{self.num_qubits}_spin_chain_time.npy')
|
|
332
|
+
plt.plot(ref_time, ref_sa, '-', label='Statevector')
|
|
333
|
+
|
|
334
|
+
plt.xlabel('Time')
|
|
335
|
+
plt.ylabel(r"$\left|\langle \psi | \psi (t) \rangle \right|$")
|
|
336
|
+
plt.tight_layout()
|
|
337
|
+
#plt.legend()
|
|
338
|
+
plt.show()
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
if __name__ == "__main__":
|
|
342
|
+
classical = True
|
|
343
|
+
num_q = 3
|
|
344
|
+
evolution_timestep = 0.1
|
|
345
|
+
n_trotter_steps = 1
|
|
346
|
+
hamiltonian_coefficients = [[0.75 / 2, 0.75 / 2, 0.0, 0.65]] + [
|
|
347
|
+
[0.5, 0.5, 0.0, 1.0] for _ in range(num_q - 1)
|
|
348
|
+
]
|
|
349
|
+
initial_state = "011" # Specify the initial state as a binary string
|
|
350
|
+
|
|
351
|
+
if classical:
|
|
352
|
+
csimulation = SpinDynamicsS(
|
|
353
|
+
num_q,
|
|
354
|
+
evolution_timestep,
|
|
355
|
+
n_trotter_steps,
|
|
356
|
+
hamiltonian_coefficients,
|
|
357
|
+
)
|
|
358
|
+
csimulation.run_dynamics(nsteps=250, state_string=initial_state)
|
|
359
|
+
csimulation.save_results(f"{num_q}_spin_chain")
|
|
360
|
+
csimulation.plot_results(f"{num_q}_spin_chain_statevector")
|
|
361
|
+
|
|
362
|
+
else:
|
|
363
|
+
qsimulation = SpinDynamicsH(
|
|
364
|
+
num_q,
|
|
365
|
+
evolution_timestep,
|
|
366
|
+
n_trotter_steps,
|
|
367
|
+
hamiltonian_coefficients,
|
|
368
|
+
)
|
|
369
|
+
qsimulation.run_simulation(state_string=initial_state, total_time=25, num_shots=100)
|
|
370
|
+
qsimulation.save_results('hadamard_test')
|
|
371
|
+
qsimulation.plot_results('hadamard_test')
|