22
22
23
23
import attrs
24
24
import numpy as np
25
+ import sympy
25
26
26
27
import cirq .contrib .shuffle_circuits .shuffle_circuits_with_readout_benchmarking as sc_readout
27
28
from cirq import circuits , ops , study , work
28
29
from cirq .experiments .readout_confusion_matrix import TensoredConfusionMatrices
30
+ from cirq .study import ResultDict
29
31
30
32
if TYPE_CHECKING :
31
33
from cirq .experiments .single_qubit_readout_calibration import (
32
34
SingleQubitReadoutCalibrationResult ,
33
35
)
34
- from cirq .study import ResultDict
35
36
36
37
37
38
@attrs .frozen
@@ -188,15 +189,15 @@ def _validate_input(
188
189
189
190
# Check pauli_repetitions is bigger than 0
190
191
if pauli_repetitions <= 0 :
191
- raise ValueError ("Must provide non-zero pauli_repetitions." )
192
+ raise ValueError ("Must provide positive pauli_repetitions." )
192
193
193
194
# Check num_random_bitstrings is bigger than or equal to 0
194
195
if num_random_bitstrings < 0 :
195
196
raise ValueError ("Must provide zero or more num_random_bitstrings." )
196
197
197
198
# Check readout_repetitions is bigger than 0
198
199
if readout_repetitions <= 0 :
199
- raise ValueError ("Must provide non-zero readout_repetitions for readout calibration." )
200
+ raise ValueError ("Must provide positive readout_repetitions for readout calibration." )
200
201
201
202
202
203
def _normalize_input_paulis (
@@ -240,6 +241,90 @@ def _pauli_strings_to_basis_change_ops(
240
241
return operations
241
242
242
243
244
+ def _pauli_strings_to_basis_change_with_sweep (
245
+ pauli_strings : list [ops .PauliString ], qid_list : list [ops .Qid ]
246
+ ) -> dict [str , float ]:
247
+ """Decide single-qubit rotation sweep parameters for basis change.
248
+
249
+ Args:
250
+ pauli_strings: A list of QWC Pauli strings.
251
+ qid_list: A list of qubits to apply the basis change on.
252
+ Returns:
253
+ A dictionary mapping parameter names to their values for basis change.
254
+ """
255
+ params_dict = {}
256
+
257
+ for qid , qubit in enumerate (qid_list ):
258
+ params_dict [f"phi{ qid } " ] = 1.0
259
+ params_dict [f"theta{ qid } " ] = 0.0
260
+ for pauli_str in pauli_strings :
261
+ pauli_op = pauli_str .get (qubit , default = ops .I )
262
+ if pauli_op == ops .X :
263
+ params_dict [f"phi{ qid } " ] = 0.0
264
+ params_dict [f"theta{ qid } " ] = 1 / 2
265
+ break
266
+ elif pauli_op == ops .Y :
267
+ params_dict [f"phi{ qid } " ] = 1.0
268
+ params_dict [f"theta{ qid } " ] = 1 / 2
269
+ break
270
+ return params_dict
271
+
272
+
273
+ def _generate_basis_change_circuits (
274
+ normalized_circuits_to_pauli : dict [circuits .FrozenCircuit , list [list [ops .PauliString ]]],
275
+ insert_strategy : circuits .InsertStrategy ,
276
+ ) -> list [circuits .Circuit ]:
277
+ """Generates basis change circuits for each group of Pauli strings."""
278
+ pauli_measurement_circuits = list [circuits .Circuit ]()
279
+
280
+ for input_circuit , pauli_string_groups in normalized_circuits_to_pauli .items ():
281
+ qid_list = list (sorted (input_circuit .all_qubits ()))
282
+ basis_change_circuits = []
283
+ input_circuit_unfrozen = input_circuit .unfreeze ()
284
+ for pauli_strings in pauli_string_groups :
285
+ basis_change_circuit = circuits .Circuit (
286
+ input_circuit_unfrozen ,
287
+ _pauli_strings_to_basis_change_ops (pauli_strings , qid_list ),
288
+ ops .measure (* qid_list , key = "result" ),
289
+ strategy = insert_strategy ,
290
+ )
291
+ basis_change_circuits .append (basis_change_circuit )
292
+ pauli_measurement_circuits .extend (basis_change_circuits )
293
+
294
+ return pauli_measurement_circuits
295
+
296
+
297
+ def _generate_basis_change_circuits_with_sweep (
298
+ normalized_circuits_to_pauli : dict [circuits .FrozenCircuit , list [list [ops .PauliString ]]],
299
+ insert_strategy : circuits .InsertStrategy ,
300
+ ) -> tuple [list [circuits .Circuit ], list [study .Sweepable ]]:
301
+ """Generates basis change circuits for each group of Pauli strings with sweep."""
302
+ parameterized_circuits = list [circuits .Circuit ]()
303
+ sweep_params = list [study .Sweepable ]()
304
+ for input_circuit , pauli_string_groups in normalized_circuits_to_pauli .items ():
305
+ qid_list = list (sorted (input_circuit .all_qubits ()))
306
+ phi_symbols = sympy .symbols (f"phi:{ len (qid_list )} " )
307
+ theta_symbols = sympy .symbols (f"theta:{ len (qid_list )} " )
308
+
309
+ # Create phased gates and measurement operator
310
+ phased_gates = [
311
+ ops .PhasedXPowGate (phase_exponent = (a - 1 ) / 2 , exponent = b )(qubit )
312
+ for a , b , qubit in zip (phi_symbols , theta_symbols , qid_list )
313
+ ]
314
+ measurement_op = ops .M (* qid_list , key = "result" )
315
+
316
+ parameterized_circuit = circuits .Circuit (
317
+ input_circuit .unfreeze (), phased_gates , measurement_op , strategy = insert_strategy
318
+ )
319
+ sweep_param = []
320
+ for pauli_strings in pauli_string_groups :
321
+ sweep_param .append (_pauli_strings_to_basis_change_with_sweep (pauli_strings , qid_list ))
322
+ sweep_params .append (sweep_param )
323
+ parameterized_circuits .append (parameterized_circuit )
324
+
325
+ return parameterized_circuits , sweep_params
326
+
327
+
243
328
def _build_one_qubit_confusion_matrix (e0 : float , e1 : float ) -> np .ndarray :
244
329
"""Builds a 2x2 confusion matrix for a single qubit.
245
330
@@ -288,7 +373,7 @@ def _build_many_one_qubits_empty_confusion_matrix(qubits_length: int) -> list[np
288
373
def _process_pauli_measurement_results (
289
374
qubits : Sequence [ops .Qid ],
290
375
pauli_string_groups : list [list [ops .PauliString ]],
291
- circuit_results : list [ResultDict ] | Sequence [study .Result ],
376
+ circuit_results : Sequence [ResultDict ] | Sequence [study .Result ],
292
377
calibration_results : dict [tuple [ops .Qid , ...], SingleQubitReadoutCalibrationResult ],
293
378
pauli_repetitions : int ,
294
379
timestamp : float ,
@@ -321,7 +406,7 @@ def _process_pauli_measurement_results(
321
406
pauli_measurement_results : list [PauliStringMeasurementResult ] = []
322
407
323
408
for pauli_group_index , circuit_result in enumerate (circuit_results ):
324
- measurement_results = circuit_result .measurements ["m " ]
409
+ measurement_results = circuit_result .measurements ["result " ]
325
410
pauli_strs = pauli_string_groups [pauli_group_index ]
326
411
pauli_readout_qubits = _extract_readout_qubits (pauli_strs )
327
412
@@ -403,6 +488,8 @@ def measure_pauli_strings(
403
488
readout_repetitions : int ,
404
489
num_random_bitstrings : int ,
405
490
rng_or_seed : np .random .Generator | int ,
491
+ use_sweep : bool = False ,
492
+ insert_strategy : circuits .InsertStrategy = circuits .InsertStrategy .INLINE ,
406
493
) -> list [CircuitToPauliStringsMeasurementResult ]:
407
494
"""Measures expectation values of Pauli strings on given circuits with/without
408
495
readout error mitigation.
@@ -411,10 +498,11 @@ def measure_pauli_strings(
411
498
For each circuit and its associated list of QWC pauli string group, it:
412
499
1. Constructs circuits to measure the Pauli string expectation value by
413
500
adding basis change moments and measurement operations.
414
- 2. Runs shuffled readout benchmarking on these circuits to calibrate readout errors.
501
+ 2. If `num_random_bitstrings` is greater than zero, performing readout
502
+ benchmarking (shuffled or sweep-based) to calibrate readout errors.
415
503
3. Mitigates readout errors using the calibrated confusion matrices.
416
504
4. Calculates and returns both error-mitigated and unmitigated expectation values for
417
- each Pauli string.
505
+ each Pauli string.
418
506
419
507
Args:
420
508
circuits_to_pauli: A dictionary mapping circuits to either:
@@ -432,6 +520,10 @@ def measure_pauli_strings(
432
520
num_random_bitstrings: The number of random bitstrings to use in readout
433
521
benchmarking.
434
522
rng_or_seed: A random number generator or seed for the readout benchmarking.
523
+ use_sweep: If True, uses parameterized circuits and sweeps parameters
524
+ for both Pauli measurements and readout benchmarking. Defaults to False.
525
+ insert_strategy: The strategy for inserting measurement operations into the circuit.
526
+ Defaults to circuits.InsertStrategy.INLINE.
435
527
436
528
Returns:
437
529
A list of CircuitToPauliStringsMeasurementResult objects, where each object contains:
@@ -460,49 +552,68 @@ def measure_pauli_strings(
460
552
461
553
# Build the basis-change circuits for each Pauli string group
462
554
pauli_measurement_circuits : list [circuits .Circuit ] = []
463
- for input_circuit , pauli_string_groups in normalized_circuits_to_pauli .items ():
464
- qid_list = sorted (input_circuit .all_qubits ())
465
- basis_change_circuits = []
466
- input_circuit_unfrozen = input_circuit .unfreeze ()
467
- for pauli_strings in pauli_string_groups :
468
- basis_change_circuit = (
469
- input_circuit_unfrozen
470
- + _pauli_strings_to_basis_change_ops (pauli_strings , qid_list )
471
- + ops .measure (* qid_list , key = "m" )
472
- )
473
- basis_change_circuits .append (basis_change_circuit )
474
- pauli_measurement_circuits .extend (basis_change_circuits )
555
+ sweep_params : list [study .Sweepable ] = []
556
+ circuits_results : Sequence [ResultDict ] | Sequence [Sequence [study .Result ]] = []
557
+ calibration_results : dict [tuple [ops .Qid , ...], SingleQubitReadoutCalibrationResult ] = {}
558
+
559
+ benchmarking_params = sc_readout .ReadoutBenchmarkingParams (
560
+ circuit_repetitions = pauli_repetitions ,
561
+ num_random_bitstrings = num_random_bitstrings ,
562
+ readout_repetitions = readout_repetitions ,
563
+ )
564
+
565
+ if use_sweep :
566
+ pauli_measurement_circuits , sweep_params = _generate_basis_change_circuits_with_sweep (
567
+ normalized_circuits_to_pauli , insert_strategy
568
+ )
475
569
476
- # Run shuffled benchmarking for readout calibration
477
- circuits_results , calibration_results = (
478
- sc_readout .run_shuffled_circuits_with_readout_benchmarking (
570
+ # Run benchmarking using sweep for readout calibration
571
+ circuits_results , calibration_results = sc_readout .run_sweep_with_readout_benchmarking (
479
572
sampler = sampler ,
480
573
input_circuits = pauli_measurement_circuits ,
481
- parameters = sc_readout .ReadoutBenchmarkingParams (
482
- circuit_repetitions = pauli_repetitions ,
483
- num_random_bitstrings = num_random_bitstrings ,
484
- readout_repetitions = readout_repetitions ,
485
- ),
574
+ sweep_params = sweep_params ,
575
+ parameters = benchmarking_params ,
486
576
rng_or_seed = rng_or_seed ,
487
577
qubits = [list (qubits ) for qubits in qubits_list ],
488
578
)
489
- )
579
+
580
+ else :
581
+ pauli_measurement_circuits = _generate_basis_change_circuits (
582
+ normalized_circuits_to_pauli , insert_strategy
583
+ )
584
+
585
+ # Run shuffled benchmarking for readout calibration
586
+ circuits_results , calibration_results = (
587
+ sc_readout .run_shuffled_circuits_with_readout_benchmarking (
588
+ sampler = sampler ,
589
+ input_circuits = pauli_measurement_circuits ,
590
+ parameters = benchmarking_params ,
591
+ rng_or_seed = rng_or_seed ,
592
+ qubits = [list (qubits ) for qubits in qubits_list ],
593
+ )
594
+ )
490
595
491
596
# Process the results to calculate expectation values
492
597
results : list [CircuitToPauliStringsMeasurementResult ] = []
493
598
circuit_result_index = 0
494
- for input_circuit , pauli_string_groups in normalized_circuits_to_pauli .items ():
495
-
599
+ for i , (input_circuit , pauli_string_groups ) in enumerate (normalized_circuits_to_pauli .items ()):
496
600
qubits_in_circuit = tuple (sorted (input_circuit .all_qubits ()))
497
601
498
602
disable_readout_mitigation = False if num_random_bitstrings != 0 else True
499
603
604
+ circuits_results_for_group : Sequence [ResultDict ] | Sequence [study .Result ] = []
605
+ if use_sweep :
606
+ circuits_results_for_group = cast (Sequence [Sequence [study .Result ]], circuits_results )[i ]
607
+ else :
608
+ circuits_results_for_group = cast (Sequence [ResultDict ], circuits_results )[
609
+ circuit_result_index : circuit_result_index + len (pauli_string_groups )
610
+ ]
611
+ circuit_result_index += len (pauli_string_groups )
612
+
500
613
pauli_measurement_results = _process_pauli_measurement_results (
501
614
list (qubits_in_circuit ),
502
615
pauli_string_groups ,
503
- circuits_results [
504
- circuit_result_index : circuit_result_index + len (pauli_string_groups )
505
- ],
616
+ circuits_results_for_group ,
506
617
calibration_results ,
507
618
pauli_repetitions ,
508
619
time .time (),
@@ -514,5 +625,4 @@ def measure_pauli_strings(
514
625
)
515
626
)
516
627
517
- circuit_result_index += len (pauli_string_groups )
518
628
return results
0 commit comments