iqm-benchmarks 1.3__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 iqm-benchmarks might be problematic. Click here for more details.

Files changed (42) hide show
  1. iqm/benchmarks/__init__.py +31 -0
  2. iqm/benchmarks/benchmark.py +109 -0
  3. iqm/benchmarks/benchmark_definition.py +264 -0
  4. iqm/benchmarks/benchmark_experiment.py +163 -0
  5. iqm/benchmarks/compressive_gst/__init__.py +20 -0
  6. iqm/benchmarks/compressive_gst/compressive_gst.py +1029 -0
  7. iqm/benchmarks/entanglement/__init__.py +18 -0
  8. iqm/benchmarks/entanglement/ghz.py +802 -0
  9. iqm/benchmarks/logging_config.py +29 -0
  10. iqm/benchmarks/optimization/__init__.py +18 -0
  11. iqm/benchmarks/optimization/qscore.py +719 -0
  12. iqm/benchmarks/quantum_volume/__init__.py +21 -0
  13. iqm/benchmarks/quantum_volume/clops.py +726 -0
  14. iqm/benchmarks/quantum_volume/quantum_volume.py +854 -0
  15. iqm/benchmarks/randomized_benchmarking/__init__.py +18 -0
  16. iqm/benchmarks/randomized_benchmarking/clifford_1q.pkl +0 -0
  17. iqm/benchmarks/randomized_benchmarking/clifford_2q.pkl +0 -0
  18. iqm/benchmarks/randomized_benchmarking/clifford_rb/__init__.py +19 -0
  19. iqm/benchmarks/randomized_benchmarking/clifford_rb/clifford_rb.py +386 -0
  20. iqm/benchmarks/randomized_benchmarking/interleaved_rb/__init__.py +19 -0
  21. iqm/benchmarks/randomized_benchmarking/interleaved_rb/interleaved_rb.py +555 -0
  22. iqm/benchmarks/randomized_benchmarking/mirror_rb/__init__.py +19 -0
  23. iqm/benchmarks/randomized_benchmarking/mirror_rb/mirror_rb.py +810 -0
  24. iqm/benchmarks/randomized_benchmarking/multi_lmfit.py +86 -0
  25. iqm/benchmarks/randomized_benchmarking/randomized_benchmarking_common.py +892 -0
  26. iqm/benchmarks/readout_mitigation.py +290 -0
  27. iqm/benchmarks/utils.py +521 -0
  28. iqm_benchmarks-1.3.dist-info/LICENSE +205 -0
  29. iqm_benchmarks-1.3.dist-info/METADATA +190 -0
  30. iqm_benchmarks-1.3.dist-info/RECORD +42 -0
  31. iqm_benchmarks-1.3.dist-info/WHEEL +5 -0
  32. iqm_benchmarks-1.3.dist-info/top_level.txt +2 -0
  33. mGST/LICENSE +21 -0
  34. mGST/README.md +54 -0
  35. mGST/additional_fns.py +962 -0
  36. mGST/algorithm.py +733 -0
  37. mGST/compatibility.py +238 -0
  38. mGST/low_level_jit.py +694 -0
  39. mGST/optimization.py +349 -0
  40. mGST/qiskit_interface.py +282 -0
  41. mGST/reporting/figure_gen.py +334 -0
  42. mGST/reporting/reporting.py +710 -0
@@ -0,0 +1,1029 @@
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
+ """
16
+ Compressive gate set tomography
17
+ """
18
+
19
+ from itertools import product
20
+ from time import perf_counter, strftime
21
+ from typing import Dict, List, Tuple, Type, Union
22
+
23
+ import matplotlib.pyplot as plt
24
+ import numpy as np
25
+ from pandas import DataFrame
26
+ from pygsti.tools import change_basis
27
+ from qiskit import QuantumCircuit
28
+ from qiskit.circuit.library import CZGate, RGate
29
+
30
+ from iqm.benchmarks.benchmark import BenchmarkBase, BenchmarkConfigurationBase
31
+ from iqm.benchmarks.logging_config import qcvv_logger
32
+ from iqm.benchmarks.utils import (
33
+ perform_backend_transpilation,
34
+ retrieve_all_counts,
35
+ retrieve_all_job_metadata,
36
+ set_coupling_map,
37
+ submit_execute,
38
+ timeit,
39
+ )
40
+ from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
41
+ from mGST import additional_fns, algorithm, compatibility, qiskit_interface
42
+ from mGST.low_level_jit import contract
43
+ from mGST.qiskit_interface import add_idle_gates, qiskit_gate_to_operator, remove_idle_wires
44
+ from mGST.reporting import figure_gen, reporting
45
+
46
+
47
+ # pylint: disable=too-many-lines
48
+
49
+
50
+ class compressive_GST(BenchmarkBase):
51
+ """
52
+ SPAM-robust characterization of a set of quantum gates
53
+ """
54
+
55
+ def __init__(self, backend: IQMBackendBase, configuration: "GSTConfiguration"):
56
+ """Construct the compressive_gst class.
57
+
58
+ Args:
59
+ backend (IQMBackendBase): the backend to execute the benchmark on
60
+ configuration (GSTConfiguration): the configuration of the benchmark
61
+ """
62
+ super().__init__(backend, configuration)
63
+
64
+ self.qubits = configuration.qubits
65
+ self.num_qubits = len(self.qubits)
66
+ self.pdim = 2**self.num_qubits
67
+ self.num_povm = self.pdim
68
+ self.gate_set, self.gate_labels, self.num_gates = parse_gate_set(configuration)
69
+ self.seq_len_list = configuration.seq_len_list
70
+ self.num_circuits = configuration.num_circuits
71
+ self.shots = configuration.shots
72
+ self.rank = configuration.rank
73
+ self.from_init = configuration.from_init
74
+ self.max_inits = configuration.max_inits
75
+ self.convergence_criteria = configuration.convergence_criteria
76
+ self.bootstrap_samples = configuration.bootstrap_samples
77
+ self.weights = dict({f"G%i" % i: 1 for i in range(self.num_gates)}, **{"spam": 1})
78
+ self.max_gates_per_batch = configuration.max_gates_per_batch
79
+ self.timestamp = strftime("%Y%m%d-%H%M%S")
80
+
81
+ if configuration.opt_method not in ["GD", "SFN", "auto"]:
82
+ raise ValueError("Invalid optimization method, valid options are: GD, SFN, auto")
83
+ if configuration.opt_method == "auto":
84
+ if (self.num_qubits == 2 and self.rank > 2) or self.num_qubits > 2:
85
+ self.opt_method = "GD"
86
+ else:
87
+ self.opt_method = "SFN"
88
+ else:
89
+ self.opt_method = configuration.opt_method
90
+
91
+ if configuration.max_iterations == "auto":
92
+ if self.opt_method == "SFN":
93
+ self.max_iterations = [100, 100]
94
+ if self.opt_method == "GD":
95
+ self.max_iterations = [250, 250]
96
+ elif isinstance(configuration.max_iterations, list):
97
+ self.max_iterations = configuration.max_iterations
98
+ if configuration.batch_size == "auto":
99
+ self.batch_size = 30 * self.pdim
100
+ else:
101
+ self.batch_size = configuration.batch_size
102
+
103
+ Pauli_labels_loc = ["I", "X", "Y", "Z"]
104
+ Pauli_labels_rep = [Pauli_labels_loc for _ in range(int(np.log2(self.pdim)))]
105
+ separator = ""
106
+ self.Pauli_labels = [separator.join(map(str, x)) for x in product(*Pauli_labels_rep)]
107
+
108
+ std_labels_loc = ["0", "1"]
109
+ std_labels_rep = [std_labels_loc for _ in range(int(np.log2(self.pdim)))]
110
+ self.std_labels = [separator.join(map(str, x)) for x in product(*std_labels_rep)]
111
+
112
+ self.y, self.J = (
113
+ np.empty((self.num_povm, self.num_circuits)),
114
+ np.empty((self.num_circuits, self.num_povm)),
115
+ ) # format used by mGST
116
+ self.bootstrap_results = List[Tuple[np.ndarray]] # List of GST outcomes from bootstrapping
117
+ self.raw_qc_list = List[QuantumCircuit] # List of circuits before transpilation
118
+ self.transpiled_qc_list = List[QuantumCircuit] # List of transpiled circuits to be executed
119
+
120
+ @staticmethod
121
+ def name() -> str:
122
+ return "compressive_GST"
123
+
124
+ @timeit
125
+ def generate_meas_circuits(self) -> float:
126
+ """Generate random circuits from the gate set
127
+
128
+ The random circuits are distributed among different depths ranging from L_MIN
129
+ to L_MAX, both are configurable and stored in self.seq_len_list.
130
+ No transpilation other than mapping to the desired qubits is performed,
131
+ as the gates need to be executed exactly as described for GST to give
132
+ meaningful results
133
+
134
+ Returns:
135
+ circuit_gen_transp_time: float
136
+ The time it took to generate and transpile the circuits
137
+
138
+ """
139
+ start_timer_data = perf_counter()
140
+ # Calculate number of short and long circuits
141
+ N_short = int(np.ceil(self.num_circuits / 2))
142
+ N_long = int(np.floor(self.num_circuits / 2))
143
+ L_MIN, L_CUT, L_MAX = self.seq_len_list
144
+
145
+ # Generate random circuits
146
+ J = additional_fns.random_seq_design(self.num_gates, L_MIN, L_CUT, L_MAX, N_short, N_long)
147
+
148
+ # Reduce circuits to exclude negative placeholder values
149
+ gate_circuits = [list(seq[seq >= 0]) for seq in J]
150
+
151
+ # Sequences in GST are applied to the state from right to left
152
+ self.J = np.array(J)[:, ::-1]
153
+
154
+ unmapped_qubits = list(np.arange(self.num_qubits))
155
+ self.raw_qc_list = qiskit_interface.get_qiskit_circuits(
156
+ gate_circuits, self.gate_set, self.num_qubits, unmapped_qubits
157
+ )
158
+
159
+ coupling_map = set_coupling_map(self.qubits, self.backend, physical_layout="fixed")
160
+
161
+ # Perform transpilation to backend
162
+ qcvv_logger.info(f"Will transpile all {self.num_circuits} circuits according to fixed physical layout")
163
+ self.transpiled_qc_list, _ = perform_backend_transpilation(
164
+ self.raw_qc_list,
165
+ self.backend,
166
+ self.qubits,
167
+ coupling_map=coupling_map,
168
+ qiskit_optim_level=0,
169
+ optimize_sqg=False,
170
+ drop_final_rz=False,
171
+ )
172
+ circuit_gen_transp_time = perf_counter() - start_timer_data
173
+
174
+ # Saving raw and transpiled circuits in a consistent format with other benchmarks
175
+ self.untranspiled_circuits.update({str(self.qubits): {str(self.seq_len_list[-1]): self.raw_qc_list}})
176
+ self.transpiled_circuits.update({str(self.qubits): {str(self.seq_len_list[-1]): self.transpiled_qc_list}})
177
+ return circuit_gen_transp_time
178
+
179
+ def job_counts_to_mGST_format(self, result_dict):
180
+ """Turns the dictionary of outcomes obtained from qiskit backend
181
+ into the format which is used in mGST
182
+
183
+ Parameters
184
+ ----------
185
+ result_dict: (dict of str: int)
186
+
187
+ Returns
188
+ -------
189
+ y : numpy array
190
+ 2D array of measurement outcomes for sequences in J;
191
+ Each column contains the outcome probabilities for a fixed sequence
192
+
193
+ """
194
+ basis_dict_list = []
195
+ for result in result_dict:
196
+ # Translate dictionary entries of bitstring on the full system to the decimal representation of bitstrings on the active qubits
197
+ basis_dict = {
198
+ entry: int("".join([entry[::-1][i] for i in range(self.num_qubits)][::-1]), 2) for entry in result
199
+ }
200
+ # Sort by index:
201
+ basis_dict = dict(sorted(basis_dict.items(), key=lambda item: item[1]))
202
+ basis_dict_list.append(basis_dict)
203
+ y = []
204
+ for i in range(len(result_dict)):
205
+ row = [result_dict[i][key] for key in basis_dict_list[i]]
206
+ if len(row) < self.num_povm:
207
+ missing_entries = list(np.arange(self.num_povm))
208
+ for given_entry in basis_dict_list[i].values():
209
+ missing_entries.remove(given_entry)
210
+ for missing_entry in missing_entries:
211
+ row.insert(missing_entry, 0) # 0 measurement outcomes in not recorded entry
212
+ y.append(row / np.sum(row))
213
+ y = np.array(y).T
214
+ return y
215
+
216
+ def run_circuits(self):
217
+ """
218
+ Runs circuits on simulator/hardware and collects counts using utils functions.
219
+
220
+ Returns:
221
+ time_submit: float
222
+ The time it took to submit to jobs.
223
+ time_retrieve: float
224
+ The time it took to retrieve counts.
225
+ """
226
+
227
+ qcvv_logger.info(f"Now executing the corresponding circuit batch")
228
+ # Submit all circuits to execute
229
+ all_jobs, time_submit = submit_execute(
230
+ {tuple(self.qubits): self.transpiled_qc_list},
231
+ self.backend,
232
+ self.shots,
233
+ self.calset_id,
234
+ max_gates_per_batch=self.max_gates_per_batch,
235
+ )
236
+
237
+ # Retrieve counts - the precise outputs do not matter
238
+ all_counts, time_retrieve = retrieve_all_counts(all_jobs)
239
+ # Save counts - ensures counts were received and can be inspected
240
+ self.raw_data = all_counts
241
+ self.y = self.job_counts_to_mGST_format(self.raw_data)
242
+ # Retrieve and save all job metadata
243
+ all_job_metadata = retrieve_all_job_metadata(all_jobs)
244
+ self.job_meta = all_job_metadata
245
+
246
+ return time_submit, time_retrieve
247
+
248
+ def dataframe_to_figure(self, df, row_labels=None, col_width=2, fontsize=12):
249
+ """Turns a pandas DataFrame into a figure
250
+ This is needed to conform with the standard file saving routine of QCVV.
251
+
252
+ Args:
253
+ df: Pandas DataFrame
254
+ A dataframe table containing GST results
255
+ row_labels: List[str]
256
+ The row labels for the dataframe
257
+ precision: int
258
+ The number of digits for entries in the dataframe
259
+ col_width: int
260
+ Used to control cell width in the table
261
+ fontsize: int
262
+ Font size of text/numbers in table cells
263
+
264
+ Returns:
265
+ figure: Matplotlib figure object
266
+ A figure representing the dataframe.
267
+ """
268
+
269
+ if row_labels is None:
270
+ row_labels = np.arange(df.shape[0])
271
+
272
+ row_height = fontsize / 70 * 2
273
+ n_cols = df.shape[1]
274
+ n_rows = df.shape[0]
275
+ figsize = np.array([n_cols + 1, n_rows + 1]) * np.array([col_width, row_height])
276
+
277
+ fig, ax = plt.subplots(figsize=figsize)
278
+
279
+ fig.patch.set_visible(False)
280
+ ax.axis("off")
281
+ ax.axis("tight")
282
+ data_array = (df.to_numpy(dtype="str")).copy()
283
+ column_names = [str(column) for column in df.columns].copy()
284
+ table = ax.table(
285
+ cellText=data_array,
286
+ colLabels=column_names,
287
+ rowLabels=row_labels,
288
+ cellLoc="center",
289
+ colColours=["#7FA1C3" for _ in range(n_cols)],
290
+ bbox=[0, 0, 1, 1],
291
+ )
292
+ table.set_fontsize(fontsize)
293
+ table.set_figure(fig)
294
+ return fig
295
+
296
+ def bootstrap_errors(self, K, X, E, rho, target_mdl, parametric=False):
297
+ """Resamples circuit outcomes a number of times and computes GST estimates for each repetition
298
+ All results are then returned in order to compute bootstrap-error bars for GST estimates.
299
+ Parametric bootstrapping uses the estimated gate set to create a newly sampled data set.
300
+ Non-parametric bootstrapping uses the initial dataset and resamples according to the
301
+ corresp. outcome probabilities.
302
+ Each bootstrap run is initialized with the estimated gate set in order to save processing time.
303
+
304
+ Parameters
305
+ ----------
306
+ K : numpy array
307
+ Each subarray along the first axis contains a set of Kraus operators.
308
+ The second axis enumerates Kraus operators for a gate specified by the first axis.
309
+ X : 3D numpy array
310
+ Array where reconstructed CPT superoperators in standard basis are stacked along the first axis.
311
+ E : numpy array
312
+ Current POVM estimate
313
+ rho : numpy array
314
+ Current initial state estimate
315
+ target_mdl : pygsti model object
316
+ The target gate set
317
+ parametric : bool
318
+ If set to True, parametric bootstrapping is used, else non-parametric bootstrapping. Default: False
319
+
320
+ Returns
321
+ -------
322
+ X_array : numpy array
323
+ Array containing all estimated gate tensors of different bootstrapping repetitions along first axis
324
+ E_array : numpy array
325
+ Array containing all estimated POVM tensors of different bootstrapping repetitions along first axis
326
+ rho_array : numpy array
327
+ Array containing all estimated initial states of different bootstrapping repetitions along first axis
328
+ df_g_array : numpy array
329
+ Contains gate quality measures of bootstrapping repetitions
330
+ df_o_array : numpy array
331
+ Contains SPAM and other quality measures of bootstrapping repetitions
332
+
333
+ """
334
+ if parametric:
335
+ y = np.real(np.array([[E[i].conj() @ contract(X, j) @ rho for j in self.J] for i in range(self.num_povm)]))
336
+ else:
337
+ y = self.y
338
+ X_array = np.zeros((self.bootstrap_samples, *X.shape)).astype(complex)
339
+ E_array = np.zeros((self.bootstrap_samples, *E.shape)).astype(complex)
340
+ rho_array = np.zeros((self.bootstrap_samples, *rho.shape)).astype(complex)
341
+ df_g_list = []
342
+ df_o_list = []
343
+
344
+ for i in range(self.bootstrap_samples):
345
+ y_sampled = additional_fns.sampled_measurements(y, self.shots).copy()
346
+ _, X_, E_, rho_, _ = algorithm.run_mGST(
347
+ y_sampled,
348
+ self.J,
349
+ self.seq_len_list[-1],
350
+ self.num_gates,
351
+ self.pdim**2,
352
+ self.rank,
353
+ self.num_povm,
354
+ self.batch_size,
355
+ self.shots,
356
+ method=self.opt_method,
357
+ max_inits=self.max_inits,
358
+ max_iter=0,
359
+ final_iter=self.max_iterations[1],
360
+ threshold_multiplier=self.convergence_criteria[0],
361
+ target_rel_prec=self.convergence_criteria[1],
362
+ init=[K, E, rho],
363
+ testing=False,
364
+ )
365
+
366
+ X_opt, E_opt, rho_opt = reporting.gauge_opt(X_, E_, rho_, target_mdl, self.weights)
367
+ df_g, df_o = reporting.report(X_opt, E_opt, rho_opt, self.J, y_sampled, target_mdl, self.gate_labels)
368
+ df_g_list.append(df_g.values)
369
+ df_o_list.append(df_o.values)
370
+
371
+ X_opt_pp, E_opt_pp, rho_opt_pp = compatibility.std2pp(X_opt, E_opt, rho_opt)
372
+
373
+ X_array[i] = X_opt_pp
374
+ E_array[i] = E_opt_pp
375
+ rho_array[i] = rho_opt_pp
376
+
377
+ return (X_array, E_array, rho_array, np.array(df_g_list), np.array(df_o_list))
378
+
379
+ def generate_non_gate_results(self, df_o):
380
+ """
381
+ Creates error bars (if bootstrapping was used) and formats results for non-gate errors.
382
+ The resulting tables are also turned into figures, so that they can be saved automatically.
383
+
384
+ Args:
385
+ df_o: Pandas DataFrame
386
+ A dataframe containing the non-gate quality metrics (SPAM errors and fit quality)
387
+
388
+ Returns:
389
+ df_o_final: Oandas DataFrame
390
+ The final formatted results
391
+ """
392
+ # filename = f"{self.results_dir}{self.device_id}_{self.timestamp}_spam_errors.html"
393
+ if self.bootstrap_samples > 0:
394
+ _, _, _, _, df_o_array = self.bootstrap_results
395
+ df_o_array[df_o_array == -1] = np.nan
396
+ percentiles_o_low, percentiles_o_high = np.nanpercentile(df_o_array, [2.5, 97.5], axis=0)
397
+ df_o_final = DataFrame(
398
+ {
399
+ f"Mean TVD: estimate - data": reporting.number_to_str(
400
+ df_o.values[0, 1].copy(), [percentiles_o_high[0, 1], percentiles_o_low[0, 1]], precision=5
401
+ ),
402
+ f"Mean TVD: target - data": reporting.number_to_str(
403
+ df_o.values[0, 2].copy(), [percentiles_o_high[0, 2], percentiles_o_low[0, 2]], precision=5
404
+ ),
405
+ f"POVM - diamond dist.": reporting.number_to_str(
406
+ df_o.values[0, 3].copy(), [percentiles_o_high[0, 3], percentiles_o_low[0, 3]], precision=5
407
+ ),
408
+ f"State - trace dist.": reporting.number_to_str(
409
+ df_o.values[0, 4].copy(), [percentiles_o_high[0, 4], percentiles_o_low[0, 4]], precision=5
410
+ ),
411
+ },
412
+ index=[""],
413
+ )
414
+ else:
415
+ df_o_final = DataFrame(
416
+ {
417
+ f"Mean TVD: estimate - data": reporting.number_to_str(df_o.values[0, 1].copy(), precision=5),
418
+ f"Mean TVD: target - data": reporting.number_to_str(df_o.values[0, 2].copy(), precision=5),
419
+ f"POVM - diamond dist.": reporting.number_to_str(df_o.values[0, 3].copy(), precision=5),
420
+ f"State - trace dist.": reporting.number_to_str(df_o.values[0, 4].copy(), precision=5),
421
+ },
422
+ index=[""],
423
+ )
424
+ # fig = self.dataframe_to_figure(df_o_final, [""])#self.dataframe_to_figure(df_o_final, [""])
425
+ # self.figures["spam_and_outcome_errors"] = fig
426
+ return df_o_final
427
+
428
+ def generate_unit_rank_gate_results(self, df_g, X_opt, K_target):
429
+ """
430
+ Produces all result tables for Kraus rank 1 estimates and turns them into figures.
431
+
432
+ This includes parameters of the Hamiltonian generators in the Pauli basis for all gates,
433
+ as well as the usual performance metrics (Fidelities and Diamond distances). If bootstrapping
434
+ data is available, error bars will also be generated.
435
+
436
+ Args:
437
+ df_g: Pandas DataFrame
438
+ The dataframe with properly formatted results
439
+ X_opt: 3D numpy array
440
+ The gate set after gauge optimization
441
+ K_target: 4D numpy array
442
+ The Kraus operators of all target gates, used to compute distance measures.
443
+
444
+ Returns:
445
+ df_g_final: Pandas DataFrame
446
+ The dataframe with properly formatted results of standard gate errors
447
+ df_g_rotation Pandas DataFrame
448
+ A dataframe containing Hamiltonian (rotation) parameters
449
+
450
+ """
451
+ if self.bootstrap_samples > 0:
452
+ X_array, E_array, rho_array, df_g_array, _ = self.bootstrap_results
453
+ df_g_array[df_g_array == -1] = np.nan
454
+ percentiles_g_low, percentiles_g_high = np.nanpercentile(df_g_array, [2.5, 97.5], axis=0)
455
+
456
+ df_g_final = DataFrame(
457
+ {
458
+ r"Avg. gate fidelity": [
459
+ reporting.number_to_str(
460
+ df_g.values[i, 0], [percentiles_g_high[i, 0], percentiles_g_low[i, 0]], precision=5
461
+ )
462
+ for i in range(len(self.gate_labels))
463
+ ],
464
+ r"Diamond distance": [
465
+ reporting.number_to_str(
466
+ df_g.values[i, 1], [percentiles_g_high[i, 1], percentiles_g_low[i, 1]], precision=5
467
+ )
468
+ for i in range(self.num_gates)
469
+ ],
470
+ }
471
+ )
472
+
473
+ U_opt = reporting.phase_opt(X_opt, K_target)
474
+ pauli_coeffs = reporting.compute_sparsest_Pauli_Hamiltonian(U_opt)
475
+
476
+ bootstrap_pauli_coeffs = np.zeros((len(X_array), self.num_gates, self.pdim**2))
477
+ for i, X_ in enumerate(X_array):
478
+ X_std, _, _ = compatibility.pp2std(X_, E_array[i], rho_array[i])
479
+ U_opt_ = reporting.phase_opt(
480
+ np.array([change_basis(X_std[j], "pp", "std") for j in range(self.num_gates)]), K_target
481
+ )
482
+ pauli_coeffs_ = reporting.compute_sparsest_Pauli_Hamiltonian(U_opt_)
483
+ bootstrap_pauli_coeffs[i, :, :] = pauli_coeffs_
484
+ pauli_coeffs_low, pauli_coeffs_high = np.nanpercentile(bootstrap_pauli_coeffs, [2.5, 97.5], axis=0)
485
+
486
+ df_g_rotation = DataFrame(
487
+ np.array(
488
+ [
489
+ [
490
+ reporting.number_to_str(
491
+ pauli_coeffs[i, j], [pauli_coeffs_high[i, j], pauli_coeffs_low[i, j]], precision=5
492
+ )
493
+ for i in range(self.num_gates)
494
+ ]
495
+ for j in range(self.pdim**2)
496
+ ]
497
+ ).T
498
+ )
499
+
500
+ df_g_rotation.columns = [f"h_%s" % label for label in self.Pauli_labels]
501
+ df_g_rotation.rename(index=self.gate_labels, inplace=True)
502
+
503
+ else:
504
+ df_g_final = DataFrame(
505
+ {
506
+ "Avg. gate fidelity": [
507
+ reporting.number_to_str(df_g.values[i, 0], precision=5) for i in range(self.num_gates)
508
+ ],
509
+ "Diamond distance": [
510
+ reporting.number_to_str(df_g.values[i, 1], precision=5) for i in range(self.num_gates)
511
+ ],
512
+ }
513
+ )
514
+ U_opt = reporting.phase_opt(X_opt, K_target)
515
+ pauli_coeffs = reporting.compute_sparsest_Pauli_Hamiltonian(U_opt)
516
+
517
+ df_g_rotation = DataFrame(
518
+ np.array(
519
+ [
520
+ [reporting.number_to_str(pauli_coeffs[i, j], precision=5) for i in range(self.num_gates)]
521
+ for j in range(self.pdim**2)
522
+ ]
523
+ ).T
524
+ )
525
+ df_g_rotation.columns = [f"h_%s" % label for label in self.Pauli_labels]
526
+ df_g_final.rename(index=self.gate_labels, inplace=True)
527
+
528
+ # fig_g = self.dataframe_to_figure(df_g_final, self.gate_labels)
529
+ # fig_rotation = self.dataframe_to_figure(df_g_rotation, self.gate_labels)
530
+ # self.figures["gate_errors"] = fig_g
531
+ # self.figures["hamiltonian_parameters"] = fig_rotation
532
+ return df_g_final, df_g_rotation
533
+
534
+ def generate_gate_results(self, df_g, X_opt, E_opt, rho_opt, max_evals=6):
535
+ """
536
+ Produces all result tables for arbitrary Kraus rank estimates and turns them into figures.
537
+
538
+ Args:
539
+ df_g: Pandas DataFrame
540
+ The dataframe with properly formatted results
541
+ X_opt: 3D numpy array
542
+ The gate set after gauge optimization
543
+ E_opt: 3D numpy array
544
+ An array containg all the POVM elements as matrices after gauge optimization
545
+ rho_opt: 2D numpy array
546
+ The density matrix after gauge optmization
547
+ max_evals: int
548
+ The maximum number of eigenvalues of the Choi matrices which are returned.
549
+
550
+ Returns:
551
+ df_g_final: Pandas DataFrame
552
+ The dataframe with properly formatted results of standard gate errors
553
+ df_g_evals Pandas DataFrame
554
+ A dataframe containing eigenvalues of the Choi matrices for each gate
555
+
556
+ """
557
+ n_evals = np.min([max_evals, self.pdim**2])
558
+ X_opt_pp, _, _ = compatibility.std2pp(X_opt, E_opt, rho_opt)
559
+ df_g_evals = reporting.generate_Choi_EV_table(X_opt, n_evals, self.gate_labels).copy()
560
+
561
+ if self.bootstrap_samples > 0:
562
+ X_array, E_array, rho_array, df_g_array, _ = self.bootstrap_results
563
+ df_g_array[df_g_array == -1] = np.nan
564
+ percentiles_g_low, percentiles_g_high = np.nanpercentile(df_g_array, [2.5, 97.5], axis=0)
565
+ bootstrap_unitarities = np.array([reporting.unitarities(X_array[i]) for i in range(self.bootstrap_samples)])
566
+ percentiles_u_low, percentiles_u_high = np.nanpercentile(bootstrap_unitarities, [2.5, 97.5], axis=0)
567
+ X_array_std = [
568
+ compatibility.pp2std(X_array[i], E_array[i], rho_array[i])[0] for i in range(self.bootstrap_samples)
569
+ ]
570
+ bootstrap_evals = np.array(
571
+ [
572
+ reporting.generate_Choi_EV_table(X_array_std[i], n_evals, self.gate_labels)
573
+ for i in range(self.bootstrap_samples)
574
+ ]
575
+ )
576
+ percentiles_evals_low, percentiles_evals_high = np.nanpercentile(bootstrap_evals, [2.5, 97.5], axis=0)
577
+ eval_strs = [
578
+ [
579
+ reporting.number_to_str(
580
+ df_g_evals.values[i, j],
581
+ [percentiles_evals_high[i, j], percentiles_evals_low[i, j]],
582
+ precision=5,
583
+ )
584
+ for i in range(self.num_gates)
585
+ ]
586
+ for j in range(n_evals)
587
+ ]
588
+
589
+ df_g_final = DataFrame(
590
+ {
591
+ r"Avg. gate fidelity": [
592
+ reporting.number_to_str(
593
+ df_g.values[i, 0], [percentiles_g_high[i, 0], percentiles_g_low[i, 0]], precision=5
594
+ )
595
+ for i in range(self.num_gates)
596
+ ],
597
+ r"Diamond distance": [
598
+ reporting.number_to_str(
599
+ df_g.values[i, 1], [percentiles_g_high[i, 1], percentiles_g_low[i, 1]], precision=5
600
+ )
601
+ for i in range(self.num_gates)
602
+ ],
603
+ r"Unitarity": [
604
+ reporting.number_to_str(
605
+ reporting.unitarities(X_opt_pp)[i],
606
+ [percentiles_u_high[i], percentiles_u_low[i]],
607
+ precision=5,
608
+ )
609
+ for i in range(self.num_gates)
610
+ ],
611
+ }
612
+ )
613
+
614
+ else:
615
+ df_g_final = DataFrame(
616
+ {
617
+ "Avg. gate fidelity": [
618
+ reporting.number_to_str(df_g.values[i, 0].copy(), precision=5)
619
+ for i in range(len(self.gate_labels))
620
+ ],
621
+ "Diamond distance": [
622
+ reporting.number_to_str(df_g.values[i, 1].copy(), precision=5)
623
+ for i in range(len(self.gate_labels))
624
+ ],
625
+ "Unitarity": [
626
+ reporting.number_to_str(reporting.unitarities(X_opt_pp)[i], precision=5)
627
+ for i in range(len(self.gate_labels))
628
+ ],
629
+ # "Entanglement fidelity to depol. channel": [reporting.number_to_str(reporting.eff_depol_params(X_opt_pp)[i], precision=5)
630
+ # for i in range(len(gate_labels))],
631
+ # "Min. spectral distances": [number_to_str(df_g.values[i, 2], precision=5) for i in range(len(gate_labels))]
632
+ }
633
+ )
634
+ eval_strs = [
635
+ [reporting.number_to_str(df_g_evals.values[i, j].copy(), precision=5) for i in range(self.num_gates)]
636
+ for j in range(n_evals)
637
+ ]
638
+
639
+ df_g_evals_final = DataFrame(eval_strs).T
640
+ df_g_evals_final.rename(index=self.gate_labels, inplace=True)
641
+
642
+ # fig_g = self.dataframe_to_figure(df_g_final, self.gate_labels)
643
+ # fig_choi = self.dataframe_to_figure(df_g_evals_final, self.gate_labels)
644
+ # self.figures["gate_errors"] = fig_g
645
+ # self.figures["choi_evals"] = fig_choi
646
+ return df_g_final, df_g_evals_final
647
+
648
+ @timeit
649
+ def execute_full_benchmark(self):
650
+ """
651
+ The main GST execution routine
652
+
653
+ The following steps are executed:
654
+ 1) Generation of circuits to measure
655
+ 2) Running of said circuits on the backend
656
+ 3) Conversion of the ideal gate set into numpy arrays to be processed by mGST
657
+ 4) Optional generation of an initialization for mGST
658
+ 5) Running mGST to get first gate set estimates
659
+ 6) Gauge optimization to the target gates using pyGSTi"s gauge optimizer
660
+ 7) Generation of error measures and result tables
661
+ 8) Generation of matrix plots for the reconstructed gate set
662
+ """
663
+
664
+ qcvv_logger.info(f"Now generating {self.num_circuits} random circuits on qubits {self.qubits}")
665
+
666
+ # Generate and run circuits on backend
667
+ circuit_gen_transp_time = self.generate_meas_circuits()
668
+
669
+ submit_time, retrieve_time = self.run_circuits()
670
+ # data_collection_time = submit_time + retrieve_time
671
+
672
+ start_timer_GST = perf_counter()
673
+ K_target = qiskit_gate_to_operator(self.gate_set)
674
+ X_target = np.einsum("ijkl,ijnm -> iknlm", K_target, K_target.conj()).reshape(
675
+ (self.num_gates, self.pdim**2, self.pdim**2)
676
+ ) # tensor of superoperators
677
+
678
+ rho_target = (
679
+ np.kron(additional_fns.basis(self.pdim, 0).T.conj(), additional_fns.basis(self.pdim, 0))
680
+ .reshape(-1)
681
+ .astype(np.complex128)
682
+ )
683
+
684
+ # Computational basis measurement:
685
+ E_target = np.array(
686
+ [
687
+ np.kron(additional_fns.basis(self.pdim, i).T.conj(), additional_fns.basis(self.pdim, i)).reshape(-1)
688
+ for i in range(self.pdim)
689
+ ]
690
+ ).astype(np.complex128)
691
+ target_mdl = compatibility.arrays_to_pygsti_model(X_target, E_target, rho_target, basis="std")
692
+
693
+ # Run mGST
694
+ if self.from_init:
695
+ K_init = additional_fns.perturbed_target_init(X_target, self.rank)
696
+ init_params = [K_init, E_target, rho_target]
697
+ else:
698
+ init_params = None
699
+
700
+ K, X, E, rho, _ = algorithm.run_mGST(
701
+ self.y,
702
+ self.J,
703
+ self.seq_len_list[-1],
704
+ self.num_gates,
705
+ self.pdim**2,
706
+ self.rank,
707
+ self.num_povm,
708
+ self.batch_size,
709
+ self.shots,
710
+ method=self.opt_method,
711
+ max_inits=self.max_inits,
712
+ max_iter=self.max_iterations[0],
713
+ final_iter=self.max_iterations[1],
714
+ threshold_multiplier=self.convergence_criteria[0],
715
+ target_rel_prec=self.convergence_criteria[1],
716
+ init=init_params,
717
+ testing=False,
718
+ )
719
+
720
+ main_mGST_time = perf_counter() - start_timer_GST
721
+ start_timer_gauge = perf_counter()
722
+
723
+ # Gauge optimization
724
+ X_opt, E_opt, rho_opt = reporting.gauge_opt(X, E, rho, target_mdl, self.weights)
725
+ gauge_optimization_time = perf_counter() - start_timer_gauge
726
+
727
+ # Quick report
728
+ start_timer_report = perf_counter()
729
+ df_g, df_o = reporting.quick_report(X_opt, E_opt, rho_opt, self.J, self.y, target_mdl, self.gate_labels)
730
+ report_gen_time = perf_counter() - start_timer_report
731
+
732
+ ### Bootstrap
733
+ if self.bootstrap_samples > 0:
734
+ self.bootstrap_results = self.bootstrap_errors(K, X, E, rho, target_mdl)
735
+
736
+ _, df_o_full = reporting.report(X_opt, E_opt, rho_opt, self.J, self.y, target_mdl, self.gate_labels)
737
+ df_o_final = self.generate_non_gate_results(df_o_full)
738
+
739
+ ### Result table generation
740
+ if self.rank == 1:
741
+ df_g_final, df_g_rotation = self.generate_unit_rank_gate_results(df_g, X_opt, K_target)
742
+ self.results.update({"hamiltonian_parameters": df_g_rotation.to_dict()})
743
+ else:
744
+ df_g_final, df_g_evals = self.generate_gate_results(df_g, X_opt, E_opt, rho_opt)
745
+ self.results.update({"choi_evals": df_g_evals.to_dict()})
746
+
747
+ # Saving results
748
+ self.results.update(
749
+ {
750
+ "qubit_set": self.qubits,
751
+ "quick_metrics": {"Gates": df_g.to_dict(), "Outcomes and SPAM": df_o.to_dict()},
752
+ "full_metrics": {"Gates": df_g_final.to_dict(), "Outcomes and SPAM": df_o_final.to_dict()},
753
+ "main_mGST_time": main_mGST_time,
754
+ "submit_time": submit_time,
755
+ "retrieve_time": retrieve_time,
756
+ "gauge_optimization_time": gauge_optimization_time,
757
+ "circuit_generation_time": circuit_gen_transp_time,
758
+ }
759
+ )
760
+
761
+ self.raw_results.update(
762
+ {
763
+ "raw_Kraus_operators": K,
764
+ "raw_gates": X,
765
+ "raw_POVM": E.reshape((self.num_povm, self.pdim, self.pdim)),
766
+ "raw_state": rho.reshape((self.pdim, self.pdim)),
767
+ "gauge_opt_gates": X_opt,
768
+ "gauge_opt_POVM": E_opt.reshape((self.num_povm, self.pdim, self.pdim)),
769
+ "gauge_opt_state": rho_opt.reshape((self.pdim, self.pdim)),
770
+ "mGST_circuits": self.J,
771
+ "mGST_outcome_probs": self.y,
772
+ }
773
+ )
774
+
775
+ ### Process matrix plots
776
+ X_opt_pp, _, _ = compatibility.std2pp(X_opt, E_opt, rho_opt)
777
+ X_target_pp, _, _ = compatibility.std2pp(X_target, E_target, rho_target)
778
+
779
+ figures = figure_gen.generate_gate_err_pdf(
780
+ "", X_opt_pp, X_target_pp, basis_labels=self.Pauli_labels, gate_labels=self.gate_labels, return_fig=True
781
+ )
782
+ for i, figure in enumerate(figures):
783
+ self.figures[f"process_mat_plot_%i" % i] = figure
784
+
785
+ self.figures["SPAM_matrices_real"] = figure_gen.generate_spam_err_std_pdf(
786
+ "",
787
+ E_opt.real,
788
+ rho_opt.real,
789
+ E_target.real,
790
+ rho_target.real,
791
+ basis_labels=self.std_labels,
792
+ title=f"Real part of state and measurement effects in the standard basis",
793
+ return_fig=True,
794
+ )
795
+ self.figures["SPAM_matrices_imag"] = figure_gen.generate_spam_err_std_pdf(
796
+ "",
797
+ E_opt.imag,
798
+ rho_opt.imag,
799
+ E_target.imag,
800
+ rho_target.imag,
801
+ basis_labels=self.std_labels,
802
+ title=f"Imaginary part of state and measurement effects in the standard basis",
803
+ return_fig=True,
804
+ )
805
+ # plt.show()
806
+ plt.close("all")
807
+
808
+ ## Result display
809
+ total_pp_time = main_mGST_time + gauge_optimization_time + report_gen_time
810
+ qcvv_logger.info(
811
+ f"Results for {self.backend.name} with qubits "
812
+ f"{self.qubits}\nTotal GST post-processing time: {total_pp_time / 60:.2f} min\nOverview results: \n"
813
+ + df_g.to_string()
814
+ + f"\n"
815
+ + df_o.to_string()
816
+ )
817
+
818
+
819
+ class GSTConfiguration(BenchmarkConfigurationBase):
820
+ """Compressive GST configuration base.
821
+
822
+ Attributes:
823
+ benchmark (Type[Benchmark]): GHZBenchmark
824
+ qubit_layouts (Sequence[Sequence[int]]): A sequence (e.g., Tuple or List) of sequences of
825
+ physical qubit layouts, as specified by integer labels, where the benchmark is meant to be run.
826
+ gate_set (Union[str, List[Type[QuantumCircuit]]]): The gate set in given either as a list of qiskit quantum
827
+ cirucuits, or as one of the predefined gate sets "1QXYI", "2QXYCZ", "2QXYCZ_extended", "3QXYCZ".
828
+ A gate set should be tomographically complete, meaning from the specified gates and the vacuum state, one
829
+ should be able to prepare states that form a frame for the state space. A practical sufficient condition
830
+ is that the gate set is able the generate all combinations of local Pauli eigenstates.
831
+ num_circuits (int): How many random circuits are generated from the gate set. Guidelines on choosing this value:
832
+ - At least 50 circuits for a single qubit gate set with 3 gates.
833
+ - At least 400 circuits for a two qubit gate set with 6 gates.
834
+ - At least 2000 circuits for a three qubit gate set with 9 gates.
835
+ The number of random circuits needed is expected to grow linearly in the number of gates and exponentially
836
+ in the number of qubits.
837
+ rank (int): The Kraus rank of the reconstruction. Choose rank=1 for coherent error analysis and rank<=dim**2
838
+ generally.
839
+ shots (int): The number of measurement shots per circuit.
840
+ * Default: 1024
841
+ gate_labels (Union[Dict, None]): Names of the gates in the gate set. Used for plots and result tables.
842
+ * Default: None
843
+ seq_len_list (list[int]): Three numbers controling the depth of the random sequences. The first is the minimal
844
+ depth, the last is the maximal depth and the middle number specifies a cutoff depth below which all possible
845
+ sequences are selected.
846
+ * Default: [1, 8, 14]
847
+ from_init (bool): Whether the target gate set is used as an initialization to the mGST algorithm.
848
+ * Default: True
849
+ max_inits (int): If from_init = False, random initial points are tried and this parameter limits the amount of
850
+ retries.
851
+ * Default: 20
852
+ opt_method (str): Which optimization method is used, can be either of "GD" or "SFN", for gradient descent or
853
+ saddle free Newton, respectively.
854
+ * Default: "auto" (Method is automatically selected based on qubit count and rank)
855
+ max_iterations (Union[str, List[int]]): How many iterations to run the optimization algorithm for. Accepted
856
+ values are either "auto" or a list of two integers. The first specifies the number of iterations for the
857
+ optimization on batches or circuit data, while the second specifies the number of iterations on the full
858
+ data for all circuits.
859
+ * Default: "auto" (Numbers are automatically selected based on qubit count and rank)
860
+ convergence_criteria (Union[str, List[float]]): Two parameters which determine when the optimization algorithm
861
+ terminates. The first is a multiplier which specifies how close the cost function should get to a threshold
862
+ for the given shot noise in order for the optimization to be considered "successful". Values in [2,10] are
863
+ usually sensible. The second parameter sets the relative change in cost function between
864
+ two consecutive iterations, below which the algorithm terminates.
865
+ * Default: [4, 1e-4]
866
+ batch_size (Union[str, int]): The number of circuits per batch in the optimization. This hyperparamters is
867
+ automatically set and determines the convergence behaviour. A smaller batch size reduces runtime but can
868
+ lead to erratic jumps in parameter space and lack of convergence.
869
+ * Default: "auto"
870
+ bootstrap_samples (int): The number of times the optimization algorithm is repeated on fake data to estimate
871
+ the uncertainty via bootstrapping.
872
+ """
873
+
874
+ benchmark: Type[BenchmarkBase] = compressive_GST
875
+ qubits: List[Union[int, List[int]]]
876
+ gate_set: Union[str, List[Type[QuantumCircuit]]]
877
+ num_circuits: int
878
+ shots: int
879
+ rank: int
880
+ gate_labels: Union[Dict, None] = None
881
+ seq_len_list: list = [1, 8, 14]
882
+ from_init: bool = True
883
+ max_inits: int = 10
884
+ opt_method: str = "auto"
885
+ max_iterations: Union[str, List[int]] = "auto"
886
+ convergence_criteria: List[float] = [4, 1e-4]
887
+ batch_size: Union[str, int] = "auto"
888
+ bootstrap_samples: int = 0
889
+
890
+
891
+ def parse_gate_set(configuration):
892
+ """
893
+ Handles different gate set inputs and produces a valid gate set
894
+
895
+ Args:
896
+ configuration: BenchmarkConfigurationBase
897
+ Configuration class containing variables
898
+
899
+ Returns:
900
+ gate_set: List[QuantumCircuit]
901
+ A list of gates defined as quantum circuit objects
902
+ gate_labels: Dict[int: str]
903
+ A dictionary with gate names
904
+ num_gates: int
905
+ The number of gates in the gate set
906
+
907
+ """
908
+ if isinstance(configuration.gate_set, str) and configuration.gate_set not in [
909
+ "1QXYI",
910
+ "2QXYCZ",
911
+ "2QXYCZ_extended",
912
+ "3QXYCZ",
913
+ ]:
914
+ raise ValueError(
915
+ "No gate set of the specified name is implemented, please choose among "
916
+ "1QXYI, 2QXYCZ, 2QXYCZ_extended, 3QXYCZ."
917
+ )
918
+ if configuration.gate_set in ["1QXYI", "2QXYCZ", "2QXYCZ_extended", "3QXYCZ"]:
919
+ gate_set, gate_labels = create_predefined_gate_set(configuration.gate_set, configuration.qubits)
920
+ num_gates = len(gate_set)
921
+ return gate_set, gate_labels, num_gates
922
+
923
+ if isinstance(configuration.gate_set, list):
924
+ gate_set = configuration.gate_set
925
+ num_gates = len(gate_set)
926
+ if configuration.gate_labels is None:
927
+ gate_labels = {i: f"Gate %i" % i for i in range(num_gates)}
928
+ else:
929
+ if configuration.gate_labels:
930
+ if len(configuration.gate_labels) != num_gates:
931
+ raise ValueError(
932
+ f"The number of gate labels (%i) does not match the number of gates (%i)"
933
+ % (len(configuration.gate_labels), num_gates)
934
+ )
935
+ gate_labels = configuration.gate_labels
936
+ return gate_set, gate_labels, num_gates
937
+
938
+ raise ValueError(
939
+ f"Invalid gate set, choose among 1QXYI, 2QXYCZ, 2QXYCZ_extended,"
940
+ f" 3QXYCZ or provide a list of Qiskti circuits to define the gates."
941
+ )
942
+
943
+
944
+ def create_predefined_gate_set(gate_set, qubits) -> Tuple[List[QuantumCircuit], Dict]:
945
+ """Create a list of quantum circuits corresponding to a predefined gate set.
946
+
947
+ The circuits are assigned to the specified qubits on the backend only during transipilation, so the qubit labels
948
+ at this stage may not represent the actual qubit labels on the backend.
949
+ Args:
950
+ gate_set: str
951
+ The name of the gate set
952
+ qubits: List[int]
953
+ The qubits on the backend where the gates are defined on
954
+ Returns:
955
+ gates: List[QuantumCircuit]
956
+ The gate set as a list of circuits
957
+ gate_labels_dict: Dict[str]
958
+ The names of gates, i.e. "Rx(pi/2)" for a pi/2 rotation around the x-axis.
959
+
960
+ """
961
+ num_qubits = len(qubits)
962
+ qubit_mapping = dict(enumerate(qubits))
963
+ unmapped_qubits = list(np.arange(num_qubits))
964
+
965
+ if gate_set == "1QXYI":
966
+ gate_list = [RGate(1e-10, 0), RGate(0.5 * np.pi, 0), RGate(0.5 * np.pi, np.pi / 2)]
967
+ gates = [QuantumCircuit(num_qubits, 0) for _ in range(len(gate_list))]
968
+ gate_qubits = [[0], [0], [0]]
969
+ for i, gate in enumerate(gate_list):
970
+ gates[i].append(gate, gate_qubits[i])
971
+ gate_labels = ["Idle", "Rx(pi/2)", "Ry(pi/2)"]
972
+ elif gate_set == "2QXYCZ":
973
+ gate_qubits = [[0], [1], [0], [1], [0, 1]]
974
+ gates = [QuantumCircuit(num_qubits, 0) for _ in range(5)]
975
+ gates[0].append(RGate(0.5 * np.pi, 0), [0])
976
+ gates[1].append(RGate(0.5 * np.pi, 0), [1])
977
+ gates[2].append(RGate(0.5 * np.pi, np.pi / 2), [0])
978
+ gates[3].append(RGate(0.5 * np.pi, np.pi / 2), [1])
979
+ gates[4].append(CZGate(), [0, 1])
980
+ gate_labels = ["Rx(pi/2)", "Rx(pi/2)", "Ry(pi/2)", "Ry(pi/2)", "CZ"]
981
+ elif gate_set == "2QXYCZ_extended":
982
+ gate_qubits = [[0], [1], [0], [1], [0, 1], [0, 1], [0, 1], [0, 1], [0, 1]]
983
+ gates = [QuantumCircuit(num_qubits, 0) for _ in range(9)]
984
+ gates[0].append(RGate(0.5 * np.pi, 0), [0])
985
+ gates[1].append(RGate(0.5 * np.pi, 0), [1])
986
+ gates[2].append(RGate(0.5 * np.pi, np.pi / 2), [0])
987
+ gates[3].append(RGate(0.5 * np.pi, np.pi / 2), [1])
988
+ gates[4].append(RGate(0.5 * np.pi, 0), [0])
989
+ gates[4].append(RGate(0.5 * np.pi, 0), [1])
990
+ gates[5].append(RGate(0.5 * np.pi, 0), [0])
991
+ gates[5].append(RGate(0.5 * np.pi, np.pi / 2), [1])
992
+ gates[6].append(RGate(0.5 * np.pi, np.pi / 2), [0])
993
+ gates[6].append(RGate(0.5 * np.pi, 0), [1])
994
+ gates[7].append(RGate(0.5 * np.pi, np.pi / 2), [0])
995
+ gates[7].append(RGate(0.5 * np.pi, np.pi / 2), [1])
996
+ gates[8].append(CZGate(), [[0], [1]])
997
+ gate_labels = [
998
+ "Rx(pi/2)",
999
+ "Rx(pi/2)",
1000
+ "Ry(pi/2)",
1001
+ "Ry(pi/2)",
1002
+ "Rx(pi/2)--Rx(pi/2)",
1003
+ "Rx(pi/2)--Ry(pi/2)",
1004
+ "Ry(pi/2)--Rx(pi/2)",
1005
+ "Ry(pi/2)--Ry(pi/2)",
1006
+ "CZ",
1007
+ ]
1008
+ elif gate_set == "3QXYCZ":
1009
+ gate_list = [
1010
+ RGate(0.5 * np.pi, 0),
1011
+ RGate(0.5 * np.pi, 0),
1012
+ RGate(0.5 * np.pi, 0),
1013
+ RGate(0.5 * np.pi, np.pi / 2),
1014
+ RGate(0.5 * np.pi, np.pi / 2),
1015
+ RGate(0.5 * np.pi, np.pi / 2),
1016
+ CZGate(),
1017
+ CZGate(),
1018
+ ]
1019
+ gates = [QuantumCircuit(num_qubits, 0) for _ in range(len(gate_list))]
1020
+ gate_qubits = [[0], [1], [2], [0], [1], [2], [0, 1], [0, 2]]
1021
+ for i, gate in enumerate(gate_list):
1022
+ gates[i].append(gate, gate_qubits[i])
1023
+ gate_labels = ["Rx(pi/2)", "Rx(pi/2)", "Rx(pi/2)", "Ry(pi/2)", "Ry(pi/2)", "Ry(pi/2)", "CZ", "CZ"]
1024
+ gates = add_idle_gates(gates, unmapped_qubits, gate_qubits)
1025
+ gates = [remove_idle_wires(qc) for qc in gates]
1026
+ gate_qubits_mapped = [[qubit_mapping[i] for i in qlist] for qlist in gate_qubits]
1027
+ gate_labels = [gate_labels[i] + ":" + str(gate_qubits_mapped[i]) for i in range(len(gates))]
1028
+ gate_label_dict = {i: gate_labels[i] for i in range(len(gate_labels))}
1029
+ return gates, gate_label_dict