db-qite 0.0.1__tar.gz

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.
db_qite-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: db-qite
3
+ Version: 0.0.1
4
+ Summary: Implements Double-Bracket Quantum Imaginary Time Evolution (DB-QITE) in Qiskit
5
+ Author-email: Reza <rasakereh.8@gmail.com>
6
+ Project-URL: Homepage, https://github.com/rasakereh/double-bracket-algo
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: qiskit>=2.4.1
10
+ Requires-Dist: qiskit_aer>=0.17.2
11
+ Requires-Dist: qiskit-ibm-runtime>=0.46.1
12
+ Requires-Dist: pylatexenc>=2.10
13
+ Requires-Dist: matplotlib>=3.10.9
14
+
15
+ # Double-Bracket Quantum Imaginary Time Evolution (DB-QITE)
16
+
17
+ Implementation of [DB-QITE](https://doi.org/10.48550/arXiv.2412.04554) (by Gluza et al)
18
+
19
+ ## Installation
20
+ ```bash
21
+ pip install qiskit-dbqite
22
+ ```
23
+
24
+ ## Usage
25
+ ### To create a `QuantumCircuit`
26
+ ```python
27
+ from db_qite import DB_QITE
28
+
29
+ dbq = DB_QITE(
30
+ hamiltonian = H, # hamiltonian: `SparsePauliOp`
31
+ initial_state = None, # initial_state array-like, default None
32
+ time_step = s, # time step. list or a single value
33
+ )
34
+ circuit = dbq.create_circuit(
35
+ num_steps, # #iterations
36
+ random_u0, # boolean. U0 is random or not
37
+ )
38
+ ```
39
+
40
+
41
+ ### Run for a range of iterations with plots
42
+ ```python
43
+ from db_qite import db_qite_range
44
+
45
+ runner, results = db_qite_range(
46
+ hamiltonian=H,
47
+ initial_state=None, # or initial state
48
+ time_step=.5, # or a list
49
+ random_u0=True, # or False
50
+ num_steps_range=range(1, 6),
51
+ backend="simulator", # or None to detect a qiskit backend
52
+ estimate_energy=True, # wether to estimate energy or just prepare the state
53
+ shots=1024
54
+ )
55
+ ```
56
+
57
+ **Note:** if you provide None, or a qiskit backend name or object, it will run on IBM quantum machines. You have to set `IBM_QUANTUM_TOKEN` environment variable for it
@@ -0,0 +1,43 @@
1
+ # Double-Bracket Quantum Imaginary Time Evolution (DB-QITE)
2
+
3
+ Implementation of [DB-QITE](https://doi.org/10.48550/arXiv.2412.04554) (by Gluza et al)
4
+
5
+ ## Installation
6
+ ```bash
7
+ pip install qiskit-dbqite
8
+ ```
9
+
10
+ ## Usage
11
+ ### To create a `QuantumCircuit`
12
+ ```python
13
+ from db_qite import DB_QITE
14
+
15
+ dbq = DB_QITE(
16
+ hamiltonian = H, # hamiltonian: `SparsePauliOp`
17
+ initial_state = None, # initial_state array-like, default None
18
+ time_step = s, # time step. list or a single value
19
+ )
20
+ circuit = dbq.create_circuit(
21
+ num_steps, # #iterations
22
+ random_u0, # boolean. U0 is random or not
23
+ )
24
+ ```
25
+
26
+
27
+ ### Run for a range of iterations with plots
28
+ ```python
29
+ from db_qite import db_qite_range
30
+
31
+ runner, results = db_qite_range(
32
+ hamiltonian=H,
33
+ initial_state=None, # or initial state
34
+ time_step=.5, # or a list
35
+ random_u0=True, # or False
36
+ num_steps_range=range(1, 6),
37
+ backend="simulator", # or None to detect a qiskit backend
38
+ estimate_energy=True, # wether to estimate energy or just prepare the state
39
+ shots=1024
40
+ )
41
+ ```
42
+
43
+ **Note:** if you provide None, or a qiskit backend name or object, it will run on IBM quantum machines. You have to set `IBM_QUANTUM_TOKEN` environment variable for it
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "db-qite"
7
+ version = "0.0.1"
8
+ authors = [
9
+ { name="Reza", email="rasakereh.8@gmail.com" },
10
+ ]
11
+ description = "Implements Double-Bracket Quantum Imaginary Time Evolution (DB-QITE) in Qiskit"
12
+ readme = "README.md"
13
+ requires-python = ">=3.10"
14
+ dependencies = [
15
+ "qiskit >= 2.4.1",
16
+ "qiskit_aer >= 0.17.2",
17
+ "qiskit-ibm-runtime >= 0.46.1",
18
+ "pylatexenc >= 2.10",
19
+ "matplotlib >= 3.10.9",
20
+ ]
21
+
22
+ [project.urls]
23
+ "Homepage" = "https://github.com/rasakereh/double-bracket-algo"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,245 @@
1
+ from qiskit import QuantumCircuit, transpile
2
+ from qiskit.circuit.library import HamiltonianGate, IGate
3
+ from qiskit_aer import AerSimulator
4
+ from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, EstimatorV2 as Estimator, accounts
5
+ from qiskit.converters import circuit_to_dag
6
+ from qiskit.visualization import plot_histogram
7
+
8
+ import numpy as np
9
+ import os
10
+ import pathlib
11
+
12
+ ibm_token = os.getenv("IBM_QUANTUM_TOKEN")
13
+
14
+ try:
15
+ QiskitRuntimeService.save_account(channel="ibm_quantum_platform", token=ibm_token)
16
+ except accounts.exceptions.AccountAlreadyExistsError:
17
+ print("IBM Quantum account already exists. Using existing account.")
18
+
19
+ class DB_QITE:
20
+ U_evolution = None
21
+ multiple_s = True
22
+
23
+ def __init__(self, hamiltonian, initial_state, time_step):
24
+ self.hamiltonian = hamiltonian
25
+ self.initial_state = initial_state
26
+ self.time_step = time_step
27
+ if isinstance(time_step, float):
28
+ self.multiple_s = False
29
+ self.e_is, self.e_P0 = self._create_rotation_projection(time_step)
30
+
31
+ def _create_rotation_projection(self, s):
32
+ e_is = HamiltonianGate(self.hamiltonian, time=-s**.5, label='$e^{i\\sqrt{s}H}$')
33
+ P0 = np.zeros(self.hamiltonian.dim)
34
+ P0[0, 0] = 1
35
+ e_P0 = HamiltonianGate(P0, time=-s**.5, label='$e^{i\\sqrt{s}|0><0|}$')
36
+
37
+ return e_is, e_P0
38
+
39
+ def create_U_k(self, k, s=None, random_u0=False):
40
+ if k == 0:
41
+ if self.initial_state is not None:
42
+ argmax = np.argmax(self.initial_state)+1
43
+ closest_qubit = int(np.ceil(np.log2(argmax)))
44
+ U0 = QuantumCircuit(self.hamiltonian.num_qubits)
45
+ for n in range(self.hamiltonian.num_qubits):
46
+ if closest_qubit == n:
47
+ U0.x(n)
48
+ else:
49
+ U0.id(n)
50
+ elif random_u0:
51
+ # np.random.seed(42)
52
+ # U0 = QuantumCircuit(self.hamiltonian.num_qubits)
53
+ # for n in range(self.hamiltonian.num_qubits):
54
+ # if np.random.rand() < 0.5:
55
+ # U0.x(n)
56
+ # else:
57
+ # U0.id(n)
58
+ U0 = QuantumCircuit(self.hamiltonian.num_qubits)
59
+ for n in range(self.hamiltonian.num_qubits):
60
+ U0.h(n)
61
+ else:
62
+ U0 = QuantumCircuit(self.hamiltonian.num_qubits)
63
+ U0.id(range(self.hamiltonian.num_qubits))
64
+ return U0
65
+
66
+ if self.multiple_s and s is None:
67
+ e_is, e_P0 = self._create_rotation_projection(self.time_step[k-1])
68
+ elif s is None:
69
+ e_is, e_P0 = self.e_is, self.e_P0
70
+ else:
71
+ e_is, e_P0 = self._create_rotation_projection(s)
72
+
73
+ U_k_1 = self.create_U_k(k - 1, s, random_u0).to_gate(label=f'$U_{k-1}$')
74
+ U_k_1_inverse = U_k_1.inverse()
75
+ U_k_1_inverse.label = f'$U_{{{k-1}}}^\\dagger$'
76
+ e_is_inverse = e_is.inverse()
77
+ e_is_inverse.label = '$e^{-i\\sqrt{s}H}$'
78
+ U_k = QuantumCircuit(self.hamiltonian.num_qubits)
79
+ U_k.append(U_k_1, range(self.hamiltonian.num_qubits))
80
+ U_k.append(e_is_inverse, range(self.hamiltonian.num_qubits))
81
+ U_k.append(U_k_1_inverse, range(self.hamiltonian.num_qubits))
82
+ U_k.append(e_P0, range(self.hamiltonian.num_qubits))
83
+ U_k.append(U_k_1, range(self.hamiltonian.num_qubits))
84
+ U_k.append(e_is, range(self.hamiltonian.num_qubits))
85
+
86
+ return U_k
87
+
88
+ def create_circuit(self, num_steps, random_u0):
89
+ circuit = QuantumCircuit(self.hamiltonian.num_qubits, self.hamiltonian.num_qubits)
90
+ circuit.append(self.create_U_k(num_steps, random_u0=random_u0), range(self.hamiltonian.num_qubits))
91
+ circuit.measure(range(self.hamiltonian.num_qubits), range(self.hamiltonian.num_qubits))
92
+ circuit.name = f'DB-QITE_{num_steps}_steps'
93
+
94
+ return circuit
95
+
96
+ class CircuitRunner:
97
+ def __init__(self, circuits, backend, estimate_energy, default_shots, hamiltonian=None, output_dir='outputs'):
98
+ # backend can be "simulator" a string name for qiskit backend or an actual backend object, None (we find the backend automatically)
99
+ self.estimate_energy = estimate_energy
100
+ if self.estimate_energy and hamiltonian is None:
101
+ raise ValueError("Hamiltonian must be provided when estimate_energy is True")
102
+ self.hamiltonian = hamiltonian
103
+ self.output_dir = output_dir
104
+ pathlib.Path(self.output_dir).mkdir(parents=True, exist_ok=True)
105
+ self.default_shots = default_shots
106
+ if backend == "simulator":
107
+ self.backend = AerSimulator()
108
+ elif isinstance(backend, str):
109
+ service = QiskitRuntimeService()
110
+ self.backend = service.backend(backend)
111
+ elif backend is not None:
112
+ self.backend = backend
113
+ else:
114
+ service = QiskitRuntimeService()
115
+ self.backend = service.least_busy(simulator=False, operational=True)
116
+ if backend != "simulator":
117
+ self.simulation = False
118
+ self.sampler = Sampler(self.backend)
119
+ else:
120
+ self.simulation = True
121
+ self.sampler = self.backend
122
+
123
+ self.estimator = Estimator(mode=self.backend, options={"default_shots": default_shots})
124
+ self.circuits = [transpile(circuit, self.backend) for circuit in circuits]
125
+
126
+ def result_by_eigensolver(self):
127
+ if self.hamiltonian is None:
128
+ raise ValueError("Hamiltonian must be provided to compute eigenvalues and eigenvectors")
129
+ np.eigenvals, np.eigvecs = np.linalg.eigh(Operator(self.hamiltonian).data)
130
+ self.eigenvalues = np.eigenvals
131
+ self.eigenvectors = np.eigvecs
132
+
133
+ return self.eigenvalues, self.eigenvectors
134
+
135
+ @staticmethod
136
+ def _get_active_qubit_count(circuit):
137
+ dag = circuit_to_dag(circuit)
138
+ # A qubit is active if it is not in the list of idle wires
139
+ active_qubits = [q for q in circuit.qubits if q not in dag.idle_wires()]
140
+ return len(active_qubits)
141
+
142
+ def draw_transpiled_circuits(self):
143
+ for i, circuit in enumerate(self.circuits):
144
+ print(f"Circuit {circuit.name} transpiled for backend {self.backend.name} with depth {circuit.depth()}, num_qubits {self._get_active_qubit_count(circuit)}, and size {circuit.size()}")
145
+ circuit_to_draw = circuit#.decompose() if self.simulation else circuit
146
+ circuit_to_draw.draw('mpl', filename=f'{self.output_dir}/transpiled_circuit_{i}.png')
147
+ plt.close()
148
+
149
+ def _run_estimate_energy(self):
150
+ self.jobs = self.estimator.run(
151
+ [(circuit, self.hamiltonian) for circuit in self.circuits],
152
+ )
153
+ self.results = [
154
+ (float(res.data.evs), float(res.data.stds))
155
+ for res in self.jobs.result()
156
+ ]
157
+
158
+ return self.results
159
+
160
+ def _run_Z_measurement(self):
161
+ self.jobs = self.sampler.run(self.circuits, shots=self.default_shots)
162
+ if self.simulation:
163
+ self.results = [result.data.counts for result in self.jobs.result().results]
164
+ else:
165
+ self.results = [result.data.c.get_counts() for result in self.jobs.result()]
166
+ return self.results
167
+
168
+ def run(self):
169
+ if self.estimate_energy:
170
+ return self._run_estimate_energy()
171
+ else:
172
+ return self._run_Z_measurement()
173
+
174
+ def _draw_estimate_energy(self):
175
+ self.result_by_eigensolver()
176
+
177
+ ground_state_energy = self.eigenvalues[0]
178
+
179
+ energies = [res[0] for res in self.results]
180
+ stds = [res[1] for res in self.results]
181
+ plt.errorbar(range(len(energies)), energies, yerr=stds, fmt='o-', ecolor='red', capsize=5)
182
+ plt.axhline(ground_state_energy, color='green', linestyle='--', label='Ground State Energy')
183
+ for energy_level in self.eigenvalues[1:]:
184
+ plt.axhline(energy_level, color='gray', linestyle='dotted', alpha=0.5)
185
+ plt.legend()
186
+ plt.xticks(range(len(energies)), [circuit.name for circuit in self.circuits], rotation=45)
187
+ plt.xlabel('Circuit')
188
+ plt.ylabel('Estimated Energy')
189
+ plt.title('Energy Estimates with Standard Deviation')
190
+ plt.tight_layout()
191
+ plt.savefig(f'{self.output_dir}/energy_estimates.png')
192
+ plt.close()
193
+
194
+ def _draw_Z_measurement(self):
195
+ for i, result in enumerate(self.results):
196
+ name = self.circuits[i].name
197
+ plot_histogram(result, title=f"Results for {name}")
198
+ plt.savefig(f'{self.output_dir}/results_{name}.png')
199
+ plt.close()
200
+
201
+ def draw_results(self):
202
+ if self.estimate_energy:
203
+ self._draw_estimate_energy()
204
+ else:
205
+ self._draw_Z_measurement()
206
+
207
+
208
+ def db_qite_range(
209
+ hamiltonian,
210
+ initial_state,
211
+ time_step,
212
+ random_u0,
213
+ num_steps_range,
214
+ backend="simulator",
215
+ estimate_energy=True,
216
+ shots=1024,
217
+ output_dir='outputs'
218
+ ):
219
+ pathlib.Path(output_dir).mkdir(parents=True, exist_ok=True)
220
+
221
+ # heatmap of the hamiltonian
222
+ plt.imshow(np.abs(Operator(hamiltonian).data), cmap='viridis')
223
+ plt.colorbar()
224
+ plt.title("Hamiltonian Matrix")
225
+ plt.savefig(f'{output_dir}/hamiltonian_matrix.png')
226
+ plt.close()
227
+
228
+ circuits = []
229
+ for num_steps in num_steps_range:
230
+ dbq = DB_QITE(hamiltonian, initial_state, time_step)
231
+ circuit = dbq.create_circuit(num_steps, random_u0)
232
+ circuit.decompose().draw('mpl', filename=f'{output_dir}/DB-QITE_{num_steps}_steps.png')
233
+ plt.close()
234
+ circuits.append(circuit)
235
+
236
+ runner = CircuitRunner(circuits, backend, estimate_energy, shots, hamiltonian, output_dir=output_dir)
237
+
238
+ runner.draw_transpiled_circuits()
239
+
240
+ print(f"Running circuits...")
241
+ results = runner.run()
242
+ runner.draw_results()
243
+
244
+ return runner, results
245
+
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: db-qite
3
+ Version: 0.0.1
4
+ Summary: Implements Double-Bracket Quantum Imaginary Time Evolution (DB-QITE) in Qiskit
5
+ Author-email: Reza <rasakereh.8@gmail.com>
6
+ Project-URL: Homepage, https://github.com/rasakereh/double-bracket-algo
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: qiskit>=2.4.1
10
+ Requires-Dist: qiskit_aer>=0.17.2
11
+ Requires-Dist: qiskit-ibm-runtime>=0.46.1
12
+ Requires-Dist: pylatexenc>=2.10
13
+ Requires-Dist: matplotlib>=3.10.9
14
+
15
+ # Double-Bracket Quantum Imaginary Time Evolution (DB-QITE)
16
+
17
+ Implementation of [DB-QITE](https://doi.org/10.48550/arXiv.2412.04554) (by Gluza et al)
18
+
19
+ ## Installation
20
+ ```bash
21
+ pip install qiskit-dbqite
22
+ ```
23
+
24
+ ## Usage
25
+ ### To create a `QuantumCircuit`
26
+ ```python
27
+ from db_qite import DB_QITE
28
+
29
+ dbq = DB_QITE(
30
+ hamiltonian = H, # hamiltonian: `SparsePauliOp`
31
+ initial_state = None, # initial_state array-like, default None
32
+ time_step = s, # time step. list or a single value
33
+ )
34
+ circuit = dbq.create_circuit(
35
+ num_steps, # #iterations
36
+ random_u0, # boolean. U0 is random or not
37
+ )
38
+ ```
39
+
40
+
41
+ ### Run for a range of iterations with plots
42
+ ```python
43
+ from db_qite import db_qite_range
44
+
45
+ runner, results = db_qite_range(
46
+ hamiltonian=H,
47
+ initial_state=None, # or initial state
48
+ time_step=.5, # or a list
49
+ random_u0=True, # or False
50
+ num_steps_range=range(1, 6),
51
+ backend="simulator", # or None to detect a qiskit backend
52
+ estimate_energy=True, # wether to estimate energy or just prepare the state
53
+ shots=1024
54
+ )
55
+ ```
56
+
57
+ **Note:** if you provide None, or a qiskit backend name or object, it will run on IBM quantum machines. You have to set `IBM_QUANTUM_TOKEN` environment variable for it
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/db_qite/__init__.py
4
+ src/db_qite/db_qite.py
5
+ src/db_qite.egg-info/PKG-INFO
6
+ src/db_qite.egg-info/SOURCES.txt
7
+ src/db_qite.egg-info/dependency_links.txt
8
+ src/db_qite.egg-info/requires.txt
9
+ src/db_qite.egg-info/top_level.txt
@@ -0,0 +1,5 @@
1
+ qiskit>=2.4.1
2
+ qiskit_aer>=0.17.2
3
+ qiskit-ibm-runtime>=0.46.1
4
+ pylatexenc>=2.10
5
+ matplotlib>=3.10.9
@@ -0,0 +1 @@
1
+ db_qite