-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add parameter sweep support for Pauli string measurements with readout mitigation #7358
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ddddddanni
wants to merge
7
commits into
quantumlib:main
Choose a base branch
from
ddddddanni:sweep-circuits
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+648
−176
Open
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
149f1eb
Add run_sweep_with_readout_benchmarking method
ddddddanni 8a535ef
Allow the measure_pauli_strings function to use sweep
ddddddanni 8157b57
format
ddddddanni f3b4ef1
Merge branch 'main' into sweep-circuits
ddddddanni f4a8f6b
Remove unused method
ddddddanni 1c46aae
Update pauli_repetitions, readout_repetitions, and num_random_bitstr…
ddddddanni 7508b58
Merge branch 'main' into sweep-circuits
NoureldinYosri File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,13 +18,18 @@ | |
|
||
import itertools | ||
import time | ||
from typing import cast, Sequence, TYPE_CHECKING | ||
from typing import cast, Sequence, TYPE_CHECKING, Union | ||
|
||
import attrs | ||
import numpy as np | ||
|
||
from cirq import circuits, ops, work | ||
from cirq.contrib.shuffle_circuits import run_shuffled_with_readout_benchmarking | ||
import sympy | ||
|
||
from cirq import circuits, ops, study, work | ||
from cirq.contrib.shuffle_circuits import ( | ||
run_shuffled_with_readout_benchmarking, | ||
run_sweep_with_readout_benchmarking, | ||
) | ||
from cirq.experiments import SingleQubitReadoutCalibrationResult | ||
from cirq.experiments.readout_confusion_matrix import TensoredConfusionMatrices | ||
|
||
if TYPE_CHECKING: | ||
|
@@ -198,10 +203,10 @@ def _validate_input( | |
|
||
|
||
def _normalize_input_paulis( | ||
circuits_to_pauli: ( | ||
dict[circuits.FrozenCircuit, list[ops.PauliString]] | ||
| dict[circuits.FrozenCircuit, list[list[ops.PauliString]]] | ||
), | ||
circuits_to_pauli: Union[ | ||
dict[circuits.FrozenCircuit, list[ops.PauliString]], | ||
dict[circuits.FrozenCircuit, list[list[ops.PauliString]]], | ||
], | ||
) -> dict[circuits.FrozenCircuit, list[list[ops.PauliString]]]: | ||
first_value = next(iter(circuits_to_pauli.values())) | ||
if ( | ||
|
@@ -233,6 +238,77 @@ def _pauli_strings_to_basis_change_ops( | |
return operations | ||
|
||
|
||
def _pauli_strings_to_basis_change_with_sweep( | ||
pauli_strings: list[ops.PauliString], qid_list: list[ops.Qid] | ||
) -> dict[str, float]: | ||
"""Decide single-qubit rotation sweep parameters for basis change.""" | ||
params_dict = {} | ||
|
||
for qid, qubit in enumerate(qid_list): | ||
params_dict[f"phi{qid}"] = 1.0 | ||
params_dict[f"theta{qid}"] = 0.0 | ||
for pauli_str in pauli_strings: | ||
pauli_op = pauli_str.get(qubit, default=ops.I) | ||
if pauli_op == ops.X: | ||
params_dict[f"phi{qid}"] = 0.0 | ||
params_dict[f"theta{qid}"] = 1 / 2 | ||
break | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is the assumption is that each qubit belongs to at most one paulistring? |
||
elif pauli_op == ops.Y: | ||
params_dict[f"phi{qid}"] = 1.0 | ||
params_dict[f"theta{qid}"] = 1 / 2 | ||
break | ||
return params_dict | ||
|
||
|
||
def _generate_basis_change_circuits( | ||
normalized_circuits_to_pauli: dict[circuits.FrozenCircuit, list[list[ops.PauliString]]], | ||
) -> list[circuits.Circuit]: | ||
"""Generates basis change circuits for each group of Pauli strings.""" | ||
pauli_measurement_circuits = list[circuits.Circuit]() | ||
|
||
for input_circuit, pauli_string_groups in normalized_circuits_to_pauli.items(): | ||
qid_list = list(sorted(input_circuit.all_qubits())) | ||
basis_change_circuits = [] | ||
input_circuit_unfrozen = input_circuit.unfreeze() | ||
for pauli_strings in pauli_string_groups: | ||
basis_change_circuit = ( | ||
input_circuit_unfrozen | ||
+ _pauli_strings_to_basis_change_ops(pauli_strings, qid_list) | ||
+ ops.measure(*qid_list, key="m") | ||
) | ||
basis_change_circuits.append(basis_change_circuit) | ||
pauli_measurement_circuits.extend(basis_change_circuits) | ||
|
||
return pauli_measurement_circuits | ||
|
||
|
||
def _generate_basis_change_circuits_with_sweep( | ||
normalized_circuits_to_pauli: dict[circuits.FrozenCircuit, list[list[ops.PauliString]]], | ||
) -> tuple[list[circuits.Circuit], list[study.Sweepable]]: | ||
"""Generates basis change circuits for each group of Pauli strings with sweep.""" | ||
parameterized_circuits = list[circuits.Circuit]() | ||
sweep_params = list[study.Sweepable]() | ||
for input_circuit, pauli_string_groups in normalized_circuits_to_pauli.items(): | ||
qid_list = list(sorted(input_circuit.all_qubits())) | ||
phi_symbols = sympy.symbols(f"phi:{len(qid_list)}") | ||
theta_symbols = sympy.symbols(f"theta:{len(qid_list)}") | ||
|
||
parameterized_circuit = input_circuit.unfreeze() + circuits.Circuit( | ||
[ | ||
ops.PhasedXPowGate(phase_exponent=(a - 1) / 2, exponent=b)(qubit) | ||
for a, b, qubit in zip(phi_symbols, theta_symbols, qid_list) | ||
], | ||
ops.M(*qid_list, key="m"), | ||
) | ||
sweep_param = [] | ||
for pauli_strings in pauli_string_groups: | ||
sweep_param.append(_pauli_strings_to_basis_change_with_sweep(pauli_strings, qid_list)) | ||
sweep_params.append(sweep_param) | ||
parameterized_circuits.append(parameterized_circuit) | ||
|
||
return parameterized_circuits, sweep_params | ||
|
||
|
||
def _build_one_qubit_confusion_matrix(e0: float, e1: float) -> np.ndarray: | ||
"""Builds a 2x2 confusion matrix for a single qubit. | ||
|
||
|
@@ -281,7 +357,7 @@ def _build_many_one_qubits_empty_confusion_matrix(qubits_length: int) -> list[np | |
def _process_pauli_measurement_results( | ||
qubits: list[ops.Qid], | ||
pauli_string_groups: list[list[ops.PauliString]], | ||
circuit_results: list[ResultDict], | ||
circuit_results: Sequence[ResultDict], | ||
calibration_results: dict[tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult], | ||
pauli_repetitions: int, | ||
timestamp: float, | ||
|
@@ -376,7 +452,8 @@ def measure_pauli_strings( | |
pauli_repetitions: int, | ||
readout_repetitions: int, | ||
num_random_bitstrings: int, | ||
rng_or_seed: np.random.Generator | int, | ||
rng_or_seed: Union[np.random.Generator, int], | ||
use_sweep: bool = False, | ||
) -> list[CircuitToPauliStringsMeasurementResult]: | ||
"""Measures expectation values of Pauli strings on given circuits with/without | ||
readout error mitigation. | ||
|
@@ -385,7 +462,8 @@ def measure_pauli_strings( | |
For each circuit and its associated list of QWC pauli string group, it: | ||
1. Constructs circuits to measure the Pauli string expectation value by | ||
adding basis change moments and measurement operations. | ||
2. Runs shuffled readout benchmarking on these circuits to calibrate readout errors. | ||
2. If `num_random_bitstrings` is greater than zero, performing readout | ||
benchmarking (shuffled or sweep-based) to calibrate readout errors. | ||
3. Mitigates readout errors using the calibrated confusion matrices. | ||
4. Calculates and returns both error-mitigated and unmitigated expectation values for | ||
each Pauli string. | ||
|
@@ -406,6 +484,8 @@ def measure_pauli_strings( | |
num_random_bitstrings: The number of random bitstrings to use in readout | ||
benchmarking. | ||
rng_or_seed: A random number generator or seed for the readout benchmarking. | ||
use_sweep: If True, uses parameterized circuits and sweeps parameters | ||
for both Pauli measurements and readout benchmarking. Defaults to False.. | ||
|
||
Returns: | ||
A list of CircuitToPauliStringsMeasurementResult objects, where each object contains: | ||
|
@@ -435,45 +515,63 @@ def measure_pauli_strings( | |
|
||
# Build the basis-change circuits for each Pauli string group | ||
pauli_measurement_circuits = list[circuits.Circuit]() | ||
for input_circuit, pauli_string_groups in normalized_circuits_to_pauli.items(): | ||
qid_list = list(sorted(input_circuit.all_qubits())) | ||
basis_change_circuits = [] | ||
input_circuit_unfrozen = input_circuit.unfreeze() | ||
for pauli_strings in pauli_string_groups: | ||
basis_change_circuit = ( | ||
input_circuit_unfrozen | ||
+ _pauli_strings_to_basis_change_ops(pauli_strings, qid_list) | ||
+ ops.measure(*qid_list, key="m") | ||
) | ||
basis_change_circuits.append(basis_change_circuit) | ||
pauli_measurement_circuits.extend(basis_change_circuits) | ||
sweep_params = list[study.Sweepable]() | ||
circuits_results: Union[Sequence[ResultDict], Sequence[Sequence[study.Result]]] = [] | ||
# calibration_results = list[Dict[Tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult]]() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ? |
||
|
||
# Run shuffled benchmarking for readout calibration | ||
circuits_results, calibration_results = run_shuffled_with_readout_benchmarking( | ||
input_circuits=pauli_measurement_circuits, | ||
sampler=sampler, | ||
circuit_repetitions=pauli_repetitions, | ||
rng_or_seed=rng_or_seed, | ||
qubits=[list(qubits) for qubits in qubits_list], | ||
num_random_bitstrings=num_random_bitstrings, | ||
readout_repetitions=readout_repetitions, | ||
) | ||
if use_sweep: | ||
pauli_measurement_circuits, sweep_params = _generate_basis_change_circuits_with_sweep( | ||
normalized_circuits_to_pauli | ||
) | ||
|
||
# Run benchmarking using sweep for readout calibration | ||
circuits_results, calibration_results = run_sweep_with_readout_benchmarking( | ||
input_circuits=pauli_measurement_circuits, | ||
sweep_params=sweep_params, | ||
sampler=sampler, | ||
circuit_repetitions=pauli_repetitions, | ||
rng_or_seed=rng_or_seed, | ||
qubits=[list(qubits) for qubits in qubits_list], | ||
num_random_bitstrings=num_random_bitstrings, | ||
readout_repetitions=readout_repetitions, | ||
) | ||
|
||
else: | ||
pauli_measurement_circuits = _generate_basis_change_circuits(normalized_circuits_to_pauli) | ||
|
||
# Run shuffled benchmarking for readout calibration | ||
circuits_results, calibration_results = run_shuffled_with_readout_benchmarking( | ||
input_circuits=pauli_measurement_circuits, | ||
sampler=sampler, | ||
circuit_repetitions=pauli_repetitions, | ||
rng_or_seed=rng_or_seed, | ||
qubits=[list(qubits) for qubits in qubits_list], | ||
num_random_bitstrings=num_random_bitstrings, | ||
readout_repetitions=readout_repetitions, | ||
) | ||
|
||
# Process the results to calculate expectation values | ||
results: list[CircuitToPauliStringsMeasurementResult] = [] | ||
circuit_result_index = 0 | ||
for input_circuit, pauli_string_groups in normalized_circuits_to_pauli.items(): | ||
for i, (input_circuit, pauli_string_groups) in enumerate(normalized_circuits_to_pauli.items()): | ||
|
||
qubits_in_circuit = tuple(sorted(input_circuit.all_qubits())) | ||
|
||
disable_readout_mitigation = False if num_random_bitstrings != 0 else True | ||
|
||
circuits_results_for_group: Union[ResultDict, Sequence[study.Result]] = [] | ||
if use_sweep: | ||
circuits_results_for_group = circuits_results[i] | ||
else: | ||
circuits_results_for_group = circuits_results[ | ||
circuit_result_index : circuit_result_index + len(pauli_string_groups) | ||
] | ||
circuit_result_index += len(pauli_string_groups) | ||
|
||
pauli_measurement_results = _process_pauli_measurement_results( | ||
list(qubits_in_circuit), | ||
pauli_string_groups, | ||
circuits_results[ | ||
circuit_result_index : circuit_result_index + len(pauli_string_groups) | ||
], | ||
circuits_results_for_group, | ||
calibration_results, | ||
pauli_repetitions, | ||
time.time(), | ||
|
@@ -485,5 +583,4 @@ def measure_pauli_strings( | |
) | ||
) | ||
|
||
circuit_result_index += len(pauli_string_groups) | ||
return results |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happens when these pauli strings share qubits?