iqm-benchmarks 2.2__py3-none-any.whl → 2.4__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.
- iqm/benchmarks/optimization/qscore.py +716 -540
- iqm/benchmarks/quantum_volume/quantum_volume.py +10 -11
- iqm/benchmarks/readout_mitigation.py +5 -7
- {iqm_benchmarks-2.2.dist-info → iqm_benchmarks-2.4.dist-info}/METADATA +1 -1
- {iqm_benchmarks-2.2.dist-info → iqm_benchmarks-2.4.dist-info}/RECORD +8 -9
- iqm/benchmarks/benchmark_experiment.py +0 -163
- {iqm_benchmarks-2.2.dist-info → iqm_benchmarks-2.4.dist-info}/LICENSE +0 -0
- {iqm_benchmarks-2.2.dist-info → iqm_benchmarks-2.4.dist-info}/WHEEL +0 -0
- {iqm_benchmarks-2.2.dist-info → iqm_benchmarks-2.4.dist-info}/top_level.txt +0 -0
|
@@ -1,53 +1,571 @@
|
|
|
1
|
-
# Copyright 2024 IQM Benchmarks developers
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
1
|
"""
|
|
16
|
-
|
|
2
|
+
Qscore benchmark
|
|
17
3
|
"""
|
|
18
4
|
|
|
19
5
|
import itertools
|
|
20
6
|
import logging
|
|
21
7
|
from time import strftime
|
|
22
|
-
from typing import Callable, Dict, List, Literal, Optional, Tuple, Type
|
|
8
|
+
from typing import Callable, Dict, List, Literal, Optional, Sequence, Tuple, Type, cast
|
|
23
9
|
|
|
24
10
|
from matplotlib.figure import Figure
|
|
25
11
|
import matplotlib.pyplot as plt
|
|
26
12
|
from networkx import Graph
|
|
27
13
|
import networkx as nx
|
|
28
14
|
import numpy as np
|
|
15
|
+
from qiskit import QuantumCircuit
|
|
29
16
|
from scipy.optimize import basinhopping, minimize
|
|
30
|
-
|
|
31
|
-
|
|
17
|
+
import xarray as xr
|
|
18
|
+
|
|
19
|
+
from iqm.benchmarks.benchmark import BenchmarkConfigurationBase
|
|
20
|
+
from iqm.benchmarks.benchmark_definition import (
|
|
21
|
+
Benchmark,
|
|
22
|
+
BenchmarkAnalysisResult,
|
|
23
|
+
BenchmarkObservation,
|
|
24
|
+
BenchmarkObservationIdentifier,
|
|
25
|
+
BenchmarkRunResult,
|
|
26
|
+
add_counts_to_dataset,
|
|
27
|
+
)
|
|
28
|
+
from iqm.benchmarks.circuit_containers import BenchmarkCircuit, CircuitGroup, Circuits
|
|
32
29
|
from iqm.benchmarks.logging_config import qcvv_logger
|
|
33
|
-
from iqm.benchmarks.utils import
|
|
34
|
-
|
|
30
|
+
from iqm.benchmarks.utils import ( # execute_with_dd,
|
|
31
|
+
perform_backend_transpilation,
|
|
32
|
+
retrieve_all_counts,
|
|
33
|
+
submit_execute,
|
|
34
|
+
xrvariable_to_counts,
|
|
35
|
+
)
|
|
35
36
|
from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
def calculate_optimal_angles_for_QAOA_p1(graph: Graph) -> List[float]:
|
|
40
|
+
"""Calculates the optimal angles for single layer QAOA MaxCut ansatz.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
graph (networkx graph): the MaxCut problem graph.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
List[float]: optimal angles gamma and beta.
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def get_Zij_maxcut_p1(edge_ij, gamma, beta):
|
|
51
|
+
"""
|
|
52
|
+
Calculates <p1_QAOA | Z_i Z_j | p1_QAOA>, assuming ij is edge of G.
|
|
53
|
+
"""
|
|
54
|
+
i, j = edge_ij
|
|
55
|
+
di = graph.degree[i]
|
|
56
|
+
dj = graph.degree[j]
|
|
57
|
+
|
|
58
|
+
first = np.cos(2 * gamma) ** (di - 1) + np.cos(2 * gamma) ** (dj - 1)
|
|
59
|
+
first *= 0.5 * np.sin(4 * beta) * np.sin(2 * gamma)
|
|
60
|
+
|
|
61
|
+
node_list = list(graph.nodes).copy()
|
|
62
|
+
node_list.remove(i)
|
|
63
|
+
node_list.remove(j)
|
|
64
|
+
f1 = 1
|
|
65
|
+
f2 = 1
|
|
66
|
+
for k in node_list:
|
|
67
|
+
if graph.has_edge(i, k) and graph.has_edge(j, k): # ijk is triangle
|
|
68
|
+
f1 *= np.cos(4 * gamma)
|
|
69
|
+
elif graph.has_edge(i, k) or graph.has_edge(j, k): # ijk is no triangle
|
|
70
|
+
f1 *= np.cos(2 * gamma)
|
|
71
|
+
f2 *= np.cos(2 * gamma)
|
|
72
|
+
second = 0.5 * np.sin(2 * beta) ** 2 * (f1 - f2)
|
|
73
|
+
return first - second
|
|
74
|
+
|
|
75
|
+
def get_expected_zz_edgedensity(x):
|
|
76
|
+
gamma = x[0]
|
|
77
|
+
beta = x[1]
|
|
78
|
+
# pylint: disable=consider-using-generator
|
|
79
|
+
return sum([get_Zij_maxcut_p1(edge, gamma, beta) for edge in graph.edges]) / graph.number_of_edges()
|
|
80
|
+
|
|
81
|
+
bounds = [(0.0, np.pi / 2), (-np.pi / 4, 0.0)]
|
|
82
|
+
x_init = [0.15, -0.28]
|
|
83
|
+
|
|
84
|
+
minimizer_kwargs = {"method": "L-BFGS-B", "bounds": bounds}
|
|
85
|
+
res = basinhopping(get_expected_zz_edgedensity, x_init, minimizer_kwargs=minimizer_kwargs, niter=10, T=2)
|
|
86
|
+
|
|
87
|
+
return res.x
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def cut_cost_function(x: str, graph: Graph) -> int:
|
|
91
|
+
"""Returns the number of cut edges in a graph (with minus sign).
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
x (str): solution bitstring.
|
|
95
|
+
graph (networkx graph): the MaxCut problem graph.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
obj (float): number of cut edges multiplied by -1.
|
|
99
|
+
"""
|
|
100
|
+
obj = 0
|
|
101
|
+
for i, j in graph.edges():
|
|
102
|
+
if x[i] != x[j]:
|
|
103
|
+
obj += 1
|
|
104
|
+
return -1 * obj
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def compute_expectation_value(
|
|
108
|
+
counts: Dict[str, int], graph: Graph, qubit_to_node: Dict[int, int], virtual_nodes: List[Tuple[int, int]]
|
|
109
|
+
) -> float:
|
|
110
|
+
"""Computes expectation value based on measurement results.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
counts (Dict[str, int]): key as bitstring, val as count
|
|
114
|
+
graph (networkx) graph: the MaxCut problem graph
|
|
115
|
+
qubit_to_node (Dict[int, int]): mapping of qubit to nodes of the graph
|
|
116
|
+
virtual_nodes (List[Tuple[int, int]]): list of virtual nodes in the graph
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
avg (float): expectation value of the cut edges for number of counts
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
avg = 0
|
|
123
|
+
sum_count = 0
|
|
124
|
+
for bitstring_aux, count in counts.items():
|
|
125
|
+
bitstring_aux_list = list(bitstring_aux)[::-1] # go from qiskit endianness to networkx endianness
|
|
126
|
+
|
|
127
|
+
# map the qubits back to nodes
|
|
128
|
+
bitstring = [""] * (len(bitstring_aux_list) + len(virtual_nodes))
|
|
129
|
+
for qubit, node in qubit_to_node.items():
|
|
130
|
+
bitstring[node] = bitstring_aux_list[qubit]
|
|
131
|
+
|
|
132
|
+
# insert virtual node(s) to bitstring
|
|
133
|
+
for virtual_node in virtual_nodes:
|
|
134
|
+
if virtual_node[0] is not None:
|
|
135
|
+
bitstring[virtual_node[0]] = str(virtual_node[1])
|
|
136
|
+
|
|
137
|
+
obj = cut_cost_function("".join(bitstring), graph)
|
|
138
|
+
avg += obj * count
|
|
139
|
+
sum_count += count
|
|
140
|
+
|
|
141
|
+
return avg / sum_count
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def create_objective_function(
|
|
145
|
+
counts: Dict[str, int], graph: Graph, qubit_to_node: Dict[int, int], virtual_nodes: List[Tuple[int, int]]
|
|
146
|
+
) -> Callable:
|
|
147
|
+
"""
|
|
148
|
+
Creates a function that maps the parameters to the parametrized circuit,
|
|
149
|
+
runs it and computes the expectation value.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
counts (Dict[str, int]): The dictionary of bitstring counts.
|
|
153
|
+
graph (networkx graph): the MaxCut problem graph.
|
|
154
|
+
qubit_to_node (Dict[int, int]): mapping of qubit to nodes of the graph
|
|
155
|
+
virtual_nodes (List[Tuple[int, int]]): list of virtual nodes in the graph
|
|
156
|
+
Returns:
|
|
157
|
+
callable: function that gives expectation value of the cut edges from counts sampled from the ansatz
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
def objective_function(temp):
|
|
161
|
+
temp = np.array(temp)
|
|
162
|
+
return compute_expectation_value(counts, graph, qubit_to_node, virtual_nodes)
|
|
163
|
+
|
|
164
|
+
return objective_function
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def is_successful(
|
|
168
|
+
approximation_ratio: float,
|
|
169
|
+
) -> bool:
|
|
170
|
+
"""Check whether a Q-score benchmark returned approximation ratio above beta*, therefore being successful.
|
|
171
|
+
|
|
172
|
+
This condition checks that the mean approximation ratio is above the beta* = 0.2 threshold.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
approximation_ratio (float): the mean approximation ratio of all problem graphs
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
bool: whether the Q-score benchmark was successful
|
|
179
|
+
"""
|
|
180
|
+
return bool(approximation_ratio > 0.2)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def plot_approximation_ratios(
|
|
184
|
+
nodes: list[int],
|
|
185
|
+
beta_ratio: list[float],
|
|
186
|
+
beta_std: list[float],
|
|
187
|
+
use_virtual_node: Optional[bool],
|
|
188
|
+
use_classically_optimized_angles: Optional[bool],
|
|
189
|
+
num_instances: int,
|
|
190
|
+
backend_name: str,
|
|
191
|
+
timestamp: str,
|
|
192
|
+
) -> tuple[str, Figure]:
|
|
193
|
+
"""Generate the figure of approximation ratios vs number of nodes,
|
|
194
|
+
including standard deviation and the acceptance threshold.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
nodes (list[int]): list nodes for the problem graph sizes.
|
|
198
|
+
beta_ratio (list[float]): Beta ratio calculated for each graph size.
|
|
199
|
+
beta_std (list[float]): Standard deviation for beta ratio of each graph size.
|
|
200
|
+
use_virtual_node (Optional[bool]): whether to use virtual nodes or not.
|
|
201
|
+
use_classically_optimized_angles (Optional[bool]): whether to use classically optimized angles or not.
|
|
202
|
+
num_instances (int): the number of instances.
|
|
203
|
+
backend_name (str): the name of the backend.
|
|
204
|
+
timestamp (str): the timestamp of the execution of the experiment.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
str: the name of the figure.
|
|
208
|
+
Figure: the figure.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
fig = plt.figure()
|
|
212
|
+
ax = plt.axes()
|
|
213
|
+
|
|
214
|
+
plt.axhline(0.2, color="red", linestyle="dashed", label="Threshold")
|
|
215
|
+
plt.errorbar(
|
|
216
|
+
nodes,
|
|
217
|
+
beta_ratio,
|
|
218
|
+
yerr=beta_std,
|
|
219
|
+
fmt="-o",
|
|
220
|
+
capsize=10,
|
|
221
|
+
markersize=8,
|
|
222
|
+
color="#759DEB",
|
|
223
|
+
label="Approximation ratio",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
ax.set_ylabel(r"Q-score ratio $\beta(n)$")
|
|
227
|
+
ax.set_xlabel("Number of nodes $(n)$")
|
|
228
|
+
plt.xticks(range(min(nodes), max(nodes) + 1))
|
|
229
|
+
plt.legend(loc="lower right")
|
|
230
|
+
plt.grid(True)
|
|
231
|
+
|
|
232
|
+
if use_virtual_node and use_classically_optimized_angles:
|
|
233
|
+
title = f"Q-score, {num_instances} instances, with virtual node and classically optimized angles\nBackend: {backend_name} / {timestamp}"
|
|
234
|
+
elif use_virtual_node and not use_classically_optimized_angles:
|
|
235
|
+
title = f"Q-score, {num_instances} instances, with virtual node \nBackend: {backend_name} / {timestamp}"
|
|
236
|
+
elif not use_virtual_node and use_classically_optimized_angles:
|
|
237
|
+
title = f"Q-score, {num_instances} instances, with classically optimized angles\nBackend: {backend_name} / {timestamp}"
|
|
238
|
+
else:
|
|
239
|
+
title = f"Q-score, {num_instances} instances \nBackend: {backend_name} / {timestamp}"
|
|
240
|
+
|
|
241
|
+
plt.title(
|
|
242
|
+
title,
|
|
243
|
+
fontsize=9,
|
|
244
|
+
)
|
|
245
|
+
fig_name = f"{max(nodes)}_nodes_{num_instances}_instances.png"
|
|
246
|
+
|
|
247
|
+
# Show plot if verbose is True
|
|
248
|
+
plt.gcf().set_dpi(250)
|
|
249
|
+
|
|
250
|
+
plt.close()
|
|
251
|
+
|
|
252
|
+
return fig_name, fig
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def qscore_analysis(run: BenchmarkRunResult) -> BenchmarkAnalysisResult:
|
|
256
|
+
"""Analysis function for a QScore experiment
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
run (RunResult): A QScore experiment run for which analysis result is created
|
|
260
|
+
Returns:
|
|
261
|
+
AnalysisResult corresponding to QScore
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
plots = {}
|
|
265
|
+
observations: list[BenchmarkObservation] = []
|
|
266
|
+
dataset = run.dataset.copy(deep=True)
|
|
267
|
+
|
|
268
|
+
backend_name = dataset.attrs["backend_name"]
|
|
269
|
+
timestamp = dataset.attrs["execution_timestamp"]
|
|
270
|
+
|
|
271
|
+
max_num_nodes = dataset.attrs["max_num_nodes"]
|
|
272
|
+
min_num_nodes = dataset.attrs["min_num_nodes"]
|
|
273
|
+
num_instances: int = dataset.attrs["num_instances"]
|
|
274
|
+
|
|
275
|
+
use_virtual_node: bool = dataset.attrs["use_virtual_node"]
|
|
276
|
+
use_classically_optimized_angles = dataset.attrs["use_classically_optimized_angles"]
|
|
277
|
+
num_qaoa_layers = dataset.attrs["num_qaoa_layers"]
|
|
278
|
+
|
|
279
|
+
qscore = 0
|
|
280
|
+
nodes_list = list(range(min_num_nodes, max_num_nodes + 1))
|
|
281
|
+
beta_ratio_list = []
|
|
282
|
+
beta_ratio_std_list = []
|
|
283
|
+
for num_nodes in nodes_list:
|
|
284
|
+
# Retrieve counts for all the instances within each executed node size.
|
|
285
|
+
execution_results = xrvariable_to_counts(dataset, num_nodes, num_instances)
|
|
286
|
+
|
|
287
|
+
# Retrieve other dataset values
|
|
288
|
+
dataset_dictionary = dataset.attrs[num_nodes]
|
|
289
|
+
|
|
290
|
+
# node_set_list = dataset_dictionary["qubit_set"]
|
|
291
|
+
graph_list = dataset_dictionary["graph"]
|
|
292
|
+
qubit_to_node_list = dataset_dictionary["qubit_to_node"]
|
|
293
|
+
virtual_node_list = dataset_dictionary["virtual_nodes"]
|
|
294
|
+
no_edge_instances = dataset_dictionary["no_edge_instances"]
|
|
295
|
+
|
|
296
|
+
cut_sizes_list = [0.0] * len(no_edge_instances)
|
|
297
|
+
instances_with_edges = set(range(num_instances)) - set(no_edge_instances)
|
|
298
|
+
|
|
299
|
+
for inst_idx in list(instances_with_edges):
|
|
300
|
+
cut_sizes = run_QAOA(
|
|
301
|
+
execution_results[inst_idx],
|
|
302
|
+
graph_list[inst_idx],
|
|
303
|
+
qubit_to_node_list[inst_idx],
|
|
304
|
+
use_classically_optimized_angles,
|
|
305
|
+
num_qaoa_layers,
|
|
306
|
+
virtual_node_list[inst_idx],
|
|
307
|
+
)
|
|
308
|
+
cut_sizes_list.append(cut_sizes)
|
|
309
|
+
|
|
310
|
+
## compute the approximation ratio beta
|
|
311
|
+
LAMBDA = 0.178
|
|
312
|
+
|
|
313
|
+
average_cut_size = np.mean(cut_sizes_list) - num_nodes * (num_nodes - 1) / 8
|
|
314
|
+
average_best_cut_size = 0.178 * pow(num_nodes, 3 / 2)
|
|
315
|
+
approximation_ratio = float(average_cut_size / average_best_cut_size)
|
|
316
|
+
|
|
317
|
+
approximation_ratio_list = [
|
|
318
|
+
(np.array(cut_sizes) - num_nodes * (num_nodes - 1) / 8) / (LAMBDA * num_nodes ** (3 / 2))
|
|
319
|
+
for cut_sizes in cut_sizes_list
|
|
320
|
+
]
|
|
321
|
+
beta_ratio_list.append(np.mean(approximation_ratio_list))
|
|
322
|
+
success = is_successful(approximation_ratio)
|
|
323
|
+
std_of_approximation_ratio = np.std(np.array(approximation_ratio_list)) / np.sqrt(
|
|
324
|
+
len(approximation_ratio_list) - 1
|
|
325
|
+
)
|
|
326
|
+
beta_ratio_std_list.append(std_of_approximation_ratio)
|
|
327
|
+
|
|
328
|
+
if success:
|
|
329
|
+
qcvv_logger.info(
|
|
330
|
+
f"Q-Score = {num_nodes} passed with approximation ratio (Beta) {approximation_ratio:.4f}; Avg MaxCut size: {np.mean(cut_sizes_list):.4f}"
|
|
331
|
+
)
|
|
332
|
+
qscore = num_nodes
|
|
333
|
+
else:
|
|
334
|
+
qcvv_logger.info(
|
|
335
|
+
f"Q-Score = {num_nodes} failed with approximation ratio (Beta) {approximation_ratio:.4f} < 0.2; Avg MaxCut size: {np.mean(cut_sizes_list):.4f}"
|
|
336
|
+
)
|
|
337
|
+
observations.extend(
|
|
338
|
+
[
|
|
339
|
+
BenchmarkObservation(
|
|
340
|
+
name="approximation_ratio",
|
|
341
|
+
value=approximation_ratio,
|
|
342
|
+
uncertainty=std_of_approximation_ratio,
|
|
343
|
+
identifier=BenchmarkObservationIdentifier(num_nodes),
|
|
344
|
+
),
|
|
345
|
+
BenchmarkObservation(
|
|
346
|
+
name="is_succesful",
|
|
347
|
+
value=str(success),
|
|
348
|
+
identifier=BenchmarkObservationIdentifier(num_nodes),
|
|
349
|
+
),
|
|
350
|
+
BenchmarkObservation(
|
|
351
|
+
name="Qscore_result",
|
|
352
|
+
value=qscore if success else 1,
|
|
353
|
+
identifier=BenchmarkObservationIdentifier(num_nodes),
|
|
354
|
+
),
|
|
355
|
+
]
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
dataset.attrs[num_nodes].update(
|
|
359
|
+
{
|
|
360
|
+
"approximate_ratio_list": approximation_ratio_list,
|
|
361
|
+
}
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
fig_name, fig = plot_approximation_ratios(
|
|
365
|
+
nodes_list,
|
|
366
|
+
beta_ratio_list,
|
|
367
|
+
beta_ratio_std_list,
|
|
368
|
+
use_virtual_node,
|
|
369
|
+
use_classically_optimized_angles,
|
|
370
|
+
num_instances,
|
|
371
|
+
backend_name,
|
|
372
|
+
timestamp,
|
|
373
|
+
)
|
|
374
|
+
plots[fig_name] = fig
|
|
375
|
+
|
|
376
|
+
return BenchmarkAnalysisResult(dataset=dataset, plots=plots, observations=observations)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def run_QAOA(
|
|
380
|
+
counts: Dict[str, int],
|
|
381
|
+
graph_physical: Graph,
|
|
382
|
+
qubit_node: Dict[int, int],
|
|
383
|
+
use_classical_angles: bool,
|
|
384
|
+
qaoa_layers: int,
|
|
385
|
+
virtual_nodes: List[Tuple[int, int]],
|
|
386
|
+
) -> float:
|
|
387
|
+
"""
|
|
388
|
+
Solves the cut size of MaxCut for a graph using QAOA.
|
|
389
|
+
The result is average value sampled from the optimized ansatz.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
counts (Dict[str, int]): key as bitstring, value as counts
|
|
393
|
+
graph_physical (Graph): the graph to be optimized
|
|
394
|
+
qubit_node (Dict[int, int]): the qubit to be optimized
|
|
395
|
+
use_classical_angles (bool): whether to use classical angles
|
|
396
|
+
qaoa_layers (int): the number of QAOA layers
|
|
397
|
+
virtual_nodes (List[Tuple[int, int]]): the presence of virtual nodes or not
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
float: the expectation value of the maximum cut size.
|
|
401
|
+
|
|
402
|
+
"""
|
|
403
|
+
|
|
404
|
+
objective_function = create_objective_function(counts, graph_physical, qubit_node, virtual_nodes)
|
|
405
|
+
if use_classical_angles:
|
|
406
|
+
if graph_physical.number_of_edges() != 0:
|
|
407
|
+
opt_angles = calculate_optimal_angles_for_QAOA_p1(graph_physical)
|
|
408
|
+
else:
|
|
409
|
+
opt_angles = [1.0, 1.0]
|
|
410
|
+
res = minimize(objective_function, opt_angles, method="COBYLA", tol=1e-5, options={"maxiter": 0})
|
|
411
|
+
else:
|
|
412
|
+
# Good initial angles from from Wurtz et.al. "The fixed angle conjecture for QAOA on regular MaxCut graphs." arXiv preprint arXiv:2107.00677 (2021).
|
|
413
|
+
OPTIMAL_INITIAL_ANGLES = {
|
|
414
|
+
"1": [-0.616, 0.393 / 2],
|
|
415
|
+
"2": [-0.488, 0.898 / 2, 0.555 / 2, 0.293 / 2],
|
|
416
|
+
"3": [-0.422, 0.798 / 2, 0.937 / 2, 0.609 / 2, 0.459 / 2, 0.235 / 2],
|
|
417
|
+
"4": [-0.409, 0.781 / 2, 0.988 / 2, 1.156 / 2, 0.600 / 2, 0.434 / 2, 0.297 / 2, 0.159 / 2],
|
|
418
|
+
"5": [-0.36, -0.707, -0.823, -1.005, -1.154, 0.632 / 2, 0.523 / 2, 0.390 / 2, 0.275 / 2, 0.149 / 2],
|
|
419
|
+
"6": [
|
|
420
|
+
-0.331,
|
|
421
|
+
-0.645,
|
|
422
|
+
-0.731,
|
|
423
|
+
-0.837,
|
|
424
|
+
-1.009,
|
|
425
|
+
-1.126,
|
|
426
|
+
0.636 / 2,
|
|
427
|
+
0.535 / 2,
|
|
428
|
+
0.463 / 2,
|
|
429
|
+
0.360 / 2,
|
|
430
|
+
0.259 / 2,
|
|
431
|
+
0.139 / 2,
|
|
432
|
+
],
|
|
433
|
+
"7": [
|
|
434
|
+
-0.310,
|
|
435
|
+
-0.618,
|
|
436
|
+
-0.690,
|
|
437
|
+
-0.751,
|
|
438
|
+
-0.859,
|
|
439
|
+
-1.020,
|
|
440
|
+
-1.122,
|
|
441
|
+
0.648 / 2,
|
|
442
|
+
0.554 / 2,
|
|
443
|
+
0.490 / 2,
|
|
444
|
+
0.445 / 2,
|
|
445
|
+
0.341 / 2,
|
|
446
|
+
0.244 / 2,
|
|
447
|
+
0.131 / 2,
|
|
448
|
+
],
|
|
449
|
+
"8": [
|
|
450
|
+
-0.295,
|
|
451
|
+
-0.587,
|
|
452
|
+
-0.654,
|
|
453
|
+
-0.708,
|
|
454
|
+
-0.765,
|
|
455
|
+
-0.864,
|
|
456
|
+
-1.026,
|
|
457
|
+
-1.116,
|
|
458
|
+
0.649 / 2,
|
|
459
|
+
0.555 / 2,
|
|
460
|
+
0.500 / 2,
|
|
461
|
+
0.469 / 2,
|
|
462
|
+
0.420 / 2,
|
|
463
|
+
0.319 / 2,
|
|
464
|
+
0.231 / 2,
|
|
465
|
+
0.123 / 2,
|
|
466
|
+
],
|
|
467
|
+
"9": [
|
|
468
|
+
-0.279,
|
|
469
|
+
-0.566,
|
|
470
|
+
-0.631,
|
|
471
|
+
-0.679,
|
|
472
|
+
-0.726,
|
|
473
|
+
-0.768,
|
|
474
|
+
-0.875,
|
|
475
|
+
-1.037,
|
|
476
|
+
-1.118,
|
|
477
|
+
0.654 / 2,
|
|
478
|
+
0.562 / 2,
|
|
479
|
+
0.509 / 2,
|
|
480
|
+
0.487 / 2,
|
|
481
|
+
0.451 / 2,
|
|
482
|
+
0.403 / 2,
|
|
483
|
+
0.305 / 2,
|
|
484
|
+
0.220 / 2,
|
|
485
|
+
0.117 / 2,
|
|
486
|
+
],
|
|
487
|
+
"10": [
|
|
488
|
+
-0.267,
|
|
489
|
+
-0.545,
|
|
490
|
+
-0.610,
|
|
491
|
+
-0.656,
|
|
492
|
+
-0.696,
|
|
493
|
+
-0.729,
|
|
494
|
+
-0.774,
|
|
495
|
+
-0.882,
|
|
496
|
+
-1.044,
|
|
497
|
+
-1.115,
|
|
498
|
+
0.656 / 2,
|
|
499
|
+
0.563 / 2,
|
|
500
|
+
0.514 / 2,
|
|
501
|
+
0.496 / 2,
|
|
502
|
+
0.496 / 2,
|
|
503
|
+
0.436 / 2,
|
|
504
|
+
0.388 / 2,
|
|
505
|
+
0.291 / 2,
|
|
506
|
+
0.211 / 2,
|
|
507
|
+
0.112 / 2,
|
|
508
|
+
],
|
|
509
|
+
"11": [
|
|
510
|
+
-0.257,
|
|
511
|
+
-0.528,
|
|
512
|
+
-0.592,
|
|
513
|
+
-0.640,
|
|
514
|
+
-0.677,
|
|
515
|
+
-0.702,
|
|
516
|
+
-0.737,
|
|
517
|
+
-0.775,
|
|
518
|
+
-0.884,
|
|
519
|
+
-1.047,
|
|
520
|
+
-1.115,
|
|
521
|
+
0.656 / 2,
|
|
522
|
+
0.563 / 2,
|
|
523
|
+
0.516 / 2,
|
|
524
|
+
0.504 / 2,
|
|
525
|
+
0.482 / 2,
|
|
526
|
+
0.456 / 2,
|
|
527
|
+
0.421 / 2,
|
|
528
|
+
0.371 / 2,
|
|
529
|
+
0.276 / 2,
|
|
530
|
+
0.201 / 2,
|
|
531
|
+
0.107 / 2,
|
|
532
|
+
],
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
theta = OPTIMAL_INITIAL_ANGLES[str(qaoa_layers)]
|
|
536
|
+
bounds = [(-np.pi, np.pi)] * qaoa_layers + [(0.0, np.pi)] * qaoa_layers
|
|
537
|
+
|
|
538
|
+
res = minimize(
|
|
539
|
+
objective_function,
|
|
540
|
+
theta,
|
|
541
|
+
bounds=bounds,
|
|
542
|
+
method="COBYLA",
|
|
543
|
+
tol=1e-5,
|
|
544
|
+
options={"maxiter": 300},
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
return -res.fun
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class QScoreBenchmark(Benchmark):
|
|
39
551
|
"""
|
|
40
552
|
Q-score estimates the size of combinatorial optimization problems a given number of qubits can execute with meaningful results.
|
|
41
553
|
"""
|
|
42
554
|
|
|
43
|
-
|
|
555
|
+
analysis_function = staticmethod(qscore_analysis)
|
|
556
|
+
|
|
557
|
+
name: str = "qscore"
|
|
558
|
+
|
|
559
|
+
def __init__(self, backend_arg: IQMBackendBase, configuration: "QScoreConfiguration"):
|
|
44
560
|
"""Construct the QScoreBenchmark class.
|
|
45
561
|
|
|
46
562
|
Args:
|
|
47
|
-
|
|
563
|
+
backend_arg (IQMBackendBase): the backend to execute the benchmark on
|
|
48
564
|
configuration (QScoreConfiguration): the configuration of the benchmark
|
|
49
565
|
"""
|
|
50
|
-
super().__init__(
|
|
566
|
+
super().__init__(backend_arg, configuration)
|
|
567
|
+
|
|
568
|
+
self.backend_configuration_name = backend_arg if isinstance(backend_arg, str) else backend_arg.name
|
|
51
569
|
|
|
52
570
|
self.num_instances = configuration.num_instances
|
|
53
571
|
self.num_qaoa_layers = configuration.num_qaoa_layers
|
|
@@ -58,7 +576,8 @@ class QScoreBenchmark(BenchmarkBase):
|
|
|
58
576
|
self.choose_qubits_routine = configuration.choose_qubits_routine
|
|
59
577
|
self.qiskit_optim_level = configuration.qiskit_optim_level
|
|
60
578
|
self.optimize_sqg = configuration.optimize_sqg
|
|
61
|
-
self.
|
|
579
|
+
self.session_timestamp = strftime("%Y%m%d-%H%M%S")
|
|
580
|
+
self.execution_timestamp = ""
|
|
62
581
|
self.seed = configuration.seed
|
|
63
582
|
|
|
64
583
|
self.graph_physical: Graph
|
|
@@ -66,6 +585,11 @@ class QScoreBenchmark(BenchmarkBase):
|
|
|
66
585
|
self.node_to_qubit: Dict[int, int]
|
|
67
586
|
self.qubit_to_node: Dict[int, int]
|
|
68
587
|
|
|
588
|
+
# Initialize the variable to contain all QScore circuits
|
|
589
|
+
self.circuits = Circuits()
|
|
590
|
+
self.untranspiled_circuits = BenchmarkCircuit(name="untranspiled_circuits")
|
|
591
|
+
self.transpiled_circuits = BenchmarkCircuit(name="transpiled_circuits")
|
|
592
|
+
|
|
69
593
|
if self.use_classically_optimized_angles and self.num_qaoa_layers > 1:
|
|
70
594
|
raise ValueError("If the `use_classically_optimized_angles` is chosen, the `num_qaoa_layers` must be 1.")
|
|
71
595
|
|
|
@@ -73,11 +597,9 @@ class QScoreBenchmark(BenchmarkBase):
|
|
|
73
597
|
raise ValueError("If the `use_virtual_node` is chosen, the `num_qaoa_layers` must be 1.")
|
|
74
598
|
|
|
75
599
|
if self.choose_qubits_routine == "custom":
|
|
76
|
-
self.custom_qubits_array =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def name() -> str:
|
|
80
|
-
return "qscore"
|
|
600
|
+
self.custom_qubits_array = [
|
|
601
|
+
list(x) for x in cast(Sequence[Sequence[int]], configuration.custom_qubits_array)
|
|
602
|
+
]
|
|
81
603
|
|
|
82
604
|
def generate_maxcut_ansatz( # pylint: disable=too-many-branches
|
|
83
605
|
self,
|
|
@@ -96,8 +618,6 @@ class QScoreBenchmark(BenchmarkBase):
|
|
|
96
618
|
gamma = theta[: self.num_qaoa_layers]
|
|
97
619
|
beta = theta[self.num_qaoa_layers :]
|
|
98
620
|
|
|
99
|
-
num_qubits = self.graph_physical.number_of_nodes()
|
|
100
|
-
|
|
101
621
|
if self.graph_physical.number_of_nodes() != graph.number_of_nodes():
|
|
102
622
|
num_qubits = self.graph_physical.number_of_nodes()
|
|
103
623
|
# re-label the nodes to be between 0 and _num_qubits
|
|
@@ -111,7 +631,6 @@ class QScoreBenchmark(BenchmarkBase):
|
|
|
111
631
|
# in case the graph is trivial: return empty circuit
|
|
112
632
|
if num_qubits == 0:
|
|
113
633
|
return QuantumCircuit(1)
|
|
114
|
-
|
|
115
634
|
qaoa_qc = QuantumCircuit(num_qubits)
|
|
116
635
|
for i in range(0, num_qubits):
|
|
117
636
|
qaoa_qc.h(i)
|
|
@@ -136,335 +655,24 @@ class QScoreBenchmark(BenchmarkBase):
|
|
|
136
655
|
for i in range(0, num_qubits):
|
|
137
656
|
qaoa_qc.rx(2 * beta[layer], i)
|
|
138
657
|
qaoa_qc.measure_all()
|
|
139
|
-
|
|
140
658
|
return qaoa_qc
|
|
141
659
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"""Returns the number of cut edges in a graph (with minus sign).
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
x (str): solution bitstring.
|
|
148
|
-
graph (networkx graph): the MaxCut problem graph.
|
|
149
|
-
|
|
150
|
-
Returns:
|
|
151
|
-
obj (float): number of cut edges multiplied by -1.
|
|
152
|
-
"""
|
|
153
|
-
|
|
154
|
-
obj = 0
|
|
155
|
-
for i, j in graph.edges():
|
|
156
|
-
if x[i] != x[j]:
|
|
157
|
-
obj += 1
|
|
158
|
-
|
|
159
|
-
return -1 * obj
|
|
160
|
-
|
|
161
|
-
def compute_expectation_value(self, counts: Dict[str, int], graph: Graph) -> float:
|
|
162
|
-
"""Computes expectation value based on measurement results.
|
|
163
|
-
|
|
164
|
-
Args:
|
|
165
|
-
counts (Dict[str, int]): key as bitstring, val as count
|
|
166
|
-
graph (networkx) graph: the MaxCut problem graph
|
|
167
|
-
|
|
168
|
-
Returns:
|
|
169
|
-
avg (float): expectation value of the cut edges for number of counts
|
|
170
|
-
"""
|
|
171
|
-
|
|
172
|
-
avg = 0
|
|
173
|
-
sum_count = 0
|
|
174
|
-
for bitstring_aux, count in counts.items():
|
|
175
|
-
bitstring_aux_list = list(bitstring_aux)[::-1] # go from qiskit endianness to networkx endianness
|
|
176
|
-
|
|
177
|
-
# map the qubits back to nodes
|
|
178
|
-
bitstring = [""] * (len(bitstring_aux_list) + len(self.virtual_nodes))
|
|
179
|
-
for qubit, node in self.qubit_to_node.items():
|
|
180
|
-
bitstring[node] = bitstring_aux_list[qubit]
|
|
181
|
-
|
|
182
|
-
# insert virtual node(s) to bitstring
|
|
183
|
-
for virtual_node in self.virtual_nodes:
|
|
184
|
-
if virtual_node[0] is not None:
|
|
185
|
-
bitstring[virtual_node[0]] = str(virtual_node[1])
|
|
186
|
-
|
|
187
|
-
obj = self.cost_function("".join(bitstring), graph)
|
|
188
|
-
avg += obj * count
|
|
189
|
-
sum_count += count
|
|
190
|
-
|
|
191
|
-
return avg / sum_count
|
|
192
|
-
|
|
193
|
-
def create_objective_function(self, graph: Graph, qubit_set: List[int]) -> Callable:
|
|
194
|
-
"""
|
|
195
|
-
Creates a function that maps the parameters to the parametrized circuit,
|
|
196
|
-
runs it and computes the expectation value.
|
|
197
|
-
|
|
198
|
-
Args:
|
|
199
|
-
graph (networkx graph): the MaxCut problem graph.
|
|
200
|
-
qubit_set (List[int]): indices of the used qubits.
|
|
201
|
-
Returns:
|
|
202
|
-
callable: function that gives expectation value of the cut edges from counts sampled from the ansatz
|
|
203
|
-
"""
|
|
204
|
-
|
|
205
|
-
def objective_function(theta):
|
|
206
|
-
qc = self.generate_maxcut_ansatz(graph, theta)
|
|
207
|
-
|
|
208
|
-
if len(qc.count_ops()) == 0:
|
|
209
|
-
counts = {"": 1.0} # to handle the case of physical graph with no edges
|
|
210
|
-
|
|
211
|
-
else:
|
|
212
|
-
coupling_map = self.backend.coupling_map.reduce(qubit_set)
|
|
213
|
-
qcvv_logger.setLevel(logging.WARNING)
|
|
214
|
-
transpiled_qc_list, _ = perform_backend_transpilation(
|
|
215
|
-
[qc],
|
|
216
|
-
backend=self.backend,
|
|
217
|
-
qubits=qubit_set,
|
|
218
|
-
coupling_map=coupling_map,
|
|
219
|
-
qiskit_optim_level=self.qiskit_optim_level,
|
|
220
|
-
optimize_sqg=self.optimize_sqg,
|
|
221
|
-
routing_method=self.routing_method,
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
sorted_transpiled_qc_list = {tuple(qubit_set): transpiled_qc_list}
|
|
225
|
-
# Execute on the backend
|
|
226
|
-
jobs, _ = submit_execute(
|
|
227
|
-
sorted_transpiled_qc_list,
|
|
228
|
-
self.backend,
|
|
229
|
-
self.shots,
|
|
230
|
-
self.calset_id,
|
|
231
|
-
max_gates_per_batch=self.max_gates_per_batch,
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
counts = retrieve_all_counts(jobs)[0][0]
|
|
235
|
-
qcvv_logger.setLevel(logging.INFO)
|
|
236
|
-
|
|
237
|
-
return self.compute_expectation_value(counts, graph)
|
|
238
|
-
|
|
239
|
-
return objective_function
|
|
240
|
-
|
|
241
|
-
@staticmethod
|
|
242
|
-
def calculate_optimal_angles_for_QAOA_p1(graph: Graph) -> List[float]:
|
|
243
|
-
"""
|
|
244
|
-
Calculates the optimal angles for single layer QAOA MaxCut ansatz.
|
|
245
|
-
|
|
246
|
-
Args:
|
|
247
|
-
graph (networkx graph): the MaxCut problem graph.
|
|
248
|
-
|
|
249
|
-
Returns:
|
|
250
|
-
List[float]: optimal angles gamma and beta.
|
|
251
|
-
|
|
252
|
-
"""
|
|
253
|
-
|
|
254
|
-
def get_Zij_maxcut_p1(edge_ij, gamma, beta):
|
|
255
|
-
"""
|
|
256
|
-
Calculates <p1_QAOA | Z_i Z_j | p1_QAOA>, assuming ij is edge of G.
|
|
257
|
-
"""
|
|
258
|
-
i, j = edge_ij
|
|
259
|
-
di = graph.degree[i]
|
|
260
|
-
dj = graph.degree[j]
|
|
261
|
-
|
|
262
|
-
first = np.cos(2 * gamma) ** (di - 1) + np.cos(2 * gamma) ** (dj - 1)
|
|
263
|
-
first *= 0.5 * np.sin(4 * beta) * np.sin(2 * gamma)
|
|
264
|
-
|
|
265
|
-
node_list = list(graph.nodes).copy()
|
|
266
|
-
node_list.remove(i)
|
|
267
|
-
node_list.remove(j)
|
|
268
|
-
f1 = 1
|
|
269
|
-
f2 = 1
|
|
270
|
-
for k in node_list:
|
|
271
|
-
if graph.has_edge(i, k) and graph.has_edge(j, k): # ijk is triangle
|
|
272
|
-
f1 *= np.cos(4 * gamma)
|
|
273
|
-
elif graph.has_edge(i, k) or graph.has_edge(j, k): # ijk is no triangle
|
|
274
|
-
f1 *= np.cos(2 * gamma)
|
|
275
|
-
f2 *= np.cos(2 * gamma)
|
|
276
|
-
second = 0.5 * np.sin(2 * beta) ** 2 * (f1 - f2)
|
|
277
|
-
return first - second
|
|
278
|
-
|
|
279
|
-
def get_expected_zz_edgedensity(x):
|
|
280
|
-
gamma = x[0]
|
|
281
|
-
beta = x[1]
|
|
282
|
-
# pylint: disable=consider-using-generator
|
|
283
|
-
return sum([get_Zij_maxcut_p1(edge, gamma, beta) for edge in graph.edges]) / graph.number_of_edges()
|
|
284
|
-
|
|
285
|
-
bounds = [(0.0, np.pi / 2), (-np.pi / 4, 0.0)]
|
|
286
|
-
x_init = [0.15, -0.28]
|
|
287
|
-
|
|
288
|
-
minimizer_kwargs = {"method": "L-BFGS-B", "bounds": bounds}
|
|
289
|
-
res = basinhopping(get_expected_zz_edgedensity, x_init, minimizer_kwargs=minimizer_kwargs, niter=10, T=2)
|
|
290
|
-
|
|
291
|
-
return res.x
|
|
292
|
-
|
|
293
|
-
def run_QAOA(self, graph: Graph, qubit_set: List[int]) -> float:
|
|
294
|
-
"""
|
|
295
|
-
Solves the cut size of MaxCut for a graph using QAOA.
|
|
296
|
-
The result is average value sampled from the optimized ansatz.
|
|
660
|
+
def add_all_meta_to_dataset(self, dataset: xr.Dataset):
|
|
661
|
+
"""Adds all configuration metadata and circuits to the dataset variable
|
|
297
662
|
|
|
298
663
|
Args:
|
|
299
|
-
|
|
300
|
-
qubit_set (List[int]): indices of the used qubits.
|
|
301
|
-
|
|
302
|
-
Returns:
|
|
303
|
-
float: the expectation value of the maximum cut size.
|
|
304
|
-
|
|
664
|
+
dataset (xr.Dataset): The xarray dataset
|
|
305
665
|
"""
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
666
|
+
dataset.attrs["session_timestamp"] = self.session_timestamp
|
|
667
|
+
dataset.attrs["execution_timestamp"] = self.execution_timestamp
|
|
668
|
+
dataset.attrs["backend_configuration_name"] = self.backend_configuration_name
|
|
669
|
+
dataset.attrs["backend_name"] = self.backend.name
|
|
670
|
+
|
|
671
|
+
for key, value in self.configuration:
|
|
672
|
+
if key == "benchmark": # Avoid saving the class object
|
|
673
|
+
dataset.attrs[key] = value.name
|
|
312
674
|
else:
|
|
313
|
-
|
|
314
|
-
res = minimize(objective_function, opt_angles, method="COBYLA", tol=1e-5, options={"maxiter": 0})
|
|
315
|
-
else:
|
|
316
|
-
# Good initial angles from from Wurtz et.al. "The fixed angle conjecture for QAOA on regular MaxCut graphs." arXiv preprint arXiv:2107.00677 (2021).
|
|
317
|
-
OPTIMAL_INITIAL_ANGLES = {
|
|
318
|
-
"1": [-0.616, 0.393 / 2],
|
|
319
|
-
"2": [-0.488, 0.898 / 2, 0.555 / 2, 0.293 / 2],
|
|
320
|
-
"3": [-0.422, 0.798 / 2, 0.937 / 2, 0.609 / 2, 0.459 / 2, 0.235 / 2],
|
|
321
|
-
"4": [-0.409, 0.781 / 2, 0.988 / 2, 1.156 / 2, 0.600 / 2, 0.434 / 2, 0.297 / 2, 0.159 / 2],
|
|
322
|
-
"5": [-0.36, -0.707, -0.823, -1.005, -1.154, 0.632 / 2, 0.523 / 2, 0.390 / 2, 0.275 / 2, 0.149 / 2],
|
|
323
|
-
"6": [
|
|
324
|
-
-0.331,
|
|
325
|
-
-0.645,
|
|
326
|
-
-0.731,
|
|
327
|
-
-0.837,
|
|
328
|
-
-1.009,
|
|
329
|
-
-1.126,
|
|
330
|
-
0.636 / 2,
|
|
331
|
-
0.535 / 2,
|
|
332
|
-
0.463 / 2,
|
|
333
|
-
0.360 / 2,
|
|
334
|
-
0.259 / 2,
|
|
335
|
-
0.139 / 2,
|
|
336
|
-
],
|
|
337
|
-
"7": [
|
|
338
|
-
-0.310,
|
|
339
|
-
-0.618,
|
|
340
|
-
-0.690,
|
|
341
|
-
-0.751,
|
|
342
|
-
-0.859,
|
|
343
|
-
-1.020,
|
|
344
|
-
-1.122,
|
|
345
|
-
0.648 / 2,
|
|
346
|
-
0.554 / 2,
|
|
347
|
-
0.490 / 2,
|
|
348
|
-
0.445 / 2,
|
|
349
|
-
0.341 / 2,
|
|
350
|
-
0.244 / 2,
|
|
351
|
-
0.131 / 2,
|
|
352
|
-
],
|
|
353
|
-
"8": [
|
|
354
|
-
-0.295,
|
|
355
|
-
-0.587,
|
|
356
|
-
-0.654,
|
|
357
|
-
-0.708,
|
|
358
|
-
-0.765,
|
|
359
|
-
-0.864,
|
|
360
|
-
-1.026,
|
|
361
|
-
-1.116,
|
|
362
|
-
0.649 / 2,
|
|
363
|
-
0.555 / 2,
|
|
364
|
-
0.500 / 2,
|
|
365
|
-
0.469 / 2,
|
|
366
|
-
0.420 / 2,
|
|
367
|
-
0.319 / 2,
|
|
368
|
-
0.231 / 2,
|
|
369
|
-
0.123 / 2,
|
|
370
|
-
],
|
|
371
|
-
"9": [
|
|
372
|
-
-0.279,
|
|
373
|
-
-0.566,
|
|
374
|
-
-0.631,
|
|
375
|
-
-0.679,
|
|
376
|
-
-0.726,
|
|
377
|
-
-0.768,
|
|
378
|
-
-0.875,
|
|
379
|
-
-1.037,
|
|
380
|
-
-1.118,
|
|
381
|
-
0.654 / 2,
|
|
382
|
-
0.562 / 2,
|
|
383
|
-
0.509 / 2,
|
|
384
|
-
0.487 / 2,
|
|
385
|
-
0.451 / 2,
|
|
386
|
-
0.403 / 2,
|
|
387
|
-
0.305 / 2,
|
|
388
|
-
0.220 / 2,
|
|
389
|
-
0.117 / 2,
|
|
390
|
-
],
|
|
391
|
-
"10": [
|
|
392
|
-
-0.267,
|
|
393
|
-
-0.545,
|
|
394
|
-
-0.610,
|
|
395
|
-
-0.656,
|
|
396
|
-
-0.696,
|
|
397
|
-
-0.729,
|
|
398
|
-
-0.774,
|
|
399
|
-
-0.882,
|
|
400
|
-
-1.044,
|
|
401
|
-
-1.115,
|
|
402
|
-
0.656 / 2,
|
|
403
|
-
0.563 / 2,
|
|
404
|
-
0.514 / 2,
|
|
405
|
-
0.496 / 2,
|
|
406
|
-
0.496 / 2,
|
|
407
|
-
0.436 / 2,
|
|
408
|
-
0.388 / 2,
|
|
409
|
-
0.291 / 2,
|
|
410
|
-
0.211 / 2,
|
|
411
|
-
0.112 / 2,
|
|
412
|
-
],
|
|
413
|
-
"11": [
|
|
414
|
-
-0.257,
|
|
415
|
-
-0.528,
|
|
416
|
-
-0.592,
|
|
417
|
-
-0.640,
|
|
418
|
-
-0.677,
|
|
419
|
-
-0.702,
|
|
420
|
-
-0.737,
|
|
421
|
-
-0.775,
|
|
422
|
-
-0.884,
|
|
423
|
-
-1.047,
|
|
424
|
-
-1.115,
|
|
425
|
-
0.656 / 2,
|
|
426
|
-
0.563 / 2,
|
|
427
|
-
0.516 / 2,
|
|
428
|
-
0.504 / 2,
|
|
429
|
-
0.482 / 2,
|
|
430
|
-
0.456 / 2,
|
|
431
|
-
0.421 / 2,
|
|
432
|
-
0.371 / 2,
|
|
433
|
-
0.276 / 2,
|
|
434
|
-
0.201 / 2,
|
|
435
|
-
0.107 / 2,
|
|
436
|
-
],
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
theta = OPTIMAL_INITIAL_ANGLES[str(self.num_qaoa_layers)]
|
|
440
|
-
bounds = [(-np.pi, np.pi)] * self.num_qaoa_layers + [(0.0, np.pi)] * self.num_qaoa_layers
|
|
441
|
-
|
|
442
|
-
res = minimize(
|
|
443
|
-
objective_function,
|
|
444
|
-
theta,
|
|
445
|
-
bounds=bounds,
|
|
446
|
-
method="COBYLA",
|
|
447
|
-
tol=1e-5,
|
|
448
|
-
options={"maxiter": 300},
|
|
449
|
-
)
|
|
450
|
-
|
|
451
|
-
return -res.fun
|
|
452
|
-
|
|
453
|
-
@staticmethod
|
|
454
|
-
def is_successful(
|
|
455
|
-
approximation_ratio: float,
|
|
456
|
-
) -> bool:
|
|
457
|
-
"""Check whether a Q-score benchmark returned approximation ratio above beta*, therefore being successful.
|
|
458
|
-
|
|
459
|
-
This condition checks that the mean approximation ratio is above the beta* = 0.2 threshold.
|
|
460
|
-
|
|
461
|
-
Args:
|
|
462
|
-
approximation_ratio (float): the mean approximation ratio of all problem graphs
|
|
463
|
-
|
|
464
|
-
Returns:
|
|
465
|
-
bool: whether the Q-score benchmark was successful
|
|
466
|
-
"""
|
|
467
|
-
return bool(approximation_ratio > 0.2)
|
|
675
|
+
dataset.attrs[key] = value
|
|
468
676
|
|
|
469
677
|
@staticmethod
|
|
470
678
|
def choose_qubits_naive(num_qubits: int) -> list[int]:
|
|
@@ -502,171 +710,19 @@ class QScoreBenchmark(BenchmarkBase):
|
|
|
502
710
|
# The execute_single_benchmark call must be looped through a COPY of custom_qubits_array
|
|
503
711
|
else:
|
|
504
712
|
chosen_qubits = selected_qubits[0]
|
|
505
|
-
return chosen_qubits
|
|
506
|
-
|
|
507
|
-
def plot_approximation_ratios(
|
|
508
|
-
self, list_of_num_nodes: list[int], list_of_cut_sizes: list[list[float]]
|
|
509
|
-
) -> tuple[str, Figure]:
|
|
510
|
-
"""Generate the figure of approximation ratios vs number of nodes,
|
|
511
|
-
including standard deviation and the acceptance threshold.
|
|
512
|
-
|
|
513
|
-
Args:
|
|
514
|
-
list_of_num_nodes (list[int]): list of problem graph sizes.
|
|
515
|
-
list_of_cut_sizes (list[list[float]]): the list of lists of maximum average cut sizes of problem graph instances for each problem size.
|
|
516
|
-
|
|
517
|
-
Returns:
|
|
518
|
-
str: the name of the figure.
|
|
519
|
-
Figure: the figure.
|
|
520
|
-
"""
|
|
521
|
-
|
|
522
|
-
LAMBDA = 0.178
|
|
523
|
-
|
|
524
|
-
approximation_ratio_lists = {
|
|
525
|
-
num_nodes: (np.array(cut_sizes) - num_nodes * (num_nodes - 1) / 8) / (LAMBDA * num_nodes ** (3 / 2))
|
|
526
|
-
for num_nodes, cut_sizes in zip(list_of_num_nodes, list_of_cut_sizes)
|
|
527
|
-
}
|
|
713
|
+
return list(chosen_qubits)
|
|
528
714
|
|
|
529
|
-
|
|
530
|
-
num_nodes: np.mean(approximation_ratios)
|
|
531
|
-
for num_nodes, approximation_ratios in approximation_ratio_lists.items()
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
std_of_approximation_ratios = {
|
|
535
|
-
num_nodes: np.std(approximation_ratios) / np.sqrt(len(approximation_ratios) - 1)
|
|
536
|
-
for num_nodes, approximation_ratios in approximation_ratio_lists.items()
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
nodes = list(avg_approximation_ratios.keys())
|
|
540
|
-
ratios = list(avg_approximation_ratios.values())
|
|
541
|
-
std = list(std_of_approximation_ratios.values())
|
|
542
|
-
|
|
543
|
-
fig = plt.figure()
|
|
544
|
-
ax = plt.axes()
|
|
545
|
-
|
|
546
|
-
plt.axhline(0.2, color="red", linestyle="dashed", label="Threshold")
|
|
547
|
-
plt.errorbar(
|
|
548
|
-
nodes, ratios, yerr=std, fmt="-o", capsize=10, markersize=8, color="#759DEB", label="Approximation ratio"
|
|
549
|
-
)
|
|
550
|
-
|
|
551
|
-
ax.set_ylabel(r"Q-score ratio $\beta(n)$")
|
|
552
|
-
ax.set_xlabel("Number of nodes $(n)$")
|
|
553
|
-
plt.xticks(range(min(nodes), max(nodes) + 1))
|
|
554
|
-
plt.legend(loc="lower right")
|
|
555
|
-
plt.grid(True)
|
|
556
|
-
|
|
557
|
-
if self.use_virtual_node and self.use_classically_optimized_angles:
|
|
558
|
-
title = f"Q-score, {self.num_instances} instances, with virtual node and classically optimized angles\nBackend: {self.backend.name} / {self.timestamp}"
|
|
559
|
-
elif self.use_virtual_node and not self.use_classically_optimized_angles:
|
|
560
|
-
title = f"Q-score, {self.num_instances} instances, with virtual node \nBackend: {self.backend.name} / {self.timestamp}"
|
|
561
|
-
elif not self.use_virtual_node and self.use_classically_optimized_angles:
|
|
562
|
-
title = f"Q-score, {self.num_instances} instances, with classically optimized angles\nBackend: {self.backend.name} / {self.timestamp}"
|
|
563
|
-
else:
|
|
564
|
-
title = f"Q-score, {self.num_instances} instances \nBackend: {self.backend.name} / {self.timestamp}"
|
|
565
|
-
|
|
566
|
-
plt.title(
|
|
567
|
-
title,
|
|
568
|
-
fontsize=9,
|
|
569
|
-
)
|
|
570
|
-
fig_name = f"{max(nodes)}_nodes_{self.num_instances}_instances.png"
|
|
571
|
-
|
|
572
|
-
# Show plot if verbose is True
|
|
573
|
-
plt.gcf().set_dpi(250)
|
|
574
|
-
plt.show()
|
|
575
|
-
|
|
576
|
-
plt.close()
|
|
577
|
-
|
|
578
|
-
return fig_name, fig
|
|
579
|
-
|
|
580
|
-
def execute_single_benchmark(
|
|
715
|
+
def execute(
|
|
581
716
|
self,
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
Returns:
|
|
590
|
-
bool: whether the benchmark was successful.
|
|
591
|
-
float: approximation_ratio.
|
|
592
|
-
list[float]: the list of maximum average cut sizes of problem graph instances.
|
|
593
|
-
list[int]: the set of qubits the Q-score benchmark was executed on.
|
|
594
|
-
"""
|
|
595
|
-
|
|
596
|
-
cut_sizes: list[float] = []
|
|
597
|
-
seed = self.seed
|
|
717
|
+
backend: IQMBackendBase,
|
|
718
|
+
# pylint: disable=too-many-branches
|
|
719
|
+
# pylint: disable=too-many-statements
|
|
720
|
+
) -> xr.Dataset:
|
|
721
|
+
"""Executes the benchmark."""
|
|
722
|
+
self.execution_timestamp = strftime("%Y%m%d-%H%M%S")
|
|
598
723
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
qcvv_logger.debug(f"graph: {graph}")
|
|
602
|
-
self.graph_physical = graph.copy()
|
|
603
|
-
self.virtual_nodes = []
|
|
604
|
-
if self.use_virtual_node:
|
|
605
|
-
virtual_node, _ = max(
|
|
606
|
-
graph.degree(), key=lambda x: x[1]
|
|
607
|
-
) # choose the virtual node as the most connected node
|
|
608
|
-
self.virtual_nodes.append(
|
|
609
|
-
(virtual_node, 1)
|
|
610
|
-
) # the second element of the tuple is the value assigned to the virtual node
|
|
611
|
-
self.graph_physical.remove_node(self.virtual_nodes[0][0])
|
|
612
|
-
# See if there are any non-connected nodes if so, remove them also
|
|
613
|
-
# and set them to be opposite value to the possible original virtual node
|
|
614
|
-
for node in self.graph_physical.nodes():
|
|
615
|
-
if self.graph_physical.degree(node) == 0:
|
|
616
|
-
self.virtual_nodes.append((node, 0))
|
|
617
|
-
for vn in self.virtual_nodes:
|
|
618
|
-
if self.graph_physical.has_node(vn[0]):
|
|
619
|
-
self.graph_physical.remove_node(vn[0])
|
|
620
|
-
|
|
621
|
-
# Graph with no edges has cut size = 0
|
|
622
|
-
if graph.number_of_edges() == 0:
|
|
623
|
-
cut_sizes.append(0)
|
|
624
|
-
seed += 1
|
|
625
|
-
qcvv_logger.debug(f"Graph {i+1}/{self.num_instances} had no edges: cut size = 0.")
|
|
626
|
-
continue
|
|
627
|
-
|
|
628
|
-
# Choose the qubit layout
|
|
629
|
-
qubit_set = []
|
|
630
|
-
if self.choose_qubits_routine.lower() == "naive":
|
|
631
|
-
qubit_set = self.choose_qubits_naive(num_nodes)
|
|
632
|
-
elif self.choose_qubits_routine.lower() == "custom":
|
|
633
|
-
qubit_set = self.choose_qubits_custom(num_nodes)
|
|
634
|
-
else:
|
|
635
|
-
raise ValueError('choose_qubits_routine must either be "naive" or "custom".')
|
|
636
|
-
|
|
637
|
-
# Solve the maximum cut size with QAOA
|
|
638
|
-
cut_sizes.append(self.run_QAOA(graph, qubit_set))
|
|
639
|
-
seed += 1
|
|
640
|
-
qcvv_logger.debug(f"Solved the MaxCut on graph {i+1}/{self.num_instances}.")
|
|
641
|
-
|
|
642
|
-
average_cut_size = np.mean(cut_sizes) - num_nodes * (num_nodes - 1) / 8
|
|
643
|
-
average_best_cut_size = 0.178 * pow(num_nodes, 3 / 2)
|
|
644
|
-
approximation_ratio = float(average_cut_size / average_best_cut_size)
|
|
645
|
-
|
|
646
|
-
self.raw_data[num_nodes] = {
|
|
647
|
-
"qubit_set": qubit_set,
|
|
648
|
-
"cut_sizes": cut_sizes,
|
|
649
|
-
}
|
|
650
|
-
self.results[num_nodes] = {
|
|
651
|
-
"qubit_set": qubit_set,
|
|
652
|
-
"is_successful": str(self.is_successful(approximation_ratio)),
|
|
653
|
-
"approximation_ratio": approximation_ratio,
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
# Return whether the single Q-score Benchmark was successful and its mean approximation ratio
|
|
657
|
-
# and cut sizes for all instances.
|
|
658
|
-
return self.is_successful(approximation_ratio), approximation_ratio, cut_sizes, qubit_set
|
|
659
|
-
|
|
660
|
-
@timeit
|
|
661
|
-
def execute_full_benchmark(self) -> tuple[int, list[float], list[list[float]]]:
|
|
662
|
-
"""Execute the full benchmark, starting with self.min_num_nodes nodes up to failure.
|
|
663
|
-
|
|
664
|
-
Returns:
|
|
665
|
-
int: the Q-score of the device.
|
|
666
|
-
list[float]: the list of approximation rations over problem graph instances for each problem size.
|
|
667
|
-
list[list[float]]: the list of lists of maximum average cut sizes of problem graph instances for each problem size.
|
|
668
|
-
"""
|
|
669
|
-
qscore = 0
|
|
724
|
+
dataset = xr.Dataset()
|
|
725
|
+
self.add_all_meta_to_dataset(dataset)
|
|
670
726
|
|
|
671
727
|
if self.max_num_nodes is None:
|
|
672
728
|
if self.use_virtual_node:
|
|
@@ -676,38 +732,158 @@ class QScoreBenchmark(BenchmarkBase):
|
|
|
676
732
|
else:
|
|
677
733
|
max_num_nodes = self.max_num_nodes
|
|
678
734
|
|
|
679
|
-
|
|
680
|
-
list_of_cut_sizes = []
|
|
735
|
+
dataset.attrs.update({"max_num_nodes": self.max_num_nodes})
|
|
681
736
|
|
|
682
737
|
for num_nodes in range(self.min_num_nodes, max_num_nodes + 1):
|
|
738
|
+
qc_list = []
|
|
739
|
+
qc_transpiled_list: List[QuantumCircuit] = []
|
|
740
|
+
execution_results = []
|
|
741
|
+
graph_list = []
|
|
742
|
+
qubit_set_list = []
|
|
743
|
+
|
|
683
744
|
qcvv_logger.debug(f"Executing on {self.num_instances} random graphs with {num_nodes} nodes.")
|
|
684
|
-
is_successful, approximation_ratio, cut_sizes = self.execute_single_benchmark(num_nodes)[0:3]
|
|
685
|
-
approximation_ratios.append(approximation_ratio)
|
|
686
745
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
746
|
+
# self.untranspiled_circuits[str(num_nodes)] = {}
|
|
747
|
+
# self.transpiled_circuits[str(num_nodes)] = {}
|
|
748
|
+
|
|
749
|
+
seed = self.seed
|
|
750
|
+
virtual_node_list = []
|
|
751
|
+
qubit_to_node_list = []
|
|
752
|
+
no_edge_instances = []
|
|
753
|
+
for instance in range(self.num_instances):
|
|
754
|
+
qcvv_logger.debug(f"Executing graph {instance} with {num_nodes} nodes.")
|
|
755
|
+
graph = nx.generators.erdos_renyi_graph(num_nodes, 0.5, seed=seed)
|
|
756
|
+
graph_list.append(graph)
|
|
757
|
+
self.graph_physical = graph.copy()
|
|
758
|
+
self.virtual_nodes = []
|
|
759
|
+
if self.use_virtual_node:
|
|
760
|
+
virtual_node, _ = max(
|
|
761
|
+
graph.degree(), key=lambda x: x[1]
|
|
762
|
+
) # choose the virtual node as the most connected node
|
|
763
|
+
self.virtual_nodes.append(
|
|
764
|
+
(virtual_node, 1)
|
|
765
|
+
) # the second element of the tuple is the value assigned to the virtual node
|
|
766
|
+
self.graph_physical.remove_node(self.virtual_nodes[0][0])
|
|
767
|
+
# See if there are any non-connected nodes if so, remove them also
|
|
768
|
+
# and set them to be opposite value to the possible original virtual node
|
|
769
|
+
for node in self.graph_physical.nodes():
|
|
770
|
+
if self.graph_physical.degree(node) == 0:
|
|
771
|
+
self.virtual_nodes.append((node, 0))
|
|
772
|
+
for vn in self.virtual_nodes:
|
|
773
|
+
if self.graph_physical.has_node(vn[0]):
|
|
774
|
+
self.graph_physical.remove_node(vn[0])
|
|
775
|
+
virtual_node_list.append(self.virtual_nodes)
|
|
776
|
+
# Graph with no edges has cut size = 0
|
|
777
|
+
if graph.number_of_edges() == 0:
|
|
778
|
+
no_edge_instances.append(instance)
|
|
779
|
+
qcvv_logger.debug(f"Graph {instance+1}/{self.num_instances} had no edges: cut size = 0.")
|
|
780
|
+
|
|
781
|
+
# Choose the qubit layout
|
|
782
|
+
|
|
783
|
+
if self.choose_qubits_routine.lower() == "naive":
|
|
784
|
+
qubit_set = self.choose_qubits_naive(num_nodes)
|
|
785
|
+
elif (
|
|
786
|
+
self.choose_qubits_routine.lower() == "custom" or self.choose_qubits_routine.lower() == "mapomatic"
|
|
787
|
+
):
|
|
788
|
+
qubit_set = self.choose_qubits_custom(num_nodes)
|
|
789
|
+
else:
|
|
790
|
+
raise ValueError('choose_qubits_routine must either be "naive" or "custom".')
|
|
791
|
+
qubit_set_list.append(qubit_set)
|
|
792
|
+
|
|
793
|
+
qc = self.generate_maxcut_ansatz(graph, theta=[float(q) for q in qubit_set])
|
|
794
|
+
qc_list.append(qc)
|
|
795
|
+
qubit_to_node_copy = self.qubit_to_node.copy()
|
|
796
|
+
qubit_to_node_list.append(qubit_to_node_copy)
|
|
797
|
+
|
|
798
|
+
if len(qc.count_ops()) == 0:
|
|
799
|
+
counts = {"": 1.0} # to handle the case of physical graph with no edges
|
|
800
|
+
qc_transpiled_list.append([])
|
|
801
|
+
execution_results.append(counts)
|
|
802
|
+
qc_list.append([])
|
|
803
|
+
qcvv_logger.debug(f"This graph instance has no edges.")
|
|
804
|
+
else:
|
|
805
|
+
# execute for a given num_node and a given instance
|
|
806
|
+
coupling_map = self.backend.coupling_map.reduce(qubit_set)
|
|
807
|
+
qcvv_logger.setLevel(logging.WARNING)
|
|
808
|
+
transpiled_qc, _ = perform_backend_transpilation(
|
|
809
|
+
[qc],
|
|
810
|
+
backend=self.backend,
|
|
811
|
+
qubits=qubit_set,
|
|
812
|
+
coupling_map=coupling_map,
|
|
813
|
+
qiskit_optim_level=self.qiskit_optim_level,
|
|
814
|
+
optimize_sqg=self.optimize_sqg,
|
|
815
|
+
routing_method=self.routing_method,
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
sorted_transpiled_qc_list = {tuple(qubit_set): transpiled_qc}
|
|
819
|
+
# Execute on the backend
|
|
820
|
+
jobs, _ = submit_execute(
|
|
821
|
+
sorted_transpiled_qc_list,
|
|
822
|
+
self.backend,
|
|
823
|
+
self.shots,
|
|
824
|
+
self.calset_id,
|
|
825
|
+
max_gates_per_batch=self.max_gates_per_batch,
|
|
826
|
+
)
|
|
827
|
+
qc_transpiled_list.append(transpiled_qc)
|
|
828
|
+
execution_results.append(retrieve_all_counts(jobs)[0][0])
|
|
829
|
+
qcvv_logger.setLevel(logging.INFO)
|
|
694
830
|
|
|
695
|
-
|
|
696
|
-
f"
|
|
831
|
+
seed += 1
|
|
832
|
+
qcvv_logger.debug(f"Solved the MaxCut on graph {instance+1}/{self.num_instances}.")
|
|
833
|
+
|
|
834
|
+
dataset.attrs.update(
|
|
835
|
+
{
|
|
836
|
+
num_nodes: {
|
|
837
|
+
"qubit_set": qubit_set_list,
|
|
838
|
+
"seed_start": seed,
|
|
839
|
+
"graph": graph_list,
|
|
840
|
+
"virtual_nodes": virtual_node_list,
|
|
841
|
+
"qubit_to_node": qubit_to_node_list,
|
|
842
|
+
"no_edge_instances": no_edge_instances,
|
|
843
|
+
}
|
|
844
|
+
}
|
|
697
845
|
)
|
|
698
846
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
847
|
+
qcvv_logger.debug(f"Adding counts for the random graph for {num_nodes} nodes to the dataset")
|
|
848
|
+
dataset, _ = add_counts_to_dataset(execution_results, str(num_nodes), dataset)
|
|
849
|
+
|
|
850
|
+
# self.untranspiled_circuits[str(num_nodes)].update({tuple(qubit_set): qc_list})
|
|
851
|
+
# self.transpiled_circuits[str(num_nodes)].update(sorted_transpiled_qc_list)
|
|
852
|
+
self.untranspiled_circuits.circuit_groups.append(CircuitGroup(name=str(num_nodes), circuits=qc_list))
|
|
853
|
+
self.transpiled_circuits.circuit_groups.append(
|
|
854
|
+
CircuitGroup(name=str(num_nodes), circuits=qc_transpiled_list)
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
self.circuits = Circuits([self.transpiled_circuits, self.untranspiled_circuits])
|
|
858
|
+
|
|
859
|
+
return dataset
|
|
705
860
|
|
|
706
861
|
|
|
707
862
|
class QScoreConfiguration(BenchmarkConfigurationBase):
|
|
708
|
-
"""Q-score configuration.
|
|
863
|
+
"""Q-score configuration.
|
|
864
|
+
|
|
865
|
+
Attributes:
|
|
866
|
+
benchmark (Type[Benchmark]): QScoreBenchmark
|
|
867
|
+
num_instances (int):
|
|
868
|
+
num_qaoa_layers (int):
|
|
869
|
+
min_num_nodes (int):
|
|
870
|
+
max_num_nodes (int):
|
|
871
|
+
use_virtual_node (bool):
|
|
872
|
+
use_classically_optimized_angles (bool):
|
|
873
|
+
choose_qubits_routine (Literal["custom"]): The routine to select qubit layouts.
|
|
874
|
+
* Default is "custom".
|
|
875
|
+
min_num_qubits (int):
|
|
876
|
+
custom_qubits_array (Optional[Sequence[Sequence[int]]]): The physical qubit layouts to perform the benchmark on.
|
|
877
|
+
* Default is None.
|
|
878
|
+
qiskit_optim_level (int): The Qiskit transpilation optimization level.
|
|
879
|
+
* Default is 3.
|
|
880
|
+
optimize_sqg (bool): Whether Single Qubit Gate Optimization is performed upon transpilation.
|
|
881
|
+
* Default is True.
|
|
882
|
+
seed (int): The random seed.
|
|
883
|
+
* Default is 1.
|
|
884
|
+
"""
|
|
709
885
|
|
|
710
|
-
benchmark: Type[
|
|
886
|
+
benchmark: Type[Benchmark] = QScoreBenchmark
|
|
711
887
|
num_instances: int
|
|
712
888
|
num_qaoa_layers: int = 1
|
|
713
889
|
min_num_nodes: int = 2
|
|
@@ -716,7 +892,7 @@ class QScoreConfiguration(BenchmarkConfigurationBase):
|
|
|
716
892
|
use_classically_optimized_angles: bool = True
|
|
717
893
|
choose_qubits_routine: Literal["naive", "custom"] = "naive"
|
|
718
894
|
min_num_qubits: int = 2 # If choose_qubits_routine is "naive"
|
|
719
|
-
custom_qubits_array: Optional[
|
|
895
|
+
custom_qubits_array: Optional[Sequence[Sequence[int]]] = None
|
|
720
896
|
qiskit_optim_level: int = 3
|
|
721
897
|
optimize_sqg: bool = True
|
|
722
898
|
seed: int = 1
|