iqm-benchmarks 2.21__py3-none-any.whl → 2.23__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.

@@ -23,6 +23,7 @@ from typing import Dict, Literal, Optional, OrderedDict, Type
23
23
  from matplotlib.figure import Figure
24
24
  from pydantic import BaseModel
25
25
 
26
+ from iqm.iqm_client.models import DDStrategy
26
27
  from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
27
28
 
28
29
 
@@ -99,6 +100,8 @@ class BenchmarkConfigurationBase(BaseModel):
99
100
  - "fixed": physical layout is constrained during transpilation to the selected initial physical qubits.
100
101
  - "batching": physical layout is allowed to use any other physical qubits, and circuits are batched according to final measured qubits.
101
102
  * Default for all benchmarks is "fixed".
103
+ use_DD (bool): Boolean flag determining if dynamical decoupling is enabled during circuit execution
104
+ * Default: False
102
105
  """
103
106
 
104
107
  benchmark: Type[BenchmarkBase]
@@ -107,3 +110,5 @@ class BenchmarkConfigurationBase(BaseModel):
107
110
  calset_id: Optional[str] = None
108
111
  routing_method: Literal["basic", "lookahead", "stochastic", "sabre", "none"] = "sabre"
109
112
  physical_layout: Literal["fixed", "batching"] = "fixed"
113
+ use_dd: Optional[bool] = False
114
+ dd_strategy: Optional[DDStrategy] = None
@@ -30,7 +30,9 @@ import xarray as xr
30
30
 
31
31
  from iqm.benchmarks.benchmark import BenchmarkConfigurationBase
32
32
  from iqm.benchmarks.circuit_containers import BenchmarkCircuit, Circuits
33
+ from iqm.benchmarks.logging_config import qcvv_logger
33
34
  from iqm.benchmarks.utils import get_iqm_backend, timeit
35
+ from iqm.iqm_client.models import CircuitCompilationOptions, DDMode
34
36
  from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
35
37
  from iqm.qiskit_iqm.iqm_provider import IQMBackend, IQMFacadeBackend
36
38
 
@@ -245,6 +247,18 @@ class Benchmark(ABC):
245
247
  self.options.update(kwargs)
246
248
  self.runs: list[BenchmarkRunResult] = []
247
249
 
250
+ # Circuit compilation options
251
+ if self.configuration.use_dd:
252
+ if self.name in ["compressive_GST", "mirror_rb", "interleaved_clifford_rb", "clifford_rb"]:
253
+ qcvv_logger.warning(
254
+ f"Beware that activating dynamical decoupling will change fidelities, error models and their interpretation."
255
+ )
256
+ self.circuit_compilation_options = CircuitCompilationOptions(
257
+ dd_mode=DDMode.ENABLED, dd_strategy=self.configuration.dd_strategy
258
+ )
259
+ else:
260
+ self.circuit_compilation_options = CircuitCompilationOptions(dd_mode=DDMode.DISABLED)
261
+
248
262
  @classmethod
249
263
  @abstractmethod
250
264
  def name(cls):
@@ -102,10 +102,9 @@ class CompressiveGST(Benchmark):
102
102
 
103
103
  # Circuit format used by mGST
104
104
  self.J = np.empty((self.configuration.num_circuits, self.num_povm))
105
- self.bootstrap_results = List[Tuple[np.ndarray]] # List of GST outcomes from bootstrapping
106
105
 
107
106
  @timeit
108
- def generate_meas_circuits(self) -> None:
107
+ def generate_meas_circuits(self) -> tuple[BenchmarkCircuit, BenchmarkCircuit]:
109
108
  """Generate random circuits from the gate set
110
109
 
111
110
  The random circuits are distributed among different depths ranging from L_MIN
@@ -115,10 +114,17 @@ class CompressiveGST(Benchmark):
115
114
  meaningful results
116
115
 
117
116
  Returns:
117
+ transpiled_circuits: BenchmarkCircuit
118
+ The transpiled circuits
119
+ untranspiled_circuits: BenchmarkCircuit
120
+ The untranspiled circuits
118
121
  circuit_gen_transp_time: float
119
122
  The time it took to generate and transpile the circuits
120
-
121
123
  """
124
+
125
+ transpiled_circuits = BenchmarkCircuit(name="transpiled_circuits")
126
+ untranspiled_circuits = BenchmarkCircuit(name="untranspiled_circuits")
127
+
122
128
  # Calculate number of short and long circuits
123
129
  N_short = int(np.ceil(self.configuration.num_circuits / 2))
124
130
  N_long = int(np.floor(self.configuration.num_circuits / 2))
@@ -142,25 +148,54 @@ class CompressiveGST(Benchmark):
142
148
  f"Transpilation on star-architectures currently allows move gates to transit barriers, "
143
149
  f"leading to context-dependent gates which GST can not accurately resolve."
144
150
  )
145
- for qubits in self.qubit_layouts:
146
- coupling_map = set_coupling_map(qubits, self.backend, physical_layout="fixed")
147
151
 
148
- # Perform transpilation to backend
149
- qcvv_logger.info(
150
- f"Will transpile all {self.configuration.num_circuits} circuits according to fixed physical layout"
151
- )
152
+ # Perform transpilation to backend
153
+ qcvv_logger.info(
154
+ f"Will transpile all {self.configuration.num_circuits} circuits according to fixed physical layout"
155
+ )
156
+ if self.configuration.parallel_execution:
157
+ all_qubits = [qubit for layout in self.qubit_layouts for qubit in layout]
158
+ if len(all_qubits) != len(set(all_qubits)):
159
+ raise ValueError(
160
+ "Qubit layouts can't overlap when parallel_execution is enabled, please choose non-overlapping layouts."
161
+ )
162
+ raw_qc_list_parallel = []
163
+ for circ in raw_qc_list:
164
+ circ_parallel = QuantumCircuit(self.backend.num_qubits, len(set(all_qubits)))
165
+ clbits = np.arange(self.num_qubits)
166
+ for qubit_layout in self.qubit_layouts:
167
+ circ_parallel.compose(circ, qubits=qubit_layout, clbits=clbits, inplace=True)
168
+ clbits += self.num_qubits
169
+ raw_qc_list_parallel.append(circ_parallel)
152
170
  transpiled_qc_list, _ = perform_backend_transpilation(
153
- raw_qc_list,
171
+ raw_qc_list_parallel,
154
172
  self.backend,
155
- qubits,
156
- coupling_map=coupling_map,
173
+ qubits=np.arange(self.backend.num_qubits),
174
+ coupling_map=self.backend.coupling_map,
157
175
  qiskit_optim_level=0,
158
176
  optimize_sqg=False,
159
177
  drop_final_rz=False,
160
178
  )
161
- # Saving raw and transpiled circuits in a consistent format with other benchmarks
162
- self.transpiled_circuits.circuit_groups.append(CircuitGroup(name=str(qubits), circuits=transpiled_qc_list))
163
- self.untranspiled_circuits.circuit_groups.append(CircuitGroup(name=str(qubits), circuits=raw_qc_list))
179
+ for qubits in self.qubit_layouts:
180
+ # Saving raw and transpiled circuits in a consistent format with other benchmarks
181
+ transpiled_circuits.circuit_groups.append(CircuitGroup(name=str(qubits), circuits=transpiled_qc_list))
182
+ untranspiled_circuits.circuit_groups.append(CircuitGroup(name=str(qubits), circuits=raw_qc_list))
183
+ else:
184
+ for qubits in self.qubit_layouts:
185
+ coupling_map = set_coupling_map(qubits, self.backend, physical_layout="fixed")
186
+ transpiled_qc_list, _ = perform_backend_transpilation(
187
+ raw_qc_list,
188
+ self.backend,
189
+ qubits,
190
+ coupling_map=coupling_map,
191
+ qiskit_optim_level=0,
192
+ optimize_sqg=False,
193
+ drop_final_rz=False,
194
+ )
195
+ # Saving raw and transpiled circuits in a consistent format with other benchmarks
196
+ transpiled_circuits.circuit_groups.append(CircuitGroup(name=str(qubits), circuits=transpiled_qc_list))
197
+ untranspiled_circuits.circuit_groups.append(CircuitGroup(name=str(qubits), circuits=raw_qc_list))
198
+ return transpiled_circuits, untranspiled_circuits
164
199
 
165
200
  def add_configuration_to_dataset(self, dataset): # CHECK
166
201
  """
@@ -190,30 +225,45 @@ class CompressiveGST(Benchmark):
190
225
  qcvv_logger.info(f"Now generating {self.configuration.num_circuits} random GST circuits...")
191
226
 
192
227
  self.circuits = Circuits()
193
- self.transpiled_circuits = BenchmarkCircuit(name="transpiled_circuits")
194
- self.untranspiled_circuits = BenchmarkCircuit(name="untranspiled_circuits")
195
228
  # Generate circuits
196
- self.generate_meas_circuits()
229
+ (transpiled_circuits, untranspiled_circuits), _ = self.generate_meas_circuits()
197
230
 
198
231
  # Submit all
199
- all_jobs: Dict = {}
200
- for qubit_layout in self.qubit_layouts:
201
- transpiled_circuit_dict = {tuple(qubit_layout): self.transpiled_circuits[str(qubit_layout)].circuits}
202
- all_jobs[str(qubit_layout)], _ = submit_execute(
232
+ if self.configuration.parallel_execution:
233
+ transpiled_circuit_dict = {
234
+ tuple(range(self.backend.num_qubits)): transpiled_circuits[str(self.qubit_layouts[0])].circuits
235
+ }
236
+ all_jobs_parallel, _ = submit_execute(
203
237
  transpiled_circuit_dict,
204
238
  backend,
205
239
  self.configuration.shots,
206
240
  self.calset_id,
207
241
  max_gates_per_batch=self.configuration.max_gates_per_batch,
242
+ circuit_compilation_options=self.circuit_compilation_options,
208
243
  )
209
- # Retrieve all
210
- qcvv_logger.info(f"Now executing the corresponding circuit batch")
211
- for qubit_layout in self.qubit_layouts:
212
- counts, _ = retrieve_all_counts(all_jobs[str(qubit_layout)])
213
- dataset, _ = add_counts_to_dataset(counts, str(qubit_layout), dataset)
244
+ # Retrieve
245
+ qcvv_logger.info(f"Now executing the corresponding circuit batch")
246
+ counts, _ = retrieve_all_counts(all_jobs_parallel)
247
+ dataset, _ = add_counts_to_dataset(counts, f"parallel_results", dataset)
248
+ else:
249
+ all_jobs: Dict = {}
250
+ for qubit_layout in self.qubit_layouts:
251
+ transpiled_circuit_dict = {tuple(qubit_layout): transpiled_circuits[str(qubit_layout)].circuits}
252
+ all_jobs[str(qubit_layout)], _ = submit_execute(
253
+ transpiled_circuit_dict,
254
+ backend,
255
+ self.configuration.shots,
256
+ self.calset_id,
257
+ max_gates_per_batch=self.configuration.max_gates_per_batch,
258
+ )
259
+ # Retrieve all
260
+ qcvv_logger.info(f"Now executing the corresponding circuit batch")
261
+ for qubit_layout in self.qubit_layouts:
262
+ counts, _ = retrieve_all_counts(all_jobs[str(qubit_layout)])
263
+ dataset, _ = add_counts_to_dataset(counts, str(qubit_layout), dataset)
214
264
 
265
+ self.circuits.benchmark_circuits = [transpiled_circuits, untranspiled_circuits]
215
266
  self.add_configuration_to_dataset(dataset)
216
- self.circuits.benchmark_circuits = [self.transpiled_circuits, self.untranspiled_circuits]
217
267
  return dataset
218
268
 
219
269
 
@@ -270,6 +320,7 @@ class GSTConfiguration(BenchmarkConfigurationBase):
270
320
  * Default: "auto"
271
321
  bootstrap_samples (int): The number of times the optimization algorithm is repeated on fake data to estimate
272
322
  the uncertainty via bootstrapping.
323
+ parallel_execution (bool): Whether to run the circuits for all layouts in parallel on the backend.
273
324
  """
274
325
 
275
326
  benchmark: Type[Benchmark] = CompressiveGST
@@ -288,6 +339,7 @@ class GSTConfiguration(BenchmarkConfigurationBase):
288
339
  batch_size: Union[str, int] = "auto"
289
340
  bootstrap_samples: int = 0
290
341
  testing: bool = False
342
+ parallel_execution: bool = False
291
343
 
292
344
 
293
345
  def parse_layouts(qubit_layouts: Union[List[int], List[List[int]]]) -> List[List[int]]:
@@ -621,16 +621,29 @@ def dataset_counts_to_mgst_format(dataset: xr.Dataset, qubit_layout: List[int])
621
621
  num_povm = dataset.attrs["num_povm"]
622
622
  y_list = []
623
623
  for run_index in range(dataset.attrs["num_circuits"]):
624
- # result = dataset[f"{qubit_layout}_counts_{run_index}"].to_dict()
625
- result = dataset[f"{qubit_layout}_state_{run_index}"].data.tolist()
626
- basis_dict = {entry: int("".join([entry[::-1][i] for i in range(num_qubits)][::-1]), 2) for entry in result}
624
+ if dataset.attrs["parallel_execution"]:
625
+ result_da = dataset[f"parallel_results_counts_{run_index}"].copy()
626
+ bit_pos = dataset.attrs["qubit_layouts"].index(qubit_layout)
627
+ # Create a new coordinate of bits at the position given by the qubit layout and reverse order
628
+ new_coords = [
629
+ coord[::-1][bit_pos * num_qubits : (bit_pos + 1) * num_qubits]
630
+ for coord in result_da.coords[result_da.dims[0]].values
631
+ ]
632
+ else:
633
+ result_da = dataset[f"{qubit_layout}_counts_{run_index}"].copy()
634
+ # Reverse order since counts are stored in qiskit order (bottom to top in circuit diagram)
635
+ new_coords = [coord[::-1] for coord in result_da.coords[result_da.dims[0]].values]
636
+ result_da.coords["new_coord"] = (result_da.dims[0], new_coords)
637
+ result_da = result_da.groupby("new_coord").sum()
638
+
639
+ coord_strings = list(result_da.coords[result_da.dims[0]].values)
640
+ # Translating from binary basis labels to integer POVM labels
641
+ basis_dict = {entry: int(entry, 2) for entry in coord_strings}
627
642
  # Sort by index:
628
643
  basis_dict = dict(sorted(basis_dict.items(), key=lambda item: item[1]))
629
644
 
630
- counts_normalized = (
631
- dataset[f"{qubit_layout}_counts_{run_index}"] / dataset[f"{qubit_layout}_counts_{run_index}"].sum()
632
- )
633
- row = [counts_normalized.loc[key].data for key in basis_dict]
645
+ counts_normalized = result_da / result_da.sum()
646
+ row = [float(counts_normalized.loc[key].data) for key in basis_dict]
634
647
  # row = [result[key] for key in basis_dict]
635
648
  if len(row) < num_povm:
636
649
  missing_entries = list(np.arange(num_povm))
@@ -16,9 +16,7 @@
16
16
  GHZ state benchmark
17
17
  """
18
18
 
19
- from io import BytesIO
20
19
  from itertools import chain
21
- import json
22
20
  from time import strftime
23
21
  from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, cast
24
22
 
@@ -27,7 +25,6 @@ import matplotlib.pyplot as plt
27
25
  import networkx
28
26
  from networkx import Graph, all_pairs_shortest_path, is_connected, minimum_spanning_tree
29
27
  import numpy as np
30
- import pycurl
31
28
  from qiskit import QuantumRegister
32
29
  from qiskit.quantum_info import random_clifford
33
30
  from qiskit.transpiler import CouplingMap
@@ -48,6 +45,7 @@ from iqm.benchmarks.circuit_containers import BenchmarkCircuit, CircuitGroup, Ci
48
45
  from iqm.benchmarks.logging_config import qcvv_logger
49
46
  from iqm.benchmarks.readout_mitigation import apply_readout_error_mitigation
50
47
  from iqm.benchmarks.utils import (
48
+ extract_fidelities,
51
49
  perform_backend_transpilation,
52
50
  reduce_to_active_qubits,
53
51
  retrieve_all_counts,
@@ -387,47 +385,6 @@ def generate_ghz_spanning_tree(
387
385
  return qc, list(participating_qubits)
388
386
 
389
387
 
390
- def extract_fidelities(cal_url: str, qubit_layout: List[int]) -> Tuple[List[List[int]], List[float]]:
391
- """Returns couplings and CZ-fidelities from calibration data URL
392
-
393
- Args:
394
- cal_url: str
395
- The url under which the calibration data for the backend can be found
396
- qubit_layout: List[int]
397
- The subset of system-qubits used in the protocol, indexed from 0
398
- Returns:
399
- list_couplings: List[List[int]]
400
- A list of pairs, each of which is a qubit coupling for which the calibration
401
- data contains a fidelity.
402
- list_fids: List[float]
403
- A list of CZ fidelities from the calibration url, ordered in the same way as list_couplings
404
- """
405
-
406
- byteobj = BytesIO() # buffer creation
407
- curlobj = pycurl.Curl() # pylint: disable=c-extension-no-member
408
- curlobj.setopt(curlobj.URL, f"{cal_url}") # type: ignore
409
- curlobj.setopt(curlobj.WRITEDATA, byteobj) # type: ignore
410
- curlobj.perform() # perform file transfer
411
- curlobj.close() # end of session
412
- body = byteobj.getvalue()
413
- res = json.loads(body.decode())
414
-
415
- qubit_mapping = {qubit: idx for idx, qubit in enumerate(qubit_layout)}
416
- list_couplings = []
417
- list_fids = []
418
- for key in res["metrics"]:
419
- if "irb.cz" in key:
420
- idx_1 = key.index(".QB")
421
- idx_2 = key.index("__QB")
422
- idx_3 = key.index(".fidelity")
423
- qb1 = int(key[idx_1 + 3 : idx_2]) - 1
424
- qb2 = int(key[idx_2 + 4 : idx_3]) - 1
425
- if all([qb1 in qubit_layout, qb2 in qubit_layout]):
426
- list_couplings.append([qubit_mapping[qb1], qubit_mapping[qb2]])
427
- list_fids.append(float(res["metrics"][key]["value"]))
428
- return list_couplings, list_fids
429
-
430
-
431
388
  def get_edges(
432
389
  coupling_map: CouplingMap,
433
390
  qubit_layout: List[int],
@@ -454,16 +411,17 @@ def get_edges(
454
411
  edges_patch = []
455
412
  for idx, edge in enumerate(coupling_map):
456
413
  if edge[0] in qubit_layout and edge[1] in qubit_layout:
457
- edges_patch.append([edge[0], edge[1]])
414
+ if not set(edge) in edges_patch:
415
+ edges_patch.append(set(edge))
458
416
 
459
- if fidelities_cal is not None:
417
+ if fidelities_cal is not None and edges_cal is not None:
460
418
  fidelities_cal = list(
461
419
  np.minimum(np.array(fidelities_cal), np.ones(len(fidelities_cal)))
462
420
  ) # get rid of > 1 fidelities
463
421
  fidelities_patch = []
464
422
  for edge in edges_patch:
465
- for idx, edge_2 in enumerate(cast(List[int], edges_cal)):
466
- if edge == edge_2:
423
+ for idx, edge_2 in enumerate(edges_cal):
424
+ if edge == set(edge_2):
467
425
  fidelities_patch.append(fidelities_cal[idx])
468
426
  weights = -np.log(np.array(fidelities_patch))
469
427
  else:
@@ -666,7 +624,7 @@ class GHZBenchmark(Benchmark):
666
624
  else:
667
625
  effective_coupling_map = self.backend.coupling_map
668
626
  if self.cal_url:
669
- edges_cal, fidelities_cal = extract_fidelities(self.cal_url, qubit_layout)
627
+ edges_cal, fidelities_cal, _ = extract_fidelities(self.cal_url)
670
628
  graph = get_edges(effective_coupling_map, qubit_layout, edges_cal, fidelities_cal)
671
629
  else:
672
630
  graph = get_edges(effective_coupling_map, qubit_layout)
@@ -893,6 +851,7 @@ class GHZBenchmark(Benchmark):
893
851
  self.shots,
894
852
  self.calset_id,
895
853
  max_gates_per_batch=self.max_gates_per_batch,
854
+ circuit_compilation_options=self.circuit_compilation_options,
896
855
  )
897
856
 
898
857
  # Retrieve all
@@ -411,8 +411,7 @@ def qscore_analysis(run: BenchmarkRunResult) -> BenchmarkAnalysisResult:
411
411
  backend_name = dataset.attrs["backend_name"]
412
412
  timestamp = dataset.attrs["execution_timestamp"]
413
413
 
414
- max_num_nodes = dataset.attrs["max_num_nodes"]
415
- min_num_nodes = dataset.attrs["min_num_nodes"]
414
+ nodes_list = dataset.attrs["node_numbers"]
416
415
  num_instances: int = dataset.attrs["num_instances"]
417
416
 
418
417
  use_virtual_node: bool = dataset.attrs["use_virtual_node"]
@@ -420,7 +419,6 @@ def qscore_analysis(run: BenchmarkRunResult) -> BenchmarkAnalysisResult:
420
419
  num_qaoa_layers = dataset.attrs["num_qaoa_layers"]
421
420
 
422
421
  qscore = 0
423
- nodes_list = list(range(min_num_nodes, max_num_nodes + 1))
424
422
  beta_ratio_list = []
425
423
  beta_ratio_std_list = []
426
424
  for num_nodes in nodes_list:
@@ -753,17 +751,26 @@ class QScoreBenchmark(Benchmark):
753
751
  else:
754
752
  nqubits = self.backend.num_qubits
755
753
 
756
- if self.max_num_nodes is None or self.max_num_nodes == nqubits + 1:
754
+ if self.custom_qubits_array is not None:
757
755
  if self.use_virtual_node:
758
- max_num_nodes = nqubits + 1
756
+ node_numbers = [len(qubit_layout) + 1 for qubit_layout in self.custom_qubits_array]
759
757
  else:
760
- max_num_nodes = nqubits
758
+ node_numbers = [len(qubit_layout) for qubit_layout in self.custom_qubits_array]
759
+
761
760
  else:
762
- max_num_nodes = self.max_num_nodes
761
+ if self.max_num_nodes is None or self.max_num_nodes == nqubits + 1:
762
+ if self.use_virtual_node:
763
+ max_num_nodes = nqubits + 1
764
+ else:
765
+ max_num_nodes = nqubits
766
+ else:
767
+ max_num_nodes = self.max_num_nodes
768
+ node_numbers = range(self.min_num_nodes, max_num_nodes + 1)
763
769
 
764
- dataset.attrs.update({"max_num_nodes": max_num_nodes})
770
+ dataset.attrs.update({"max_num_nodes": node_numbers[-1]})
771
+ dataset.attrs.update({"node_numbers": node_numbers})
765
772
 
766
- for num_nodes in range(self.min_num_nodes, max_num_nodes + 1):
773
+ for num_nodes in node_numbers:
767
774
  qc_list = []
768
775
  qc_transpiled_list: List[QuantumCircuit] = []
769
776
  execution_results = []
@@ -874,6 +881,7 @@ class QScoreBenchmark(Benchmark):
874
881
  self.shots,
875
882
  self.calset_id,
876
883
  max_gates_per_batch=self.max_gates_per_batch,
884
+ circuit_compilation_options=self.circuit_compilation_options,
877
885
  )
878
886
  qc_transpiled_list.append(transpiled_qc)
879
887
  qcvv_logger.setLevel(logging.INFO)
@@ -565,6 +565,7 @@ class CLOPSBenchmark(Benchmark):
565
565
  self.num_shots,
566
566
  self.calset_id,
567
567
  max_gates_per_batch=self.max_gates_per_batch,
568
+ circuit_compilation_options=self.circuit_compilation_options,
568
569
  )
569
570
 
570
571
  qcvv_logger.info(f"Retrieving counts")
@@ -698,12 +698,8 @@ class QuantumVolumeBenchmark(Benchmark):
698
698
  self.shots,
699
699
  self.calset_id,
700
700
  max_gates_per_batch=self.max_gates_per_batch,
701
+ circuit_compilation_options=self.circuit_compilation_options,
701
702
  )
702
- # else:
703
- # DD IN DIQE VERSION PREVENTS SUBMITTING JOBS DYNAMICALLY:
704
- # I.E., IT AUTOMATICALLY RETRIEVES COUNTS
705
- # TODO: change this when job manager for DD in Pulla is updated! # pylint: disable=fixme
706
- # raise ValueError("Dynamical decoupling is not yet enabled in the new base")
707
703
  # qcvv_logger.info(
708
704
  # f"Now executing {self.num_circuits} circuits with default strategy Dynamical Decoupling"
709
705
  # )
@@ -353,6 +353,7 @@ class CliffordRandomizedBenchmarking(Benchmark):
353
353
  self.backend,
354
354
  self.calset_id,
355
355
  max_gates_per_batch=self.max_gates_per_batch,
356
+ circuit_compilation_options=self.circuit_compilation_options,
356
357
  )
357
358
  )
358
359
  qcvv_logger.info(
@@ -513,6 +513,7 @@ class InterleavedRandomizedBenchmarking(Benchmark):
513
513
  backend,
514
514
  self.calset_id,
515
515
  max_gates_per_batch=self.max_gates_per_batch,
516
+ circuit_compilation_options=self.circuit_compilation_options,
516
517
  )
517
518
  )
518
519
  all_rb_jobs["interleaved"].extend(
@@ -523,6 +524,7 @@ class InterleavedRandomizedBenchmarking(Benchmark):
523
524
  backend,
524
525
  self.calset_id,
525
526
  max_gates_per_batch=self.max_gates_per_batch,
527
+ circuit_compilation_options=self.circuit_compilation_options,
526
528
  )
527
529
  )
528
530
  qcvv_logger.info(
@@ -662,6 +662,7 @@ class MirrorRandomizedBenchmarking(Benchmark):
662
662
  self.shots,
663
663
  self.calset_id,
664
664
  max_gates_per_batch=self.max_gates_per_batch,
665
+ circuit_compilation_options=self.circuit_compilation_options,
665
666
  )
666
667
  mrb_submit_results = {
667
668
  "qubits": qubits,
@@ -36,6 +36,7 @@ import xarray as xr
36
36
  from iqm.benchmarks.logging_config import qcvv_logger
37
37
  from iqm.benchmarks.randomized_benchmarking.multi_lmfit import create_multi_dataset_params, multi_dataset_residual
38
38
  from iqm.benchmarks.utils import get_iqm_backend, marginal_distribution, submit_execute, timeit
39
+ from iqm.iqm_client.models import CircuitCompilationOptions
39
40
  from iqm.qiskit_iqm import IQMCircuit as QuantumCircuit
40
41
  from iqm.qiskit_iqm import optimize_single_qubit_gates
41
42
  from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
@@ -473,6 +474,7 @@ def submit_sequential_rb_jobs(
473
474
  backend_arg: str | IQMBackendBase,
474
475
  calset_id: Optional[str] = None,
475
476
  max_gates_per_batch: Optional[int] = None,
477
+ circuit_compilation_options: Optional[CircuitCompilationOptions] = None,
476
478
  ) -> List[Dict[str, Any]]:
477
479
  """Submit sequential RB jobs for execution in the specified IQMBackend
478
480
  Args:
@@ -482,6 +484,7 @@ def submit_sequential_rb_jobs(
482
484
  backend_arg (IQMBackendBase): the IQM backend to submit the job
483
485
  calset_id (Optional[str]): the calibration identifier
484
486
  max_gates_per_batch (Optional[int]): the maximum number of gates per batch
487
+ circuit_compilation_options (Optional[CircuitCompilationOptions]): Compilation options passed to submit_execute
485
488
  Returns:
486
489
  Dict with qubit layout, submitted job objects, type (vanilla/DD) and submission time
487
490
  """
@@ -496,7 +499,12 @@ def submit_sequential_rb_jobs(
496
499
  # Submit - send to execute on backend
497
500
  # pylint: disable=unbalanced-tuple-unpacking
498
501
  execution_jobs, time_submit = submit_execute(
499
- {tuple(qubits): transpiled_circuits[depth]}, backend, shots, calset_id, max_gates_per_batch
502
+ {tuple(qubits): transpiled_circuits[depth]},
503
+ backend,
504
+ shots,
505
+ calset_id,
506
+ max_gates_per_batch,
507
+ circuit_compilation_options=circuit_compilation_options,
500
508
  )
501
509
  rb_submit_results[depth] = {
502
510
  "qubits": qubits,
iqm/benchmarks/utils.py CHANGED
@@ -17,11 +17,14 @@ General utility functions
17
17
  """
18
18
 
19
19
  from collections import defaultdict
20
+ from dataclasses import dataclass
20
21
  from functools import wraps
21
22
  from math import floor
23
+ import os
22
24
  from time import time
23
25
  from typing import Any, Dict, Iterable, List, Literal, Optional, Sequence, Tuple, Union, cast
24
26
 
27
+ import matplotlib.pyplot as plt
25
28
  from more_itertools import chunked
26
29
  from mthree.utils import final_measurement_mapping
27
30
  import numpy as np
@@ -29,9 +32,12 @@ from numpy.random import Generator
29
32
  from qiskit import ClassicalRegister, transpile
30
33
  from qiskit.converters import circuit_to_dag
31
34
  from qiskit.transpiler import CouplingMap
35
+ import requests
36
+ from rustworkx import PyGraph, spring_layout, visualization # pylint: disable=no-name-in-module
32
37
  import xarray as xr
33
38
 
34
39
  from iqm.benchmarks.logging_config import qcvv_logger
40
+ from iqm.iqm_client.models import CircuitCompilationOptions
35
41
  from iqm.qiskit_iqm import IQMCircuit as QuantumCircuit
36
42
  from iqm.qiskit_iqm import transpile_to_IQM
37
43
  from iqm.qiskit_iqm.fake_backends.fake_adonis import IQMFakeAdonis
@@ -495,8 +501,9 @@ def submit_execute(
495
501
  sorted_transpiled_qc_list: Dict[Tuple, List[QuantumCircuit]],
496
502
  backend: IQMBackendBase,
497
503
  shots: int,
498
- calset_id: Optional[str],
499
- max_gates_per_batch: Optional[int],
504
+ calset_id: Optional[str] = None,
505
+ max_gates_per_batch: Optional[int] = None,
506
+ circuit_compilation_options: Optional[CircuitCompilationOptions] = None,
500
507
  ) -> List[IQMJob]:
501
508
  """Submit for execute a list of quantum circuits on the specified Backend.
502
509
 
@@ -506,8 +513,11 @@ def submit_execute(
506
513
  shots (int): the number of shots per circuit.
507
514
  calset_id (Optional[str]): the calibration set ID, uses the latest one if None.
508
515
  max_gates_per_batch (int): the maximum number of gates per batch sent to the backend, used to make manageable batches.
516
+ circuit_compilation_options (CircuitCompilationOptions): Ability to pass a compilation options object,
517
+ enabling execution with dynamical decoupling, among other options - see qiskit-iqm documentation.
509
518
  Returns:
510
519
  List[IQMJob]: the IQMJob objects of the executed circuits.
520
+
511
521
  """
512
522
  final_jobs = []
513
523
  for k in sorted(
@@ -538,7 +548,12 @@ def submit_execute(
538
548
  qcvv_logger.info(
539
549
  f"max_gates_per_batch restriction: submitting subbatch #{index+1} with {len(qc_batch)} circuits corresponding to qubits {list(k)}"
540
550
  )
541
- batch_jobs = backend.run(qc_batch, shots=shots, calibration_set_id=calset_id)
551
+ batch_jobs = backend.run(
552
+ qc_batch,
553
+ shots=shots,
554
+ calibration_set_id=calset_id,
555
+ circuit_compilation_options=circuit_compilation_options,
556
+ )
542
557
  final_batch_jobs.append(batch_jobs)
543
558
  final_jobs.extend(final_batch_jobs)
544
559
 
@@ -559,3 +574,233 @@ def xrvariable_to_counts(dataset: xr.Dataset, identifier: str, counts_range: int
559
574
  dict(zip(list(dataset[f"{identifier}_state_{u}"].data), dataset[f"{identifier}_counts_{u}"].data))
560
575
  for u in range(counts_range)
561
576
  ]
577
+
578
+
579
+ @dataclass
580
+ class GraphPositions:
581
+ """A class to store and generate graph positions for different chip layouts.
582
+
583
+ This class contains predefined node positions for various quantum chip topologies and
584
+ provides methods to generate positions for different layout types.
585
+
586
+ Attributes:
587
+ garnet_positions (Dict[int, Tuple[int, int]]): Mapping of node indices to (x,y) positions for Garnet chip.
588
+ deneb_positions (Dict[int, Tuple[int, int]]): Mapping of node indices to (x,y) positions for Deneb chip.
589
+ predefined_stations (Dict[str, Dict[int, Tuple[int, int]]]): Mapping of chip names to their position dictionaries.
590
+ """
591
+
592
+ garnet_positions = {
593
+ 0: (5.0, 7.0),
594
+ 1: (6.0, 6.0),
595
+ 2: (3.0, 7.0),
596
+ 3: (4.0, 6.0),
597
+ 4: (5.0, 5.0),
598
+ 5: (6.0, 4.0),
599
+ 6: (7.0, 3.0),
600
+ 7: (2.0, 6.0),
601
+ 8: (3.0, 5.0),
602
+ 9: (4.0, 4.0),
603
+ 10: (5.0, 3.0),
604
+ 11: (6.0, 2.0),
605
+ 12: (1.0, 5.0),
606
+ 13: (2.0, 4.0),
607
+ 14: (3.0, 3.0),
608
+ 15: (4.0, 2.0),
609
+ 16: (5.0, 1.0),
610
+ 17: (1.0, 3.0),
611
+ 18: (2.0, 2.0),
612
+ 19: (3.0, 1.0),
613
+ }
614
+
615
+ deneb_positions = {
616
+ 0: (2.0, 2.0),
617
+ 1: (1.0, 1.0),
618
+ 3: (2.0, 1.0),
619
+ 5: (3.0, 1.0),
620
+ 2: (1.0, 3.0),
621
+ 4: (2.0, 3.0),
622
+ 6: (3.0, 3.0),
623
+ }
624
+
625
+ predefined_stations = {
626
+ "Garnet": garnet_positions,
627
+ "Deneb": deneb_positions,
628
+ }
629
+
630
+ @staticmethod
631
+ def create_positions(graph: PyGraph, topology: Optional[str] = None) -> Dict[int, Tuple[float, float]]:
632
+ """Generate node positions for a given graph and topology.
633
+
634
+ Args:
635
+ graph: The graph to generate positions for.
636
+ topology: The type of layout to generate. Must be either "star" or "crystal".
637
+
638
+ Returns:
639
+ A dictionary mapping node indices to (x,y) coordinates.
640
+ """
641
+ n_nodes = len(graph.node_indices())
642
+
643
+ if topology == "star":
644
+ # Place center node at (0,0)
645
+ pos = {0: (0.0, 0.0)}
646
+
647
+ if n_nodes > 1:
648
+ # Place other nodes in a circle around the center
649
+ angles = np.linspace(0, 2 * np.pi, n_nodes - 1, endpoint=False)
650
+ radius = 1.0
651
+
652
+ for i, angle in enumerate(angles, start=1):
653
+ x = radius * np.cos(angle)
654
+ y = radius * np.sin(angle)
655
+ pos[i] = (x, y)
656
+
657
+ # Crystal and other topologies
658
+ else:
659
+ # Fix first node position in bottom right
660
+ fixed_pos = {0: (1.0, 1.0)} # For more consistent layouts
661
+
662
+ # Get spring layout with one fixed position
663
+ pos = {
664
+ int(k): (float(v[0]), float(v[1]))
665
+ for k, v in spring_layout(graph, scale=2, pos=fixed_pos, num_iter=300, fixed={0}).items()
666
+ }
667
+ return pos
668
+
669
+
670
+ def extract_fidelities(cal_url: str) -> tuple[list[list[int]], list[float], str]:
671
+ """Returns couplings and CZ-fidelities from calibration data URL
672
+
673
+ Args:
674
+ cal_url: str
675
+ The url under which the calibration data for the backend can be found
676
+ Returns:
677
+ list_couplings: List[List[int]]
678
+ A list of pairs, each of which is a qubit coupling for which the calibration
679
+ data contains a fidelity.
680
+ list_fids: List[float]
681
+ A list of CZ fidelities from the calibration url, ordered in the same way as list_couplings
682
+ topology: str
683
+ Name of the chip topology layout, currently either "star" or "crystal"
684
+ """
685
+ headers = {"Accept": "application/json", "Authorization": "Bearer " + os.environ["IQM_TOKEN"]}
686
+ r = requests.get(cal_url, headers=headers, timeout=60)
687
+ calibration = r.json()
688
+ cal_keys = {
689
+ el2["key"]: (i, j) for i, el1 in enumerate(calibration["calibrations"]) for j, el2 in enumerate(el1["metrics"])
690
+ }
691
+ list_couplings = []
692
+ list_fids = []
693
+ if "double_move_gate_fidelity" in cal_keys.keys():
694
+ i, j = cal_keys["double_move_gate_fidelity"]
695
+ topology = "star"
696
+ else:
697
+ i, j = cal_keys["cz_gate_fidelity"]
698
+ topology = "crystal"
699
+ for item in calibration["calibrations"][i]["metrics"][j]["metrics"]:
700
+ qb1 = int(item["locus"][0][2:]) if item["locus"][0] != "COMP_R" else 0
701
+ qb2 = int(item["locus"][1][2:]) if item["locus"][1] != "COMP_R" else 0
702
+ if topology == "star":
703
+ list_couplings.append([qb1, qb2])
704
+ else:
705
+ list_couplings.append([qb1 - 1, qb2 - 1])
706
+ list_fids.append(float(item["value"]))
707
+ calibrated_qubits = set(np.array(list_couplings).reshape(-1))
708
+ qubit_mapping = {qubit: idx for idx, qubit in enumerate(calibrated_qubits)}
709
+ list_couplings = [[qubit_mapping[edge[0]], qubit_mapping[edge[1]]] for edge in list_couplings]
710
+
711
+ return list_couplings, list_fids, topology
712
+
713
+
714
+ def plot_layout_fidelity_graph(
715
+ cal_url: str, qubit_layouts: Optional[list[list[int]]] = None, station: Optional[str] = None
716
+ ):
717
+ """Plot a graph showing the quantum chip layout with fidelity information.
718
+
719
+ Creates a visualization of the quantum chip topology where nodes represent qubits
720
+ and edges represent connections between qubits. Edge thickness indicates gate errors
721
+ (thinner edges mean better fidelity) and selected qubits are highlighted in orange.
722
+
723
+ Args:
724
+ cal_url: URL to retrieve calibration data from
725
+ qubit_layouts: List of qubit layouts where each layout is a list of qubit indices
726
+ station: Name of the quantum computing station to use predefined positions for.
727
+ If None, positions will be generated algorithmically.
728
+
729
+ Returns:
730
+ matplotlib.figure.Figure: The generated figure object containing the graph visualization
731
+ """
732
+ edges_cal, fidelities_cal, topology = extract_fidelities(cal_url)
733
+ weights = -np.log(np.array(fidelities_cal))
734
+ edges_graph = [tuple(edge) + (weight,) for edge, weight in zip(edges_cal, weights)]
735
+
736
+ graph = PyGraph()
737
+
738
+ # Add nodes
739
+ nodes: set[int] = set()
740
+ for edge in edges_graph:
741
+ nodes.update(edge[:2])
742
+ graph.add_nodes_from(list(nodes))
743
+
744
+ # Add edges
745
+ graph.add_edges_from(edges_graph)
746
+
747
+ # Define qubit positions in plot
748
+ if station in GraphPositions.predefined_stations:
749
+ pos = GraphPositions.predefined_stations[station]
750
+ else:
751
+ pos = GraphPositions.create_positions(graph, topology)
752
+
753
+ # Define node colors
754
+ node_colors = ["lightgrey" for _ in range(len(nodes))]
755
+ if qubit_layouts is not None:
756
+ for qb in {qb for layout in qubit_layouts for qb in layout}:
757
+ node_colors[qb] = "orange"
758
+
759
+ # Ensuring weights are in correct order for the plot
760
+ edge_list = graph.edge_list()
761
+ weights_dict = {}
762
+ edge_pos = set()
763
+
764
+ # Create a mapping between edge positions as defined in rustworkx and their weights
765
+ for e, w in zip(edge_list, weights):
766
+ pos_tuple = (tuple(pos[e[0]]), tuple(pos[e[1]]))
767
+ weights_dict[pos_tuple] = w
768
+ edge_pos.add(pos_tuple)
769
+
770
+ # Get corresponding weights in the same order
771
+ weights_ordered = np.array([weights_dict[edge] for edge in list(edge_pos)])
772
+
773
+ plt.subplots(figsize=(6, 6))
774
+
775
+ # Draw the graph
776
+ visualization.mpl_draw(
777
+ graph,
778
+ with_labels=True,
779
+ node_color=node_colors,
780
+ pos=pos,
781
+ labels=lambda node: node,
782
+ width=7 * weights_ordered / np.max(weights_ordered),
783
+ ) # type: ignore[call-arg]
784
+
785
+ # Add edge labels using matplotlib's annotate
786
+ for edge in edges_graph:
787
+ x1, y1 = pos[edge[0]]
788
+ x2, y2 = pos[edge[1]]
789
+ x = (x1 + x2) / 2
790
+ y = (y1 + y2) / 2
791
+ plt.annotate(
792
+ f"{edge[2]:.1e}",
793
+ xy=(x, y),
794
+ xytext=(0, 0),
795
+ textcoords="offset points",
796
+ ha="center",
797
+ va="center",
798
+ bbox={"boxstyle": "round,pad=0.2", "fc": "white", "ec": "none", "alpha": 0.6},
799
+ )
800
+
801
+ plt.gca().invert_yaxis()
802
+ plt.title(
803
+ "Chip layout with selected qubits in orange\n"
804
+ + "and gate errors indicated by edge thickness (thinner is better)"
805
+ )
806
+ plt.show()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: iqm-benchmarks
3
- Version: 2.21
3
+ Version: 2.23
4
4
  Summary: A package for implementation of Quantum Characterization, Verification and Validation (QCVV) techniques on IQM's hardware at gate level abstraction
5
5
  Author-email: IQM Finland Oy <developers@meetiqm.com>, Adrian Auer <adrian.auer@meetiqm.com>, Raphael Brieger <raphael.brieger@meetiqm.com>, Alessio Calzona <alessio.calzona@meetiqm.com>, Pedro Figueroa Romero <pedro.romero@meetiqm.com>, Amin Hosseinkhani <amin.hosseinkhani@meetiqm.com>, Miikka Koistinen <miikka@meetiqm.com>, Nadia Milazzo <nadia.milazzo@meetiqm.com>, Vicente Pina Canelles <vicente.pina@meetiqm.com>, Aniket Rath <aniket.rath@meetiqm.com>, Jami Rönkkö <jami@meetiqm.com>, Stefan Seegerer <stefan.seegerer@meetiqm.com>
6
6
  Project-URL: Homepage, https://github.com/iqm-finland/iqm-benchmarks
@@ -24,7 +24,7 @@ Requires-Dist: tabulate<1.0.0,>=0.9.0
24
24
  Requires-Dist: uncertainties<3.3.0,>=3.2.2
25
25
  Requires-Dist: pycurl<8.0,>=7.45.3
26
26
  Requires-Dist: xarray<2025.0.0,>=2024.6.0
27
- Requires-Dist: types-pycurl
27
+ Requires-Dist: types-requests
28
28
  Requires-Dist: myst-nb==1.1.0
29
29
  Provides-Extra: cicd
30
30
  Requires-Dist: build==1.0.3; extra == "cicd"
@@ -1,43 +1,43 @@
1
1
  iqm/benchmarks/__init__.py,sha256=8SRHlADqZjlP8PtbKTMvu-ejpcEZkW8cPmgW8GVKJg8,2638
2
- iqm/benchmarks/benchmark.py,sha256=SGhBcSxLPUu-cVXAjG4Db2TRobFCRBYoE1NtTDK1lJg,4432
3
- iqm/benchmarks/benchmark_definition.py,sha256=AZkvANrf0_0glbq_P_uo_YqbBU9IZa2gJlMVz6qT6VU,10500
2
+ iqm/benchmarks/benchmark.py,sha256=F0WdGFpYGVWMeyKmScfM7b7jvuS8P8UArB3_JwUKJrY,4694
3
+ iqm/benchmarks/benchmark_definition.py,sha256=e4xe0wlWKZqj48_6-zTglMaMeoiA9aGkHrrSgoCfPkM,11271
4
4
  iqm/benchmarks/circuit_containers.py,sha256=anEtZEsodYqOX-34oZRmuKGeEpp_VfgG5045Mz4-4hI,7562
5
5
  iqm/benchmarks/logging_config.py,sha256=U7olP5Kr75AcLJqNODf9VBhJLVqIvA4AYR6J39D5rww,1052
6
6
  iqm/benchmarks/readout_mitigation.py,sha256=Q2SOGWTNgmklOYkNxepAaSaXlxSj0QQyymYY1bOkT8A,11756
7
- iqm/benchmarks/utils.py,sha256=B3dusTNC4n0gW3KZSaQA6ZZeGok3ABtte-N3JEXCtN0,23022
7
+ iqm/benchmarks/utils.py,sha256=ZwAGYIhPe4Ij2Ds8drd_0eHwpdBj1L7rm1d33oqN4L8,31928
8
8
  iqm/benchmarks/compressive_gst/__init__.py,sha256=LneifgYXtcwo2jcXo7GdUEHL6_peipukShhkrdaTRCA,929
9
- iqm/benchmarks/compressive_gst/compressive_gst.py,sha256=spq6jP0NLbjaJDzESpbfRPs0N_YheFWl2Wa6USHS5sI,22398
10
- iqm/benchmarks/compressive_gst/gst_analysis.py,sha256=PxCThBfh2zdQpipI-6gAvLKdmkbYv_KSEpejltVkk7o,35330
9
+ iqm/benchmarks/compressive_gst/compressive_gst.py,sha256=D54hnaxxOIyD3ygUMKyxfRMwfUXfK4O5UdzdgscwkHA,25291
10
+ iqm/benchmarks/compressive_gst/gst_analysis.py,sha256=2qY46e-euAcdThcits_Wo7hGb0m69mDZY0d1Qv_wq4M,36104
11
11
  iqm/benchmarks/entanglement/__init__.py,sha256=9T7prOwqMmFWdb4t6ETAHZXKK5o6FvU2DvVb6WhNi-U,682
12
- iqm/benchmarks/entanglement/ghz.py,sha256=tQXKiav6pKZlV3ZU4BOrYrP7PfBAGn0zOsSUORQn5kw,42652
12
+ iqm/benchmarks/entanglement/ghz.py,sha256=HqvdRaiwM879QUrFnULcprdZ-frr1-i4bQ1U8jKjIn0,40993
13
13
  iqm/benchmarks/optimization/__init__.py,sha256=_ajW_OibYLCtzU5AUv5c2zuuVYn8ZNeZUcUUSIGt51M,747
14
- iqm/benchmarks/optimization/qscore.py,sha256=xAv97MRVUNB_YjRbxzM6Ph0Pz0ctJjSA46J9lrbwe-w,37541
14
+ iqm/benchmarks/optimization/qscore.py,sha256=Ns3Sik90YQ_Cry1I5tY-QQWcLukpyipAfVb1Lmkkd7M,37957
15
15
  iqm/benchmarks/quantum_volume/__init__.py,sha256=i-Q4SpDWELBw7frXnxm1j4wJRcxbIyrS5uEK_v06YHo,951
16
- iqm/benchmarks/quantum_volume/clops.py,sha256=abuYvc7XaRTuqLDim9jwZvG_W_KybEbiBs6SD0JUtcA,31319
17
- iqm/benchmarks/quantum_volume/quantum_volume.py,sha256=njX5lBty9jcWMuJnl7uNqRfwE9akMe5gcX-f1_uYDXk,36791
16
+ iqm/benchmarks/quantum_volume/clops.py,sha256=kMzofZEtB_SmbGWcB9LWGbG96I342Im3xIWDt9nTqUY,31393
17
+ iqm/benchmarks/quantum_volume/quantum_volume.py,sha256=2nNlZeR2VhtKkc3gpCkaR24NE23T0aQXNrepTlUL-8I,36549
18
18
  iqm/benchmarks/randomized_benchmarking/__init__.py,sha256=IkKo-7zUChxZZd3my_csQCJfJfZNsV3-JTvdG8uqys4,734
19
19
  iqm/benchmarks/randomized_benchmarking/clifford_1q.pkl,sha256=vvSd0pRWxtzyirohO9yf_58mjevkc2-pbuWIEb-4gaw,46928
20
20
  iqm/benchmarks/randomized_benchmarking/clifford_2q.pkl,sha256=ZipqU3crPhz2T35qGFgB4GvMyoi_7pnu8NqW5ZP8NXg,90707258
21
21
  iqm/benchmarks/randomized_benchmarking/multi_lmfit.py,sha256=Se1ygR4mXn_2_P82Ch31KBnCmY-g_A9NKzE9Ir8nEvw,3247
22
- iqm/benchmarks/randomized_benchmarking/randomized_benchmarking_common.py,sha256=xMiQyikGEVj3zoFQfVMS50K15MszArc0kiBWvZ7gWL8,41492
22
+ iqm/benchmarks/randomized_benchmarking/randomized_benchmarking_common.py,sha256=SKpNukg_vOEVKpd_MAGj4xTFK5FpFJEwx2Zr_2uSUAQ,41867
23
23
  iqm/benchmarks/randomized_benchmarking/clifford_rb/__init__.py,sha256=bTDA156LAl7OLGcMec--1nzDrV1XpPRVq3CquTmucgE,677
24
- iqm/benchmarks/randomized_benchmarking/clifford_rb/clifford_rb.py,sha256=v8GDsEC3JscZVJXc6ZqfJaaSb1LocdFTSeOcwHxp50Y,18317
24
+ iqm/benchmarks/randomized_benchmarking/clifford_rb/clifford_rb.py,sha256=H-nfuQaIUWcxYeW61iEc79eZwuAFbDvozTVT0xC0HRw,18403
25
25
  iqm/benchmarks/randomized_benchmarking/interleaved_rb/__init__.py,sha256=sq6MgN_hwlpkOj10vyCU4e6eKSX-oLcF2L9na6W2Gt4,681
26
- iqm/benchmarks/randomized_benchmarking/interleaved_rb/interleaved_rb.py,sha256=4hBjIXxqy18WKC8hgpCU6T6Vv2cvJu4AImj7QQDqI1k,27956
26
+ iqm/benchmarks/randomized_benchmarking/interleaved_rb/interleaved_rb.py,sha256=CsnajadrpxQOqEvX8PpMGB5_9_g6W1IT4aaQRYa1Z08,28128
27
27
  iqm/benchmarks/randomized_benchmarking/mirror_rb/__init__.py,sha256=ZekEqI_89nXzGO1vjM-b5Uwwicy59M4fYHXfA-f0MIg,674
28
- iqm/benchmarks/randomized_benchmarking/mirror_rb/mirror_rb.py,sha256=XYJpfXHeN4PtkiuoCQeyRQIeaKr1PxFNasBjLs2ns7k,34830
28
+ iqm/benchmarks/randomized_benchmarking/mirror_rb/mirror_rb.py,sha256=KNIfu3ucb-oxAwOIl5zarRUeWM1SPmiVEQFo6970oUU,34904
29
29
  mGST/LICENSE,sha256=TtHNq55cUcbglb7uhVudeBLUh_qPdUoAEvU0BBwFz-k,1098
30
30
  mGST/README.md,sha256=v_5kw253csHF4-RfE-44KqFmBXIsSMRmOtN0AUPrRxE,5050
31
31
  mGST/additional_fns.py,sha256=_SEJ10FRNM7_CroysT8hCLZTfpm6ZhEIDCY5zPTnhjo,31390
32
- mGST/algorithm.py,sha256=ikedzOYC6M0FEaBsbtcWstl2FYQ9eW7i9P4sLw9pRps,26327
32
+ mGST/algorithm.py,sha256=SRwC1sVFShRVxQDfH1bhUvnquvgwJFLSiZ70qo5SwxE,26314
33
33
  mGST/compatibility.py,sha256=00DsPnNfOtrQcDTvxBDs-0aMhmuXmOIIxl_Ohy-Emkg,8920
34
- mGST/low_level_jit.py,sha256=czEk_GV8rlDUD4a5dJOgoTv5_83QuXAEwmSJAMKelRw,26540
34
+ mGST/low_level_jit.py,sha256=uE1D3v01FbPpsbP92C4220OQalzOfxgL1Ku89BNkxLY,27377
35
35
  mGST/optimization.py,sha256=YHwkzIkYvsZOPjclR-BCQWh24jeqjuXp0BB0WX5Lwow,10559
36
- mGST/qiskit_interface.py,sha256=L4H-4SdhP_bjSFFvpQoF1E7EyGbIJ_CI_y4a7_YEwmU,10102
36
+ mGST/qiskit_interface.py,sha256=ajx6Zn5FnrX_T7tMP8xnBLyG4c2ddFRm0Fu2_3r1t30,10118
37
37
  mGST/reporting/figure_gen.py,sha256=6Xd8vwfy09hLY1YbJY6TRevuMsQSU4MsWqemly3ZO0I,12970
38
- mGST/reporting/reporting.py,sha256=We1cccz9BKbITYcSlZHdmBGdjMWAa1xNZe5tKP-yh_E,26004
39
- iqm_benchmarks-2.21.dist-info/LICENSE,sha256=2Ncb40-hqkTil78RPv3-YiJfKaJ8te9USJgliKqIdSY,11558
40
- iqm_benchmarks-2.21.dist-info/METADATA,sha256=y-gRyH1c_MMl0hDu-Kv6d_yL78We2Ksctl3UkKc_7tE,10384
41
- iqm_benchmarks-2.21.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
42
- iqm_benchmarks-2.21.dist-info/top_level.txt,sha256=3G23Z-1LGf-IOzTCUl6QwWqiQ3USz25Zt90Ihq192to,9
43
- iqm_benchmarks-2.21.dist-info/RECORD,,
38
+ mGST/reporting/reporting.py,sha256=B8NWfpZrrSmyH7lwZxd0EbZMYLsAGK1YsHRB4D5qXH4,26002
39
+ iqm_benchmarks-2.23.dist-info/LICENSE,sha256=2Ncb40-hqkTil78RPv3-YiJfKaJ8te9USJgliKqIdSY,11558
40
+ iqm_benchmarks-2.23.dist-info/METADATA,sha256=BvgUDcjk3LfQCM3ncFvwEILGrFt9oK471iLIM5P0KL8,10386
41
+ iqm_benchmarks-2.23.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
42
+ iqm_benchmarks-2.23.dist-info/top_level.txt,sha256=3G23Z-1LGf-IOzTCUl6QwWqiQ3USz25Zt90Ihq192to,9
43
+ iqm_benchmarks-2.23.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
mGST/algorithm.py CHANGED
@@ -83,7 +83,7 @@ def A_SFN_riem_Hess(K, A, B, y, J, d, r, n_povm, lam=1e-3):
83
83
  # derivative
84
84
  Fy = dA_.reshape(n, pdim)
85
85
  Y = A.reshape(n, pdim)
86
- rGrad = Fy.conj() - Y @ Fy.T @ Y
86
+ rGrad = 2 * (Fy.conj() - Y @ Fy.T @ Y)
87
87
  G = np.array([rGrad, rGrad.conj()]).reshape(-1)
88
88
 
89
89
  P = np.eye(n) - Y @ Y.T.conj()
@@ -193,7 +193,7 @@ def B_SFN_riem_Hess(K, A, B, y, J, d, r, n_povm, lam=1e-3):
193
193
  # derivative
194
194
  Fy = dB_.reshape(n)
195
195
  Y = B.reshape(n)
196
- rGrad = Fy.conj() - Y * (Fy.T @ Y)
196
+ rGrad = 2 * (Fy.conj() - Y * (Fy.T @ Y))
197
197
  G = np.array([rGrad, rGrad.conj()]).reshape(-1)
198
198
 
199
199
  P = np.eye(n) - np.outer(Y, Y.T.conj())
@@ -297,10 +297,8 @@ def gd(K, E, rho, y, J, d, r, rK, fixed_gates, ls="COBYLA"):
297
297
  Fy = dK_[k].reshape(n, pdim)
298
298
  Y = K[k].reshape(n, pdim)
299
299
  # Riem. gradient taken from conjugate derivative
300
- rGrad = Fy.conj() - Y @ Fy.T @ Y
300
+ rGrad = 2 * (Fy.conj() - Y @ Fy.T @ Y)
301
301
  Delta[k] = rGrad
302
-
303
- Delta = tangent_proj(K, Delta, d, rK)
304
302
  res = minimize(lineobjf_isom_geodesic, 1e-8, args=(Delta, K, E, rho, J, y), method=ls, options={"maxiter": 200})
305
303
  a = res.x
306
304
  K_new = update_K_geodesic(K, Delta, a)
@@ -368,7 +366,7 @@ def SFN_riem_Hess(K, E, rho, y, J, d, r, rK, lam=1e-3, ls="COBYLA", fixed_gates=
368
366
  Fy = dK_[k].reshape(n, pdim)
369
367
  Y = K[k].reshape(n, pdim)
370
368
  # riemannian gradient, taken from conjugate derivative
371
- rGrad = Fy.conj() - Y @ Fy.T @ Y
369
+ rGrad = 2 * (Fy.conj() - Y @ Fy.T @ Y)
372
370
  G = np.array([rGrad, rGrad.conj()]).reshape(-1)
373
371
 
374
372
  P = np.eye(n) - Y @ Y.T.conj()
@@ -471,7 +469,7 @@ def SFN_riem_Hess_full(K, E, rho, y, J, d, r, rK, lam=1e-3, ls="COBYLA"):
471
469
  for k in range(d):
472
470
  Fy = dK_[k].reshape((n, pdim))
473
471
  Y = K[k].reshape((n, pdim))
474
- rGrad = Fy.conj() - Y @ Fy.T @ Y
472
+ rGrad = 2 * (Fy.conj() - Y @ Fy.T @ Y)
475
473
 
476
474
  G[0, k, :] = rGrad.reshape(-1)
477
475
  G[1, k, :] = rGrad.conj().reshape(-1)
mGST/low_level_jit.py CHANGED
@@ -104,7 +104,7 @@ def contract(X, j_vec):
104
104
  return res
105
105
 
106
106
 
107
- @njit(cache=True, fastmath=True)
107
+ @njit(cache=True, fastmath=True, parallel=True)
108
108
  def objf(X, E, rho, J, y):
109
109
  """Calculate the objective function value for matrices, POVM elements, and target values.
110
110
 
@@ -132,12 +132,14 @@ def objf(X, E, rho, J, y):
132
132
  """
133
133
  m = len(J)
134
134
  n_povm = y.shape[0]
135
- objf_ = 0
135
+ objf_: float = 0
136
136
  for i in prange(m): # pylint: disable=not-an-iterable
137
137
  j = J[i][J[i] >= 0]
138
- C = contract(X, j)
138
+ state = rho
139
+ for ind in j[::-1]:
140
+ state = X[ind] @ state
139
141
  for o in range(n_povm):
140
- objf_ += abs(E[o].conj() @ C @ rho - y[o, i]) ** 2
142
+ objf_ += abs(E[o].conj() @ state - y[o, i]) ** 2
141
143
  return objf_ / m / n_povm
142
144
 
143
145
 
@@ -240,7 +242,7 @@ def Mp_norm_lower(X_true, E_true, rho_true, X, E, rho, J, n_povm, p):
240
242
  return dist ** (1 / p) / m / n_povm, max_dist ** (1 / p)
241
243
 
242
244
 
243
- @njit(cache=True)
245
+ @njit(cache=True, parallel=True)
244
246
  def dK(X, K, E, rho, J, y, d, r, rK):
245
247
  """Compute the derivative of the objective function with respect to the Kraus tensor K.
246
248
 
@@ -274,26 +276,32 @@ def dK(X, K, E, rho, J, y, d, r, rK):
274
276
  The derivative objective function with respect to the Kraus tensor K,
275
277
  reshaped to (d, rK, pdim, pdim), and scaled by 2/m/n_povm.
276
278
  """
279
+ # pylint: disable=too-many-nested-blocks
277
280
  K = K.reshape(d, rK, -1)
278
281
  pdim = int(np.sqrt(r))
279
282
  n_povm = y.shape[0]
280
283
  dK_ = np.zeros((d, rK, r))
281
284
  dK_ = np.ascontiguousarray(dK_.astype(np.complex128))
282
285
  m = len(J)
283
- for k in range(d):
286
+
287
+ for k in prange(d): # pylint: disable=not-an-iterable
284
288
  for n in range(m):
285
289
  j = J[n][J[n] >= 0]
286
290
  for i, j_curr in enumerate(j):
287
291
  if j_curr == k:
292
+ R = rho.copy()
293
+ for ind in j[i + 1 :][::-1]:
294
+ R = X[ind] @ R
288
295
  for o in range(n_povm):
289
- L = E[o].conj() @ contract(X, j[:i])
290
- R = contract(X, j[i + 1 :]) @ rho
296
+ L = E[o].conj()
297
+ for ind in j[:i]:
298
+ L = L @ X[ind]
291
299
  D_ind = L @ X[k] @ R - y[o, n]
292
300
  dK_[k] += D_ind * K[k].conj() @ np.kron(L.reshape(pdim, pdim).T, R.reshape(pdim, pdim).T)
293
301
  return dK_.reshape(d, rK, pdim, pdim) * 2 / m / n_povm
294
302
 
295
303
 
296
- @njit(cache=True)
304
+ @njit(cache=True, parallel=False)
297
305
  def dK_dMdM(X, K, E, rho, J, y, d, r, rK):
298
306
  """Compute the derivatives of the objective function with respect to K and the
299
307
  product of derivatives of the measurement map with respect to K.
@@ -397,7 +405,7 @@ def ddM(X, K, E, rho, J, y, d, r, rK):
397
405
  ddK = np.zeros((d**2, rK**2, r, r))
398
406
  ddK = np.ascontiguousarray(ddK.astype(np.complex128))
399
407
  dconjdK = np.zeros((d**2, rK**2, r, r))
400
- dconjdK = np.ascontiguousarray(ddK.astype(np.complex128))
408
+ dconjdK = np.ascontiguousarray(dconjdK.astype(np.complex128))
401
409
  m = len(J)
402
410
  for k in range(d**2):
403
411
  k1, k2 = local_basis(k, d, 2)
@@ -516,16 +524,17 @@ def dA(X, A, B, J, y, r, pdim, n_povm):
516
524
  for k in range(n_povm):
517
525
  E[k] = (A[k].T.conj() @ A[k]).reshape(-1)
518
526
  rho = (B @ B.T.conj()).reshape(-1)
519
- dA_ = np.zeros((n_povm, pdim, pdim))
520
- dA_ = dA_.astype(np.complex128)
527
+ dA_ = np.zeros((n_povm, pdim, pdim)).astype(np.complex128)
521
528
  m = len(J)
522
529
  for n in prange(m): # pylint: disable=not-an-iterable
523
- jE = J[n][J[n] >= 0][0]
524
- j = J[n][J[n] >= 0][1:]
530
+ j = J[n][J[n] >= 0]
525
531
  inner_deriv = contract(X, j) @ rho
526
- D_ind = E[jE].conj().dot(inner_deriv) - y[n]
527
- dA_[jE] += D_ind * A[jE] @ inner_deriv.reshape(pdim, pdim).T.conj()
528
- return dA_
532
+ dA_step = np.zeros((n_povm, pdim, pdim)).astype(np.complex128)
533
+ for o in range(n_povm):
534
+ D_ind = E[o].conj() @ inner_deriv - y[o, n]
535
+ dA_step[o] += D_ind * A[o].conj() @ inner_deriv.reshape(pdim, pdim).T
536
+ dA_ += dA_step
537
+ return dA_ * 2 / m / n_povm
529
538
 
530
539
 
531
540
  @njit(parallel=True, cache=True)
@@ -615,13 +624,21 @@ def ddA_derivs(X, A, B, J, y, r, pdim, n_povm):
615
624
  for n in prange(m): # pylint: disable=not-an-iterable
616
625
  j = J[n][J[n] >= 0]
617
626
  R = contract(X, j) @ rho
627
+ dA_step = np.zeros((n_povm, pdim, pdim)).astype(np.complex128)
628
+ dMdM_step = np.zeros((n_povm, r, r)).astype(np.complex128)
629
+ dMconjdM_step = np.zeros((n_povm, r, r)).astype(np.complex128)
630
+ dconjdA_step = np.zeros((n_povm, r, r)).astype(np.complex128)
618
631
  for o in range(n_povm):
619
632
  D_ind = E[o].conj() @ R - y[o, n]
620
633
  dM = A[o].conj() @ R.reshape(pdim, pdim).T
621
- dMdM[o] += np.outer(dM, dM)
622
- dMconjdM[o] += np.outer(dM.conj(), dM)
623
- dA_[o] += D_ind * dM
624
- dconjdA[o] += D_ind * np.kron(np.eye(pdim).astype(np.complex128), R.reshape(pdim, pdim).T)
634
+ dMdM_step[o] += np.outer(dM, dM)
635
+ dMconjdM_step[o] += np.outer(dM.conj(), dM)
636
+ dA_step[o] += D_ind * dM
637
+ dconjdA_step[o] += D_ind * np.kron(np.eye(pdim).astype(np.complex128), R.reshape(pdim, pdim).T)
638
+ dA_ += dA_step
639
+ dMdM += dMdM_step
640
+ dMconjdM += dMconjdM_step
641
+ dconjdA += dconjdA_step
625
642
  return dA_ * 2 / m / n_povm, dMdM * 2 / m / n_povm, dMconjdM * 2 / m / n_povm, dconjdA * 2 / m / n_povm
626
643
 
627
644
 
mGST/qiskit_interface.py CHANGED
@@ -31,7 +31,7 @@ def qiskit_gate_to_operator(gate_set):
31
31
  numpy.ndarray
32
32
  An array of process matrices. Each element in the array is a 2D NumPy array.
33
33
  """
34
- return np.array([[Operator(gate).to_matrix()] for gate in gate_set])
34
+ return np.array([[Operator(gate).reverse_qargs().to_matrix()] for gate in gate_set])
35
35
 
36
36
 
37
37
  def add_idle_gates(gate_set, active_qubits, gate_qubits):
@@ -339,7 +339,7 @@ def compute_sparsest_Pauli_Hamiltonian(U_set):
339
339
  pdim = U_set.shape[1]
340
340
  pp_vecs = []
341
341
 
342
- for num, U in enumerate(U_set):
342
+ for _, U in enumerate(U_set):
343
343
  # Schur decomposition finds the unitary diagonalization of a unitary matrix, which is not always returned by np.linalg.eig
344
344
  T, evecs = schur(U)
345
345
  evals = np.diag(T)