-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Allow users to run readout benchmarking with sweep #7435
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
9
commits into
quantumlib:main
Choose a base branch
from
ddddddanni:shuffle-sweep
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.
Open
Changes from 2 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
5f884f4
Allow users to run readout benchmarking with sweep
ddddddanni e7e0077
Merge branch 'main' into shuffle-sweep
ddddddanni 684ffa8
Make changes based on Nour's suggestions
ddddddanni 9d56e00
Fix tests and lint
ddddddanni 236ce12
Resolve comments
ddddddanni 1b8b10b
Fix lint and type checks
ddddddanni 17d46e3
Address Nour's comments
ddddddanni 4c0a160
Add test coverage
ddddddanni f175cc4
Remove the rng checks
ddddddanni 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
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 | ||||
---|---|---|---|---|---|---|
|
@@ -17,21 +17,22 @@ | |||||
from __future__ import annotations | ||||||
|
||||||
import time | ||||||
from typing import TYPE_CHECKING | ||||||
from typing import Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union | ||||||
|
||||||
import numpy as np | ||||||
import sympy | ||||||
|
||||||
from cirq import circuits, ops, protocols, work | ||||||
from cirq import circuits, ops, protocols, study, work | ||||||
from cirq.experiments import SingleQubitReadoutCalibrationResult | ||||||
|
||||||
if TYPE_CHECKING: | ||||||
from cirq.study import ResultDict | ||||||
|
||||||
|
||||||
def _validate_input( | ||||||
input_circuits: list[circuits.Circuit], | ||||||
circuit_repetitions: int | list[int], | ||||||
rng_or_seed: np.random.Generator | int, | ||||||
input_circuits: Sequence[circuits.Circuit], | ||||||
circuit_repetitions: Union[int, list[int]], | ||||||
rng_or_seed: Union[np.random.Generator, int], | ||||||
num_random_bitstrings: int, | ||||||
readout_repetitions: int, | ||||||
): | ||||||
|
@@ -65,6 +66,22 @@ def _validate_input( | |||||
raise ValueError("Must provide non-zero readout_repetitions for readout calibration.") | ||||||
|
||||||
|
||||||
def _validate_input_with_sweep( | ||||||
input_circuits: Sequence[circuits.Circuit], | ||||||
sweep_params: Sequence[study.Sweepable], | ||||||
circuit_repetitions: Union[int, list[int]], | ||||||
rng_or_seed: Union[np.random.Generator, int], | ||||||
num_random_bitstrings: int, | ||||||
readout_repetitions: int, | ||||||
): | ||||||
"""Validates the input for the run_sweep_with_readout_benchmarking function.""" | ||||||
if not sweep_params: | ||||||
raise ValueError("Sweep parameters must not be empty.") | ||||||
return _validate_input( | ||||||
input_circuits, circuit_repetitions, rng_or_seed, num_random_bitstrings, readout_repetitions | ||||||
) | ||||||
|
||||||
|
||||||
def _generate_readout_calibration_circuits( | ||||||
qubits: list[ops.Qid], rng: np.random.Generator, num_random_bitstrings: int | ||||||
) -> tuple[list[circuits.Circuit], np.ndarray]: | ||||||
|
@@ -84,6 +101,79 @@ def _generate_readout_calibration_circuits( | |||||
return readout_calibration_circuits, random_bitstrings | ||||||
|
||||||
|
||||||
def _generate_parameterized_readout_calibration_circuit_with_sweep( | ||||||
qubits: list[ops.Qid], rng: np.random.Generator, num_random_bitstrings: int | ||||||
) -> tuple[circuits.Circuit, study.Sweepable, np.ndarray]: | ||||||
"""Generates a parameterized readout calibration circuit, sweep parameters, | ||||||
NoureldinYosri marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
and the random bitstrings.""" | ||||||
random_bitstrings = rng.integers(0, 2, size=(num_random_bitstrings, len(qubits))) | ||||||
|
||||||
exp_symbols = [sympy.Symbol(f'exp_{qubit}') for qubit in qubits] | ||||||
parameterized_readout_calibration_circuit = circuits.Circuit( | ||||||
[ops.X(qubit) ** exp for exp, qubit in zip(exp_symbols, qubits)], ops.M(*qubits, key="m") | ||||||
) | ||||||
sweep_params = [] | ||||||
for bitstr in random_bitstrings: | ||||||
sweep_params.append({exp: bit for exp, bit in zip(exp_symbols, bitstr)}) | ||||||
|
||||||
return parameterized_readout_calibration_circuit, sweep_params, random_bitstrings | ||||||
|
||||||
|
||||||
def _generate_all_readout_calibration_circuits( | ||||||
rng: np.random.Generator, | ||||||
num_random_bitstrings: int, | ||||||
qubits_to_measure: List[List[ops.Qid]], | ||||||
is_sweep: bool, | ||||||
) -> Tuple[List[circuits.Circuit], List[np.ndarray], List[study.Sweepable]]: | ||||||
"""Generates all readout calibration circuits and random bitstrings.""" | ||||||
all_readout_calibration_circuits: list[circuits.Circuit] = [] | ||||||
all_random_bitstrings: list[np.ndarray] = [] | ||||||
all_readout_sweep_params: list[study.Sweepable] = [] | ||||||
|
||||||
if num_random_bitstrings <= 0: | ||||||
return all_readout_calibration_circuits, all_random_bitstrings, all_readout_sweep_params | ||||||
|
||||||
if not is_sweep: | ||||||
for qubit_group in qubits_to_measure: | ||||||
readout_calibration_circuits, random_bitstrings = ( | ||||||
_generate_readout_calibration_circuits(qubit_group, rng, num_random_bitstrings) | ||||||
) | ||||||
all_readout_calibration_circuits.extend(readout_calibration_circuits) | ||||||
all_random_bitstrings.append(random_bitstrings) | ||||||
else: | ||||||
for qubit_group in qubits_to_measure: | ||||||
(parameterized_readout_calibration_circuit, readout_sweep_params, random_bitstrings) = ( | ||||||
_generate_parameterized_readout_calibration_circuit_with_sweep( | ||||||
qubit_group, rng, num_random_bitstrings | ||||||
) | ||||||
) | ||||||
all_readout_calibration_circuits.append(parameterized_readout_calibration_circuit) | ||||||
all_readout_sweep_params.append([readout_sweep_params]) | ||||||
all_random_bitstrings.append(random_bitstrings) | ||||||
|
||||||
return all_readout_calibration_circuits, all_random_bitstrings, all_readout_sweep_params | ||||||
|
||||||
|
||||||
def _determine_qubits_to_measure( | ||||||
input_circuits: Sequence[circuits.Circuit], | ||||||
qubits: Optional[Union[Sequence[ops.Qid], Sequence[Sequence[ops.Qid]]]], | ||||||
) -> List[List[ops.Qid]]: | ||||||
"""Determine the qubits to measure based on the input circuits and provided qubits.""" | ||||||
# If input qubits is None, extract qubits from input circuits | ||||||
qubits_to_measure: List[List[ops.Qid]] = [] | ||||||
if qubits is None: | ||||||
qubits_set: set[ops.Qid] = set() | ||||||
for circuit in input_circuits: | ||||||
qubits_set.update(circuit.all_qubits()) | ||||||
qubits_to_measure = [sorted(qubits_set)] | ||||||
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. nit: this can be a single line
Suggested change
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. Done! |
||||||
|
||||||
elif isinstance(qubits[0], ops.Qid): | ||||||
qubits_to_measure = [qubits] # type: ignore | ||||||
else: | ||||||
qubits_to_measure = qubits # type: ignore | ||||||
return qubits_to_measure | ||||||
|
||||||
|
||||||
def _shuffle_circuits( | ||||||
all_circuits: list[circuits.Circuit], all_repetitions: list[int], rng: np.random.Generator | ||||||
) -> tuple[list[circuits.Circuit], list[int], np.ndarray]: | ||||||
|
@@ -97,7 +187,7 @@ def _shuffle_circuits( | |||||
|
||||||
|
||||||
def _analyze_readout_results( | ||||||
unshuffled_readout_measurements: list[ResultDict], | ||||||
unshuffled_readout_measurements: Union[Sequence[ResultDict], Sequence[study.Result]], | ||||||
random_bitstrings: np.ndarray, | ||||||
readout_repetitions: int, | ||||||
qubits: list[ops.Qid], | ||||||
|
@@ -163,8 +253,8 @@ def run_shuffled_with_readout_benchmarking( | |||||
rng_or_seed: np.random.Generator | int, | ||||||
num_random_bitstrings: int = 100, | ||||||
readout_repetitions: int = 1000, | ||||||
qubits: list[ops.Qid] | list[list[ops.Qid]] | None = None, | ||||||
) -> tuple[list[ResultDict], dict[tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult]]: | ||||||
qubits: Optional[Union[Sequence[ops.Qid], Sequence[Sequence[ops.Qid]]]] = None, | ||||||
) -> tuple[Sequence[ResultDict], Dict[Tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult]]: | ||||||
"""Run the circuits in a shuffled order with readout error benchmarking. | ||||||
|
||||||
Args: | ||||||
|
@@ -191,35 +281,21 @@ def run_shuffled_with_readout_benchmarking( | |||||
input_circuits, circuit_repetitions, rng_or_seed, num_random_bitstrings, readout_repetitions | ||||||
) | ||||||
|
||||||
# If input qubits is None, extract qubits from input circuits | ||||||
qubits_to_measure: list[list[ops.Qid]] = [] | ||||||
if qubits is None: | ||||||
qubits_set: set[ops.Qid] = set() | ||||||
for circuit in input_circuits: | ||||||
qubits_set.update(circuit.all_qubits()) | ||||||
qubits_to_measure = [sorted(qubits_set)] | ||||||
elif isinstance(qubits[0], ops.Qid): | ||||||
qubits_to_measure = [qubits] # type: ignore | ||||||
else: | ||||||
qubits_to_measure = qubits # type: ignore | ||||||
qubits_to_measure = _determine_qubits_to_measure(input_circuits, qubits) | ||||||
|
||||||
# Generate the readout calibration circuits if num_random_bitstrings>0 | ||||||
# Else all_readout_calibration_circuits and all_random_bitstrings are empty | ||||||
all_readout_calibration_circuits = [] | ||||||
all_random_bitstrings = [] | ||||||
|
||||||
rng = ( | ||||||
rng_or_seed | ||||||
if isinstance(rng_or_seed, np.random.Generator) | ||||||
else np.random.default_rng(rng_or_seed) | ||||||
) | ||||||
if num_random_bitstrings > 0: | ||||||
for qubit_group in qubits_to_measure: | ||||||
readout_calibration_circuits, random_bitstrings = ( | ||||||
_generate_readout_calibration_circuits(qubit_group, rng, num_random_bitstrings) | ||||||
) | ||||||
all_readout_calibration_circuits.extend(readout_calibration_circuits) | ||||||
all_random_bitstrings.append(random_bitstrings) | ||||||
|
||||||
all_readout_calibration_circuits, all_random_bitstrings, _ = ( | ||||||
_generate_all_readout_calibration_circuits( | ||||||
rng, num_random_bitstrings, qubits_to_measure, False | ||||||
) | ||||||
) | ||||||
|
||||||
# Shuffle the circuits | ||||||
if isinstance(circuit_repetitions, int): | ||||||
|
@@ -254,3 +330,94 @@ def run_shuffled_with_readout_benchmarking( | |||||
start_idx = end_idx | ||||||
|
||||||
return unshuffled_input_circuits_measiurements, readout_calibration_results | ||||||
|
||||||
|
||||||
def run_sweep_with_readout_benchmarking( | ||||||
input_circuits: list[circuits.Circuit], | ||||||
NoureldinYosri marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
sweep_params: Sequence[study.Sweepable], | ||||||
sampler: work.Sampler, | ||||||
circuit_repetitions: Union[int, list[int]], | ||||||
rng_or_seed: Union[np.random.Generator, int], | ||||||
num_random_bitstrings: int = 100, | ||||||
readout_repetitions: int = 1000, | ||||||
qubits: Optional[Union[Sequence[ops.Qid], Sequence[Sequence[ops.Qid]]]] = None, | ||||||
) -> tuple[ | ||||||
Sequence[Sequence[study.Result]], Dict[Tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult] | ||||||
]: | ||||||
"""Run the sweep circuits with readout error benchmarking (no shuffling). | ||||||
Args: | ||||||
input_circuits: The circuits to run. | ||||||
sweep_params: The sweep parameters for the input circuits. | ||||||
sampler: The sampler to use. | ||||||
circuit_repetitions: The repetitions for `circuits`. | ||||||
rng_or_seed: A random number generator used to generate readout circuits. | ||||||
Or an integer seed. | ||||||
num_random_bitstrings: The number of random bitstrings for measuring readout. | ||||||
If set to 0, no readout calibration circuits are generated. | ||||||
readout_repetitions: The number of repetitions for each readout bitstring. | ||||||
qubits: The qubits to benchmark readout errors. If None, all qubits in the | ||||||
input_circuits are used. Can be a list of qubits or a list of tuples | ||||||
of qubits. | ||||||
Returns: | ||||||
A tuple containing: | ||||||
- A list of lists of dictionaries with the measurement results. | ||||||
- A dictionary mapping each tuple of qubits to a SingleQubitReadoutCalibrationResult. | ||||||
""" | ||||||
|
||||||
_validate_input_with_sweep( | ||||||
input_circuits, | ||||||
sweep_params, | ||||||
circuit_repetitions, | ||||||
rng_or_seed, | ||||||
num_random_bitstrings, | ||||||
readout_repetitions, | ||||||
) | ||||||
|
||||||
qubits_to_measure = _determine_qubits_to_measure(input_circuits, qubits) | ||||||
|
||||||
# Generate the readout calibration circuits (parameterized circuits) and sweep params | ||||||
# if num_random_bitstrings>0 | ||||||
# Else all_readout_calibration_circuits and all_random_bitstrings are empty | ||||||
rng = ( | ||||||
rng_or_seed | ||||||
if isinstance(rng_or_seed, np.random.Generator) | ||||||
else np.random.default_rng(rng_or_seed) | ||||||
) | ||||||
|
||||||
all_readout_calibration_circuits, all_random_bitstrings, all_readout_sweep_params = ( | ||||||
_generate_all_readout_calibration_circuits( | ||||||
rng, num_random_bitstrings, qubits_to_measure, True | ||||||
) | ||||||
) | ||||||
|
||||||
if isinstance(circuit_repetitions, int): | ||||||
circuit_repetitions = [circuit_repetitions] * len(input_circuits) | ||||||
all_repetitions = circuit_repetitions + [readout_repetitions] * len( | ||||||
all_readout_calibration_circuits | ||||||
) | ||||||
|
||||||
# Run the sweep circuits and measure | ||||||
results = sampler.run_batch( | ||||||
input_circuits + all_readout_calibration_circuits, | ||||||
list(sweep_params) + all_readout_sweep_params, | ||||||
repetitions=all_repetitions, | ||||||
) | ||||||
|
||||||
timestamp = time.time() | ||||||
|
||||||
input_circuits_measurement = results[: len(input_circuits)] | ||||||
readout_measurements = results[len(input_circuits) :] | ||||||
|
||||||
# Analyze results | ||||||
readout_calibration_results = {} | ||||||
i = 0 | ||||||
for qubit_group, random_bitstrings in zip(qubits_to_measure, all_random_bitstrings): | ||||||
ddddddanni marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
group_measurements = readout_measurements[i] | ||||||
i += 1 | ||||||
|
||||||
calibration_result = _analyze_readout_results( | ||||||
group_measurements, random_bitstrings, readout_repetitions, qubit_group, timestamp | ||||||
) | ||||||
readout_calibration_results[tuple(qubit_group)] = calibration_result | ||||||
|
||||||
return input_circuits_measurement, readout_calibration_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.
traditionally
rng
should be the last input, same belowThere 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.
Done!