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

@@ -2,16 +2,14 @@
2
2
  Mirror Randomized Benchmarking.
3
3
  """
4
4
 
5
- from copy import deepcopy
6
- import random
7
5
  from time import strftime
8
- from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, cast
6
+ from typing import Any, Dict, List, Literal, Optional, Sequence, Tuple, Type
9
7
  import warnings
10
8
 
11
9
  import numpy as np
12
10
  from qiskit import transpile
13
11
  from qiskit.quantum_info import Clifford, random_clifford, random_pauli
14
- from qiskit_aer import Aer, AerSimulator
12
+ from qiskit_aer import AerSimulator
15
13
  from scipy.spatial.distance import hamming
16
14
  import xarray as xr
17
15
 
@@ -26,11 +24,11 @@ from iqm.benchmarks.benchmark_definition import Benchmark, add_counts_to_dataset
26
24
  from iqm.benchmarks.circuit_containers import BenchmarkCircuit, CircuitGroup, Circuits
27
25
  from iqm.benchmarks.logging_config import qcvv_logger
28
26
  from iqm.benchmarks.randomized_benchmarking.randomized_benchmarking_common import (
27
+ edge_grab,
29
28
  exponential_rb,
30
29
  fit_decay_lmfit,
31
30
  lmfit_minimizer,
32
31
  plot_rb_decay,
33
- validate_irb_gate,
34
32
  )
35
33
  from iqm.benchmarks.utils import (
36
34
  get_iqm_backend,
@@ -93,143 +91,6 @@ def compute_polarizations(
93
91
  return polarizations
94
92
 
95
93
 
96
- # TODO: Let edge_grab also admit a 1Q gate ensemble! Currently uniform Clifford by default # pylint: disable=fixme
97
- # pylint: disable=too-many-branches
98
- def edge_grab(
99
- qubit_set: List[int],
100
- n_layers: int,
101
- backend_arg: IQMBackendBase | str,
102
- density_2q_gates: float = 0.25,
103
- two_qubit_gate_ensemble: Optional[Dict[str, float]] = None,
104
- ) -> List[QuantumCircuit]:
105
- """Generate a list of random layers containing single-qubit Cliffords and two-qubit gates,
106
- sampled according to the edge-grab algorithm (see arXiv:2204.07568 [quant-ph]).
107
-
108
- Args:
109
- qubit_set (List[int]): The set of qubits of the backend.
110
- n_layers (int): The number of layers.
111
- backend_arg (IQMBackendBase | str): IQM backend.
112
- density_2q_gates (float): The expected density of 2Q gates in a circuit formed by subsequent application of layers
113
- two_qubit_gate_ensemble (Dict[str, float]): A dictionary with keys being str specifying 2Q gates, and values being corresponding probabilities
114
- Raises:
115
- ValueError: if the probabilities in the gate ensembles do not add up to unity.
116
- Returns:
117
- List[QuantumCircuit]: the list of gate layers, in the form of quantum circuits.
118
- """
119
- # Check the ensemble of 2Q gates, otherwise assign
120
- if two_qubit_gate_ensemble is None:
121
- two_qubit_gate_ensemble = cast(Dict[str, float], {"CZGate": 1.0})
122
- elif sum(two_qubit_gate_ensemble.values()) != 1.0:
123
- raise ValueError("The 2Q gate ensemble probabilities must sum to 1.0")
124
-
125
- # Validate 2Q gates and get circuits
126
- two_qubit_circuits = {}
127
- for k in two_qubit_gate_ensemble.keys():
128
- two_qubit_circuits[k] = validate_irb_gate(k, backend_arg, gate_params=None)
129
- # TODO: Admit parametrized 2Q gates! # pylint: disable=fixme
130
-
131
- # Check backend and retrieve if necessary
132
- if isinstance(backend_arg, str):
133
- backend = get_iqm_backend(backend_arg)
134
- else:
135
- backend = backend_arg
136
-
137
- # Definitions
138
- num_qubits = len(qubit_set)
139
- physical_to_virtual_map = {q: i for i, q in enumerate(qubit_set)}
140
-
141
- # Get the possible edges where to place 2Q gates given the backend connectivity
142
- twoq_edges = []
143
- for i, q0 in enumerate(qubit_set):
144
- for q1 in qubit_set[i + 1 :]:
145
- if (q0, q1) in list(backend.coupling_map):
146
- twoq_edges.append([q0, q1])
147
- twoq_edges = list(sorted(twoq_edges))
148
-
149
- # Generate the layers
150
- layer_list = []
151
- for _ in range(n_layers):
152
- # Pick edges at random and store them in a new list "edge_list"
153
- aux = deepcopy(twoq_edges)
154
- edge_list = []
155
- layer = QuantumCircuit(num_qubits)
156
- # Take (and remove) edges from "aux", then add to "edge_list"
157
- edge_qubits = []
158
- while aux:
159
- new_edge = random.choice(aux)
160
- edge_list.append(new_edge)
161
- edge_qubits = list(np.array(edge_list).flatten())
162
- # Removes all edges which include either of the qubits in new_edge
163
- aux = [e for e in aux if ((new_edge[0] not in e) and (new_edge[1] not in e))]
164
-
165
- # Define the probability for adding 2Q gates, given the input density
166
- if len(edge_list) != 0:
167
- prob_2qgate = num_qubits * density_2q_gates / len(edge_list)
168
- else:
169
- prob_2qgate = 0
170
-
171
- # Add gates in selected edges
172
- for e in edge_list:
173
- # Sample the 2Q gate
174
- two_qubit_gate = random.choices(
175
- list(two_qubit_gate_ensemble.keys()),
176
- weights=list(two_qubit_gate_ensemble.values()),
177
- k=1,
178
- )[0]
179
-
180
- # Pick whether to place the sampled 2Q gate according to the probability above
181
- is_gate_placed = random.choices(
182
- [True, False],
183
- weights=[prob_2qgate, 1 - prob_2qgate],
184
- k=1,
185
- )[0]
186
-
187
- if is_gate_placed:
188
- if two_qubit_gate == "clifford":
189
- layer.compose(
190
- random_clifford(2).to_instruction(),
191
- qubits=[
192
- physical_to_virtual_map[e[0]],
193
- physical_to_virtual_map[e[1]],
194
- ],
195
- inplace=True,
196
- )
197
- else:
198
- layer.append(
199
- two_qubit_circuits[two_qubit_gate],
200
- [
201
- physical_to_virtual_map[e[0]],
202
- physical_to_virtual_map[e[1]],
203
- ],
204
- )
205
- else:
206
- layer.compose(
207
- random_clifford(1).to_instruction(),
208
- qubits=[physical_to_virtual_map[e[0]]],
209
- inplace=True,
210
- )
211
- layer.compose(
212
- random_clifford(1).to_instruction(),
213
- qubits=[physical_to_virtual_map[e[1]]],
214
- inplace=True,
215
- )
216
-
217
- # Add 1Q gates in remaining qubits
218
- remaining_qubits = [q for q in qubit_set if q not in edge_qubits]
219
- while remaining_qubits:
220
- for q in remaining_qubits:
221
- layer.compose(
222
- random_clifford(1).to_instruction(),
223
- qubits=[physical_to_virtual_map[q]],
224
- inplace=True,
225
- )
226
- remaining_qubits.remove(q)
227
-
228
- layer_list.append(layer)
229
-
230
- return layer_list
231
-
232
-
233
94
  def generate_pauli_dressed_mrb_circuits(
234
95
  qubits: List[int],
235
96
  pauli_samples_per_circ: int,
@@ -237,8 +98,13 @@ def generate_pauli_dressed_mrb_circuits(
237
98
  backend_arg: IQMBackendBase | str,
238
99
  density_2q_gates: float = 0.25,
239
100
  two_qubit_gate_ensemble: Optional[Dict[str, float]] = None,
101
+ clifford_sqg_probability=1.0,
102
+ sqg_gate_ensemble: Optional[Dict[str, float]] = None,
240
103
  qiskit_optim_level: int = 1,
241
104
  routing_method: str = "basic",
105
+ simulation_method: Literal[
106
+ "automatic", "statevector", "stabilizer", "extended_stabilizer", "matrix_product_state"
107
+ ] = "automatic",
242
108
  ) -> Dict[str, List[QuantumCircuit]]:
243
109
  """Samples a mirror circuit and generates samples of "Pauli-dressed" circuits,
244
110
  where for each circuit, random Pauli layers are interleaved between each layer of the circuit
@@ -249,16 +115,34 @@ def generate_pauli_dressed_mrb_circuits(
249
115
  depth (int): the depth (number of canonical layers) of the circuit
250
116
  backend_arg (IQMBackendBase | str): the backend
251
117
  density_2q_gates (float): the expected density of 2Q gates
252
- two_qubit_gate_ensemble (Optional[Dict[str, float]]):
253
- qiskit_optim_level (int):
254
- routing_method (str):
118
+ two_qubit_gate_ensemble (Optional[Dict[str, float]]): A dictionary with keys being str specifying 2Q gates, and values being corresponding probabilities.
119
+ * Default is None.
120
+ clifford_sqg_probability (float): Probability with which to uniformly sample Clifford 1Q gates.
121
+ * Default is 1.0.
122
+ sqg_gate_ensemble (Optional[Dict[str, float]]): A dictionary with keys being str specifying 1Q gates, and values being corresponding probabilities.
123
+ * Default is None.
124
+ qiskit_optim_level (int): Qiskit transpiler optimization level.
125
+ * Default is 1.
126
+ routing_method (str): Qiskit transpiler routing method.
127
+ * Default is "basic".
128
+ simulation_method (Literal["automatic", "statevector", "stabilizer", "extended_stabilizer", "matrix_product_state"]):
129
+ Qiskit's Aer simulation method
130
+ * Default is "automatic".
255
131
  Returns:
256
-
132
+ Dict[str, List[QuantumCircuit]]
257
133
  """
258
134
  num_qubits = len(qubits)
259
135
 
260
136
  # Sample the layers using edge grab sampler - different samplers may be conditionally chosen here in the future
261
- cycle_layers = edge_grab(qubits, depth, backend_arg, density_2q_gates, two_qubit_gate_ensemble)
137
+ cycle_layers = edge_grab(
138
+ qubits,
139
+ depth,
140
+ backend_arg,
141
+ density_2q_gates,
142
+ two_qubit_gate_ensemble,
143
+ clifford_sqg_probability,
144
+ sqg_gate_ensemble,
145
+ )
262
146
 
263
147
  # Sample the edge (initial/final) random Single-qubit Clifford layer
264
148
  clifford_layer = [random_clifford(1) for _ in range(num_qubits)]
@@ -268,13 +152,11 @@ def generate_pauli_dressed_mrb_circuits(
268
152
  pauli_dressed_circuits_untranspiled: List[QuantumCircuit] = []
269
153
  pauli_dressed_circuits_transpiled: List[QuantumCircuit] = []
270
154
 
271
- sim_method = "stabilizer"
272
- simulator = AerSimulator(method=sim_method)
155
+ simulator = AerSimulator(method=simulation_method)
273
156
 
274
157
  for _ in range(pauli_samples_per_circ):
275
158
  # Initialize the quantum circuit object
276
159
  circ = QuantumCircuit(num_qubits)
277
- circ_untransp = QuantumCircuit(num_qubits)
278
160
  # Sample all the random Paulis
279
161
  paulis = [random_pauli(num_qubits) for _ in range(depth + 1)]
280
162
 
@@ -292,7 +174,6 @@ def generate_pauli_dressed_mrb_circuits(
292
174
  )
293
175
  circ.barrier()
294
176
  circ.compose(cycle_layers[k], inplace=True)
295
- circ_untransp.compose(cycle_layers[k], inplace=True)
296
177
  circ.barrier()
297
178
 
298
179
  # Apply middle Pauli
@@ -369,8 +250,13 @@ def generate_fixed_depth_mrb_circuits(
369
250
  backend_arg: IQMBackendBase | str,
370
251
  density_2q_gates: float = 0.25,
371
252
  two_qubit_gate_ensemble: Optional[Dict[str, float]] = None,
253
+ clifford_sqg_probability=1.0,
254
+ sqg_gate_ensemble: Optional[Dict[str, float]] = None,
372
255
  qiskit_optim_level: int = 1,
373
256
  routing_method: str = "basic",
257
+ simulation_method: Literal[
258
+ "automatic", "statevector", "stabilizer", "extended_stabilizer", "matrix_product_state"
259
+ ] = "automatic",
374
260
  ) -> Dict[int, Dict[str, List[QuantumCircuit]]]:
375
261
  """Generates a dictionary MRB circuits at fixed depth, indexed by sample number
376
262
 
@@ -380,10 +266,21 @@ def generate_fixed_depth_mrb_circuits(
380
266
  pauli_samples_per_circ (int): the number of pauli samples per circuit
381
267
  depth (int): the depth (number of canonical layers) of the circuits
382
268
  backend_arg (IQMBackendBase | str): the backend
383
- density_2q_gates (float):
269
+ density_2q_gates (float): the expected density of 2Q gates
270
+ two_qubit_gate_ensemble (Optional[Dict[str, float]]): A dictionary with keys being str specifying 2Q gates, and values being corresponding probabilities.
271
+ * Default is None.
384
272
  two_qubit_gate_ensemble (Optional[Dict[str, float]]):
385
- qiskit_optim_level (int):
386
- routing_method (str):
273
+ clifford_sqg_probability (float): Probability with which to uniformly sample Clifford 1Q gates.
274
+ * Default is 1.0.
275
+ sqg_gate_ensemble (Optional[Dict[str, float]]): A dictionary with keys being str specifying 1Q gates, and values being corresponding probabilities.
276
+ * Default is None.
277
+ qiskit_optim_level (int): Qiskit transpiler optimization level.
278
+ * Default is 1.
279
+ routing_method (str): Qiskit transpiler routing method.
280
+ * Default is "basic".
281
+ simulation_method (Literal["automatic", "statevector", "stabilizer", "extended_stabilizer", "matrix_product_state"]):
282
+ Qiskit's Aer simulation method
283
+ * Default is "automatic".
387
284
  Returns:
388
285
  A dictionary of lists of Pauli-dressed quantum circuits corresponding to the circuit sample index
389
286
  """
@@ -397,8 +294,11 @@ def generate_fixed_depth_mrb_circuits(
397
294
  backend_arg,
398
295
  density_2q_gates,
399
296
  two_qubit_gate_ensemble,
400
- qiskit_optim_level,
401
- routing_method,
297
+ clifford_sqg_probability,
298
+ sqg_gate_ensemble,
299
+ qiskit_optim_level=qiskit_optim_level,
300
+ routing_method=routing_method,
301
+ simulation_method=simulation_method,
402
302
  )
403
303
 
404
304
  return circuits
@@ -532,7 +432,7 @@ def mrb_analysis(run: BenchmarkRunResult) -> BenchmarkAnalysisResult:
532
432
  "fit_offset": {"value": popt["offset"].value, "uncertainty": popt["offset"].stderr},
533
433
  "polarizations": polarizations,
534
434
  "average_polarization_nominal_values": average_polarizations,
535
- "average_polatization_stderr": stddevs_from_mean,
435
+ "average_polarization_stderr": stddevs_from_mean,
536
436
  "fitting_method": str(rb_fit_results.method),
537
437
  "num_function_evals": int(rb_fit_results.nfev),
538
438
  "data_points": int(rb_fit_results.ndata),
@@ -606,18 +506,26 @@ class MirrorRandomizedBenchmarking(Benchmark):
606
506
 
607
507
  self.qubits_array = configuration.qubits_array
608
508
  self.depths_array = configuration.depths_array
509
+
609
510
  self.num_circuit_samples = configuration.num_circuit_samples
610
511
  self.num_pauli_samples = configuration.num_pauli_samples
512
+
611
513
  self.two_qubit_gate_ensemble = configuration.two_qubit_gate_ensemble
612
514
  self.density_2q_gates = configuration.density_2q_gates
515
+ self.clifford_sqg_probability = configuration.clifford_sqg_probability
516
+ self.sqg_gate_ensemble = configuration.sqg_gate_ensemble
613
517
 
614
518
  self.qiskit_optim_level = configuration.qiskit_optim_level
615
519
 
616
- self.simulator = Aer.get_backend("qasm_simulator")
520
+ self.simulation_method = configuration.simulation_method
617
521
 
618
522
  self.session_timestamp = strftime("%Y%m%d-%H%M%S")
619
523
  self.execution_timestamp = ""
620
524
 
525
+ # Initialize the variable to contain the circuits for each layout
526
+ self.untranspiled_circuits = BenchmarkCircuit("untranspiled_circuits")
527
+ self.transpiled_circuits = BenchmarkCircuit("transpiled_circuits")
528
+
621
529
  def add_all_meta_to_dataset(self, dataset: xr.Dataset):
622
530
  """Adds all configuration metadata and circuits to the dataset variable
623
531
 
@@ -685,10 +593,6 @@ class MirrorRandomizedBenchmarking(Benchmark):
685
593
  all_mrb_jobs: List[Dict[str, Any]] = []
686
594
  time_circuit_generation: Dict[str, float] = {}
687
595
 
688
- # Initialize the variable to contain the circuits for each layout
689
- self.untranspiled_circuits = BenchmarkCircuit("untranspiled_circuits")
690
- self.transpiled_circuits = BenchmarkCircuit("transpiled_circuits")
691
-
692
596
  # The depths should be assigned to each set of qubits!
693
597
  # The real final MRB depths are twice the originally specified, must be taken into account here!
694
598
  assigned_mrb_depths = {}
@@ -731,8 +635,11 @@ class MirrorRandomizedBenchmarking(Benchmark):
731
635
  backend,
732
636
  self.density_2q_gates,
733
637
  self.two_qubit_gate_ensemble,
638
+ self.clifford_sqg_probability,
639
+ self.sqg_gate_ensemble,
734
640
  self.qiskit_optim_level,
735
641
  self.routing_method,
642
+ self.simulation_method,
736
643
  )
737
644
  time_circuit_generation[str(qubits)] += elapsed_time
738
645
 
@@ -759,7 +666,6 @@ class MirrorRandomizedBenchmarking(Benchmark):
759
666
  dataset.attrs[qubits_idx] = {"qubits": qubits}
760
667
 
761
668
  # Retrieve counts of jobs for all qubit layouts
762
- all_job_metadata = {}
763
669
  for job_dict in all_mrb_jobs:
764
670
  qubits = job_dict["qubits"]
765
671
  depth = job_dict["depth"]
@@ -813,6 +719,13 @@ class MirrorRBConfiguration(BenchmarkConfigurationBase):
813
719
  * Default is {"CZGate": 1.0}.
814
720
  density_2q_gates (float): The expected density of 2-qubit gates in the final circuits.
815
721
  * Default is 0.25.
722
+ clifford_sqg_probability (float): Probability with which to uniformly sample Clifford 1Q gates.
723
+ * Default is 1.0.
724
+ sqg_gate_ensemble (Optional[Dict[str, float]]): A dictionary with keys being str specifying 1Q gates, and values being corresponding probabilities.
725
+ * Default is None.
726
+ simulation_method (Literal["automatic", "statevector", "stabilizer", "extended_stabilizer", "matrix_product_state"]):
727
+ Qiskit's Aer simulation method
728
+ * Default is "automatic".
816
729
  """
817
730
 
818
731
  benchmark: Type[Benchmark] = MirrorRandomizedBenchmarking
@@ -825,3 +738,8 @@ class MirrorRBConfiguration(BenchmarkConfigurationBase):
825
738
  "CZGate": 1.0,
826
739
  }
827
740
  density_2q_gates: float = 0.25
741
+ clifford_sqg_probability: float = 1.0
742
+ sqg_gate_ensemble: Optional[Dict[str, float]] = None
743
+ simulation_method: Literal[
744
+ "automatic", "statevector", "stabilizer", "extended_stabilizer", "matrix_product_state"
745
+ ] = "automatic"