From 5f884f48cf0c3faa14045c7546e4146c20066f74 Mon Sep 17 00:00:00 2001 From: ddddddanni Date: Tue, 24 Jun 2025 15:48:05 -0700 Subject: [PATCH 1/3] Allow users to run readout benchmarking with sweep --- .../cirq/contrib/shuffle_circuits/__init__.py | 1 + ...ffle_circuits_with_readout_benchmarking.py | 225 +++++++++++++-- ...circuits_with_readout_benchmarking_test.py | 269 +++++++++++++----- 3 files changed, 401 insertions(+), 94 deletions(-) diff --git a/cirq-core/cirq/contrib/shuffle_circuits/__init__.py b/cirq-core/cirq/contrib/shuffle_circuits/__init__.py index 7f97f8834a4..ee9b9777d67 100644 --- a/cirq-core/cirq/contrib/shuffle_circuits/__init__.py +++ b/cirq-core/cirq/contrib/shuffle_circuits/__init__.py @@ -15,4 +15,5 @@ from cirq.contrib.shuffle_circuits.shuffle_circuits_with_readout_benchmarking import ( run_shuffled_with_readout_benchmarking as run_shuffled_with_readout_benchmarking, + run_sweep_with_readout_benchmarking as run_sweep_with_readout_benchmarking, ) diff --git a/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py b/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py index df7fa2bdb92..0ad0027be8b 100644 --- a/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py +++ b/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py @@ -17,11 +17,12 @@ 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: @@ -29,9 +30,9 @@ 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, + 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)] + + 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], + 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): + 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 diff --git a/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py b/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py index 46b19794065..95184220547 100644 --- a/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py +++ b/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py @@ -15,11 +15,17 @@ from __future__ import annotations import itertools +from typing import Sequence import numpy as np import pytest +import sympy import cirq +from cirq.contrib.shuffle_circuits import ( + run_shuffled_with_readout_benchmarking, + run_sweep_with_readout_benchmarking, +) from cirq.experiments import ( random_quantum_circuit_generation as rqcg, SingleQubitReadoutCalibrationResult, @@ -28,7 +34,7 @@ from cirq.study import ResultDict -def _create_test_circuits(qubits: list[cirq.Qid], n_circuits: int) -> list[cirq.Circuit]: +def _create_test_circuits(qubits: Sequence[cirq.Qid], n_circuits: int) -> list[cirq.Circuit]: """Helper function to generate circuits for testing.""" if len(qubits) < 2: raise ValueError( @@ -50,32 +56,80 @@ def _create_test_circuits(qubits: list[cirq.Qid], n_circuits: int) -> list[cirq. return input_circuits -def test_shuffled_circuits_with_readout_benchmarking_errors_no_noise(): - """Test shuffled circuits with readout benchmarking with no noise from sampler.""" - qubits = cirq.LineQubit.range(5) +def _create_test_circuits_with_sweep( + qubits: Sequence[cirq.Qid], n_circuits: int +) -> tuple[list[cirq.Circuit], list[cirq.ParamResolver]]: + """Helper function to generate sweep circuits for testing.""" + if len(qubits) < 2: + raise ValueError( + "Need at least two qubits to generate two-qubit circuits." + ) # pragma: no cover + theta_symbol = sympy.Symbol('theta') + phi_symbol = sympy.Symbol('phi') + + two_qubit_gates = [cirq.ISWAP, cirq.CNOT] + + input_circuits = [] + sweep_params: list[cirq.ParamResolver] = [] + qubit_pairs = list(itertools.combinations(qubits, 2)) + num_pairs = len(qubit_pairs) + for i in range(n_circuits): + gate = two_qubit_gates[i % len(two_qubit_gates)] + q0, q1 = qubit_pairs[i % num_pairs] + circuits = rqcg.generate_library_of_2q_circuits( + n_library_circuits=5, two_qubit_gate=gate, q0=q0, q1=q1 + ) + for circuit in circuits: + circuit += cirq.Circuit(cirq.X(q0) ** theta_symbol, cirq.Y(q1) ** phi_symbol) + circuit.append(cirq.measure(*qubits, key="m")) + sweep_params.append(cirq.ParamResolver({'theta': 0, 'phi': 1})) + input_circuits.extend(circuits) + + return input_circuits, sweep_params - # Generate random input circuits - input_circuits = _create_test_circuits(qubits, 3) + +@pytest.mark.parametrize("mode", ["shuffled", "sweep"]) +def test_circuits_with_readout_benchmarking_errors_no_noise(mode: str): + """Test shuffled/sweep circuits with readout benchmarking with no noise from sampler.""" + qubits = cirq.LineQubit.range(5) sampler = cirq.Simulator() circuit_repetitions = 1 # allow passing a seed rng = 123 readout_repetitions = 1000 + num_random_bitstrings = 100 + + if mode == "shuffled": + input_circuits = _create_test_circuits(qubits, 3) - measurements, readout_calibration_results = ( - cirq.contrib.shuffle_circuits.run_shuffled_with_readout_benchmarking( + sweep_measurements, readout_calibration_results = run_shuffled_with_readout_benchmarking( input_circuits, sampler, circuit_repetitions, rng, - num_random_bitstrings=100, + num_random_bitstrings=num_random_bitstrings, + readout_repetitions=readout_repetitions, + ) + + for measurement in sweep_measurements: + assert isinstance(measurement, ResultDict) + elif mode == "sweep": + input_circuits, sweep_params = _create_test_circuits_with_sweep(qubits, 3) + + measurements, readout_calibration_results = run_sweep_with_readout_benchmarking( + input_circuits, + sweep_params, + sampler, + circuit_repetitions, + rng, + num_random_bitstrings=num_random_bitstrings, readout_repetitions=readout_repetitions, ) - ) - for measurement in measurements: - assert isinstance(measurement, ResultDict) + for measurement_group in measurements: # Treat as a list of lists + for single_sweep_measurement in measurement_group: + assert isinstance(single_sweep_measurement, ResultDict) for qlist, readout_calibration_result in readout_calibration_results.items(): assert isinstance(qlist, tuple) @@ -88,31 +142,46 @@ def test_shuffled_circuits_with_readout_benchmarking_errors_no_noise(): assert isinstance(readout_calibration_result.timestamp, float) -def test_shuffled_circuits_with_readout_benchmarking_errors_with_noise(): - """Test shuffled circuits with readout benchmarking with noise from sampler.""" +@pytest.mark.parametrize("mode", ["shuffled", "sweep"]) +def test_circuits_with_readout_benchmarking_errors_with_noise(mode: str): + """Test shuffled/sweep circuits with readout benchmarking with noise from sampler.""" qubits = cirq.LineQubit.range(6) - - # Generate random input circuits - input_circuits = _create_test_circuits(qubits, 6) - sampler = NoisySingleQubitReadoutSampler(p0=0.1, p1=0.2, seed=1234) circuit_repetitions = 1 rng = np.random.default_rng() readout_repetitions = 1000 + num_random_bitstrings = 100 + + if mode == "shuffled": + input_circuits = _create_test_circuits(qubits, 6) - measurements, readout_calibration_results = ( - cirq.contrib.shuffle_circuits.run_shuffled_with_readout_benchmarking( + measurements, readout_calibration_results = run_shuffled_with_readout_benchmarking( input_circuits, sampler, circuit_repetitions, rng, - num_random_bitstrings=100, + num_random_bitstrings=num_random_bitstrings, + readout_repetitions=readout_repetitions, + ) + + for measurement in measurements: + assert isinstance(measurement, ResultDict) + elif mode == "sweep": + input_circuits, sweep_params = _create_test_circuits_with_sweep(qubits, 6) + + sweep_measurements, readout_calibration_results = run_sweep_with_readout_benchmarking( + input_circuits, + sweep_params, + sampler, + circuit_repetitions, + rng, + num_random_bitstrings=num_random_bitstrings, readout_repetitions=readout_repetitions, ) - ) - for measurement in measurements: - assert isinstance(measurement, ResultDict) + for measurement_group in sweep_measurements: # Treat as a list of lists + for single_sweep_measurement in measurement_group: + assert isinstance(single_sweep_measurement, ResultDict) for qlist, readout_calibration_result in readout_calibration_results.items(): assert isinstance(qlist, tuple) @@ -127,33 +196,48 @@ def test_shuffled_circuits_with_readout_benchmarking_errors_with_noise(): assert isinstance(readout_calibration_result.timestamp, float) -def test_shuffled_circuits_with_readout_benchmarking_errors_with_noise_and_input_qubits(): - """Test shuffled circuits with readout benchmarking with noise from sampler and input qubits.""" +@pytest.mark.parametrize("mode", ["shuffled", "sweep"]) +def test_circuits_with_readout_benchmarking_errors_with_noise_and_input_qubits(mode: str): + """Test shuffled/sweep circuits with readout benchmarking with noise from sampler and input qubits.""" qubits = cirq.LineQubit.range(6) readout_qubits = qubits[:4] - # Generate random input circuits - input_circuits = _create_test_circuits(qubits, 6) - sampler = NoisySingleQubitReadoutSampler(p0=0.1, p1=0.3, seed=1234) circuit_repetitions = 1 rng = np.random.default_rng() readout_repetitions = 1000 + num_random_bitstrings = 100 + + if mode == "shuffled": + input_circuits = _create_test_circuits(qubits, 6) - measurements, readout_calibration_results = ( - cirq.contrib.shuffle_circuits.run_shuffled_with_readout_benchmarking( + measurements, readout_calibration_results = run_shuffled_with_readout_benchmarking( input_circuits, sampler, circuit_repetitions, rng, - num_random_bitstrings=100, + num_random_bitstrings=num_random_bitstrings, readout_repetitions=readout_repetitions, qubits=readout_qubits, ) - ) + for measurement in measurements: + assert isinstance(measurement, ResultDict) - for measurement in measurements: - assert isinstance(measurement, ResultDict) + elif mode == "sweep": + input_circuits, sweep_params = _create_test_circuits_with_sweep(qubits, 6) + sweep_measurements, readout_calibration_results = run_sweep_with_readout_benchmarking( + input_circuits, + sweep_params, + sampler, + circuit_repetitions, + rng, + num_random_bitstrings=num_random_bitstrings, + readout_repetitions=readout_repetitions, + qubits=readout_qubits, + ) + for measurement_group in sweep_measurements: # Treat as a list of lists + for single_sweep_measurement in measurement_group: + assert isinstance(single_sweep_measurement, ResultDict) for qlist, readout_calibration_result in readout_calibration_results.items(): assert isinstance(qlist, tuple) @@ -168,24 +252,43 @@ def test_shuffled_circuits_with_readout_benchmarking_errors_with_noise_and_input assert isinstance(readout_calibration_result.timestamp, float) -def test_shuffled_circuits_with_readout_benchmarking_errors_with_noise_and_lists_input_qubits(): - """Test shuffled circuits with readout benchmarking with noise from sampler and input qubits.""" +@pytest.mark.parametrize("mode", ["shuffled", "sweep"]) +def test_circuits_with_readout_benchmarking_errors_with_noise_and_lists_input_qubits(mode: str): + """Test shuffled/sweep circuits with readout benchmarking with noise from sampler and input qubits.""" qubits_1 = cirq.LineQubit.range(3) qubits_2 = cirq.LineQubit.range(4) - readout_qubits = [qubits_1, qubits_2] - # Generate random input circuits and append measurements - input_circuits = _create_test_circuits(qubits_1, 6) + _create_test_circuits(qubits_2, 4) - sampler = NoisySingleQubitReadoutSampler(p0=0.1, p1=0.3, seed=1234) circuit_repetitions = 1 rng = np.random.default_rng() readout_repetitions = 1000 - measurements, readout_calibration_results = ( - cirq.contrib.shuffle_circuits.run_shuffled_with_readout_benchmarking( + if mode == "shuffled": + input_circuits = _create_test_circuits(qubits_1, 6) + _create_test_circuits(qubits_2, 4) + + measurements, readout_calibration_results = run_shuffled_with_readout_benchmarking( + input_circuits, + sampler, + circuit_repetitions, + rng, + num_random_bitstrings=100, + readout_repetitions=readout_repetitions, + qubits=readout_qubits, + ) + + for measurement in measurements: + assert isinstance(measurement, ResultDict) + + elif mode == "sweep": + input_circuits, sweep_params = _create_test_circuits_with_sweep(qubits_1, 6) + additional_circuits, additional_sweep_params = _create_test_circuits_with_sweep(qubits_2, 4) + input_circuits += additional_circuits + sweep_params += additional_sweep_params + + sweep_measurements, readout_calibration_results = run_sweep_with_readout_benchmarking( input_circuits, + sweep_params, sampler, circuit_repetitions, rng, @@ -193,10 +296,10 @@ def test_shuffled_circuits_with_readout_benchmarking_errors_with_noise_and_lists readout_repetitions=readout_repetitions, qubits=readout_qubits, ) - ) - for measurement in measurements: - assert isinstance(measurement, ResultDict) + for measurement_group in sweep_measurements: # Treat as a list of lists + for single_sweep_measurement in measurement_group: + assert isinstance(single_sweep_measurement, ResultDict) for qlist, readout_calibration_result in readout_calibration_results.items(): assert isinstance(qlist, tuple) @@ -211,23 +314,22 @@ def test_shuffled_circuits_with_readout_benchmarking_errors_with_noise_and_lists assert isinstance(readout_calibration_result.timestamp, float) -def test_can_handle_zero_random_bitstring(): - """Test shuffled circuits without readout benchmarking.""" +@pytest.mark.parametrize("mode", ["shuffled", "sweep"]) +def test_can_handle_zero_random_bitstring(mode: str): + """Test shuffled/sweep circuits without readout benchmarking.""" qubits_1 = cirq.LineQubit.range(3) qubits_2 = cirq.LineQubit.range(4) - readout_qubits = [qubits_1, qubits_2] - # Generate random input circuits and append measurements - input_circuits = _create_test_circuits(qubits_1, 6) + _create_test_circuits(qubits_2, 4) - sampler = NoisySingleQubitReadoutSampler(p0=0.1, p1=0.3, seed=1234) circuit_repetitions = 1 rng = np.random.default_rng() readout_repetitions = 1000 - measurements, readout_calibration_results = ( - cirq.contrib.shuffle_circuits.run_shuffled_with_readout_benchmarking( + if mode == "shuffled": + input_circuits = _create_test_circuits(qubits_1, 6) + _create_test_circuits(qubits_2, 4) + + measurements, readout_calibration_results = run_shuffled_with_readout_benchmarking( input_circuits, sampler, circuit_repetitions, @@ -236,10 +338,31 @@ def test_can_handle_zero_random_bitstring(): readout_repetitions=readout_repetitions, qubits=readout_qubits, ) - ) - for measurement in measurements: - assert isinstance(measurement, ResultDict) + for measurement in measurements: + assert isinstance(measurement, ResultDict) + + elif mode == "sweep": + input_circuits, sweep_params = _create_test_circuits_with_sweep(qubits_1, 6) + additional_circuits, additional_sweep_params = _create_test_circuits_with_sweep(qubits_2, 4) + input_circuits += additional_circuits + sweep_params += additional_sweep_params + + sweep_measurements, readout_calibration_results = run_sweep_with_readout_benchmarking( + input_circuits, + sweep_params, + sampler, + circuit_repetitions, + rng, + num_random_bitstrings=0, + readout_repetitions=readout_repetitions, + qubits=readout_qubits, + ) + + for sweep_measurement in sweep_measurements: + for single_sweep_measurement in sweep_measurement: + assert isinstance(single_sweep_measurement, ResultDict) + # Check that the readout_calibration_results is empty assert len(readout_calibration_results.items()) == 0 @@ -247,7 +370,7 @@ def test_can_handle_zero_random_bitstring(): def test_empty_input_circuits(): """Test that the input circuits are empty.""" with pytest.raises(ValueError, match="Input circuits must not be empty."): - cirq.contrib.shuffle_circuits.run_shuffled_with_readout_benchmarking( + run_shuffled_with_readout_benchmarking( [], cirq.ZerosSampler(), circuit_repetitions=10, @@ -261,7 +384,7 @@ def test_non_circuit_input(): """Test that the input circuits are not of type cirq.Circuit.""" q = cirq.LineQubit(0) with pytest.raises(ValueError, match="Input circuits must be of type cirq.Circuit."): - cirq.contrib.shuffle_circuits.run_shuffled_with_readout_benchmarking( + run_shuffled_with_readout_benchmarking( [q], cirq.ZerosSampler(), circuit_repetitions=10, @@ -276,7 +399,7 @@ def test_no_measurements(): q = cirq.LineQubit(0) circuit = cirq.Circuit(cirq.H(q)) with pytest.raises(ValueError, match="Input circuits must have measurements."): - cirq.contrib.shuffle_circuits.run_shuffled_with_readout_benchmarking( + run_shuffled_with_readout_benchmarking( [circuit], cirq.ZerosSampler(), circuit_repetitions=10, @@ -291,7 +414,7 @@ def test_zero_circuit_repetitions(): q = cirq.LineQubit(0) circuit = cirq.Circuit(cirq.H(q), cirq.measure(q)) with pytest.raises(ValueError, match="Must provide non-zero circuit_repetitions."): - cirq.contrib.shuffle_circuits.run_shuffled_with_readout_benchmarking( + run_shuffled_with_readout_benchmarking( [circuit], cirq.ZerosSampler(), circuit_repetitions=0, @@ -309,7 +432,7 @@ def test_mismatch_circuit_repetitions(): ValueError, match="Number of circuit_repetitions must match the number of" + " input circuits.", ): - cirq.contrib.shuffle_circuits.run_shuffled_with_readout_benchmarking( + run_shuffled_with_readout_benchmarking( [circuit], cirq.ZerosSampler(), circuit_repetitions=[10, 20], @@ -324,7 +447,7 @@ def test_zero_num_random_bitstrings(): q = cirq.LineQubit(0) circuit = cirq.Circuit(cirq.H(q), cirq.measure(q)) with pytest.raises(ValueError, match="Must provide zero or more num_random_bitstrings."): - cirq.contrib.shuffle_circuits.run_shuffled_with_readout_benchmarking( + run_shuffled_with_readout_benchmarking( [circuit], cirq.ZerosSampler(), circuit_repetitions=10, @@ -341,7 +464,7 @@ def test_zero_readout_repetitions(): with pytest.raises( ValueError, match="Must provide non-zero readout_repetitions for readout" + " calibration." ): - cirq.contrib.shuffle_circuits.run_shuffled_with_readout_benchmarking( + run_shuffled_with_readout_benchmarking( [circuit], cirq.ZerosSampler(), circuit_repetitions=10, @@ -356,7 +479,7 @@ def test_rng_type_mismatch(): q = cirq.LineQubit(0) circuit = cirq.Circuit(cirq.H(q), cirq.measure(q)) with pytest.raises(ValueError, match="Must provide a numpy random generator or a seed"): - cirq.contrib.shuffle_circuits.run_shuffled_with_readout_benchmarking( + run_shuffled_with_readout_benchmarking( [circuit], cirq.ZerosSampler(), circuit_repetitions=10, @@ -364,3 +487,19 @@ def test_rng_type_mismatch(): num_random_bitstrings=5, readout_repetitions=100, ) + + +def test_empty_sweep_params(): + """Test that the sweep params are empty.""" + q = cirq.LineQubit(5) + circuit = cirq.Circuit(cirq.H(q)) + with pytest.raises(ValueError, match="Sweep parameters must not be empty."): + run_sweep_with_readout_benchmarking( + [circuit], + [], + cirq.ZerosSampler(), + circuit_repetitions=10, + rng_or_seed=np.random.default_rng(456), + num_random_bitstrings=5, + readout_repetitions=100, + ) From 684ffa8be3490b47978913760abdf9e035591c1b Mon Sep 17 00:00:00 2001 From: ddddddanni Date: Tue, 1 Jul 2025 23:09:54 -0700 Subject: [PATCH 2/3] Make changes based on Nour's suggestions --- .../cirq/contrib/shuffle_circuits/__init__.py | 1 + ...ffle_circuits_with_readout_benchmarking.py | 232 ++++++++++++----- ...circuits_with_readout_benchmarking_test.py | 240 ++++++++---------- 3 files changed, 280 insertions(+), 193 deletions(-) diff --git a/cirq-core/cirq/contrib/shuffle_circuits/__init__.py b/cirq-core/cirq/contrib/shuffle_circuits/__init__.py index ee9b9777d67..a2b90c4f96b 100644 --- a/cirq-core/cirq/contrib/shuffle_circuits/__init__.py +++ b/cirq-core/cirq/contrib/shuffle_circuits/__init__.py @@ -15,5 +15,6 @@ from cirq.contrib.shuffle_circuits.shuffle_circuits_with_readout_benchmarking import ( run_shuffled_with_readout_benchmarking as run_shuffled_with_readout_benchmarking, + run_shuffled_circuits_with_readout_benchmarking as run_shuffled_circuits_with_readout_benchmarking, run_sweep_with_readout_benchmarking as run_sweep_with_readout_benchmarking, ) diff --git a/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py b/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py index 0ad0027be8b..0056dc39082 100644 --- a/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py +++ b/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py @@ -19,22 +19,64 @@ import time from typing import Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union +import attrs import numpy as np import sympy from cirq import circuits, ops, protocols, study, work from cirq.experiments import SingleQubitReadoutCalibrationResult +from cirq._compat import deprecated if TYPE_CHECKING: from cirq.study import ResultDict -def _validate_input( +@attrs.frozen +class ReadoutBenchmarkingParams: + """Parameters for configuring readout benchmarking. + + Attributes: + circuit_repetitions: The repetitions for `circuits`. + 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. + """ + + circuit_repetitions: Union[int, list[int]] + num_random_bitstrings: int = 100 + readout_repetitions: int = 1000 + qubits: Optional[Union[Sequence[ops.Qid], Sequence[Sequence[ops.Qid]]]] = None + + def __attrs_post_init__(self): + _validate_benchmarking_setup( + self.circuit_repetitions, self.num_random_bitstrings, self.readout_repetitions + ) + + +def _validate_benchmarking_setup( + circuit_repetitions: Union[int, list[int]], num_random_bitstrings: int, readout_repetitions: int +): + # Check circuit_repetitions + if isinstance(circuit_repetitions, int): + if circuit_repetitions <= 0: + raise ValueError("Must provide non-zero circuit_repetitions.") + + # Check num_random_bitstrings is bigger than or equal to 0 + if num_random_bitstrings < 0: + raise ValueError("Must provide zero or more num_random_bitstrings.") + + # Check readout_repetitions is bigger than 0 + if readout_repetitions <= 0: + raise ValueError("Must provide non-zero readout_repetitions for readout calibration.") + + +def _validate_experiment_input( 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, ): if not input_circuits: raise ValueError("Input circuits must not be empty.") @@ -46,10 +88,6 @@ def _validate_input( if not any(protocols.is_measurement(circuit) for op in circuit.all_operations()): raise ValueError("Input circuits must have measurements.") - # Check circuit_repetitions - if isinstance(circuit_repetitions, int): - if circuit_repetitions <= 0: - raise ValueError("Must provide non-zero circuit_repetitions.") if isinstance(circuit_repetitions, list) and len(circuit_repetitions) != len(input_circuits): raise ValueError("Number of circuit_repetitions must match the number of input circuits.") @@ -57,33 +95,21 @@ def _validate_input( if not isinstance(rng_or_seed, np.random.Generator) and not isinstance(rng_or_seed, int): raise ValueError("Must provide a numpy random generator or a seed") - # Check num_random_bitstrings is bigger than or equal to 0 - if num_random_bitstrings < 0: - raise ValueError("Must provide zero or more num_random_bitstrings.") - # Check readout_repetitions is bigger than 0 - if readout_repetitions <= 0: - raise ValueError("Must provide non-zero readout_repetitions for readout calibration.") - - -def _validate_input_with_sweep( +def _validate_experiment_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 - ) + return _validate_experiment_input(input_circuits, circuit_repetitions, rng_or_seed) def _generate_readout_calibration_circuits( - qubits: list[ops.Qid], rng: np.random.Generator, num_random_bitstrings: int + qubits: list[ops.Qid], num_random_bitstrings: int, rng: np.random.Generator ) -> tuple[list[circuits.Circuit], np.ndarray]: """Generates the readout calibration circuits with random bitstrings.""" bit_to_gate = (ops.I, ops.X) @@ -102,10 +128,27 @@ def _generate_readout_calibration_circuits( def _generate_parameterized_readout_calibration_circuit_with_sweep( - qubits: list[ops.Qid], rng: np.random.Generator, num_random_bitstrings: int + qubits: list[ops.Qid], num_random_bitstrings: int, rng: np.random.Generator ) -> tuple[circuits.Circuit, study.Sweepable, np.ndarray]: """Generates a parameterized readout calibration circuit, sweep parameters, - and the random bitstrings.""" + and the random bitstrings. + + The function generates a single cirq.Circuit with parameterized X gates. + The function also generates a set of random bitstrings and creates a list + of sweep parameters to map the parameters in the circuit to the values in + each bitstring, allowing efficient calibration of readout errors of input qubits. + + Args: + qubits: The list of qubits to include in the calibration circuit. + num_random_bitstrings: The number of random bitstrings to generate for calibration. + rng: A numpy random number generator used to generate the random bitstrings. + + Returns: + A tuple containing: + - The parameterized readout calibration circuit (cirq.Circuit). + - A list of parameter sweeps (one for each random bitstring). + - The numpy array of generated 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] @@ -120,10 +163,10 @@ def _generate_parameterized_readout_calibration_circuit_with_sweep( def _generate_all_readout_calibration_circuits( - rng: np.random.Generator, num_random_bitstrings: int, qubits_to_measure: List[List[ops.Qid]], is_sweep: bool, + rng: np.random.Generator, ) -> 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] = [] @@ -136,7 +179,7 @@ def _generate_all_readout_calibration_circuits( 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) + _generate_readout_calibration_circuits(qubit_group, num_random_bitstrings, rng) ) all_readout_calibration_circuits.extend(readout_calibration_circuits) all_random_bitstrings.append(random_bitstrings) @@ -144,7 +187,7 @@ def _generate_all_readout_calibration_circuits( 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 + qubit_group, num_random_bitstrings, rng ) ) all_readout_calibration_circuits.append(parameterized_readout_calibration_circuit) @@ -162,10 +205,9 @@ def _determine_qubits_to_measure( # 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)] + qubits_to_measure = [ + sorted(set(q for circuit in input_circuits for q in circuit.all_qubits())) + ] elif isinstance(qubits[0], ops.Qid): qubits_to_measure = [qubits] # type: ignore @@ -246,6 +288,7 @@ def _analyze_readout_results( ) +@deprecated(deadline="v2.0", fix="Use run_shuffled_circuits_with_readout_benchmarking() instead.") def run_shuffled_with_readout_benchmarking( input_circuits: list[circuits.Circuit], sampler: work.Sampler, @@ -277,9 +320,8 @@ def run_shuffled_with_readout_benchmarking( """ - _validate_input( - input_circuits, circuit_repetitions, rng_or_seed, num_random_bitstrings, readout_repetitions - ) + _validate_benchmarking_setup(circuit_repetitions, num_random_bitstrings, readout_repetitions) + _validate_experiment_input(input_circuits, circuit_repetitions, rng_or_seed) qubits_to_measure = _determine_qubits_to_measure(input_circuits, qubits) @@ -293,7 +335,7 @@ def run_shuffled_with_readout_benchmarking( all_readout_calibration_circuits, all_random_bitstrings, _ = ( _generate_all_readout_calibration_circuits( - rng, num_random_bitstrings, qubits_to_measure, False + num_random_bitstrings, qubits_to_measure, False, rng ) ) @@ -332,48 +374,115 @@ def run_shuffled_with_readout_benchmarking( return unshuffled_input_circuits_measiurements, readout_calibration_results +def run_shuffled_circuits_with_readout_benchmarking( + sampler: work.Sampler, + input_circuits: list[circuits.Circuit], + parameters: ReadoutBenchmarkingParams, + rng_or_seed: np.random.Generator | int, +) -> tuple[Sequence[ResultDict], Dict[Tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult]]: + """Run the circuits in a shuffled order with readout error benchmarking. + + Args: + sampler: The sampler to use. + input_circuits: The circuits to run. + parameters: The readout benchmarking parameters. + rng_or_seed: A random number generator used to generate readout circuits. + Or an integer seed. + + Returns: + A tuple containing: + - A list of dictionaries with the unshuffled measurement results. + - A dictionary mapping each tuple of qubits to a SingleQubitReadoutCalibrationResult. + + """ + + _validate_experiment_input(input_circuits, parameters.circuit_repetitions, rng_or_seed) + + qubits_to_measure = _determine_qubits_to_measure(input_circuits, parameters.qubits) + + # Generate the readout calibration circuits 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, _ = ( + _generate_all_readout_calibration_circuits( + parameters.num_random_bitstrings, qubits_to_measure, False, rng + ) + ) + + # Shuffle the circuits + circuit_repetitions = parameters.circuit_repetitions + if isinstance(circuit_repetitions, int): + circuit_repetitions = [circuit_repetitions] * len(input_circuits) + all_repetitions = circuit_repetitions + [parameters.readout_repetitions] * len( + all_readout_calibration_circuits + ) + + shuffled_circuits, all_repetitions, unshuf_order = _shuffle_circuits( + input_circuits + all_readout_calibration_circuits, all_repetitions, rng + ) + + # Run the shuffled circuits and measure + results = sampler.run_batch(shuffled_circuits, repetitions=all_repetitions) + timestamp = time.time() + shuffled_measurements = [res[0] for res in results] + unshuffled_measurements = [shuffled_measurements[i] for i in unshuf_order] + + unshuffled_input_circuits_measiurements = unshuffled_measurements[: len(input_circuits)] + unshuffled_readout_measurements = unshuffled_measurements[len(input_circuits) :] + + # Analyze results + readout_calibration_results = {} + start_idx = 0 + for qubit_group, random_bitstrings in zip(qubits_to_measure, all_random_bitstrings): + end_idx = start_idx + len(random_bitstrings) + group_measurements = unshuffled_readout_measurements[start_idx:end_idx] + calibration_result = _analyze_readout_results( + group_measurements, + random_bitstrings, + parameters.readout_repetitions, + qubit_group, + timestamp, + ) + readout_calibration_results[tuple(qubit_group)] = calibration_result + start_idx = end_idx + + return unshuffled_input_circuits_measiurements, readout_calibration_results + + def run_sweep_with_readout_benchmarking( + sampler: work.Sampler, input_circuits: list[circuits.Circuit], sweep_params: Sequence[study.Sweepable], - sampler: work.Sampler, - circuit_repetitions: Union[int, list[int]], + parameters: ReadoutBenchmarkingParams, 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: + sampler: The sampler to use. 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`. + parameters: The readout benchmarking parameters. 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, + _validate_experiment_input_with_sweep( + input_circuits, sweep_params, parameters.circuit_repetitions, rng_or_seed ) - qubits_to_measure = _determine_qubits_to_measure(input_circuits, qubits) + qubits_to_measure = _determine_qubits_to_measure(input_circuits, parameters.qubits) # Generate the readout calibration circuits (parameterized circuits) and sweep params # if num_random_bitstrings>0 @@ -386,13 +495,14 @@ def run_sweep_with_readout_benchmarking( all_readout_calibration_circuits, all_random_bitstrings, all_readout_sweep_params = ( _generate_all_readout_calibration_circuits( - rng, num_random_bitstrings, qubits_to_measure, True + parameters.num_random_bitstrings, qubits_to_measure, True, rng ) ) + circuit_repetitions = parameters.circuit_repetitions if isinstance(circuit_repetitions, int): circuit_repetitions = [circuit_repetitions] * len(input_circuits) - all_repetitions = circuit_repetitions + [readout_repetitions] * len( + all_repetitions = circuit_repetitions + [parameters.readout_repetitions] * len( all_readout_calibration_circuits ) @@ -416,7 +526,11 @@ def run_sweep_with_readout_benchmarking( i += 1 calibration_result = _analyze_readout_results( - group_measurements, random_bitstrings, readout_repetitions, qubit_group, timestamp + group_measurements, + random_bitstrings, + parameters.readout_repetitions, + qubit_group, + timestamp, ) readout_calibration_results[tuple(qubit_group)] = calibration_result diff --git a/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py b/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py index 95184220547..958aa9062b4 100644 --- a/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py +++ b/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py @@ -17,14 +17,19 @@ import itertools from typing import Sequence +import cirq.contrib.shuffle_circuits import numpy as np import pytest import sympy import cirq from cirq.contrib.shuffle_circuits import ( - run_shuffled_with_readout_benchmarking, + run_shuffled_circuits_with_readout_benchmarking, run_sweep_with_readout_benchmarking, + run_shuffled_with_readout_benchmarking, +) +from cirq.contrib.shuffle_circuits.shuffle_circuits_with_readout_benchmarking import ( + ReadoutBenchmarkingParams, ) from cirq.experiments import ( random_quantum_circuit_generation as rqcg, @@ -95,39 +100,36 @@ def test_circuits_with_readout_benchmarking_errors_no_noise(mode: str): sampler = cirq.Simulator() circuit_repetitions = 1 + num_random_bitstrings = 100 + readout_repetitions = 1000 + + readout_benchmarking_params = ReadoutBenchmarkingParams( + circuit_repetitions=circuit_repetitions, + num_random_bitstrings=num_random_bitstrings, + readout_repetitions=readout_repetitions, + qubits=qubits, + ) # allow passing a seed rng = 123 - readout_repetitions = 1000 - num_random_bitstrings = 100 if mode == "shuffled": input_circuits = _create_test_circuits(qubits, 3) - sweep_measurements, readout_calibration_results = run_shuffled_with_readout_benchmarking( - input_circuits, - sampler, - circuit_repetitions, - rng, - num_random_bitstrings=num_random_bitstrings, - readout_repetitions=readout_repetitions, + measurements, readout_calibration_results = run_shuffled_circuits_with_readout_benchmarking( + sampler, input_circuits, readout_benchmarking_params, rng ) - for measurement in sweep_measurements: + for measurement in measurements: assert isinstance(measurement, ResultDict) + elif mode == "sweep": input_circuits, sweep_params = _create_test_circuits_with_sweep(qubits, 3) - measurements, readout_calibration_results = run_sweep_with_readout_benchmarking( - input_circuits, - sweep_params, - sampler, - circuit_repetitions, - rng, - num_random_bitstrings=num_random_bitstrings, - readout_repetitions=readout_repetitions, + sweep_measurements, readout_calibration_results = run_sweep_with_readout_benchmarking( + sampler, input_circuits, sweep_params, readout_benchmarking_params, rng ) - for measurement_group in measurements: # Treat as a list of lists + for measurement_group in sweep_measurements: for single_sweep_measurement in measurement_group: assert isinstance(single_sweep_measurement, ResultDict) @@ -152,16 +154,18 @@ def test_circuits_with_readout_benchmarking_errors_with_noise(mode: str): readout_repetitions = 1000 num_random_bitstrings = 100 + readout_benchmarking_params = ReadoutBenchmarkingParams( + circuit_repetitions=circuit_repetitions, + num_random_bitstrings=num_random_bitstrings, + readout_repetitions=readout_repetitions, + qubits=qubits, + ) + if mode == "shuffled": input_circuits = _create_test_circuits(qubits, 6) - measurements, readout_calibration_results = run_shuffled_with_readout_benchmarking( - input_circuits, - sampler, - circuit_repetitions, - rng, - num_random_bitstrings=num_random_bitstrings, - readout_repetitions=readout_repetitions, + measurements, readout_calibration_results = run_shuffled_circuits_with_readout_benchmarking( + sampler, input_circuits, readout_benchmarking_params, rng ) for measurement in measurements: @@ -170,13 +174,7 @@ def test_circuits_with_readout_benchmarking_errors_with_noise(mode: str): input_circuits, sweep_params = _create_test_circuits_with_sweep(qubits, 6) sweep_measurements, readout_calibration_results = run_sweep_with_readout_benchmarking( - input_circuits, - sweep_params, - sampler, - circuit_repetitions, - rng, - num_random_bitstrings=num_random_bitstrings, - readout_repetitions=readout_repetitions, + sampler, input_circuits, sweep_params, readout_benchmarking_params, rng ) for measurement_group in sweep_measurements: # Treat as a list of lists @@ -208,17 +206,18 @@ def test_circuits_with_readout_benchmarking_errors_with_noise_and_input_qubits(m readout_repetitions = 1000 num_random_bitstrings = 100 + readout_benchmarking_params = ReadoutBenchmarkingParams( + circuit_repetitions=circuit_repetitions, + num_random_bitstrings=num_random_bitstrings, + readout_repetitions=readout_repetitions, + qubits=readout_qubits, + ) + if mode == "shuffled": input_circuits = _create_test_circuits(qubits, 6) - measurements, readout_calibration_results = run_shuffled_with_readout_benchmarking( - input_circuits, - sampler, - circuit_repetitions, - rng, - num_random_bitstrings=num_random_bitstrings, - readout_repetitions=readout_repetitions, - qubits=readout_qubits, + measurements, readout_calibration_results = run_shuffled_circuits_with_readout_benchmarking( + sampler, input_circuits, readout_benchmarking_params, rng ) for measurement in measurements: assert isinstance(measurement, ResultDict) @@ -226,14 +225,7 @@ def test_circuits_with_readout_benchmarking_errors_with_noise_and_input_qubits(m elif mode == "sweep": input_circuits, sweep_params = _create_test_circuits_with_sweep(qubits, 6) sweep_measurements, readout_calibration_results = run_sweep_with_readout_benchmarking( - input_circuits, - sweep_params, - sampler, - circuit_repetitions, - rng, - num_random_bitstrings=num_random_bitstrings, - readout_repetitions=readout_repetitions, - qubits=readout_qubits, + sampler, input_circuits, sweep_params, readout_benchmarking_params, rng ) for measurement_group in sweep_measurements: # Treat as a list of lists for single_sweep_measurement in measurement_group: @@ -263,18 +255,20 @@ def test_circuits_with_readout_benchmarking_errors_with_noise_and_lists_input_qu circuit_repetitions = 1 rng = np.random.default_rng() readout_repetitions = 1000 + num_random_bitstrings = 100 + + readout_benchmarking_params = ReadoutBenchmarkingParams( + circuit_repetitions=circuit_repetitions, + num_random_bitstrings=num_random_bitstrings, + readout_repetitions=readout_repetitions, + qubits=readout_qubits, + ) if mode == "shuffled": input_circuits = _create_test_circuits(qubits_1, 6) + _create_test_circuits(qubits_2, 4) - measurements, readout_calibration_results = run_shuffled_with_readout_benchmarking( - input_circuits, - sampler, - circuit_repetitions, - rng, - num_random_bitstrings=100, - readout_repetitions=readout_repetitions, - qubits=readout_qubits, + measurements, readout_calibration_results = run_shuffled_circuits_with_readout_benchmarking( + sampler, input_circuits, readout_benchmarking_params, rng ) for measurement in measurements: @@ -287,14 +281,7 @@ def test_circuits_with_readout_benchmarking_errors_with_noise_and_lists_input_qu sweep_params += additional_sweep_params sweep_measurements, readout_calibration_results = run_sweep_with_readout_benchmarking( - input_circuits, - sweep_params, - sampler, - circuit_repetitions, - rng, - num_random_bitstrings=100, - readout_repetitions=readout_repetitions, - qubits=readout_qubits, + sampler, input_circuits, sweep_params, readout_benchmarking_params, rng ) for measurement_group in sweep_measurements: # Treat as a list of lists @@ -325,18 +312,20 @@ def test_can_handle_zero_random_bitstring(mode: str): circuit_repetitions = 1 rng = np.random.default_rng() readout_repetitions = 1000 + num_random_bitstrings = 0 + + readout_benchmarking_params = ReadoutBenchmarkingParams( + circuit_repetitions=circuit_repetitions, + num_random_bitstrings=num_random_bitstrings, + readout_repetitions=readout_repetitions, + qubits=readout_qubits, + ) if mode == "shuffled": input_circuits = _create_test_circuits(qubits_1, 6) + _create_test_circuits(qubits_2, 4) - measurements, readout_calibration_results = run_shuffled_with_readout_benchmarking( - input_circuits, - sampler, - circuit_repetitions, - rng, - num_random_bitstrings=0, - readout_repetitions=readout_repetitions, - qubits=readout_qubits, + measurements, readout_calibration_results = run_shuffled_circuits_with_readout_benchmarking( + sampler, input_circuits, readout_benchmarking_params, rng ) for measurement in measurements: @@ -349,14 +338,7 @@ def test_can_handle_zero_random_bitstring(mode: str): sweep_params += additional_sweep_params sweep_measurements, readout_calibration_results = run_sweep_with_readout_benchmarking( - input_circuits, - sweep_params, - sampler, - circuit_repetitions, - rng, - num_random_bitstrings=0, - readout_repetitions=readout_repetitions, - qubits=readout_qubits, + sampler, input_circuits, sweep_params, readout_benchmarking_params, rng ) for sweep_measurement in sweep_measurements: @@ -369,28 +351,30 @@ def test_can_handle_zero_random_bitstring(mode: str): def test_empty_input_circuits(): """Test that the input circuits are empty.""" + readout_benchmarking_params = ReadoutBenchmarkingParams( + circuit_repetitions=10, num_random_bitstrings=5, readout_repetitions=100 + ) with pytest.raises(ValueError, match="Input circuits must not be empty."): - run_shuffled_with_readout_benchmarking( - [], + run_shuffled_circuits_with_readout_benchmarking( cirq.ZerosSampler(), - circuit_repetitions=10, + [], + readout_benchmarking_params, rng_or_seed=np.random.default_rng(456), - num_random_bitstrings=5, - readout_repetitions=100, ) def test_non_circuit_input(): """Test that the input circuits are not of type cirq.Circuit.""" q = cirq.LineQubit(0) + readout_benchmarking_params = ReadoutBenchmarkingParams( + circuit_repetitions=10, num_random_bitstrings=5, readout_repetitions=100, qubits=q + ) with pytest.raises(ValueError, match="Input circuits must be of type cirq.Circuit."): - run_shuffled_with_readout_benchmarking( - [q], + run_shuffled_circuits_with_readout_benchmarking( cirq.ZerosSampler(), - circuit_repetitions=10, + [q], + readout_benchmarking_params, rng_or_seed=np.random.default_rng(456), - num_random_bitstrings=5, - readout_repetitions=100, ) @@ -398,29 +382,25 @@ def test_no_measurements(): """Test that the input circuits don't have measurements.""" q = cirq.LineQubit(0) circuit = cirq.Circuit(cirq.H(q)) + readout_benchmarking_params = ReadoutBenchmarkingParams( + circuit_repetitions=10, num_random_bitstrings=5, readout_repetitions=100, qubits=q + ) with pytest.raises(ValueError, match="Input circuits must have measurements."): - run_shuffled_with_readout_benchmarking( - [circuit], + run_shuffled_circuits_with_readout_benchmarking( cirq.ZerosSampler(), - circuit_repetitions=10, + [circuit], + readout_benchmarking_params, rng_or_seed=np.random.default_rng(456), - num_random_bitstrings=5, - readout_repetitions=100, ) def test_zero_circuit_repetitions(): """Test that the circuit repetitions are zero.""" q = cirq.LineQubit(0) - circuit = cirq.Circuit(cirq.H(q), cirq.measure(q)) + with pytest.raises(ValueError, match="Must provide non-zero circuit_repetitions."): - run_shuffled_with_readout_benchmarking( - [circuit], - cirq.ZerosSampler(), - circuit_repetitions=0, - rng_or_seed=np.random.default_rng(456), - num_random_bitstrings=5, - readout_repetitions=100, + ReadoutBenchmarkingParams( + circuit_repetitions=0, num_random_bitstrings=5, readout_repetitions=100, qubits=q ) @@ -428,32 +408,27 @@ def test_mismatch_circuit_repetitions(): """Test that the number of circuit repetitions don't match the number of input circuits.""" q = cirq.LineQubit(0) circuit = cirq.Circuit(cirq.H(q), cirq.measure(q)) + readout_benchmarking_params = ReadoutBenchmarkingParams( + circuit_repetitions=[10, 20], num_random_bitstrings=5, readout_repetitions=100, qubits=q + ) with pytest.raises( ValueError, match="Number of circuit_repetitions must match the number of" + " input circuits.", ): - run_shuffled_with_readout_benchmarking( - [circuit], + run_shuffled_circuits_with_readout_benchmarking( cirq.ZerosSampler(), - circuit_repetitions=[10, 20], + [circuit], + readout_benchmarking_params, rng_or_seed=np.random.default_rng(456), - num_random_bitstrings=5, - readout_repetitions=100, ) def test_zero_num_random_bitstrings(): """Test that the number of random bitstrings is smaller than zero.""" q = cirq.LineQubit(0) - circuit = cirq.Circuit(cirq.H(q), cirq.measure(q)) with pytest.raises(ValueError, match="Must provide zero or more num_random_bitstrings."): - run_shuffled_with_readout_benchmarking( - [circuit], - cirq.ZerosSampler(), - circuit_repetitions=10, - rng_or_seed=np.random.default_rng(456), - num_random_bitstrings=-1, - readout_repetitions=100, + ReadoutBenchmarkingParams( + circuit_repetitions=10, num_random_bitstrings=-1, readout_repetitions=100, qubits=q ) @@ -464,13 +439,8 @@ def test_zero_readout_repetitions(): with pytest.raises( ValueError, match="Must provide non-zero readout_repetitions for readout" + " calibration." ): - run_shuffled_with_readout_benchmarking( - [circuit], - cirq.ZerosSampler(), - circuit_repetitions=10, - rng_or_seed=np.random.default_rng(456), - num_random_bitstrings=5, - readout_repetitions=0, + ReadoutBenchmarkingParams( + circuit_repetitions=10, num_random_bitstrings=5, readout_repetitions=0, qubits=q ) @@ -478,14 +448,15 @@ def test_rng_type_mismatch(): """Test that the rng is not a numpy random generator or a seed.""" q = cirq.LineQubit(0) circuit = cirq.Circuit(cirq.H(q), cirq.measure(q)) + readout_benchmarking_params = ReadoutBenchmarkingParams( + circuit_repetitions=10, num_random_bitstrings=5, readout_repetitions=100, qubits=q + ) with pytest.raises(ValueError, match="Must provide a numpy random generator or a seed"): - run_shuffled_with_readout_benchmarking( - [circuit], + run_shuffled_circuits_with_readout_benchmarking( cirq.ZerosSampler(), - circuit_repetitions=10, + [circuit], + readout_benchmarking_params, rng_or_seed="not a random generator or seed", - num_random_bitstrings=5, - readout_repetitions=100, ) @@ -493,13 +464,14 @@ def test_empty_sweep_params(): """Test that the sweep params are empty.""" q = cirq.LineQubit(5) circuit = cirq.Circuit(cirq.H(q)) + readout_benchmarking_params = ReadoutBenchmarkingParams( + circuit_repetitions=10, num_random_bitstrings=5, readout_repetitions=100, qubits=q + ) with pytest.raises(ValueError, match="Sweep parameters must not be empty."): run_sweep_with_readout_benchmarking( + cirq.ZerosSampler(), [circuit], [], - cirq.ZerosSampler(), - circuit_repetitions=10, + readout_benchmarking_params, rng_or_seed=np.random.default_rng(456), - num_random_bitstrings=5, - readout_repetitions=100, ) From 9d56e006e1c54136850e99c3aa09bfac8ea95c1a Mon Sep 17 00:00:00 2001 From: ddddddanni Date: Tue, 1 Jul 2025 23:29:44 -0700 Subject: [PATCH 3/3] Fix tests and lint --- ...ing_measurement_with_readout_mitigation.py | 19 +++++---- ...ffle_circuits_with_readout_benchmarking.py | 40 +++++++++---------- ...circuits_with_readout_benchmarking_test.py | 10 ++--- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/cirq-core/cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation.py b/cirq-core/cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation.py index 0928db748f3..820ec6b1ee9 100644 --- a/cirq-core/cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation.py +++ b/cirq-core/cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation.py @@ -24,7 +24,10 @@ import numpy as np from cirq import circuits, ops, work -from cirq.contrib.shuffle_circuits import run_shuffled_with_readout_benchmarking +from cirq.contrib.shuffle_circuits import run_shuffled_circuits_with_readout_benchmarking +from cirq.contrib.shuffle_circuits.shuffle_circuits_with_readout_benchmarking import ( + ReadoutBenchmarkingParams, +) from cirq.experiments.readout_confusion_matrix import TensoredConfusionMatrices if TYPE_CHECKING: @@ -473,14 +476,16 @@ def measure_pauli_strings( pauli_measurement_circuits.extend(basis_change_circuits) # Run shuffled benchmarking for readout calibration - circuits_results, calibration_results = run_shuffled_with_readout_benchmarking( - input_circuits=pauli_measurement_circuits, + circuits_results, calibration_results = run_shuffled_circuits_with_readout_benchmarking( sampler=sampler, - circuit_repetitions=pauli_repetitions, + input_circuits=pauli_measurement_circuits, + parameters=ReadoutBenchmarkingParams( + circuit_repetitions=pauli_repetitions, + num_random_bitstrings=num_random_bitstrings, + readout_repetitions=readout_repetitions, + qubits=[list(qubits) for qubits in qubits_list], + ), 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 diff --git a/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py b/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py index 0056dc39082..66e0c5ddf34 100644 --- a/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py +++ b/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py @@ -17,15 +17,15 @@ from __future__ import annotations import time -from typing import Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union +from typing import Optional, Sequence, TYPE_CHECKING import attrs import numpy as np import sympy from cirq import circuits, ops, protocols, study, work -from cirq.experiments import SingleQubitReadoutCalibrationResult from cirq._compat import deprecated +from cirq.experiments import SingleQubitReadoutCalibrationResult if TYPE_CHECKING: from cirq.study import ResultDict @@ -45,10 +45,10 @@ class ReadoutBenchmarkingParams: of qubits. """ - circuit_repetitions: Union[int, list[int]] + circuit_repetitions: int | list[int] num_random_bitstrings: int = 100 readout_repetitions: int = 1000 - qubits: Optional[Union[Sequence[ops.Qid], Sequence[Sequence[ops.Qid]]]] = None + qubits: Optional[Sequence[ops.Qid] | Sequence[Sequence[ops.Qid]]] = None def __attrs_post_init__(self): _validate_benchmarking_setup( @@ -57,7 +57,7 @@ def __attrs_post_init__(self): def _validate_benchmarking_setup( - circuit_repetitions: Union[int, list[int]], num_random_bitstrings: int, readout_repetitions: int + circuit_repetitions: int | list[int], num_random_bitstrings: int, readout_repetitions: int ): # Check circuit_repetitions if isinstance(circuit_repetitions, int): @@ -75,8 +75,8 @@ def _validate_benchmarking_setup( def _validate_experiment_input( input_circuits: Sequence[circuits.Circuit], - circuit_repetitions: Union[int, list[int]], - rng_or_seed: Union[np.random.Generator, int], + circuit_repetitions: int | list[int], + rng_or_seed: np.random.Generator | int, ): if not input_circuits: raise ValueError("Input circuits must not be empty.") @@ -99,8 +99,8 @@ def _validate_experiment_input( def _validate_experiment_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], + circuit_repetitions: int | list[int], + rng_or_seed: np.random.Generator | int, ): """Validates the input for the run_sweep_with_readout_benchmarking function.""" if not sweep_params: @@ -164,10 +164,10 @@ def _generate_parameterized_readout_calibration_circuit_with_sweep( def _generate_all_readout_calibration_circuits( num_random_bitstrings: int, - qubits_to_measure: List[List[ops.Qid]], + qubits_to_measure: list[list[ops.Qid]], is_sweep: bool, rng: np.random.Generator, -) -> Tuple[List[circuits.Circuit], List[np.ndarray], List[study.Sweepable]]: +) -> 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] = [] @@ -199,11 +199,11 @@ def _generate_all_readout_calibration_circuits( def _determine_qubits_to_measure( input_circuits: Sequence[circuits.Circuit], - qubits: Optional[Union[Sequence[ops.Qid], Sequence[Sequence[ops.Qid]]]], -) -> List[List[ops.Qid]]: + qubits: Optional[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]] = [] + qubits_to_measure: list[list[ops.Qid]] = [] if qubits is None: qubits_to_measure = [ sorted(set(q for circuit in input_circuits for q in circuit.all_qubits())) @@ -229,7 +229,7 @@ def _shuffle_circuits( def _analyze_readout_results( - unshuffled_readout_measurements: Union[Sequence[ResultDict], Sequence[study.Result]], + unshuffled_readout_measurements: list[ResultDict] | list[study.Result], random_bitstrings: np.ndarray, readout_repetitions: int, qubits: list[ops.Qid], @@ -296,8 +296,8 @@ def run_shuffled_with_readout_benchmarking( rng_or_seed: 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[ResultDict], Dict[Tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult]]: + qubits: Optional[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: @@ -379,7 +379,7 @@ def run_shuffled_circuits_with_readout_benchmarking( input_circuits: list[circuits.Circuit], parameters: ReadoutBenchmarkingParams, rng_or_seed: np.random.Generator | int, -) -> tuple[Sequence[ResultDict], Dict[Tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult]]: +) -> tuple[Sequence[ResultDict], dict[tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult]]: """Run the circuits in a shuffled order with readout error benchmarking. Args: @@ -459,9 +459,9 @@ def run_sweep_with_readout_benchmarking( input_circuits: list[circuits.Circuit], sweep_params: Sequence[study.Sweepable], parameters: ReadoutBenchmarkingParams, - rng_or_seed: Union[np.random.Generator, int], + rng_or_seed: np.random.Generator | int, ) -> tuple[ - Sequence[Sequence[study.Result]], Dict[Tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult] + Sequence[Sequence[study.Result]], dict[tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult] ]: """Run the sweep circuits with readout error benchmarking (no shuffling). Args: diff --git a/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py b/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py index 958aa9062b4..ccdb9ecfcb3 100644 --- a/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py +++ b/cirq-core/cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py @@ -17,16 +17,15 @@ import itertools from typing import Sequence -import cirq.contrib.shuffle_circuits import numpy as np import pytest import sympy import cirq +import cirq.contrib.shuffle_circuits from cirq.contrib.shuffle_circuits import ( run_shuffled_circuits_with_readout_benchmarking, run_sweep_with_readout_benchmarking, - run_shuffled_with_readout_benchmarking, ) from cirq.contrib.shuffle_circuits.shuffle_circuits_with_readout_benchmarking import ( ReadoutBenchmarkingParams, @@ -196,7 +195,8 @@ def test_circuits_with_readout_benchmarking_errors_with_noise(mode: str): @pytest.mark.parametrize("mode", ["shuffled", "sweep"]) def test_circuits_with_readout_benchmarking_errors_with_noise_and_input_qubits(mode: str): - """Test shuffled/sweep circuits with readout benchmarking with noise from sampler and input qubits.""" + """Test shuffled/sweep circuits with readout benchmarking with + noise from sampler and input qubits.""" qubits = cirq.LineQubit.range(6) readout_qubits = qubits[:4] @@ -246,7 +246,8 @@ def test_circuits_with_readout_benchmarking_errors_with_noise_and_input_qubits(m @pytest.mark.parametrize("mode", ["shuffled", "sweep"]) def test_circuits_with_readout_benchmarking_errors_with_noise_and_lists_input_qubits(mode: str): - """Test shuffled/sweep circuits with readout benchmarking with noise from sampler and input qubits.""" + """Test shuffled/sweep circuits with readout benchmarking with noise + from sampler and input qubits.""" qubits_1 = cirq.LineQubit.range(3) qubits_2 = cirq.LineQubit.range(4) readout_qubits = [qubits_1, qubits_2] @@ -435,7 +436,6 @@ def test_zero_num_random_bitstrings(): def test_zero_readout_repetitions(): """Test that the readout repetitions is zero.""" q = cirq.LineQubit(0) - circuit = cirq.Circuit(cirq.H(q), cirq.measure(q)) with pytest.raises( ValueError, match="Must provide non-zero readout_repetitions for readout" + " calibration." ):