qoro-divi 0.2.0b1__py3-none-any.whl → 0.6.0__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.
Files changed (92) hide show
  1. divi/__init__.py +1 -2
  2. divi/backends/__init__.py +10 -0
  3. divi/backends/_backend_properties_conversion.py +227 -0
  4. divi/backends/_circuit_runner.py +70 -0
  5. divi/backends/_execution_result.py +70 -0
  6. divi/backends/_parallel_simulator.py +486 -0
  7. divi/backends/_qoro_service.py +663 -0
  8. divi/backends/_qpu_system.py +101 -0
  9. divi/backends/_results_processing.py +133 -0
  10. divi/circuits/__init__.py +13 -0
  11. divi/{exp/cirq → circuits/_cirq}/__init__.py +1 -2
  12. divi/circuits/_cirq/_parser.py +110 -0
  13. divi/circuits/_cirq/_qasm_export.py +78 -0
  14. divi/circuits/_core.py +391 -0
  15. divi/{qasm.py → circuits/_qasm_conversion.py} +73 -14
  16. divi/circuits/_qasm_validation.py +694 -0
  17. divi/qprog/__init__.py +27 -8
  18. divi/qprog/_expectation.py +181 -0
  19. divi/qprog/_hamiltonians.py +281 -0
  20. divi/qprog/algorithms/__init__.py +16 -0
  21. divi/qprog/algorithms/_ansatze.py +368 -0
  22. divi/qprog/algorithms/_custom_vqa.py +263 -0
  23. divi/qprog/algorithms/_pce.py +262 -0
  24. divi/qprog/algorithms/_qaoa.py +579 -0
  25. divi/qprog/algorithms/_vqe.py +262 -0
  26. divi/qprog/batch.py +387 -74
  27. divi/qprog/checkpointing.py +556 -0
  28. divi/qprog/exceptions.py +9 -0
  29. divi/qprog/optimizers.py +1014 -43
  30. divi/qprog/quantum_program.py +243 -412
  31. divi/qprog/typing.py +62 -0
  32. divi/qprog/variational_quantum_algorithm.py +1208 -0
  33. divi/qprog/workflows/__init__.py +10 -0
  34. divi/qprog/{_graph_partitioning.py → workflows/_graph_partitioning.py} +139 -95
  35. divi/qprog/workflows/_qubo_partitioning.py +221 -0
  36. divi/qprog/workflows/_vqe_sweep.py +560 -0
  37. divi/reporting/__init__.py +7 -0
  38. divi/reporting/_pbar.py +127 -0
  39. divi/reporting/_qlogger.py +68 -0
  40. divi/reporting/_reporter.py +155 -0
  41. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.6.0.dist-info}/METADATA +43 -15
  42. qoro_divi-0.6.0.dist-info/RECORD +47 -0
  43. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.6.0.dist-info}/WHEEL +1 -1
  44. qoro_divi-0.6.0.dist-info/licenses/LICENSES/.license-header +3 -0
  45. divi/_pbar.py +0 -73
  46. divi/circuits.py +0 -139
  47. divi/exp/cirq/_lexer.py +0 -126
  48. divi/exp/cirq/_parser.py +0 -889
  49. divi/exp/cirq/_qasm_export.py +0 -37
  50. divi/exp/cirq/_qasm_import.py +0 -35
  51. divi/exp/cirq/exception.py +0 -21
  52. divi/exp/scipy/_cobyla.py +0 -342
  53. divi/exp/scipy/pyprima/LICENCE.txt +0 -28
  54. divi/exp/scipy/pyprima/__init__.py +0 -263
  55. divi/exp/scipy/pyprima/cobyla/__init__.py +0 -0
  56. divi/exp/scipy/pyprima/cobyla/cobyla.py +0 -599
  57. divi/exp/scipy/pyprima/cobyla/cobylb.py +0 -849
  58. divi/exp/scipy/pyprima/cobyla/geometry.py +0 -240
  59. divi/exp/scipy/pyprima/cobyla/initialize.py +0 -269
  60. divi/exp/scipy/pyprima/cobyla/trustregion.py +0 -540
  61. divi/exp/scipy/pyprima/cobyla/update.py +0 -331
  62. divi/exp/scipy/pyprima/common/__init__.py +0 -0
  63. divi/exp/scipy/pyprima/common/_bounds.py +0 -41
  64. divi/exp/scipy/pyprima/common/_linear_constraints.py +0 -46
  65. divi/exp/scipy/pyprima/common/_nonlinear_constraints.py +0 -64
  66. divi/exp/scipy/pyprima/common/_project.py +0 -224
  67. divi/exp/scipy/pyprima/common/checkbreak.py +0 -107
  68. divi/exp/scipy/pyprima/common/consts.py +0 -48
  69. divi/exp/scipy/pyprima/common/evaluate.py +0 -101
  70. divi/exp/scipy/pyprima/common/history.py +0 -39
  71. divi/exp/scipy/pyprima/common/infos.py +0 -30
  72. divi/exp/scipy/pyprima/common/linalg.py +0 -452
  73. divi/exp/scipy/pyprima/common/message.py +0 -336
  74. divi/exp/scipy/pyprima/common/powalg.py +0 -131
  75. divi/exp/scipy/pyprima/common/preproc.py +0 -393
  76. divi/exp/scipy/pyprima/common/present.py +0 -5
  77. divi/exp/scipy/pyprima/common/ratio.py +0 -56
  78. divi/exp/scipy/pyprima/common/redrho.py +0 -49
  79. divi/exp/scipy/pyprima/common/selectx.py +0 -346
  80. divi/interfaces.py +0 -25
  81. divi/parallel_simulator.py +0 -258
  82. divi/qlogger.py +0 -119
  83. divi/qoro_service.py +0 -343
  84. divi/qprog/_mlae.py +0 -182
  85. divi/qprog/_qaoa.py +0 -440
  86. divi/qprog/_vqe.py +0 -275
  87. divi/qprog/_vqe_sweep.py +0 -144
  88. divi/utils.py +0 -116
  89. qoro_divi-0.2.0b1.dist-info/RECORD +0 -58
  90. /divi/{qem.py → circuits/qem.py} +0 -0
  91. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.6.0.dist-info/licenses}/LICENSE +0 -0
  92. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.6.0.dist-info/licenses}/LICENSES/Apache-2.0.txt +0 -0
divi/qlogger.py DELETED
@@ -1,119 +0,0 @@
1
- # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- import logging
6
- import shutil
7
- import sys
8
-
9
-
10
- def _is_jupyter():
11
- """
12
- Checks if the code is running inside a Jupyter Notebook or IPython environment.
13
- """
14
- try:
15
- from IPython import get_ipython
16
-
17
- # Check if get_ipython() returns a shell instance (not None)
18
- # and if the shell class is 'ZMQInteractiveShell' for Jupyter notebooks/qtconsole
19
- # or 'TerminalInteractiveShell' for IPython console.
20
- shell = get_ipython().__class__.__name__
21
- if shell == "ZMQInteractiveShell":
22
- return True # Jupyter notebook or qtconsole
23
- elif shell == "TerminalInteractiveShell":
24
- return False # IPython terminal
25
- else:
26
- return False # Other IPython environment (less common for typical Jupyter detection)
27
- except NameError:
28
- return False # Not running in IPython
29
- except ImportError:
30
- return False # IPython is not installed
31
-
32
-
33
- class OverwriteStreamHandler(logging.StreamHandler):
34
- def __init__(self, stream=None):
35
- super().__init__(stream)
36
-
37
- self._last_record = ""
38
- self._last_message = ""
39
-
40
- # Worst case: 2 complex emojis (8 chars each) + buffer = 21 extra chars
41
- self._emoji_buffer = 21
42
-
43
- self._is_jupyter = _is_jupyter()
44
-
45
- def emit(self, record):
46
- msg = self.format(record)
47
-
48
- if record.message.startswith(r"\c"):
49
- sep = r"\c"
50
- msg = f"{msg.split(sep)[0]}{self._last_record} [{record.message[2:-2]}]\r"
51
-
52
- if msg.endswith("\r\n"):
53
- overwrite_and_newline = True
54
- clean_msg = msg[:-2]
55
-
56
- if not record.message.startswith("\c"):
57
- self._last_record = record.message[:-2]
58
- elif msg.endswith("\r"):
59
- overwrite_and_newline = False
60
- clean_msg = msg[:-1]
61
-
62
- if not record.message.startswith(r"\c"):
63
- self._last_record = record.message[:-1]
64
- else:
65
- # Normal message - no overwriting
66
- self.stream.write(msg + "\n")
67
- self.stream.flush()
68
- return
69
-
70
- # Clear previous line if needed
71
- if len(self._last_message) > 0:
72
- if self._is_jupyter:
73
- clear_length = len(self._last_message) + self._emoji_buffer + 50
74
- else:
75
- clear_length = min(
76
- len(self._last_message) + self._emoji_buffer,
77
- shutil.get_terminal_size().columns,
78
- )
79
-
80
- self.stream.write("\r" + " " * clear_length + "\r")
81
- self.stream.flush()
82
-
83
- # Write message with appropriate ending
84
- if overwrite_and_newline:
85
- self.stream.write(clean_msg + "\n")
86
- self._last_message = ""
87
- else:
88
- self.stream.write(clean_msg + "\r")
89
- self._last_message = self._strip_ansi(clean_msg)
90
-
91
- self.stream.flush()
92
-
93
- def _strip_ansi(self, text):
94
- """Remove ANSI escape sequences for accurate length calculation"""
95
- import re
96
-
97
- ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
98
- return ansi_escape.sub("", text)
99
-
100
-
101
- def enable_logging(level=logging.INFO):
102
- root_logger = logging.getLogger(__name__.split(".")[0])
103
-
104
- formatter = logging.Formatter(
105
- "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
106
- )
107
-
108
- handler = OverwriteStreamHandler(sys.stdout)
109
- handler.setFormatter(formatter)
110
-
111
- root_logger.setLevel(level)
112
- root_logger.handlers.clear()
113
- root_logger.addHandler(handler)
114
-
115
-
116
- def disable_logging():
117
- root_logger = logging.getLogger(__name__.split(".")[0])
118
- root_logger.handlers.clear()
119
- root_logger.setLevel(logging.CRITICAL + 1)
divi/qoro_service.py DELETED
@@ -1,343 +0,0 @@
1
- # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- import base64
6
- import gzip
7
- import json
8
- import logging
9
- import time
10
- from collections.abc import Callable
11
- from enum import Enum
12
- from http import HTTPStatus
13
- from typing import Optional
14
-
15
- import requests
16
- from requests.adapters import HTTPAdapter, Retry
17
-
18
- from divi.interfaces import CircuitRunner
19
-
20
- API_URL = "https://app.qoroquantum.net/api"
21
- MAX_PAYLOAD_SIZE_MB = 0.95
22
-
23
- session = requests.Session()
24
- retries = Retry(
25
- total=5,
26
- backoff_factor=0.1,
27
- status_forcelist=[502],
28
- allowed_methods=["GET", "POST", "DELETE"],
29
- )
30
-
31
- session.mount("http://", HTTPAdapter(max_retries=retries))
32
- session.mount("https://", HTTPAdapter(max_retries=retries))
33
-
34
- logger = logging.getLogger(__name__)
35
-
36
-
37
- class JobStatus(Enum):
38
- PENDING = "PENDING"
39
- RUNNING = "RUNNING"
40
- COMPLETED = "COMPLETED"
41
- FAILED = "FAILED"
42
- CANCELLED = "CANCELLED"
43
-
44
-
45
- class JobType(Enum):
46
- EXECUTE = "EXECUTE"
47
- SIMULATE = "SIMULATE"
48
- ESTIMATE = "ESTIMATE"
49
- CIRCUIT_CUT = "CIRCUIT_CUT"
50
-
51
-
52
- class MaxRetriesReachedError(Exception):
53
- """Exception raised when the maximum number of retries is reached."""
54
-
55
- def __init__(self, retries, message="Maximum retries reached"):
56
- self.retries = retries
57
- self.message = f"{message}: {retries} retries attempted"
58
- super().__init__(self.message)
59
-
60
-
61
- class QoroService(CircuitRunner):
62
-
63
- def __init__(
64
- self,
65
- auth_token: str,
66
- polling_interval: float = 3.0,
67
- max_retries: int = 5000,
68
- shots: int = 1000,
69
- use_circuit_packing: Optional[bool] = False,
70
- ):
71
- super().__init__(shots=shots)
72
-
73
- self.auth_token = "Bearer " + auth_token
74
- self.polling_interval = polling_interval
75
- self.max_retries = max_retries
76
- self.use_circuit_packing = use_circuit_packing
77
-
78
- def test_connection(self):
79
- """Test the connection to the Qoro API"""
80
- response = session.get(
81
- API_URL, headers={"Authorization": self.auth_token}, timeout=10
82
- )
83
-
84
- if response.status_code != HTTPStatus.OK:
85
- raise requests.exceptions.HTTPError(
86
- f"Connection failed with error: {response.status_code}: {response.reason}"
87
- )
88
-
89
- return response
90
-
91
- def submit_circuits(
92
- self,
93
- circuits: dict[str, str],
94
- tag: str = "default",
95
- job_type: JobType = JobType.SIMULATE,
96
- override_circuit_packing: bool | None = None,
97
- ):
98
- """
99
- Submit quantum circuits to the Qoro API for execution.
100
-
101
- Args:
102
- circuits (dict[str, str]):
103
- Dictionary mapping unique circuit IDs to QASM circuit strings.
104
- tag (str, optional):
105
- Tag to associate with the job for identification. Defaults to "default".
106
- job_type (JobType, optional):
107
- Type of job to execute (e.g., SIMULATE, EXECUTE, ESTIMATE, CIRCUIT_CUT). Defaults to JobType.SIMULATE.
108
- use_packing (bool):
109
- Whether to use circuit packing optimization. Defaults to False.
110
-
111
- Raises:
112
- ValueError: If more than one circuit is submitted for a CIRCUIT_CUT job.
113
-
114
- Returns:
115
- str or list[str]:
116
- The job ID(s) of the created job(s). Returns a single job ID if only one job is created,
117
- otherwise returns a list of job IDs if the circuits are split into multiple jobs due to payload size.
118
- """
119
-
120
- if job_type == JobType.CIRCUIT_CUT and len(circuits) > 1:
121
- raise ValueError("Only one circuit allowed for circuit-cutting jobs.")
122
-
123
- def _compress_data(value) -> bytes:
124
- return base64.b64encode(gzip.compress(value.encode("utf-8"))).decode(
125
- "utf-8"
126
- )
127
-
128
- def _split_circuits(circuits: dict[str, str]) -> list[dict[str, str]]:
129
- """
130
- Split circuits into smaller chunks if the payload size exceeds the maximum allowed size.
131
-
132
- Args:
133
- circuits: Dictionary of circuits to be sent
134
-
135
- Returns:
136
- List of circuit chunks
137
- """
138
-
139
- def _estimate_size(data):
140
- payload_json = json.dumps(data)
141
- return len(payload_json.encode("utf-8")) / 1024 / 1024
142
-
143
- circuit_chunks = []
144
- current_chunk = {}
145
- current_size = 0
146
-
147
- for key, value in circuits.items():
148
- compressed_value = _compress_data(value)
149
- estimated_size = _estimate_size({key: compressed_value})
150
-
151
- if current_size + estimated_size > MAX_PAYLOAD_SIZE_MB:
152
- circuit_chunks.append(current_chunk)
153
- current_chunk = {key: compressed_value}
154
- current_size = estimated_size
155
- else:
156
- current_chunk[key] = compressed_value
157
- current_size += estimated_size
158
-
159
- if current_chunk:
160
- circuit_chunks.append(current_chunk)
161
-
162
- return circuit_chunks
163
-
164
- circuit_chunks = _split_circuits(circuits)
165
-
166
- job_ids = []
167
- for chunk in circuit_chunks:
168
- response = session.post(
169
- API_URL + "/job/",
170
- headers={
171
- "Authorization": self.auth_token,
172
- "Content-Type": "application/json",
173
- },
174
- json={
175
- "circuits": chunk,
176
- "shots": self.shots,
177
- "tag": tag,
178
- "job_type": job_type.value,
179
- "use_packing": (
180
- override_circuit_packing
181
- if override_circuit_packing is not None
182
- else self.use_circuit_packing
183
- ),
184
- },
185
- timeout=100,
186
- )
187
-
188
- if response.status_code == HTTPStatus.CREATED:
189
- job_ids.append(response.json()["job_id"])
190
- else:
191
- raise requests.exceptions.HTTPError(
192
- f"{response.status_code}: {response.reason}"
193
- )
194
-
195
- return job_ids if len(job_ids) > 1 else job_ids[0]
196
-
197
- def delete_job(self, job_ids):
198
- """
199
- Delete a job from the Qoro Database.
200
-
201
- Args:
202
- job_id: The ID of the jobs to be deleted
203
- Returns:
204
- response: The response from the API
205
- """
206
- if not isinstance(job_ids, list):
207
- job_ids = [job_ids]
208
-
209
- responses = []
210
-
211
- for job_id in job_ids:
212
- response = session.delete(
213
- API_URL + f"/job/{job_id}",
214
- headers={"Authorization": self.auth_token},
215
- timeout=50,
216
- )
217
-
218
- responses.append(response)
219
-
220
- return responses if len(responses) > 1 else responses[0]
221
-
222
- def get_job_results(self, job_ids):
223
- """
224
- Get the results of a job from the Qoro Database.
225
-
226
- Args:
227
- job_id: The ID of the job to get results from
228
- Returns:
229
- results: The results of the job
230
- """
231
- if not isinstance(job_ids, list):
232
- job_ids = [job_ids]
233
-
234
- responses = []
235
- for job_id in job_ids:
236
- response = session.get(
237
- API_URL + f"/job/{job_id}/results",
238
- headers={"Authorization": self.auth_token},
239
- timeout=100,
240
- )
241
- responses.append(response)
242
-
243
- if all(response.status_code == HTTPStatus.OK for response in responses):
244
- responses = [response.json() for response in responses]
245
- return sum(responses, [])
246
- elif any(
247
- response.status_code == HTTPStatus.BAD_REQUEST for response in responses
248
- ):
249
- raise requests.exceptions.HTTPError(
250
- "400 Bad Request: Job results not available, likely job is still running"
251
- )
252
- else:
253
- for response in responses:
254
- if response.status_code not in [HTTPStatus.OK, HTTPStatus.BAD_REQUEST]:
255
- raise requests.exceptions.HTTPError(
256
- f"{response.status_code}: {response.reason}"
257
- )
258
-
259
- def poll_job_status(
260
- self,
261
- job_ids: str | list[str],
262
- loop_until_complete: bool = False,
263
- on_complete: Optional[Callable] = None,
264
- verbose: bool = True,
265
- pbar_update_fn: Optional[Callable] = None,
266
- ):
267
- """
268
- Get the status of a job and optionally execute function *on_complete* on the results
269
- if the status is COMPLETE.
270
-
271
- Args:
272
- job_ids: The job id of the jobs to check
273
- loop_until_complete (bool): A flag to loop until the job is completed
274
- on_complete (optional): A function to be called when the job is completed
275
- polling_interval (optional): The time to wait between retries
276
- max_retries (optional): The maximum number of retries
277
- verbose (optional): A flag to print the when retrying
278
- pbar_update_fn (optional): A function for updating progress bars while polling.
279
- Returns:
280
- status: The status of the job
281
- """
282
- if not isinstance(job_ids, list):
283
- job_ids = [job_ids]
284
-
285
- def _poll_job_status(job_id):
286
- response = session.get(
287
- API_URL + f"/job/{job_id}/status/",
288
- headers={
289
- "Authorization": self.auth_token,
290
- "Content-Type": "application/json",
291
- },
292
- timeout=200,
293
- )
294
-
295
- if response.status_code == HTTPStatus.OK:
296
- return response.json()["status"], response
297
- else:
298
- raise requests.exceptions.HTTPError(
299
- f"{response.status_code}: {response.reason}"
300
- )
301
-
302
- if loop_until_complete:
303
- retries = 0
304
- completed = False
305
- while True:
306
- responses = []
307
- statuses = []
308
-
309
- for job_id in job_ids:
310
- job_status, response = _poll_job_status(job_id)
311
- statuses.append(job_status)
312
- responses.append(response)
313
-
314
- if all(status == JobStatus.COMPLETED.value for status in statuses):
315
- responses = [response.json() for response in responses]
316
- completed = True
317
- break
318
-
319
- if retries >= self.max_retries:
320
- break
321
-
322
- retries += 1
323
-
324
- time.sleep(self.polling_interval)
325
-
326
- if verbose:
327
- if pbar_update_fn:
328
- pbar_update_fn(retries)
329
- else:
330
- logger.info(
331
- rf"\cPolling {retries} / {self.max_retries} retries\r"
332
- )
333
-
334
- if completed and on_complete:
335
- on_complete(responses)
336
- return JobStatus.COMPLETED
337
- elif completed:
338
- return JobStatus.COMPLETED
339
- else:
340
- raise MaxRetriesReachedError(retries)
341
- else:
342
- statuses = [_poll_job_status(job_id)[0] for job_id in job_ids]
343
- return statuses if len(statuses) > 1 else statuses[0]
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]