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.

Files changed (38) hide show
  1. qflux/GQME/__init__.py +7 -0
  2. qflux/GQME/dynamics_GQME.py +438 -0
  3. qflux/GQME/params.py +62 -0
  4. qflux/GQME/readwrite.py +119 -0
  5. qflux/GQME/tdvp.py +233 -0
  6. qflux/GQME/tt_tfd.py +448 -0
  7. qflux/__init__.py +5 -0
  8. qflux/closed_systems/__init__.py +17 -0
  9. qflux/closed_systems/classical_methods.py +427 -0
  10. qflux/closed_systems/custom_execute.py +22 -0
  11. qflux/closed_systems/hamiltonians.py +88 -0
  12. qflux/closed_systems/qubit_methods.py +266 -0
  13. qflux/closed_systems/spin_dynamics_oo.py +371 -0
  14. qflux/closed_systems/spin_propagators.py +300 -0
  15. qflux/closed_systems/utils.py +205 -0
  16. qflux/open_systems/__init__.py +2 -0
  17. qflux/open_systems/dilation_circuit.py +183 -0
  18. qflux/open_systems/numerical_methods.py +303 -0
  19. qflux/open_systems/params.py +29 -0
  20. qflux/open_systems/quantum_simulation.py +360 -0
  21. qflux/open_systems/trans_basis.py +121 -0
  22. qflux/open_systems/walsh_gray_optimization.py +311 -0
  23. qflux/typing/__init__.py +0 -0
  24. qflux/typing/examples.py +24 -0
  25. qflux/utils/__init__.py +0 -0
  26. qflux/utils/io.py +16 -0
  27. qflux/utils/logging_config.py +61 -0
  28. qflux/variational_methods/__init__.py +1 -0
  29. qflux/variational_methods/qmad/__init__.py +0 -0
  30. qflux/variational_methods/qmad/ansatz.py +64 -0
  31. qflux/variational_methods/qmad/ansatzVect.py +61 -0
  32. qflux/variational_methods/qmad/effh.py +75 -0
  33. qflux/variational_methods/qmad/solver.py +356 -0
  34. qflux-0.0.1.dist-info/METADATA +144 -0
  35. qflux-0.0.1.dist-info/RECORD +38 -0
  36. qflux-0.0.1.dist-info/WHEEL +5 -0
  37. qflux-0.0.1.dist-info/licenses/LICENSE +674 -0
  38. 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')