qoro-divi 0.2.2b1__py3-none-any.whl → 0.3.0b1__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 qoro-divi might be problematic. Click here for more details.
- divi/_pbar.py +1 -3
- divi/circuits.py +3 -3
- divi/exp/cirq/__init__.py +1 -0
- divi/exp/cirq/_validator.py +645 -0
- divi/parallel_simulator.py +9 -9
- divi/qasm.py +2 -3
- divi/qoro_service.py +210 -141
- divi/qprog/__init__.py +2 -2
- divi/qprog/_graph_partitioning.py +103 -66
- divi/qprog/_qaoa.py +33 -8
- divi/qprog/_qubo_partitioning.py +199 -0
- divi/qprog/_vqe.py +48 -39
- divi/qprog/_vqe_sweep.py +413 -46
- divi/qprog/batch.py +61 -14
- divi/qprog/quantum_program.py +10 -11
- divi/qpu_system.py +20 -0
- qoro_divi-0.3.0b1.dist-info/LICENSES/.license-header +3 -0
- {qoro_divi-0.2.2b1.dist-info → qoro_divi-0.3.0b1.dist-info}/METADATA +5 -2
- {qoro_divi-0.2.2b1.dist-info → qoro_divi-0.3.0b1.dist-info}/RECORD +22 -19
- divi/qprog/_mlae.py +0 -182
- {qoro_divi-0.2.2b1.dist-info → qoro_divi-0.3.0b1.dist-info}/LICENSE +0 -0
- {qoro_divi-0.2.2b1.dist-info → qoro_divi-0.3.0b1.dist-info}/LICENSES/Apache-2.0.txt +0 -0
- {qoro_divi-0.2.2b1.dist-info → qoro_divi-0.3.0b1.dist-info}/WHEEL +0 -0
divi/qprog/batch.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
+
import atexit
|
|
5
6
|
import traceback
|
|
6
7
|
from abc import ABC, abstractmethod
|
|
7
8
|
from concurrent.futures import ProcessPoolExecutor, as_completed
|
|
@@ -51,6 +52,10 @@ def queue_listener(
|
|
|
51
52
|
)
|
|
52
53
|
|
|
53
54
|
|
|
55
|
+
def _default_task_function(program: QuantumProgram):
|
|
56
|
+
return program.run()
|
|
57
|
+
|
|
58
|
+
|
|
54
59
|
class ProgramBatch(ABC):
|
|
55
60
|
"""This abstract class provides the basic scaffolding for higher-order
|
|
56
61
|
computations that require more than one quantum program to achieve its goal.
|
|
@@ -74,6 +79,7 @@ class ProgramBatch(ABC):
|
|
|
74
79
|
|
|
75
80
|
self.backend = backend
|
|
76
81
|
self._executor = None
|
|
82
|
+
self._task_fn = _default_task_function
|
|
77
83
|
self.programs = {}
|
|
78
84
|
|
|
79
85
|
self._total_circuit_count = 0
|
|
@@ -95,6 +101,12 @@ class ProgramBatch(ABC):
|
|
|
95
101
|
|
|
96
102
|
@abstractmethod
|
|
97
103
|
def create_programs(self):
|
|
104
|
+
if len(self.programs) > 0:
|
|
105
|
+
raise RuntimeError(
|
|
106
|
+
"Some programs already exist. "
|
|
107
|
+
"Clear the program dictionary before creating new ones by using batch.reset()."
|
|
108
|
+
)
|
|
109
|
+
|
|
98
110
|
self._manager = Manager()
|
|
99
111
|
self._queue = self._manager.Queue()
|
|
100
112
|
|
|
@@ -118,27 +130,36 @@ class ProgramBatch(ABC):
|
|
|
118
130
|
if getattr(self, "_listener_thread", None) is not None:
|
|
119
131
|
self._listener_thread.join(timeout=1)
|
|
120
132
|
if self._listener_thread.is_alive():
|
|
121
|
-
warn(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
self.
|
|
127
|
-
self.
|
|
133
|
+
warn("Listener thread did not terminate within timeout.")
|
|
134
|
+
self._listener_thread = None
|
|
135
|
+
|
|
136
|
+
# Shut down the manager process, which handles the queue cleanup.
|
|
137
|
+
if hasattr(self, "_manager") and self._manager is not None:
|
|
138
|
+
self._manager.shutdown()
|
|
139
|
+
self._manager = None
|
|
128
140
|
self._queue = None
|
|
129
141
|
|
|
130
142
|
# Stop the progress bar if it's still active
|
|
131
143
|
if getattr(self, "_progress_bar", None) is not None:
|
|
132
144
|
try:
|
|
133
145
|
self._progress_bar.stop()
|
|
134
|
-
self._progress_bar = None
|
|
135
146
|
except Exception:
|
|
136
147
|
pass # Already stopped or not running
|
|
137
|
-
|
|
148
|
+
self._progress_bar = None
|
|
138
149
|
self._pb_task_map.clear()
|
|
139
150
|
|
|
151
|
+
def _atexit_cleanup_hook(self):
|
|
152
|
+
# This hook is only registered for non-blocking runs.
|
|
153
|
+
if self._executor is not None:
|
|
154
|
+
warn(
|
|
155
|
+
"A non-blocking ProgramBatch run was not explicitly closed with "
|
|
156
|
+
"'join()'. The batch was cleaned up automatically on exit.",
|
|
157
|
+
UserWarning,
|
|
158
|
+
)
|
|
159
|
+
self.reset()
|
|
160
|
+
|
|
140
161
|
def add_program_to_executor(self, program):
|
|
141
|
-
self.futures.append(self._executor.submit(program
|
|
162
|
+
self.futures.append(self._executor.submit(self._task_fn, program))
|
|
142
163
|
|
|
143
164
|
if self._progress_bar is not None:
|
|
144
165
|
with self._pb_lock:
|
|
@@ -153,7 +174,7 @@ class ProgramBatch(ABC):
|
|
|
153
174
|
mode=("simulation" if self._is_local else "network"),
|
|
154
175
|
)
|
|
155
176
|
|
|
156
|
-
def run(self):
|
|
177
|
+
def run(self, blocking: bool = False):
|
|
157
178
|
if self._executor is not None:
|
|
158
179
|
raise RuntimeError("A batch is already being run.")
|
|
159
180
|
|
|
@@ -193,10 +214,19 @@ class ProgramBatch(ABC):
|
|
|
193
214
|
for program in self.programs.values():
|
|
194
215
|
self.add_program_to_executor(program)
|
|
195
216
|
|
|
217
|
+
if not blocking:
|
|
218
|
+
# Arm safety net
|
|
219
|
+
atexit.register(self._atexit_cleanup_hook)
|
|
220
|
+
|
|
221
|
+
if blocking:
|
|
222
|
+
self.join()
|
|
223
|
+
|
|
224
|
+
return self
|
|
225
|
+
|
|
196
226
|
def check_all_done(self):
|
|
197
227
|
return all(future.done() for future in self.futures)
|
|
198
228
|
|
|
199
|
-
def
|
|
229
|
+
def join(self):
|
|
200
230
|
if self._executor is None:
|
|
201
231
|
return
|
|
202
232
|
|
|
@@ -228,8 +258,25 @@ class ProgramBatch(ABC):
|
|
|
228
258
|
|
|
229
259
|
self._total_circuit_count += sum(future.result()[0] for future in self.futures)
|
|
230
260
|
self._total_run_time += sum(future.result()[1] for future in self.futures)
|
|
231
|
-
self.futures
|
|
261
|
+
self.futures.clear()
|
|
262
|
+
|
|
263
|
+
# After successful cleanup, try to unregister the hook.
|
|
264
|
+
# This will only succeed if it was a non-blocking run.
|
|
265
|
+
try:
|
|
266
|
+
atexit.unregister(self._atexit_cleanup_hook)
|
|
267
|
+
except TypeError:
|
|
268
|
+
# This is expected for blocking runs where the hook was never registered.
|
|
269
|
+
pass
|
|
232
270
|
|
|
233
271
|
@abstractmethod
|
|
234
272
|
def aggregate_results(self):
|
|
235
|
-
|
|
273
|
+
if len(self.programs) == 0:
|
|
274
|
+
raise RuntimeError("No programs to aggregate. Run create_programs() first.")
|
|
275
|
+
|
|
276
|
+
if self._executor is not None:
|
|
277
|
+
self.join()
|
|
278
|
+
|
|
279
|
+
if any(len(program.losses) == 0 for program in self.programs.values()):
|
|
280
|
+
raise RuntimeError(
|
|
281
|
+
"Some/All programs have empty losses. Did you call run()?"
|
|
282
|
+
)
|
divi/qprog/quantum_program.py
CHANGED
|
@@ -7,10 +7,9 @@ import pickle
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from functools import partial
|
|
9
9
|
from queue import Queue
|
|
10
|
-
from typing import Optional
|
|
11
10
|
|
|
12
11
|
import numpy as np
|
|
13
|
-
from
|
|
12
|
+
from pennylane.measurements import ExpectationMP
|
|
14
13
|
from scipy.optimize import OptimizeResult, minimize
|
|
15
14
|
|
|
16
15
|
from divi import QoroService
|
|
@@ -28,8 +27,8 @@ class QuantumProgram(ABC):
|
|
|
28
27
|
def __init__(
|
|
29
28
|
self,
|
|
30
29
|
backend: CircuitRunner,
|
|
31
|
-
seed:
|
|
32
|
-
progress_queue:
|
|
30
|
+
seed: int | None = None,
|
|
31
|
+
progress_queue: Queue | None = None,
|
|
33
32
|
**kwargs,
|
|
34
33
|
):
|
|
35
34
|
"""
|
|
@@ -52,14 +51,14 @@ class QuantumProgram(ABC):
|
|
|
52
51
|
progress_queue (Queue): a queue for progress bar updates.
|
|
53
52
|
|
|
54
53
|
**kwargs: Additional keyword arguments that influence behaviour.
|
|
55
|
-
- grouping_strategy (
|
|
54
|
+
- grouping_strategy (Literal["default", "wires", "qwc"]): A strategy for grouping operations, used in Pennylane's transforms.
|
|
56
55
|
Defaults to None.
|
|
57
|
-
- qem_protocol (
|
|
56
|
+
- qem_protocol (QEMProtocol, optional): the quantum error mitigation protocol to apply.
|
|
58
57
|
Must be of type QEMProtocol. Defaults to None.
|
|
59
58
|
|
|
60
59
|
The following key values are reserved for internal use and should not be set by the user:
|
|
61
|
-
- losses (
|
|
62
|
-
- final_params (
|
|
60
|
+
- losses (list, optional): A list to initialize the `losses` attribute. Defaults to an empty list.
|
|
61
|
+
- final_params (list, optional): A list to initialize the `final_params` attribute. Defaults to an empty list.
|
|
63
62
|
|
|
64
63
|
"""
|
|
65
64
|
|
|
@@ -264,9 +263,9 @@ class QuantumProgram(ABC):
|
|
|
264
263
|
curr_marginal_results = []
|
|
265
264
|
for observable in curr_measurement_group:
|
|
266
265
|
intermediate_exp_values = [
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
266
|
+
ExpectationMP(observable).process_counts(
|
|
267
|
+
shots_dict,
|
|
268
|
+
tuple(reversed(range(len(next(iter(shots_dict.keys())))))),
|
|
270
269
|
)
|
|
271
270
|
for shots_dict in group_results.values()
|
|
272
271
|
]
|
divi/qpu_system.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, repr=True)
|
|
9
|
+
class QPU:
|
|
10
|
+
nickname: str
|
|
11
|
+
q_bits: int
|
|
12
|
+
status: str
|
|
13
|
+
system_kind: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, repr=True)
|
|
17
|
+
class QPUSystem:
|
|
18
|
+
name: str
|
|
19
|
+
qpus: list[QPU]
|
|
20
|
+
access_level: str
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: qoro-divi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0b1
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Ahmed Darwish
|
|
6
6
|
Author-email: ahmed@qoroquantum.de
|
|
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
11
11
|
Requires-Dist: cirq-core (<1.5.0)
|
|
12
12
|
Requires-Dist: dill (>=0.4.0,<0.5.0)
|
|
13
|
+
Requires-Dist: dwave-hybrid (>=0.6.14,<0.7.0)
|
|
13
14
|
Requires-Dist: matplotlib (>=3.10.3,<4.0.0)
|
|
14
15
|
Requires-Dist: mitiq (>=0.46,<0.47)
|
|
15
16
|
Requires-Dist: networkx (>=3.5,<4.0)
|
|
@@ -19,7 +20,6 @@ Requires-Dist: pymetis (>=2025.1.1,<2026.0.0)
|
|
|
19
20
|
Requires-Dist: python-dotenv (>=1.1.1,<2.0.0)
|
|
20
21
|
Requires-Dist: qiskit (<2.0)
|
|
21
22
|
Requires-Dist: qiskit-aer (>=0.17.1,<0.18.0)
|
|
22
|
-
Requires-Dist: qiskit-algorithms (>=0.3.1,<0.4.0)
|
|
23
23
|
Requires-Dist: qiskit-ibm-runtime (>=0.37,<0.38)
|
|
24
24
|
Requires-Dist: qiskit-optimization (>=0.6.1,<0.7.0)
|
|
25
25
|
Requires-Dist: requests (>=2.32.4,<3.0.0)
|
|
@@ -37,6 +37,9 @@ Divi is designed to allow researchers, students, and enterprises to deploy quant
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
+
> [!IMPORTANT]
|
|
41
|
+
> Divi is under active development and in early stages. Users should expect frequent changes that are likely to be incompatible with previously published versions.
|
|
42
|
+
|
|
40
43
|
## 🚀 Features
|
|
41
44
|
|
|
42
45
|
- 🧠 **Smart Job Parallelization**: Automatically parallelizes your quantum programs based on task structure.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
divi/__init__.py,sha256=WNAlzdjio0M5la_NTGk4-0AFqItI6HEBExRs6ck9j0Y,203
|
|
2
|
-
divi/_pbar.py,sha256=
|
|
3
|
-
divi/circuits.py,sha256=
|
|
4
|
-
divi/exp/cirq/__init__.py,sha256=
|
|
2
|
+
divi/_pbar.py,sha256=M6FQByH63bWwzsKtedW49Hm7hFsnb1X5AlFfCZFSvuU,2019
|
|
3
|
+
divi/circuits.py,sha256=OKcwTMXIvCIjvpLvdeQBWGYElcVUNNyA3z8yXnxUXeo,4476
|
|
4
|
+
divi/exp/cirq/__init__.py,sha256=4wd5rEiNgsPVSK8uLYdJMf54B1ITdtKfMJtdnRpfwTE,350
|
|
5
5
|
divi/exp/cirq/_lexer.py,sha256=x5UArrnN_JEyiq7E02ikq0wdmqZ2vEQ3_FADL1LIhQI,3187
|
|
6
6
|
divi/exp/cirq/_parser.py,sha256=z_bSn4m03-sfRlN8eL99Mo4LnjY-zmR-Xt6UrjzstZc,29279
|
|
7
7
|
divi/exp/cirq/_qasm_export.py,sha256=8C5xLYvIIkQTWWAAYo7ZjwtQjvYXNSflbf5UyUx6YUE,1024
|
|
8
8
|
divi/exp/cirq/_qasm_import.py,sha256=HbehrgfLl3iDdRyWr4o26Bek3ZpN-_dvNVSexl5-aVE,969
|
|
9
|
+
divi/exp/cirq/_validator.py,sha256=nYbJdT_0hCVUgZA0YiKZ8G1oDgwmcqEiy-LZH41EgUw,20481
|
|
9
10
|
divi/exp/cirq/exception.py,sha256=w1w2vSubOGMRmyKBFqXejxfeIAzkPZ6V7gSrDX_ap4A,765
|
|
10
11
|
divi/exp/scipy/_cobyla.py,sha256=cnCf5AsOM8JWIMiujuUbWMNOgmUr3ZET9y04hUyumHs,10937
|
|
11
12
|
divi/exp/scipy/pyprima/LICENCE.txt,sha256=mXN5ssG_U6OR0v0yldznW_PJTtKNZIgu3jDRtRjLDdY,1533
|
|
@@ -36,23 +37,25 @@ divi/exp/scipy/pyprima/common/ratio.py,sha256=taadehpW0c3ULUggH5frIMpvTom53dsEhv
|
|
|
36
37
|
divi/exp/scipy/pyprima/common/redrho.py,sha256=J6rJILcOoCeo942LUAXxpUvKLarUGNCGdC-zcmIlVHE,1264
|
|
37
38
|
divi/exp/scipy/pyprima/common/selectx.py,sha256=mXVS2L6AuTmbOhpp1KlXwOBR54ttnbjwagYfnXhezJY,14713
|
|
38
39
|
divi/interfaces.py,sha256=P-8lekQIGwbhHv4ewfMFtKYIjmQHu7nmscwAmxsx72U,594
|
|
39
|
-
divi/parallel_simulator.py,sha256=
|
|
40
|
-
divi/qasm.py,sha256=
|
|
40
|
+
divi/parallel_simulator.py,sha256=2JCwmootXLtDplplEqtykKc8HiYe6G0zKWemIb-IIdE,8947
|
|
41
|
+
divi/qasm.py,sha256=lfWky4E9qetotVo8BII58svutVVZEV14BntoC14bo40,7384
|
|
41
42
|
divi/qem.py,sha256=o6rMPUcxLuCBitBb8-QcxveUiKZVsP3HMamxyVFLi6M,6805
|
|
42
43
|
divi/qlogger.py,sha256=VaCnw-u8VkR7JukjOp39r0V89wO-SSpDj5l50ejnHUg,3801
|
|
43
|
-
divi/qoro_service.py,sha256=
|
|
44
|
-
divi/qprog/__init__.py,sha256=
|
|
45
|
-
divi/qprog/_graph_partitioning.py,sha256=
|
|
46
|
-
divi/qprog/
|
|
47
|
-
divi/qprog/
|
|
48
|
-
divi/qprog/_vqe.py,sha256=
|
|
49
|
-
divi/qprog/_vqe_sweep.py,sha256=
|
|
50
|
-
divi/qprog/batch.py,sha256=
|
|
44
|
+
divi/qoro_service.py,sha256=sv94S_xdY4k4YYSK3PgX4sgvgldFMkjJ3wtQO1a_2Yc,13557
|
|
45
|
+
divi/qprog/__init__.py,sha256=quz1zEdrY71TuNVXUHq9u92sORgPmmNq8y6ozMiYnTI,507
|
|
46
|
+
divi/qprog/_graph_partitioning.py,sha256=ZVDKc5--YB9EmuA7Qsyc0Rc5P-eojWpglO-XqoMmbZk,23620
|
|
47
|
+
divi/qprog/_qaoa.py,sha256=FCO3s0mA6rlrymqUjvpfY640DoLLIFP8tqi8z7UAamM,16129
|
|
48
|
+
divi/qprog/_qubo_partitioning.py,sha256=7MxeUfZ5PDkfzPprcXh_1omdvUuCK_I4qh1915XAVhk,7388
|
|
49
|
+
divi/qprog/_vqe.py,sha256=0MNSZA9wb6CKhLIT5SpM_J2ncW-p1frKBp8k0ipOYwQ,9985
|
|
50
|
+
divi/qprog/_vqe_sweep.py,sha256=lTWPG7vwtVS91Piqc5-_tlhglO8RFkMhlatCTz-8PZg,17214
|
|
51
|
+
divi/qprog/batch.py,sha256=UWiDsABFWNggvR136jBnTg6r5OUuHHh3nOCGL1keAy8,9654
|
|
51
52
|
divi/qprog/optimizers.py,sha256=l19zIyt1l0IhUbLofEXlwE9XnsILk7ANlwACi3KKN4w,2034
|
|
52
|
-
divi/qprog/quantum_program.py,sha256=
|
|
53
|
+
divi/qprog/quantum_program.py,sha256=hJ5uraX9ZNicfoJu3X0aAe8KTJUzndgX5GyoHZ1GOB8,16921
|
|
54
|
+
divi/qpu_system.py,sha256=teVeG18ukyzMFgbPSr4BLx4MJUHVK382RqZMOy2voFk,374
|
|
53
55
|
divi/utils.py,sha256=EEyGakoz33AvU0Rq7iijL8AULMg4FGcGBtmOlpSw-tY,3603
|
|
54
|
-
qoro_divi-0.
|
|
55
|
-
qoro_divi-0.
|
|
56
|
-
qoro_divi-0.
|
|
57
|
-
qoro_divi-0.
|
|
58
|
-
qoro_divi-0.
|
|
56
|
+
qoro_divi-0.3.0b1.dist-info/LICENSE,sha256=NS4JlQrgNwg1bvB3kE5shE-P4cJgnntgl-kClbOpG_Q,10760
|
|
57
|
+
qoro_divi-0.3.0b1.dist-info/LICENSES/.license-header,sha256=2jN_xtJscqP8LG-NaveY2KHUkfRCC543Y_XjOyKEfWY,105
|
|
58
|
+
qoro_divi-0.3.0b1.dist-info/LICENSES/Apache-2.0.txt,sha256=yoILHpvVuguUBpk8UwMnzJbcHUUyst9iGNNuEwUtWVc,10270
|
|
59
|
+
qoro_divi-0.3.0b1.dist-info/METADATA,sha256=P2Au2m2UuCwiwB1TM9eJl6tjTQc6RgTcGnhy6qp2drE,2278
|
|
60
|
+
qoro_divi-0.3.0b1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
61
|
+
qoro_divi-0.3.0b1.dist-info/RECORD,,
|
divi/qprog/_mlae.py
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
|
-
#
|
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
|
|
5
|
-
from functools import reduce
|
|
6
|
-
|
|
7
|
-
import numpy as np
|
|
8
|
-
import scipy.optimize as optimize
|
|
9
|
-
from qiskit import QuantumCircuit
|
|
10
|
-
from qiskit_algorithms import EstimationProblem, MaximumLikelihoodAmplitudeEstimation
|
|
11
|
-
|
|
12
|
-
from divi.circuits import Circuit
|
|
13
|
-
from divi.qprog.quantum_program import QuantumProgram
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class BernoulliA(QuantumCircuit):
|
|
17
|
-
"""A circuit representing the Bernoulli A operator."""
|
|
18
|
-
|
|
19
|
-
def __init__(self, probability):
|
|
20
|
-
super().__init__(1)
|
|
21
|
-
|
|
22
|
-
theta_p = 2 * np.arcsin(np.sqrt(probability))
|
|
23
|
-
self.ry(theta_p, 0)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class BernoulliQ(QuantumCircuit):
|
|
27
|
-
"""A circuit representing the Bernoulli Q operator."""
|
|
28
|
-
|
|
29
|
-
def __init__(self, probability):
|
|
30
|
-
super().__init__(1)
|
|
31
|
-
|
|
32
|
-
self._theta_p = 2 * np.arcsin(np.sqrt(probability))
|
|
33
|
-
self.ry(2 * self._theta_p, 0)
|
|
34
|
-
|
|
35
|
-
def power(self, k):
|
|
36
|
-
# implement the efficient power of Q
|
|
37
|
-
q_k = QuantumCircuit(1)
|
|
38
|
-
q_k.ry(2 * k * self._theta_p, 0)
|
|
39
|
-
return q_k
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class MLAE(QuantumProgram):
|
|
43
|
-
"""
|
|
44
|
-
An implementation of the Maximum Likelihood Amplitude Estimateion described in
|
|
45
|
-
https://arxiv.org/pdf/1904.10246
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
def __init__(
|
|
49
|
-
self,
|
|
50
|
-
grovers: list[int],
|
|
51
|
-
qubits_to_measure: list[int],
|
|
52
|
-
probability: float,
|
|
53
|
-
**kwargs,
|
|
54
|
-
):
|
|
55
|
-
"""
|
|
56
|
-
Initializes the MLAE problem.
|
|
57
|
-
args:
|
|
58
|
-
grovers (list): A list of non-negative integers corresponding to the powers of the Grover
|
|
59
|
-
operator for each iteration
|
|
60
|
-
qubits: An integer or list of integers containing the index of the qubits to measure
|
|
61
|
-
probability: The probability of being in the good state to estimate
|
|
62
|
-
shots: The number of shots to run for each circuit. Default set at 5000.
|
|
63
|
-
"""
|
|
64
|
-
|
|
65
|
-
super().__init__(**kwargs)
|
|
66
|
-
|
|
67
|
-
self.grovers = grovers
|
|
68
|
-
self.qubits_to_measure = qubits_to_measure
|
|
69
|
-
self.probability = probability
|
|
70
|
-
self.likelihood_functions = []
|
|
71
|
-
|
|
72
|
-
def _create_meta_circuits_dict(self):
|
|
73
|
-
return super()._create_meta_circuits_dict()
|
|
74
|
-
|
|
75
|
-
def _generate_circuits(self, params=None, **kwargs):
|
|
76
|
-
"""
|
|
77
|
-
Generates the circuits that perform step one of the MLAE algorithm,
|
|
78
|
-
the quantum amplitude amplification.
|
|
79
|
-
|
|
80
|
-
Inputs a selection of m values corresponding to the powers of the
|
|
81
|
-
Grover operatorfor each iteration.
|
|
82
|
-
|
|
83
|
-
Returns:
|
|
84
|
-
A list of QASM circuits to run on various devices
|
|
85
|
-
"""
|
|
86
|
-
self.circuits.clear()
|
|
87
|
-
|
|
88
|
-
A = BernoulliA(self.probability)
|
|
89
|
-
Q = BernoulliQ(self.probability)
|
|
90
|
-
|
|
91
|
-
problem = EstimationProblem(
|
|
92
|
-
state_preparation=A,
|
|
93
|
-
grover_operator=Q,
|
|
94
|
-
objective_qubits=self.qubits_to_measure,
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
qiskit_circuits = MaximumLikelihoodAmplitudeEstimation(
|
|
98
|
-
self.grovers
|
|
99
|
-
).construct_circuits(problem)
|
|
100
|
-
|
|
101
|
-
for circuit, grover in zip(qiskit_circuits, self.grovers):
|
|
102
|
-
circuit.measure_all()
|
|
103
|
-
self.circuits.append(Circuit(circuit, tags=[f"{grover}"]))
|
|
104
|
-
|
|
105
|
-
def run(self, store_data=False, data_file=None):
|
|
106
|
-
self._generate_circuits()
|
|
107
|
-
self._dispatch_circuits_and_process_results(
|
|
108
|
-
store_data=store_data, data_file=data_file
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
def _post_process_results(self, results):
|
|
112
|
-
"""
|
|
113
|
-
Generates the likelihood function for each circuit of the quantum
|
|
114
|
-
amplitude amplification. These likelihood functions will then
|
|
115
|
-
be combined to create a maximum likelihood function to analyze.
|
|
116
|
-
|
|
117
|
-
Returns:
|
|
118
|
-
A callable maximum likelihood function
|
|
119
|
-
"""
|
|
120
|
-
|
|
121
|
-
# Define the necessary variables Nk, Mk, Lk
|
|
122
|
-
for label, shots_dict in results.items():
|
|
123
|
-
mk = int(label)
|
|
124
|
-
Nk = 0
|
|
125
|
-
hk = 0
|
|
126
|
-
for key, shots in shots_dict.items():
|
|
127
|
-
Nk += shots
|
|
128
|
-
hk += shots if key.count("1") == len(key) else 0
|
|
129
|
-
|
|
130
|
-
def likelihood_function(theta, mk=mk, hk=hk, Nk=Nk):
|
|
131
|
-
as_theta = np.arcsin(np.sqrt(theta))
|
|
132
|
-
return ((np.sin((2 * mk + 1) * as_theta)) ** (2 * hk)) * (
|
|
133
|
-
(np.cos((2 * mk + 1) * as_theta)) ** (2 * (Nk - hk))
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
self.likelihood_functions.append(likelihood_function)
|
|
137
|
-
|
|
138
|
-
def generate_maximum_likelihood_function(self, factor=1.0):
|
|
139
|
-
"""
|
|
140
|
-
Post-processing takes in likelihood functions.
|
|
141
|
-
|
|
142
|
-
A large factor (e.g. 1e200) should be used for visualization purposes.
|
|
143
|
-
Returns:
|
|
144
|
-
The maximum likelihood function.
|
|
145
|
-
"""
|
|
146
|
-
|
|
147
|
-
def combined_likelihood_function(theta):
|
|
148
|
-
return (
|
|
149
|
-
reduce(
|
|
150
|
-
lambda result, f: result * f(theta), self.likelihood_functions, 1.0
|
|
151
|
-
)
|
|
152
|
-
* factor
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
self.maximum_likelihood_fn = combined_likelihood_function
|
|
156
|
-
|
|
157
|
-
return combined_likelihood_function
|
|
158
|
-
|
|
159
|
-
def estimate_amplitude(self, factor):
|
|
160
|
-
"""
|
|
161
|
-
Uses the maximum likelihood function to ascertain
|
|
162
|
-
a value for the amplitude.
|
|
163
|
-
|
|
164
|
-
Returns
|
|
165
|
-
Estimation of the amplitude
|
|
166
|
-
"""
|
|
167
|
-
|
|
168
|
-
def minimum_likelihood_function(theta):
|
|
169
|
-
# The factor to set to -10e30 in the older branch
|
|
170
|
-
return (
|
|
171
|
-
reduce(
|
|
172
|
-
lambda result, f: result * f(theta), self.likelihood_functions, 1.0
|
|
173
|
-
)
|
|
174
|
-
* factor
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
# create the range of possible amplitudes
|
|
178
|
-
amplitudes = np.linspace(0, 1, 100)
|
|
179
|
-
|
|
180
|
-
bounds = [(min(amplitudes), max(amplitudes))]
|
|
181
|
-
|
|
182
|
-
return optimize.differential_evolution(minimum_likelihood_function, bounds).x[0]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|