iqm-benchmarks 2.53__py3-none-any.whl → 2.55__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.
@@ -109,10 +109,12 @@ class BenchmarkConfigurationBase(BaseModel):
109
109
 
110
110
  benchmark: Type[BenchmarkBase]
111
111
  shots: int = 2**8
112
+ quantum_computer: str | None = None
112
113
  max_gates_per_batch: Optional[int] = None
113
114
  max_circuits_per_batch: Optional[int] = None
114
115
  calset_id: Optional[str] = None
115
116
  routing_method: Literal["basic", "lookahead", "stochastic", "sabre", "none"] = "sabre"
116
117
  physical_layout: Literal["fixed", "batching"] = "fixed"
117
118
  use_dd: Optional[bool] = False
119
+ active_reset_cycles: int | None = None
118
120
  dd_strategy: Optional[DDStrategy] = None
@@ -254,11 +254,16 @@ class Benchmark(ABC):
254
254
  qcvv_logger.warning(
255
255
  f"Beware that activating dynamical decoupling will change fidelities, error models and their interpretation."
256
256
  )
257
- self.circuit_compilation_options = CircuitCompilationOptions(
258
- dd_mode=DDMode.ENABLED, dd_strategy=self.configuration.dd_strategy
259
- )
257
+ dd_mode = DDMode.ENABLED
258
+
260
259
  else:
261
- self.circuit_compilation_options = CircuitCompilationOptions(dd_mode=DDMode.DISABLED)
260
+ dd_mode = DDMode.DISABLED
261
+
262
+ self.circuit_compilation_options = CircuitCompilationOptions(
263
+ dd_mode=dd_mode,
264
+ dd_strategy=self.configuration.dd_strategy,
265
+ active_reset_cycles=self.configuration.active_reset_cycles
266
+ )
262
267
 
263
268
  @classmethod
264
269
  @abstractmethod
@@ -294,7 +299,7 @@ class Benchmark(ABC):
294
299
  """
295
300
  backend_for_execute = copy.copy(self.backend)
296
301
  backend_for_execute.run = functools.partial(
297
- self.backend.run, calibration_set_id=calibration_set_id
302
+ self.backend.run
298
303
  ) # type: ignore
299
304
  dataset = self.execute(backend_for_execute)
300
305
  run = BenchmarkRunResult(dataset, self.circuits)
@@ -19,6 +19,7 @@ GHZ state benchmark
19
19
  from itertools import chain
20
20
  from time import strftime
21
21
  from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, cast
22
+ import warnings
22
23
 
23
24
  from matplotlib.figure import Figure
24
25
  import matplotlib.pyplot as plt
@@ -27,7 +28,6 @@ from networkx import Graph, all_pairs_shortest_path, is_connected, minimum_spann
27
28
  import numpy as np
28
29
  from qiskit import ClassicalRegister, QuantumRegister
29
30
  from qiskit.quantum_info import random_clifford
30
- from qiskit.transpiler import CouplingMap
31
31
  from qiskit_aer import Aer
32
32
  from scipy.spatial.distance import hamming
33
33
  import xarray as xr
@@ -45,7 +45,7 @@ from iqm.benchmarks.circuit_containers import BenchmarkCircuit, CircuitGroup, Ci
45
45
  from iqm.benchmarks.logging_config import qcvv_logger
46
46
  from iqm.benchmarks.readout_mitigation import apply_readout_error_mitigation
47
47
  from iqm.benchmarks.utils import (
48
- extract_fidelities,
48
+ extract_fidelities_unified,
49
49
  perform_backend_transpilation,
50
50
  reduce_to_active_qubits,
51
51
  retrieve_all_counts,
@@ -309,7 +309,7 @@ def generate_ghz_log_cruz(num_qubits: int) -> QuantumCircuit:
309
309
 
310
310
 
311
311
  def generate_ghz_star_optimal(
312
- qubit_layout: List[int], cal_url: str, backend: IQMBackendBase, inv: bool = False
312
+ qubit_layout: List[int], iqm_server_url: str, backend: IQMBackendBase, quantum_computer: str, inv: bool = False,
313
313
  ) -> QuantumCircuit:
314
314
  """
315
315
  Generates the circuit for creating a GHZ state by maximizing the number of CZ gates between a pair of MOVE gates.
@@ -317,10 +317,12 @@ def generate_ghz_star_optimal(
317
317
  Args:
318
318
  qubit_layout: List[int]
319
319
  The layout of qubits for the GHZ state.
320
- cal_url: str
321
- The calibration URL for extracting fidelities.
320
+ iqm_server_url: str
321
+ The IQM server URL for extracting fidelities.
322
322
  backend: IQMBackendBase
323
323
  The backend to be used for the quantum circuit.
324
+ quantum_computer: str
325
+ The name of the quantum computer to be used for calbration data, i.e. "garnet", "emerald", ...
324
326
  inv: bool
325
327
  Whether to generate the inverse circuit.
326
328
 
@@ -330,49 +332,48 @@ def generate_ghz_star_optimal(
330
332
  num_qubits = len(qubit_layout)
331
333
 
332
334
  # Initialize quantum and classical registers
333
- comp_r = QuantumRegister(1, "comp_r") # Computational resonator
334
335
  q = QuantumRegister(backend.num_qubits, "q") # Qubits
335
336
  c = ClassicalRegister(num_qubits, "c")
336
- qc = QuantumCircuit(comp_r, q, c, name="GHZ_star_optimal")
337
-
338
- cal_data = extract_fidelities(cal_url, all_metrics=True)
337
+ qc = QuantumCircuit(q, c, name="GHZ_star_optimal")
338
+ # Extract calibration data
339
+ cal_data = extract_fidelities_unified(iqm_server_url, backend, quantum_computer)
339
340
  # Determine the best move qubit
340
- move_dict = {q + 1: cal_data[1][q] for q in qubit_layout} ## +1 to match qubit indexing in cal data
341
+ double_move_fidelities = {k[0]: v for k, v in list(cal_data[-1]["double_move_gate_fidelity"].items())[::2]}
342
+ move_dict = {q: double_move_fidelities[q + 1] for q in qubit_layout} ## +1 to match qubit indexing in cal data
343
+ # move_dict = {q + 1: cal_data[1][q] for q in qubit_layout} ## +1 to match qubit indexing in cal data
341
344
  best_move = max(move_dict, key=move_dict.get)
342
345
 
343
- T2 = cal_data[-1]["t2_echo_time"]
344
- t2_dict = {qubit + 1: T2[qubit + 1] for qubit in qubit_layout} ## +1 to match qubit indexing in cal data
346
+ T2 = cal_data[-1]["t2_time"]
347
+ t2_dict = {qubit: T2[qubit + 1] for qubit in qubit_layout} ## +1 to match qubit indexing in cal data
345
348
  cz_order = dict(sorted(t2_dict.items(), key=lambda item: item[1], reverse=True))
346
349
  qubits_to_measure = list(cz_order.keys())
347
350
  cz_order.pop(best_move)
348
351
 
349
352
  # Construct the quantum circuit
350
- qc.h(best_move)
351
- qc.move(best_move, 0)
353
+ qc.r(np.pi/2, 3*np.pi/2, best_move)
354
+ qc.barrier(best_move)
352
355
  for qubit in cz_order.keys():
353
- qc.cx(0, qubit)
354
- qc.barrier()
355
- qc.move(best_move, 0)
356
+ qc.barrier(qubit)
357
+ qc.r(np.pi/2, 3*np.pi/2, qubit)
358
+ qc.cz(best_move, qubit)
359
+ qc.r(np.pi/2, 5*np.pi/2, qubit)
360
+ qc.barrier(qubit)
356
361
  qc.barrier()
357
362
  qc.measure(sorted(qubits_to_measure), list(range(num_qubits)))
358
-
363
+ qcvv_logger.info(f"Best MOVE qubit selected: {best_move} with double MOVE fidelity {move_dict[best_move]:.4f} and T2 time {t2_dict[best_move]:.2f} us.")
359
364
  if inv:
360
- comp_r = QuantumRegister(1, "comp_r") # Computational resonator
361
365
  q = QuantumRegister(backend.num_qubits, "q") # Qubits
362
366
  c = ClassicalRegister(num_qubits, "c")
363
- qc = QuantumCircuit(comp_r, q, c, name="GHZ_star_optimal_inv")
364
-
365
- qc.move(best_move, 0)
367
+ qc = QuantumCircuit(q, c, name="GHZ_star_optimal_inv")
366
368
  for qubit in reversed(cz_order.keys()):
367
- qc.cx(0, qubit)
368
- qc.barrier()
369
- qc.move(best_move, 0)
370
- qc.h(best_move)
371
- qc.barrier()
372
-
369
+ qc.barrier(qubit)
370
+ qc.r(np.pi/2, 5*np.pi/2, qubit)
371
+ qc.cz(best_move, qubit)
372
+ qc.r(np.pi/2, 3*np.pi/2, qubit)
373
+ qc.barrier(qubit)
374
+ qc.r(np.pi/2, 3*np.pi/2, best_move)
373
375
  return qc
374
376
 
375
-
376
377
  def generate_ghz_star(num_qubits: int) -> QuantumCircuit:
377
378
  """
378
379
  Generates the circuit for creating a GHZ state by maximizing the number of CZ gates between a pair of MOVE gates.
@@ -456,7 +457,7 @@ def generate_ghz_spanning_tree(
456
457
 
457
458
 
458
459
  def get_edges(
459
- coupling_map: CouplingMap,
460
+ coupling_map: list[tuple[int]],
460
461
  qubit_layout: List[int],
461
462
  edges_cal: Optional[List[List[int]]] = None,
462
463
  fidelities_cal: Optional[List[float]] = None,
@@ -482,7 +483,7 @@ def get_edges(
482
483
  for idx, edge in enumerate(coupling_map):
483
484
  if edge[0] in qubit_layout and edge[1] in qubit_layout:
484
485
  if not set(edge) in edges_patch:
485
- edges_patch.append(set(edge))
486
+ edges_patch.append(edge)
486
487
 
487
488
  if fidelities_cal is not None and edges_cal is not None:
488
489
  fidelities_cal = list(
@@ -491,9 +492,15 @@ def get_edges(
491
492
  fidelities_patch = []
492
493
  for edge in edges_patch:
493
494
  for idx, edge_2 in enumerate(edges_cal):
494
- if edge == set(edge_2):
495
+ if set(edge) == set(edge_2):
495
496
  fidelities_patch.append(fidelities_cal[idx])
496
- weights = -np.log(np.array(fidelities_patch))
497
+ if len(fidelities_patch) != len(edges_patch):
498
+ warnings.warn(
499
+ f"Not all calibration fidelities were found for the selected qubit layout {qubit_layout}, using unweighted graph for circuit creation."
500
+ )
501
+ weights = np.ones(len(edges_patch))
502
+ else:
503
+ weights = -np.log(np.array(fidelities_patch))
497
504
  else:
498
505
  weights = np.ones(len(edges_patch))
499
506
  graph = Graph()
@@ -646,7 +653,8 @@ class GHZBenchmark(Benchmark):
646
653
  self.num_RMs = configuration.num_RMs
647
654
  self.rem = configuration.rem
648
655
  self.mit_shots = configuration.mit_shots
649
- self.cal_url = configuration.cal_url
656
+ self.iqm_server_url = configuration.iqm_server_url
657
+ self.quantum_computer = configuration.quantum_computer
650
658
  self.timestamp = strftime("%Y%m%d-%H%M%S")
651
659
  self.execution_timestamp = ""
652
660
 
@@ -689,8 +697,13 @@ class GHZBenchmark(Benchmark):
689
697
  qcvv_logger.warning(
690
698
  f"The current backend is a star architecture for which a suboptimal state generation routine is chosen. Consider setting state_generation_routine={routine}."
691
699
  )
692
- if self.cal_url:
693
- edges_cal, fidelities_cal, _ = extract_fidelities(self.cal_url)
700
+ if self.iqm_server_url is not None:
701
+ edges_cal, fidelities_cal, _, _, _ = extract_fidelities_unified(self.iqm_server_url, self.backend, self.quantum_computer)
702
+ # Replace fidelities >= 1.0 with median of fidelities < 1.0
703
+ valid_fidelities = [f for f in fidelities_cal if f < 1.0]
704
+ if valid_fidelities:
705
+ median_fidelity = np.median(valid_fidelities)
706
+ fidelities_cal = [f if f < 1.0 else median_fidelity for f in fidelities_cal]
694
707
  graph = get_edges(self.backend.coupling_map, qubit_layout, edges_cal, fidelities_cal)
695
708
  else:
696
709
  graph = get_edges(self.backend.coupling_map, qubit_layout)
@@ -718,15 +731,14 @@ class GHZBenchmark(Benchmark):
718
731
  )
719
732
  final_ghz = ghz_native_transpiled
720
733
  elif routine == "star_optimal":
721
- if self.cal_url is None:
722
- raise ValueError("Calibration URL must be provided for 'star_optimal' routine.")
723
- ghz = generate_ghz_star_optimal(qubit_layout, self.cal_url, self.backend)
734
+ if self.iqm_server_url is None or self.quantum_computer is None:
735
+ raise ValueError("IQM server url and quantum_computer argument must be provided "
736
+ "for 'star_optimal' routine.")
737
+ ghz = generate_ghz_star_optimal(qubit_layout, self.iqm_server_url, self.backend, self.quantum_computer)
724
738
  circuit_group.add_circuit(ghz)
725
739
  ghz_native_transpiled = transpile_to_IQM(
726
740
  ghz,
727
741
  self.backend,
728
- existing_moves_handling=True,
729
- perform_move_routing=False,
730
742
  optimize_single_qubits=self.optimize_sqg,
731
743
  optimization_level=self.qiskit_optim_level,
732
744
  )
@@ -775,7 +787,7 @@ class GHZBenchmark(Benchmark):
775
787
  qc.remove_final_measurements()
776
788
  phases = [np.pi * i / (qubit_count + 1) for i in range(2 * qubit_count + 2)]
777
789
  if self.state_generation_routine == "star_optimal":
778
- qc_inv = generate_ghz_star_optimal(qubit_layout, self.cal_url, self.backend, inv=True)
790
+ qc_inv = generate_ghz_star_optimal(qubit_layout, self.iqm_server_url, self.backend, self.quantum_computer, inv=True)
779
791
  for phase in phases:
780
792
  qc_phase = qc.copy()
781
793
  qc_phase.barrier()
@@ -804,8 +816,6 @@ class GHZBenchmark(Benchmark):
804
816
  transpile_to_IQM(
805
817
  ghz,
806
818
  self.backend,
807
- existing_moves_handling=True,
808
- perform_move_routing=False,
809
819
  optimize_single_qubits=self.optimize_sqg,
810
820
  optimization_level=self.qiskit_optim_level,
811
821
  )
@@ -1026,7 +1036,7 @@ class GHZConfiguration(BenchmarkConfigurationBase):
1026
1036
  * Default: True
1027
1037
  mit_shots (int): Total number of shots for readout error mitigation
1028
1038
  * Default: 1000
1029
- cal_url (Optional[str]): Optional URL where the calibration data for the selected backend can be retrieved from
1039
+ iqm_server_url (Optional[str]): Optional iqm server URL where the calibration data for the selected backend can be retrieved from
1030
1040
  The calibration data is used for the "tree" state generation routine to prioritize couplings with high
1031
1041
  CZ fidelity.
1032
1042
  * Default: None
@@ -1043,4 +1053,4 @@ class GHZConfiguration(BenchmarkConfigurationBase):
1043
1053
  num_RMs: Optional[int] = 100
1044
1054
  rem: bool = True
1045
1055
  mit_shots: int = 1_000
1046
- cal_url: Optional[str] = None
1056
+ iqm_server_url: Optional[str] = None
@@ -170,15 +170,23 @@ def retrieve_clops_elapsed_times(job_meta: Dict[str, Dict[str, Any]]) -> Dict[st
170
170
  # ["timestamps"] might be empty if backend is a simulator
171
171
  if job_meta[update][batch]["timestamps"] is not None:
172
172
  x = job_meta[update][batch]["timestamps"]
173
- job_time_format = "%Y-%m-%dT%H:%M:%S.%f%z" # Is it possible to extract this automatically?
174
- compile_f = datetime.strptime(x["compilation_ended"], job_time_format)
175
- compile_i = datetime.strptime(x["compilation_started"], job_time_format)
176
- # submit_f = datetime.strptime(x["submit_end"], job_time_format)
177
- # submit_i = datetime.strptime(x["submit_start"], job_time_format)
178
- execution_f = datetime.strptime(x["execution_ended"], job_time_format)
179
- execution_i = datetime.strptime(x["execution_started"], job_time_format)
180
- job_f = datetime.strptime(x["ready"], job_time_format)
181
- job_i = datetime.strptime(x["received"], job_time_format)
173
+
174
+ # Check if timestamps need parsing or are already datetime objects
175
+ if isinstance(x["compilation_ended"], str):
176
+ job_time_format = "%Y-%m-%dT%H:%M:%S.%f%z"
177
+ compile_f = datetime.strptime(x["compilation_ended"], job_time_format)
178
+ compile_i = datetime.strptime(x["compilation_started"], job_time_format)
179
+ execution_f = datetime.strptime(x["execution_ended"], job_time_format)
180
+ execution_i = datetime.strptime(x["execution_started"], job_time_format)
181
+ job_f = datetime.strptime(x["ready"], job_time_format)
182
+ job_i = datetime.strptime(x["received"], job_time_format)
183
+ else:
184
+ compile_f = x["compilation_ended"]
185
+ compile_i = x["compilation_started"]
186
+ execution_f = x["execution_ended"]
187
+ execution_i = x["execution_started"]
188
+ job_f = x["ready"]
189
+ job_i = x["received"]
182
190
 
183
191
  all_job_elapsed[update][batch] = {
184
192
  "job_total": job_f - job_i,
@@ -187,6 +195,7 @@ def retrieve_clops_elapsed_times(job_meta: Dict[str, Dict[str, Any]]) -> Dict[st
187
195
  "execution_total": execution_f - execution_i,
188
196
  }
189
197
 
198
+
190
199
  # Save the keys, will be needed later
191
200
  totals_keys = all_job_elapsed[update][batch].keys()
192
201
 
@@ -574,6 +583,7 @@ class CLOPSBenchmark(Benchmark):
574
583
  # Retrieve and save all job metadata
575
584
  all_job_metadata = retrieve_all_job_metadata(all_jobs)
576
585
  self.job_meta_per_update["update_" + str(update + 1)] = all_job_metadata
586
+ self.all_jobs = all_jobs
577
587
 
578
588
  return time_parameter_assign, time_submit, time_retrieve
579
589
 
@@ -470,6 +470,7 @@ class QuantumVolumeBenchmark(Benchmark):
470
470
 
471
471
  self.qiskit_optim_level = configuration.qiskit_optim_level
472
472
  self.optimize_sqg = configuration.optimize_sqg
473
+ self.approximation_degree = configuration.approximation_degree
473
474
 
474
475
  self.rem = configuration.rem
475
476
  self.mit_shots = configuration.mit_shots
@@ -529,65 +530,6 @@ class QuantumVolumeBenchmark(Benchmark):
529
530
  }
530
531
  dataset.attrs[key] = dictionary
531
532
 
532
- # def get_mapomatic_average_qv_scores(self) -> List[List[int]]:
533
- # """Estimate the average mapomatic scores for N quantum volume circuit samples
534
- # Returns:
535
- # List[List[object]]: the mapomatic layout scores sorted from the smallest mean*(1+std/sqrt(N)) cost score
536
- # """
537
- # qv_circ_samples = self.mapomatic_qv_samples
538
- #
539
- # # Get calibration data once
540
- # token = input("Trying to access IQM calibration data\nEnter your IQM token (without quote marks): ")
541
- # calibration_data = get_calibration_fidelities(self.backend.architecture.name, token)
542
- #
543
- # qcvv_logger.info(
544
- # "Evaluating matching layouts for QV",
545
- # )
546
- #
547
- # all_scores: Dict[Tuple, List[float]] = {}
548
- # for mapomatic_qv_sample in range(qv_circ_samples):
549
- # qcvv_logger.info(
550
- # f"Estimating layout costs for QV circuit sample {mapomatic_qv_sample+1}"
551
- # )
552
- # # Generate a representative quantum circuit sample
553
- # qv_circ = get_circuit("quantum_volume", self.mapomatic_num_qubits)
554
- # # Get all matching layouts
555
- # layouts = matching_layouts(
556
- # qv_circ,
557
- # self.backend,
558
- # self.backend.coupling_map,
559
- # self.qiskit_optim_level,
560
- # )
561
- # # Evaluate all layouts
562
- # scores = evaluate_costs(
563
- # layouts,
564
- # qv_circ,
565
- # self.backend,
566
- # calibration_data,
567
- # self.qiskit_optim_level,
568
- # )
569
- # for score in scores:
570
- # all_scores.setdefault(tuple(cast(list[int], score[0])), []).extend([cast(float, score[1])])
571
- # qv_circ.clear()
572
- #
573
- # # Consider mean and std of the cost scores as mean*(1+std/sqrt(N))
574
- # all_scores_mean_plus_std = {
575
- # s_k: np.mean(s_v) * (1 + np.std(s_v) / np.sqrt(qv_circ_samples)) for s_k, s_v in all_scores.items()
576
- # }
577
- # # Turn into an array ([[tuple_layout0, score0], [tuple_layout1,score1], ... ])
578
- # sorted_scores_mean_plus_std = list(sorted(all_scores_mean_plus_std.items(), key=lambda y: y[1]))
579
- # # Return indices to list type for layouts ([[list_layout0, score0], [list_layout1,score1], ... ])
580
- # layout_meanscore = [[list(s[0]), s[1]] for s in sorted_scores_mean_plus_std]
581
- #
582
- # mapomatic_qv_layouts = [x[0] for x in layout_meanscore[: self.mapomatic_num_layouts]]
583
- # mapomatic_qv_costs = [x[1] for x in layout_meanscore[: self.mapomatic_num_layouts]]
584
- #
585
- # qcvv_logger.info(
586
- # f"Will execute QV on layouts {mapomatic_qv_layouts}, of mapomatic average costs {mapomatic_qv_costs}"
587
- # )
588
- #
589
- # return mapomatic_qv_layouts
590
-
591
533
  @staticmethod
592
534
  def generate_single_circuit(
593
535
  num_qubits: int,
@@ -755,6 +697,7 @@ class QuantumVolumeBenchmark(Benchmark):
755
697
  qiskit_optim_level=self.qiskit_optim_level,
756
698
  optimize_sqg=self.optimize_sqg,
757
699
  routing_method=self.routing_method,
700
+ approximation_degree=self.approximation_degree,
758
701
  )
759
702
  # Batching
760
703
  sorted_transpiled_qc_list: Dict[Tuple[int, ...], List[QuantumCircuit]] = {}
@@ -845,27 +788,23 @@ class QuantumVolumeConfiguration(BenchmarkConfigurationBase):
845
788
  Attributes:
846
789
  benchmark (Type[Benchmark]): QuantumVolumeBenchmark
847
790
  num_circuits (int): The number of circuits to use.
848
- * Should be at least 100 for a meaningful QV experiment.
791
+ Should be at least 100 for a meaningful QV experiment.
849
792
  num_sigmas (int): The number of sample standard deviations to consider with for the threshold criteria.
850
- * Default by consensus is 2
793
+ Default by consensus is 2
851
794
  choose_qubits_routine (Literal["custom"]): The routine to select qubit layouts.
852
- * Default is "custom".
795
+ Default is "custom".
853
796
  custom_qubits_array (Optional[Sequence[Sequence[int]]]): The physical qubit layouts to perform the benchmark on.
854
- * Default is [[0, 2]].
797
+ Default is [[0, 2]].
855
798
  qiskit_optim_level (int): The Qiskit transpilation optimization level.
856
- * Default is 3.
799
+ Default is 3.
857
800
  optimize_sqg (bool): Whether Single Qubit Gate Optimization is performed upon transpilation.
858
- * Default is True.
859
- routing_method (Literal["basic", "lookahead", "stochastic", "sabre", "none"]): The Qiskit transpilation routing method to use.
860
- * Default is "sabre".
861
- physical_layout (Literal["fixed", "batching"]): Whether the coupling map is restricted to qubits in the input layout or not.
862
- - "fixed": Restricts the coupling map to only the specified qubits.
863
- - "batching": Considers the full coupling map of the backend and circuit execution is batched per final layout.
864
- * Default is "fixed"
865
- rem (bool): Whether Readout Error Mitigation is applied in post-processing. When set to True, both results (readout-unmitigated and -mitigated) are produced.
866
- - Default is True.
801
+ Default is True.
802
+ approximation_degree (float): The target fidelity to which arbitrary two qubit gates are approximated.
803
+ rem (bool): Whether Readout Error Mitigation is applied in post-processing. When set to True, both results
804
+ (readout-unmitigated and -mitigated) are produced.
805
+ Default is True.
867
806
  mit_shots (int): The measurement shots to use for readout calibration.
868
- * Default is 1_000.
807
+ Default is 1_000.
869
808
  """
870
809
 
871
810
  benchmark: Type[Benchmark] = QuantumVolumeBenchmark
@@ -875,5 +814,6 @@ class QuantumVolumeConfiguration(BenchmarkConfigurationBase):
875
814
  custom_qubits_array: Sequence[Sequence[int]]
876
815
  qiskit_optim_level: int = 3
877
816
  optimize_sqg: bool = True
817
+ approximation_degree: float = 1.0
878
818
  rem: bool = True
879
819
  mit_shots: int = 1_000
@@ -245,7 +245,8 @@ class EPLGBenchmark(Benchmark):
245
245
  self.chain_length = configuration.chain_length
246
246
  self.chain_path_samples = configuration.chain_path_samples
247
247
  self.num_disjoint_layers = configuration.num_disjoint_layers
248
- self.calibration_url = configuration.calibration_url
248
+ self.iqm_server_url = configuration.iqm_server_url
249
+ self.quantum_computer = configuration.quantum_computer
249
250
  self.max_hamiltonian_path_tries = configuration.max_hamiltonian_path_tries
250
251
 
251
252
  def add_all_meta_to_dataset(self, dataset: xr.Dataset):
@@ -298,8 +299,8 @@ class EPLGBenchmark(Benchmark):
298
299
  raise ValueError("The number of chain path samples must be a positive integer.")
299
300
 
300
301
  # Check calibration URL - this is a temporary solution, normally the backend should be enough to specify this
301
- if self.calibration_url is None:
302
- raise ValueError("The calibration URL must be specified if custom qubits array is not specified.")
302
+ if self.iqm_server_url is None:
303
+ raise ValueError("The IQM server URL must be specified if custom qubits array is not specified.")
303
304
 
304
305
  if self.num_disjoint_layers is None:
305
306
  self.num_disjoint_layers = 2
@@ -332,11 +333,14 @@ class EPLGBenchmark(Benchmark):
332
333
  self.validate_random_chain_inputs()
333
334
  num_qubits = cast(int, self.chain_length)
334
335
  qcvv_logger.info("Generating linear chain path")
336
+ if self.iqm_server_url is None or self.quantum_computer is None:
337
+ raise ValueError("IQM server URL and quantum computer name must be specified for random chain evaluation.")
335
338
  h_path_costs = evaluate_hamiltonian_paths(
336
339
  self.chain_length,
337
340
  self.chain_path_samples,
338
341
  self.backend,
339
- self.calibration_url,
342
+ self.iqm_server_url,
343
+ self.quantum_computer,
340
344
  self.max_hamiltonian_path_tries,
341
345
  )
342
346
  qcvv_logger.info("Extracting the path that maximizes total 2Q calibration fidelity")
@@ -391,7 +395,7 @@ class EPLGConfiguration(BenchmarkConfigurationBase):
391
395
  * Default is None: assigns 2 disjoint layers (arbitrary).
392
396
  max_hamiltonian_path_tries (Optional[int]): The maximum number of tries to find a Hamiltonian path.
393
397
  * Default is None: assigns 10 tries (arbitrary).
394
- calibration_url (Optional[str]): The URL of the IQM station to retrieve calibration data from.
398
+ iqm_server_url (Optional[str]): The URL of the IQM station to retrieve calibration data from.
395
399
  * It must be specified if custom_qubits_array is not specified.
396
400
  * Default is None - raises an error if custom_qubits_array is not specified.
397
401
 
@@ -405,4 +409,4 @@ class EPLGConfiguration(BenchmarkConfigurationBase):
405
409
  chain_path_samples: Optional[int] = None
406
410
  num_disjoint_layers: Optional[int] = None
407
411
  max_hamiltonian_path_tries: Optional[int] = None
408
- calibration_url: Optional[str] = None
412
+ iqm_server_url: Optional[str] = None
iqm/benchmarks/utils.py CHANGED
@@ -16,11 +16,13 @@
16
16
  General utility functions
17
17
  """
18
18
  from collections import defaultdict
19
+ from enum import Enum
19
20
  from functools import wraps
20
21
  import itertools
21
22
  from math import floor
22
23
  import os
23
24
  import random
25
+ import re
24
26
  from time import time
25
27
  from typing import Any, Dict, Iterable, List, Literal, Optional, Sequence, Set, Tuple, Union, cast
26
28
  import warnings
@@ -38,6 +40,7 @@ import requests
38
40
  import xarray as xr
39
41
 
40
42
  from iqm.benchmarks.logging_config import qcvv_logger
43
+ from iqm.iqm_client import IQMClient
41
44
  from iqm.iqm_client.models import CircuitCompilationOptions
42
45
  from iqm.qiskit_iqm import IQMCircuit as QuantumCircuit
43
46
  from iqm.qiskit_iqm import IQMFakeDeneb, optimize_single_qubit_gates, transpile_to_IQM
@@ -286,11 +289,23 @@ def get_active_qubits(qc: QuantumCircuit) -> List[int]:
286
289
  return list(active_qubits)
287
290
 
288
291
 
289
- def extract_fidelities(cal_url: str, all_metrics: bool = False) -> Union[
290
- Tuple[List[List[int]], List[float], str, Dict[int, int]],
291
- Tuple[List[List[int]], List[float], str, Dict[int, int], Dict[str, Dict[Union[int, Tuple[int, int]], float]]],
292
- ]:
293
- """Returns couplings and CZ-fidelities from calibration data URL
292
+ class ObservationType(Enum):
293
+ """Enumeration representing relevant keys to fetch for each operation in the observations."""
294
+
295
+ CZ = "cz"
296
+ CLIFFORD = ("cz", "clifford")
297
+ SQG = "prx"
298
+ READOUT = ("measure_fidelity", ".fidelity")
299
+ READOUT_QNDNESS = ("measure", "qndness", ".fidelity")
300
+ DOUBLE_MOVE = "move"
301
+ T1 = ("t1", "QB")
302
+ T2 = ("t2", "QB")
303
+
304
+
305
+ def extract_fidelities_unified(
306
+ iqm_server_url: str, backend: IQMBackendBase, quantum_computer: str
307
+ ) -> tuple[list[list[int]], list[float], str, dict[Any, int], dict[str, dict[int | tuple[int, int], float]]]:
308
+ """Returns couplings and CZ-fidelities from calibration data URL for external station API
294
309
 
295
310
  Args:
296
311
  cal_url: str
@@ -304,73 +319,93 @@ def extract_fidelities(cal_url: str, all_metrics: bool = False) -> Union[
304
319
  data contains a fidelity.
305
320
  list_fids: List[float]
306
321
  A list of CZ fidelities from the calibration url, ordered in the same way as list_couplings
307
- topology: str
308
- Name of the chip topology layout, currently either "star" or "crystal"
309
- qubit_mapping: Dict[int, int]
310
- Enumerating all calibrated qubits starting from 0. For instance if on a 5 qubit chip the qubits 2, 3 are calibrated,
311
- the mapping will be {2: 0, 3: 1}.
312
322
  metrics_dict: Dict
313
323
  Dictionary of all metrics (returned only if all_metrics=True)
314
324
  Format: {metric_name: {qubit: value}} for single qubit metrics
315
325
  Format: {metric_name: {(qubit_1, qubit_2): value}} for two qubit metrics
316
326
  """
317
- headers = {"Accept": "application/json", "Authorization": "Bearer " + os.environ["IQM_TOKEN"]}
318
- r = requests.get(cal_url, headers=headers, timeout=60)
319
- calibration = r.json()
320
- cal_keys = {
321
- el2["key"]: (i, j) for i, el1 in enumerate(calibration["calibrations"]) for j, el2 in enumerate(el1["metrics"])
322
- }
323
- resonator_names = ["COMPR", "COMP_R", "MPR1", "MPR_1"]
327
+ # Create dictionaries to map key names to their corresponding metrics
328
+ cz_fidelity: Dict[Union[int, Tuple[int, int]], float] = {}
329
+ single_qubit_fidelity: Dict[int, float] = {}
330
+ readout_fidelity: Dict[int, float] = {}
331
+ t1: Dict[int, float] = {}
332
+ t2: Dict[int, float] = {}
333
+ move_fidelity: Dict[Tuple[int, int], float] = {}
334
+ quality_metric_set = IQMClient(iqm_server_url, quantum_computer=quantum_computer).get_quality_metric_set()
335
+ calibration_metrics = quality_metric_set.observations
336
+
324
337
  list_couplings = []
325
338
  list_fids = []
326
- if "double_move_gate_fidelity" in cal_keys.keys():
327
- i, j = cal_keys["double_move_gate_fidelity"]
339
+ gates_info: Dict[str, Dict[str, Any]] = {}
340
+ for gate in backend.architecture.gates.keys():
341
+ gates_info[gate] = {
342
+ x: backend.architecture.gates[gate].implementations[x].loci
343
+ for x in backend.architecture.gates[gate].implementations.keys()
344
+ }
345
+
346
+ if backend.has_resonators():
328
347
  topology = "star"
329
348
  else:
330
- i, j = cal_keys["cz_gate_fidelity"]
331
349
  topology = "crystal"
332
- for item in calibration["calibrations"][i]["metrics"][j]["metrics"]:
333
- qb1 = (
334
- int(item["locus"][0][2:])
335
- if not any(resonator in item["locus"][0] for resonator in resonator_names)
336
- else 0
337
- )
338
- qb2 = (
339
- int(item["locus"][1][2:])
340
- if not any(resonator in item["locus"][1] for resonator in resonator_names)
341
- else 0
342
- )
343
- list_couplings.append([qb1, qb2])
344
- list_fids.append(float(item["value"]))
345
- calibrated_qubits = set(np.array(list_couplings).reshape(-1))
346
-
347
- # Process all metrics if all_metrics is True
348
- metrics_dict: Dict[str, Dict[Union[int, Tuple[int, int]], float]] = {}
349
- for metric_key, (i, j) in cal_keys.items():
350
- metric_data = calibration["calibrations"][i]["metrics"][j]["metrics"]
351
- metrics_dict[metric_key] = {}
352
-
353
- for item in metric_data:
354
- # Determine if it's a single or two-qubit metric
355
- if "component" in item:
356
- # Single qubit metric
357
- component = item["component"]
358
- if not any(resonator in component for resonator in resonator_names):
359
- qb = int(component[2:])
360
- metrics_dict[metric_key][qb] = float(item["value"])
361
- calibrated_qubits.add(qb) # Add qubits that have a single qubit metric
362
- if "locus" in item and len(item["locus"]) == 2:
363
- # Two qubit metric
364
- locus = item["locus"]
365
- if not (any(resonator in locus[0] for resonator in resonator_names) or any(resonator in locus[1] for resonator in resonator_names)):
366
- qb1 = int(locus[0][2:])
367
- qb2 = int(locus[1][2:])
368
- metrics_dict[metric_key][(qb1, qb2)] = float(item["value"])
369
350
 
351
+ # Iterate over the calibration metrics
352
+ for metrics in calibration_metrics:
353
+ dut_field = metrics.dut_field
354
+ values = metrics.value
355
+ if all(obs in dut_field for obs in ObservationType.READOUT.value):
356
+ qubit_index = int(dut_field.split("QB")[1].split(".")[0])
357
+ readout_fidelity[qubit_index] = values
358
+ elif ObservationType.SQG.value in dut_field and any(
359
+ x in dut_field for x in gates_info[ObservationType.SQG.value]
360
+ ):
361
+ qubit_index = int(dut_field.split("QB")[1].split(".")[0])
362
+ single_qubit_fidelity[qubit_index] = values
363
+ elif all(obs in dut_field for obs in ObservationType.T1.value):
364
+ qubit_index = int(dut_field.split("QB")[1].split(".")[0])
365
+ t1[qubit_index] = values * 10**6
366
+ elif all(obs in dut_field for obs in ObservationType.T2.value):
367
+ qubit_index = int(dut_field.split("QB")[1].split(".")[0])
368
+ t2[qubit_index] = values * 10**6
369
+ elif ObservationType.DOUBLE_MOVE.value in dut_field and any(
370
+ x in dut_field for x in gates_info[ObservationType.DOUBLE_MOVE.value]
371
+ ):
372
+ qb_matches = re.findall(r"QB\d+", dut_field)
373
+ qbx = int(qb_matches[0].split("QB")[1])
374
+ move_fidelity[(qbx, 0)] = values
375
+ move_fidelity[(0, qbx)] = values
376
+ elif (
377
+ ObservationType.CZ.value in dut_field
378
+ and any(x in dut_field for x in gates_info[ObservationType.CZ.value])
379
+ and backend.has_resonators()
380
+ ):
381
+ qb_matches = re.findall(r"QB\d+", dut_field)
382
+ qbx = int(qb_matches[0].split("QB")[1])
383
+ list_couplings.append([qbx, 0])
384
+ list_fids.append(values)
385
+ cz_fidelity[(qbx, 0)] = values
386
+ cz_fidelity[(0, qbx)] = values
387
+ elif ObservationType.CZ.value in dut_field and any(
388
+ x in dut_field for x in gates_info[ObservationType.CZ.value]
389
+ ):
390
+ qb_matches = re.findall(r"QB\d+", dut_field)
391
+ qbx, qby = int(qb_matches[0].split("QB")[1]), int(qb_matches[1].split("QB")[1])
392
+ cz_fidelity[(qbx, qby)] = values
393
+ cz_fidelity[(qby, qbx)] = values
394
+ list_couplings.append([qbx, qby])
395
+ list_fids.append(values)
396
+
397
+ metrics_dict: Dict[str, Dict[Union[int, Tuple[int, int]], float]] = {
398
+ "cz_gate_fidelity": cz_fidelity,
399
+ "fidelity_1qb_gates_averaged": single_qubit_fidelity,
400
+ "single_shot_readout_fidelity": readout_fidelity,
401
+ "t1_time": t1,
402
+ "t2_time": t2,
403
+ "double_move_gate_fidelity": move_fidelity,
404
+ }
370
405
  # Enumerate all calibrated qubits starting from 0
406
+ calibrated_qubits = set(np.array(list_couplings).reshape(-1))
371
407
  qubit_mapping = {qubit: idx for idx, qubit in enumerate(calibrated_qubits)}
372
408
  list_couplings = [[qubit_mapping[edge[0]], qubit_mapping[edge[1]]] for edge in list_couplings]
373
-
374
409
  # Apply the qubit mapping to metrics_dict
375
410
  remapped_metrics_dict = {}
376
411
  for metric_key, metric_values in metrics_dict.items():
@@ -384,10 +419,6 @@ def extract_fidelities(cal_url: str, all_metrics: bool = False) -> Union[
384
419
  remapped_metrics_dict[metric_key][qubit_mapping[key]] = value
385
420
  metrics_dict = remapped_metrics_dict
386
421
 
387
- # If all_metrics is False, only return everything related to CZ fidelites
388
- if not all_metrics:
389
- return list_couplings, list_fids, topology, qubit_mapping
390
-
391
422
  return list_couplings, list_fids, topology, qubit_mapping, metrics_dict
392
423
 
393
424
 
@@ -606,6 +637,7 @@ def perform_backend_transpilation(
606
637
  optimize_sqg: bool = False,
607
638
  drop_final_rz: bool = True,
608
639
  routing_method: Optional[str] = "sabre",
640
+ approximation_degree: float = 1.0,
609
641
  ) -> List[QuantumCircuit]:
610
642
  """
611
643
  Transpile a list of circuits to backend specifications.
@@ -620,6 +652,7 @@ def perform_backend_transpilation(
620
652
  optimize_sqg (bool): Whether SQG optimization is performed taking into account virtual Z.
621
653
  drop_final_rz (bool): Whether the SQG optimizer drops a final RZ gate.
622
654
  routing_method (Optional[str]): The routing method employed by Qiskit's transpilation pass.
655
+ approximation_degree (int): The target fidelity to which arbitrary two qubit gates are approximated.
623
656
 
624
657
  Returns:
625
658
  List[QuantumCircuit]: A list of transpiled quantum circuits.
@@ -641,6 +674,7 @@ def perform_backend_transpilation(
641
674
  optimize_single_qubits=optimize_sqg,
642
675
  remove_final_rzs=drop_final_rz,
643
676
  coupling_map=coupling_map_red,
677
+ approximation_degree=approximation_degree,
644
678
  # initial_layout=qubits if aux_qc is None else None,
645
679
  )
646
680
  else:
@@ -651,6 +685,7 @@ def perform_backend_transpilation(
651
685
  optimization_level=qiskit_optim_level,
652
686
  initial_layout=qubits if aux_qc is None else None,
653
687
  routing_method=routing_method,
688
+ approximation_degree=approximation_degree,
654
689
  )
655
690
  if aux_qc is not None:
656
691
  transpiled = aux_qc.compose(transpiled, qubits=qubits, clbits=list(range(qc.num_clbits)))
@@ -797,31 +832,45 @@ def retrieve_all_counts(iqm_jobs: List[IQMJob], identifier: Optional[str] = None
797
832
 
798
833
 
799
834
  def retrieve_all_job_metadata(
800
- iqm_jobs: List[IQMJob],
801
- ) -> Dict[str, Dict[str, Any]]:
802
- """Retrieve the counts from a list of IQMJob objects.
835
+ iqm_jobs: list[IQMJob],
836
+ ) -> dict[str, dict[str, Any]]:
837
+ """Retrieve the metadata from a list of Job objects.
838
+
803
839
  Args:
804
- iqm_jobs List[IQMJob]: The list of IQMJob objects.
840
+ iqm_jobs : List[IQMJob]
841
+ The list of IQMJob objects.
805
842
 
806
843
  Returns:
807
844
  Dict[str, Dict[str, Any]]: Relevant metadata of all the IQMJob objects.
845
+
808
846
  """
809
847
  all_meta = {}
810
-
811
848
  for index, j in enumerate(iqm_jobs):
812
849
  all_attributes_j = dir(j)
850
+ if not hasattr(j, "_iqm_job"):
851
+ shots = j.metadata["shots"] if "shots" in j.metadata.keys() else None
852
+ timestamps = j.metadata["timestamps"] if "timestamps" in j.metadata.keys() else None
853
+ else:
854
+ job_parameters = j._iqm_job._parameters
855
+ if job_parameters is not None:
856
+ shots = job_parameters.shots if "shots" in job_parameters.__dict__.keys() else None
857
+ else:
858
+ raise ValueError("Job parameters return None, cannot retrieve shots information.")
859
+ timestamps = {}
860
+ for entry in j._iqm_job.data.timeline:
861
+ timestamps.update({entry.status: entry.timestamp})
813
862
  all_meta.update(
814
863
  {
815
864
  "batch_job_"
816
865
  + str(index + 1): {
817
866
  "job_id": j.job_id() if "job_id" in all_attributes_j else None,
818
- "backend": j.backend().name if "backend" in all_attributes_j else None,
819
- "status": j.status().value if "status" in all_attributes_j else None,
867
+ "backend": (j.backend().name if "backend" in all_attributes_j else None),
868
+ "status": (j.status().value if "status" in all_attributes_j else None),
820
869
  "circuits_in_batch": (
821
- len(cast(List, j.circuit_metadata)) if "circuit_metadata" in all_attributes_j else None
870
+ len(cast(list, j.circuit_metadata)) if "circuit_metadata" in all_attributes_j else None
822
871
  ),
823
- "shots": j.metadata["shots"] if "shots" in j.metadata.keys() else None,
824
- "timestamps": j.metadata["timestamps"] if "timestamps" in j.metadata.keys() else None,
872
+ "shots": shots,
873
+ "timestamps": timestamps,
825
874
  }
826
875
  }
827
876
  )
@@ -25,11 +25,11 @@ import matplotlib.pyplot as plt
25
25
  import networkx as nx
26
26
  import numpy as np
27
27
  from qiskit.transpiler import CouplingMap
28
- import requests
29
28
  from rustworkx import PyGraph, spring_layout, visualization # pylint: disable=no-name-in-module
30
29
 
30
+ from iqm.benchmarks.entanglement.ghz import get_cx_map, get_edges
31
31
  from iqm.benchmarks.logging_config import qcvv_logger
32
- from iqm.benchmarks.utils import extract_fidelities, get_iqm_backend, random_hamiltonian_path
32
+ from iqm.benchmarks.utils import extract_fidelities_unified, get_iqm_backend, random_hamiltonian_path
33
33
  from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
34
34
 
35
35
 
@@ -189,6 +189,10 @@ class GraphPositions:
189
189
  "iqmfakedeneb": deneb_positions,
190
190
  "emerald": emerald_positions,
191
191
  "sirius": sirius_positions,
192
+ "crystal54": emerald_positions,
193
+ "crystal20": garnet_positions,
194
+ "star": sirius_positions,
195
+ "star24": sirius_positions,
192
196
  }
193
197
 
194
198
  @staticmethod
@@ -359,6 +363,7 @@ def evaluate_hamiltonian_paths(
359
363
  path_samples: int,
360
364
  backend_arg: str | IQMBackendBase,
361
365
  url: str,
366
+ quantum_computer: str,
362
367
  max_tries: int = 10,
363
368
  ) -> Dict[int, List[Tuple[int, int]]]:
364
369
  """Evaluates Hamiltonian paths according to the product of 2Q gate fidelities on the corresponding edges of the backend graph.
@@ -400,32 +405,23 @@ def evaluate_hamiltonian_paths(
400
405
 
401
406
  # Get scores for all paths
402
407
  # Retrieve fidelity data
403
- two_qubit_fidelity = {}
404
-
405
- headers = {"Accept": "application/json", "Authorization": "Bearer " + os.environ["IQM_TOKEN"]}
406
- r = requests.get(url, headers=headers, timeout=60)
407
- calibration = r.json()
408
-
409
- for iq in calibration["calibrations"][0]["metrics"][0]["metrics"]:
410
- temp = list(iq.values())
411
- two_qubit_fidelity[str(temp[0])] = temp[1]
412
- two_qubit_fidelity[str([temp[0][1], temp[0][0]])] = temp[1]
413
-
408
+ cal_data = extract_fidelities_unified(url, backend, quantum_computer=quantum_computer)
409
+ two_qubit_fidelity = cal_data[-1]["cz_gate_fidelity"]
414
410
  # Rate all the paths
415
411
  path_costs = {} # keys are costs, values are edge paths
416
412
  for h_path in all_paths:
417
413
  total_cost = 1
418
414
  for edge in h_path:
419
415
  if len(edge) == 2:
420
- total_cost *= two_qubit_fidelity[
421
- str([backend.index_to_qubit_name(edge[0]), backend.index_to_qubit_name(edge[1])])
422
- ]
416
+ total_cost *= two_qubit_fidelity[edge]
423
417
  path_costs[total_cost] = h_path
424
418
 
425
419
  return path_costs
426
420
 
427
421
 
428
- def calculate_node_radii(metric_dict: Dict[str, Dict[int, float]], qubit_nodes: List[int], sq_metric: str) -> np.ndarray:
422
+ def calculate_node_radii(
423
+ metric_dict: Dict[str, Dict[int, float]], qubit_nodes: List[int], sq_metric: str
424
+ ) -> np.ndarray:
429
425
  """Calculate node radii based on the specified single qubit metric. For the coherence metric, the fidelity is calculated as the idling fidelity of a single qubit gate duration.
430
426
 
431
427
  Args:
@@ -443,9 +439,7 @@ def calculate_node_radii(metric_dict: Dict[str, Dict[int, float]], qubit_nodes:
443
439
  if sq_metric == "fidelity":
444
440
  radii = -np.log(np.array([metric_dict["fidelity_1qb_gates_averaged"][node] for node in qubit_nodes]))
445
441
  if "fidelity_1qb_gates_averaged" not in metric_dict:
446
- raise ValueError(
447
- "The metric 'fidelity_1qb_gates_averaged' is not available in the backend metrics."
448
- )
442
+ raise ValueError("The metric 'fidelity_1qb_gates_averaged' is not available in the backend metrics.")
449
443
  elif sq_metric == "coherence":
450
444
  if "t1_time" not in metric_dict or "t2_time" not in metric_dict:
451
445
  raise ValueError(
@@ -458,9 +452,7 @@ def calculate_node_radii(metric_dict: Dict[str, Dict[int, float]], qubit_nodes:
458
452
  radii = -np.log(idle_fidelities)
459
453
  elif sq_metric == "readout":
460
454
  if "single_shot_readout_fidelity" not in metric_dict:
461
- raise ValueError(
462
- "The metric 'single_shot_readout_fidelity' is both available in the backend metrics."
463
- )
455
+ raise ValueError("The metric 'single_shot_readout_fidelity' is both available in the backend metrics.")
464
456
  readout_fidelities = [metric_dict["single_shot_readout_fidelity"][node] for node in qubit_nodes]
465
457
  radii = -np.log(readout_fidelities)
466
458
  else:
@@ -469,11 +461,14 @@ def calculate_node_radii(metric_dict: Dict[str, Dict[int, float]], qubit_nodes:
469
461
  )
470
462
  return radii
471
463
 
464
+
472
465
  def plot_layout_fidelity_graph(
473
- cal_url: str,
466
+ iqm_server_url: str,
467
+ backend: IQMBackendBase,
474
468
  qubit_layouts: Optional[list[list[int]]] = None,
475
- station: Optional[str] = None,
469
+ quantum_computer: Optional[str] = None,
476
470
  sq_metric: Optional[str] = "coherence",
471
+ show_ghz_path: bool = False,
477
472
  ):
478
473
  """Plot a graph showing the quantum chip layout with fidelity information.
479
474
 
@@ -482,33 +477,61 @@ def plot_layout_fidelity_graph(
482
477
  (thinner edges mean better fidelity) and selected qubits are highlighted in orange.
483
478
 
484
479
  Args:
485
- cal_url: URL to retrieve calibration data from
480
+ iqm_server_url: URL to retrieve calibration data from.
481
+ backend: IQM backend instance.
486
482
  qubit_layouts: List of qubit layouts where each layout is a list of qubit indices
487
- station: Name of the quantum computing station to use predefined positions for.
483
+ quantum_computer: Name of the quantum computing station to use predefined positions for.
488
484
  If None, positions will be generated algorithmically.
489
485
  sq_metric: Optional single qubit metric to use for the visualization, can be either "fidelity", "coherence",
490
486
  or "readout".
487
+ show_ghz_path: Whether to highlight the edges that are part of the GHZ state creation tree path.
491
488
 
492
489
  Returns:
493
490
  matplotlib.figure.Figure: The generated figure object containing the graph visualization
494
491
  """
495
492
  # pylint: disable=unbalanced-tuple-unpacking, disable=too-many-statements
496
- edges_cal, fidelities_cal, topology, qubit_mapping, metric_dict = extract_fidelities(cal_url, all_metrics=True)
493
+
494
+ edges_cal, fidelities_cal, topology, qubit_mapping, metric_dict = extract_fidelities_unified(
495
+ iqm_server_url, backend, quantum_computer
496
+ )
497
+
497
498
  if topology == "star":
498
499
  idx_to_qubit = {idx: qubit for qubit, idx in qubit_mapping.items()}
500
+ qubit_to_idx = qubit_mapping
499
501
  qubit_nodes = list(idx_to_qubit.keys())[1:]
500
502
  fig, ax = plt.subplots(figsize=(len(qubit_nodes), 3))
501
503
  else:
502
504
  # For other topologies, qubits are indexed starting from 0 as per the Qiskit convention
503
505
  idx_to_qubit = {idx: qubit - 1 for qubit, idx in qubit_mapping.items()}
506
+ qubit_to_idx = {qubit - 1: idx for qubit, idx in qubit_mapping.items()}
504
507
  qubit_nodes = list(idx_to_qubit.keys())
505
508
  fig, ax = plt.subplots(figsize=(1.5 * np.sqrt(len(qubit_nodes)), 1.5 * np.sqrt(len(qubit_nodes))))
506
509
 
510
+ # Filter out any edges that are not in the backend's coupling map
511
+ fidelities_cal = [
512
+ fidelity
513
+ for edge, fidelity in zip(edges_cal, fidelities_cal, strict=True)
514
+ if (edge[0], edge[1]) in backend.coupling_map or (edge[1], edge[0]) in backend.coupling_map
515
+ ]
516
+ edges_cal = [edge for edge in edges_cal if (edge[0], edge[1])
517
+ in backend.coupling_map or (edge[1], edge[0]) in backend.coupling_map]
518
+
507
519
  weights = -np.log(np.array(fidelities_cal))
508
520
  calibrated_nodes = list(idx_to_qubit.keys())
509
521
 
522
+ if show_ghz_path and qubit_layouts is not None and any(len(layout) >= 2 for layout in qubit_layouts):
523
+ valid_fidelities = [f for f in fidelities_cal if f < 1.0]
524
+ if valid_fidelities:
525
+ median_fidelity = np.median(valid_fidelities)
526
+ fidelities_cal = [f if f < 1.0 else median_fidelity for f in fidelities_cal]
527
+ from iqm.benchmarks.entanglement.ghz import generate_ghz_spanning_tree, get_cx_map, get_edges
528
+
529
+ graph = get_edges(backend.coupling_map, qubit_layouts[0], edges_cal, fidelities_cal)
530
+ cx_map = get_cx_map(qubit_layouts[0], graph)
531
+ print(f"Edge map:", cx_map)
532
+
510
533
  # Define qubit positions in plot
511
- qubit_positions = GraphPositions.get_positions(station=station, graph=None, num_qubits=len(calibrated_nodes))
534
+ qubit_positions = GraphPositions.get_positions(station=quantum_computer, graph=None, num_qubits=len(calibrated_nodes))
512
535
 
513
536
  graph = PyGraph()
514
537
  nodes = list(set(qubit_positions.keys()))
@@ -520,24 +543,47 @@ def plot_layout_fidelity_graph(
520
543
  else:
521
544
  graph.add_edge(idx_to_qubit[edge[0]], idx_to_qubit[edge[1]], weight)
522
545
 
523
- # Draw the main graph
546
+ # # Draw the main graph
524
547
  visualization.mpl_draw(
525
548
  graph,
526
549
  ax=ax,
527
550
  with_labels=True,
528
551
  node_color="none", # No node color since we're using circles
529
552
  pos=qubit_positions,
530
- labels=lambda node: node,
553
+ labels=lambda node: str(qubit_to_idx[node]) if node in qubit_to_idx else "",
531
554
  font_color="white",
532
- width=graph.edges() / np.max(graph.edges()) * 10,
533
- ) # type: ignore[call-arg]
555
+ edge_color="white",
556
+ )
557
+
558
+ # Draw edges manually with custom styles and colors
559
+ graph_edge_pairs = [list(edge) for edge in graph.edge_list()]
560
+ for _, (edge, weight) in enumerate(zip(graph_edge_pairs, graph.edges(), strict=True)):
561
+ x1, y1 = qubit_positions[edge[0]]
562
+ x2, y2 = qubit_positions[edge[1]]
563
+
564
+ # Define edge properties (customize these as needed)
565
+ edge_width = weight / np.max(list(graph.edges())) * 10 + 0.1
566
+ edge_color = "black"
567
+ edge_style = "solid"
568
+
569
+ # Highlight edges with zero weight (wrong calibration)
570
+ if weight == 0:
571
+ edge_width = 1
572
+ edge_style = "dashed"
573
+ # Highlight edges that are part of the GHZ path
574
+ if show_ghz_path and qubit_layouts is not None and any(len(layout) >= 2 for layout in qubit_layouts):
575
+ mapped_edge = [qubit_to_idx[edge[0]], qubit_to_idx[edge[1]]]
576
+ if mapped_edge in cx_map or list(reversed(mapped_edge)) in cx_map:
577
+ edge_color = "red"
578
+
579
+ ax.plot([x1, x2], [y1, y2], color=edge_color, linewidth=edge_width, linestyle=edge_style, zorder=1)
534
580
 
535
581
  # Draw nodes as circles with varying radii given by the single qubit metric
536
582
  radii = calculate_node_radii(metric_dict, qubit_nodes, sq_metric)
537
583
  node_colors = ["darkgray" for _ in range(len(nodes))]
538
584
  if qubit_layouts is not None:
539
585
  for qb in {qb for layout in qubit_layouts for qb in layout}:
540
- node_colors[qb] = "orange"
586
+ node_colors[idx_to_qubit[qb]] = "orange"
541
587
  max_radius = 0.12 + np.max(radii) / np.max(radii) / 2.5
542
588
 
543
589
  for idx, node in enumerate(qubit_nodes):
@@ -548,7 +594,7 @@ def plot_layout_fidelity_graph(
548
594
 
549
595
  # Add edge labels using matplotlib's annotate
550
596
  # for idx, edge in enumerate(edges_cal):
551
- for edge, weight in zip(list(graph.edge_list()), graph.edges()):
597
+ for edge, weight in zip(graph_edge_pairs, list(graph.edges()), strict=True):
552
598
  x1, y1 = qubit_positions[edge[0]]
553
599
  x2, y2 = qubit_positions[edge[1]]
554
600
  x = (x1 + x2) / 2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iqm-benchmarks
3
- Version: 2.53
3
+ Version: 2.55
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
@@ -19,8 +19,9 @@ Requires-Dist: networkx<4.0,>=3.3
19
19
  Requires-Dist: rustworkx>=0.16.0
20
20
  Requires-Dist: numpy<2.0,>=1.25.2
21
21
  Requires-Dist: qiskit<=1.4.2,>=1.2.4
22
- Requires-Dist: iqm-client[qiskit]<33.0,>=32.1.1
23
- Requires-Dist: iqm-station-control-client<12.0,>=11.3.1
22
+ Requires-Dist: iqm-client[qiskit]<34.0,>=32.1.1
23
+ Requires-Dist: qiskit-ibm-runtime<0.44.0
24
+ Requires-Dist: iqm-station-control-client<13.0,>=11.3.1
24
25
  Requires-Dist: requests<3.0,>=2.32.3
25
26
  Requires-Dist: scikit-optimize<0.11.0,>=0.10.2
26
27
  Requires-Dist: tabulate<1.0.0,>=0.9.0
@@ -1,11 +1,11 @@
1
1
  iqm/benchmarks/__init__.py,sha256=-drZ_whue067Tu4I9RxBCqrrzu38Tm5Kqf9jHTftUPk,3070
2
- iqm/benchmarks/benchmark.py,sha256=3E7g7RQjCIGIpSI1gOSrI3V9SAVs-XEOMrPgToK_7vw,4972
3
- iqm/benchmarks/benchmark_definition.py,sha256=lDu__vowp15udoo1tFl_h0LQA8WHyyHdIRi7UDbwAzg,11323
2
+ iqm/benchmarks/benchmark.py,sha256=w5dexVB9G9KKSBgMMIlZP1OA7AMp_ZmeMPOVfhGcK0A,5055
3
+ iqm/benchmarks/benchmark_definition.py,sha256=_fvMCGP2b6OjtQwfO8_FSXy1FJRvYc3nNgEe3sjlq28,11332
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=kJz9T9nJXpLl_iFYYUDtSq83N-Y3JFQBuvW1o7-AVSM,44137
8
- iqm/benchmarks/utils_plots.py,sha256=CaqA9fJNgRnrbYqwBdpzFUlhwvKw5lhZX3KfRlroQV4,24420
7
+ iqm/benchmarks/utils.py,sha256=yCTWo0lGMg82DK1fkUICEntzF0aocJktMfcY231pUxg,46114
8
+ iqm/benchmarks/utils_plots.py,sha256=Ei7_dsp94q652UMq5CszxoPCfJ1TNo6A7t-wSAAdpP8,26805
9
9
  iqm/benchmarks/utils_shadows.py,sha256=e77PV_uaAO5m_woox9lAzompKAvFeDJ-0AKJrNJ7NFg,9728
10
10
  iqm/benchmarks/coherence/__init__.py,sha256=yeyhk-_Lp8IbJ-f5lQj0HP5Q1HSKK_FzuXHazotUrVY,704
11
11
  iqm/benchmarks/coherence/coherence.py,sha256=RvbOxV2sJpAyr_bXqjBfSjk9v8XjRN-nrkK8w7RyfH0,21596
@@ -13,13 +13,13 @@ iqm/benchmarks/compressive_gst/__init__.py,sha256=LneifgYXtcwo2jcXo7GdUEHL6_peip
13
13
  iqm/benchmarks/compressive_gst/compressive_gst.py,sha256=_thQfc9qmIJqAcS3Kg4ITEYl8Ofi8xgC_oZotrmyzVk,28484
14
14
  iqm/benchmarks/compressive_gst/gst_analysis.py,sha256=H6EQGbpI_sig69Jy6hflg6alMTtjB0t9tHftygzA2YA,41240
15
15
  iqm/benchmarks/entanglement/__init__.py,sha256=sHVVToRWRCz0LSntk1rQaoSNNeyZLPoiTjUKWZWrk1E,778
16
- iqm/benchmarks/entanglement/ghz.py,sha256=drTmV2JQi9ZYr2_Gl28aizFDbV4TQ2NYynsRou-6re0,45946
16
+ iqm/benchmarks/entanglement/ghz.py,sha256=Tq7gPqOgS8WWywgp-1Jlu2ZgeTYEJNSiSxH2S8_D66E,47379
17
17
  iqm/benchmarks/entanglement/graph_states.py,sha256=6qACedd3UXpiowXc9GW4QhSwO-CzHXnBA3dIC6nCIbE,62788
18
18
  iqm/benchmarks/optimization/__init__.py,sha256=_ajW_OibYLCtzU5AUv5c2zuuVYn8ZNeZUcUUSIGt51M,747
19
19
  iqm/benchmarks/optimization/qscore.py,sha256=7JJIVrJvmec77kSdwM4YkwJJoNBCT-LuZ7Ay8JMZ7Nc,46847
20
20
  iqm/benchmarks/quantum_volume/__init__.py,sha256=i-Q4SpDWELBw7frXnxm1j4wJRcxbIyrS5uEK_v06YHo,951
21
- iqm/benchmarks/quantum_volume/clops.py,sha256=EUtO-_OYBYvwqb4xY3aubI2gc2Z6cBokRzt_E0608WA,31242
22
- iqm/benchmarks/quantum_volume/quantum_volume.py,sha256=af9C4SdEPcYyZgQgtJYy2h_F8QWv1a0hEtN6hr4KeM0,36861
21
+ iqm/benchmarks/quantum_volume/clops.py,sha256=HwO0bTRfDZrDtINLNtbXCIMqlqqFhsb6kmyma1gsDJU,31562
22
+ iqm/benchmarks/quantum_volume/quantum_volume.py,sha256=zKaD4ze3Msblo9tIxMmP8l_huq9mfDNukIJhSL72QYo,33576
23
23
  iqm/benchmarks/randomized_benchmarking/__init__.py,sha256=IkKo-7zUChxZZd3my_csQCJfJfZNsV3-JTvdG8uqys4,734
24
24
  iqm/benchmarks/randomized_benchmarking/clifford_1q.pkl,sha256=yrmSJqhv7Lb1yqiqU9-2baqTljJPNmTUPQR-AH6GGfc,7800
25
25
  iqm/benchmarks/randomized_benchmarking/clifford_2q.pkl,sha256=mJQLubWPOb-DbmFi4oKYJqAMW_Yyo3eJjRjLGl9Sqmo,10282247
@@ -30,12 +30,12 @@ iqm/benchmarks/randomized_benchmarking/clifford_rb/clifford_rb.py,sha256=1BOYA9x
30
30
  iqm/benchmarks/randomized_benchmarking/direct_rb/__init__.py,sha256=lCIIeWMFZHnMUUEUTjUBvrhhUur6uBTHIVkxFBSfHC4,681
31
31
  iqm/benchmarks/randomized_benchmarking/direct_rb/direct_rb.py,sha256=X4MVojCcv1KabopKql4-58sNXP_q0WcFZBeUbDTY83w,49399
32
32
  iqm/benchmarks/randomized_benchmarking/eplg/__init__.py,sha256=1MeGZTErElXJypQV2rQf7hwqLLvIp_JNVbwNhaP5vyI,696
33
- iqm/benchmarks/randomized_benchmarking/eplg/eplg.py,sha256=3A_gxzAs6mi3APKvqCwYDcNwRogIZNy5SDL33Cro89E,17036
33
+ iqm/benchmarks/randomized_benchmarking/eplg/eplg.py,sha256=kmet3yxF1bl1spKf-UrHqHxyEQhRnM7954oRmUKJrX8,17332
34
34
  iqm/benchmarks/randomized_benchmarking/interleaved_rb/__init__.py,sha256=sq6MgN_hwlpkOj10vyCU4e6eKSX-oLcF2L9na6W2Gt4,681
35
35
  iqm/benchmarks/randomized_benchmarking/interleaved_rb/interleaved_rb.py,sha256=OHoAWajCE48dRDInwQUT8VvtzKad0ExefdqvZFTaYzs,28918
36
36
  iqm/benchmarks/randomized_benchmarking/mirror_rb/__init__.py,sha256=jRKbivWCZ3xdO1k0sx-ygC3s5DUkGSModd975PoAtcg,692
37
37
  iqm/benchmarks/randomized_benchmarking/mirror_rb/mirror_rb.py,sha256=ijieNymik3BeEUpXS-m64mtgdHz9iAFELuLooHeZY0E,33252
38
- iqm_benchmarks-2.53.dist-info/licenses/LICENSE,sha256=2Ncb40-hqkTil78RPv3-YiJfKaJ8te9USJgliKqIdSY,11558
38
+ iqm_benchmarks-2.55.dist-info/licenses/LICENSE,sha256=2Ncb40-hqkTil78RPv3-YiJfKaJ8te9USJgliKqIdSY,11558
39
39
  mGST/LICENSE,sha256=TtHNq55cUcbglb7uhVudeBLUh_qPdUoAEvU0BBwFz-k,1098
40
40
  mGST/README.md,sha256=v_5kw253csHF4-RfE-44KqFmBXIsSMRmOtN0AUPrRxE,5050
41
41
  mGST/additional_fns.py,sha256=MV0Pm5ap59IjhT_E3QhsZyM7lXOF1RZ9SD11zoaf43A,31781
@@ -46,7 +46,7 @@ mGST/optimization.py,sha256=x9tJ9wMQ5aONWpNpBMVtK0rwE6DRcOU33htNgrt0tx4,11015
46
46
  mGST/qiskit_interface.py,sha256=uCdn-Q9CXI2f4FQSxGUy8GmmzQhr9NhCOFb2VPj0gTs,10061
47
47
  mGST/reporting/figure_gen.py,sha256=xFPAHx1Trdqz7swn0kRqwc_jbRaNxhG9Nvx0jeitooo,25847
48
48
  mGST/reporting/reporting.py,sha256=Wss1-zFsMEhzrrXKfP-RICau80ezjDIzcN555KhSehc,34160
49
- iqm_benchmarks-2.53.dist-info/METADATA,sha256=xEhFw6S0jM8KgTwQklUI9RvWzw6W3oQypPMpMue4-80,10968
50
- iqm_benchmarks-2.53.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
- iqm_benchmarks-2.53.dist-info/top_level.txt,sha256=3G23Z-1LGf-IOzTCUl6QwWqiQ3USz25Zt90Ihq192to,9
52
- iqm_benchmarks-2.53.dist-info/RECORD,,
49
+ iqm_benchmarks-2.55.dist-info/METADATA,sha256=ZkaOMyHFWMsU0fPFyG2qMu1L8fnAcQ5oUeT_It0dxOQ,11009
50
+ iqm_benchmarks-2.55.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
51
+ iqm_benchmarks-2.55.dist-info/top_level.txt,sha256=3G23Z-1LGf-IOzTCUl6QwWqiQ3USz25Zt90Ihq192to,9
52
+ iqm_benchmarks-2.55.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5