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 23e3ce47717..6c7fc1e6dc5 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 @@ -22,16 +22,17 @@ import attrs import numpy as np +import sympy import cirq.contrib.shuffle_circuits.shuffle_circuits_with_readout_benchmarking as sc_readout from cirq import circuits, ops, study, work from cirq.experiments.readout_confusion_matrix import TensoredConfusionMatrices +from cirq.study import ResultDict if TYPE_CHECKING: from cirq.experiments.single_qubit_readout_calibration import ( SingleQubitReadoutCalibrationResult, ) - from cirq.study import ResultDict @attrs.frozen @@ -188,7 +189,7 @@ def _validate_input( # Check pauli_repetitions is bigger than 0 if pauli_repetitions <= 0: - raise ValueError("Must provide non-zero pauli_repetitions.") + raise ValueError("Must provide positive pauli_repetitions.") # Check num_random_bitstrings is bigger than or equal to 0 if num_random_bitstrings < 0: @@ -196,7 +197,7 @@ def _validate_input( # Check readout_repetitions is bigger than 0 if readout_repetitions <= 0: - raise ValueError("Must provide non-zero readout_repetitions for readout calibration.") + raise ValueError("Must provide positive readout_repetitions for readout calibration.") def _normalize_input_paulis( @@ -240,6 +241,90 @@ 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. + + Args: + pauli_strings: A list of QWC Pauli strings. + qid_list: A list of qubits to apply the basis change on. + Returns: + A dictionary mapping parameter names to their values 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 + 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]]], + insert_strategy: circuits.InsertStrategy, +) -> 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 = circuits.Circuit( + input_circuit_unfrozen, + _pauli_strings_to_basis_change_ops(pauli_strings, qid_list), + ops.measure(*qid_list, key="result"), + strategy=insert_strategy, + ) + 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]]], + insert_strategy: circuits.InsertStrategy, +) -> 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)}") + + # Create phased gates and measurement operator + phased_gates = [ + ops.PhasedXPowGate(phase_exponent=(a - 1) / 2, exponent=b)(qubit) + for a, b, qubit in zip(phi_symbols, theta_symbols, qid_list) + ] + measurement_op = ops.M(*qid_list, key="result") + + parameterized_circuit = circuits.Circuit( + input_circuit.unfreeze(), phased_gates, measurement_op, strategy=insert_strategy + ) + 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. @@ -288,7 +373,7 @@ def _build_many_one_qubits_empty_confusion_matrix(qubits_length: int) -> list[np def _process_pauli_measurement_results( qubits: Sequence[ops.Qid], pauli_string_groups: list[list[ops.PauliString]], - circuit_results: list[ResultDict] | Sequence[study.Result], + circuit_results: Sequence[ResultDict] | Sequence[study.Result], calibration_results: dict[tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult], pauli_repetitions: int, timestamp: float, @@ -321,7 +406,7 @@ def _process_pauli_measurement_results( pauli_measurement_results: list[PauliStringMeasurementResult] = [] for pauli_group_index, circuit_result in enumerate(circuit_results): - measurement_results = circuit_result.measurements["m"] + measurement_results = circuit_result.measurements["result"] pauli_strs = pauli_string_groups[pauli_group_index] pauli_readout_qubits = _extract_readout_qubits(pauli_strs) @@ -403,6 +488,8 @@ def measure_pauli_strings( readout_repetitions: int, num_random_bitstrings: int, rng_or_seed: np.random.Generator | int, + use_sweep: bool = False, + insert_strategy: circuits.InsertStrategy = circuits.InsertStrategy.INLINE, ) -> list[CircuitToPauliStringsMeasurementResult]: """Measures expectation values of Pauli strings on given circuits with/without readout error mitigation. @@ -411,10 +498,11 @@ 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. + each Pauli string. Args: circuits_to_pauli: A dictionary mapping circuits to either: @@ -432,6 +520,10 @@ 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. + insert_strategy: The strategy for inserting measurement operations into the circuit. + Defaults to circuits.InsertStrategy.INLINE. Returns: A list of CircuitToPauliStringsMeasurementResult objects, where each object contains: @@ -460,49 +552,68 @@ 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 = 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: Sequence[ResultDict] | Sequence[Sequence[study.Result]] = [] + calibration_results: dict[tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult] = {} + + benchmarking_params = sc_readout.ReadoutBenchmarkingParams( + circuit_repetitions=pauli_repetitions, + 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, insert_strategy + ) - # Run shuffled benchmarking for readout calibration - circuits_results, calibration_results = ( - sc_readout.run_shuffled_circuits_with_readout_benchmarking( + # Run benchmarking using sweep for readout calibration + circuits_results, calibration_results = sc_readout.run_sweep_with_readout_benchmarking( sampler=sampler, input_circuits=pauli_measurement_circuits, - parameters=sc_readout.ReadoutBenchmarkingParams( - circuit_repetitions=pauli_repetitions, - num_random_bitstrings=num_random_bitstrings, - readout_repetitions=readout_repetitions, - ), + sweep_params=sweep_params, + parameters=benchmarking_params, rng_or_seed=rng_or_seed, qubits=[list(qubits) for qubits in qubits_list], ) - ) + + else: + pauli_measurement_circuits = _generate_basis_change_circuits( + normalized_circuits_to_pauli, insert_strategy + ) + + # Run shuffled benchmarking for readout calibration + circuits_results, calibration_results = ( + sc_readout.run_shuffled_circuits_with_readout_benchmarking( + sampler=sampler, + input_circuits=pauli_measurement_circuits, + parameters=benchmarking_params, + rng_or_seed=rng_or_seed, + qubits=[list(qubits) for qubits in qubits_list], + ) + ) # 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: Sequence[ResultDict] | Sequence[study.Result] = [] + if use_sweep: + circuits_results_for_group = cast(Sequence[Sequence[study.Result]], circuits_results)[i] + else: + circuits_results_for_group = cast(Sequence[ResultDict], 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(), @@ -514,5 +625,4 @@ def measure_pauli_strings( ) ) - circuit_result_index += len(pauli_string_groups) return results diff --git a/cirq-core/cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation_test.py b/cirq-core/cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation_test.py index ef3d63967c5..56f9d884f1f 100644 --- a/cirq-core/cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation_test.py +++ b/cirq-core/cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation_test.py @@ -23,10 +23,7 @@ import cirq from cirq.contrib.paulistring import measure_pauli_strings -from cirq.contrib.paulistring.pauli_string_measurement_with_readout_mitigation import ( - _process_pauli_measurement_results, -) -from cirq.experiments.single_qubit_readout_calibration import SingleQubitReadoutCalibrationResult +from cirq.experiments import SingleQubitReadoutCalibrationResult from cirq.experiments.single_qubit_readout_calibration_test import NoisySingleQubitReadoutSampler @@ -107,7 +104,8 @@ def _ideal_expectation_based_on_pauli_string( ) -def test_pauli_string_measurement_errors_no_noise() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_pauli_string_measurement_errors_no_noise(use_sweep: bool) -> None: """Test that the mitigated expectation is close to the ideal expectation based on the Pauli string""" @@ -119,55 +117,7 @@ def test_pauli_string_measurement_errors_no_noise() -> None: circuits_to_pauli[circuit] = [_generate_random_pauli_string(qubits) for _ in range(3)] circuits_with_pauli_expectations = measure_pauli_strings( - circuits_to_pauli, sampler, 1000, 1000, 1000, 1000 - ) - - for circuit_with_pauli_expectations in circuits_with_pauli_expectations: - assert isinstance(circuit_with_pauli_expectations.circuit, cirq.FrozenCircuit) - - expected_val_simulation = sampler.simulate( - circuit_with_pauli_expectations.circuit.unfreeze() - ) - final_state_vector = expected_val_simulation.final_state_vector - - for pauli_string_measurement_results in circuit_with_pauli_expectations.results: - # Since there is no noise, the mitigated and unmitigated expectations should be the same - assert np.isclose( - pauli_string_measurement_results.mitigated_expectation, - pauli_string_measurement_results.unmitigated_expectation, - ) - assert np.isclose( - pauli_string_measurement_results.mitigated_expectation, - _ideal_expectation_based_on_pauli_string( - pauli_string_measurement_results.pauli_string, final_state_vector - ), - atol=10 * pauli_string_measurement_results.mitigated_stddev, - ) - assert isinstance( - pauli_string_measurement_results.calibration_result, - SingleQubitReadoutCalibrationResult, - ) - assert pauli_string_measurement_results.calibration_result.zero_state_errors == { - q: 0 for q in pauli_string_measurement_results.pauli_string.qubits - } - assert pauli_string_measurement_results.calibration_result.one_state_errors == { - q: 0 for q in pauli_string_measurement_results.pauli_string.qubits - } - - -def test_pauli_string_measurement_errors_with_coefficient_no_noise() -> None: - """Test that the mitigated expectation is close to the ideal expectation - based on the Pauli string""" - - qubits = cirq.LineQubit.range(5) - circuit = cirq.FrozenCircuit(_create_ghz(5, qubits)) - sampler = cirq.Simulator() - - circuits_to_pauli: dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {} - circuits_to_pauli[circuit] = [_generate_random_pauli_string(qubits, True) for _ in range(3)] - - circuits_with_pauli_expectations = measure_pauli_strings( - circuits_to_pauli, sampler, 1000, 1000, 1000, 1000 + circuits_to_pauli, sampler, 1000, 1000, 1000, 1000, use_sweep ) for circuit_with_pauli_expectations in circuits_with_pauli_expectations: @@ -203,7 +153,8 @@ def test_pauli_string_measurement_errors_with_coefficient_no_noise() -> None: } -def test_group_pauli_string_measurement_errors_no_noise_with_coefficient() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_group_pauli_string_measurement_errors_no_noise_with_coefficient(use_sweep: bool) -> None: """Test that the mitigated expectation is close to the ideal expectation based on the group of Pauli strings""" @@ -214,14 +165,14 @@ def test_group_pauli_string_measurement_errors_no_noise_with_coefficient() -> No circuits_to_pauli: dict[cirq.FrozenCircuit, list[list[cirq.PauliString]]] = {} circuits_to_pauli[circuit] = [ _generate_qwc_paulis( - _generate_random_pauli_string(qubits, enable_coeff=True, allow_pauli_i=False), 100, True + _generate_random_pauli_string(qubits, enable_coeff=True, allow_pauli_i=False), 10, True ) for _ in range(3) ] circuits_to_pauli[circuit].append([cirq.PauliString({q: cirq.X for q in qubits})]) circuits_with_pauli_expectations = measure_pauli_strings( - circuits_to_pauli, sampler, 1000, 1000, 1000, 1000 + circuits_to_pauli, sampler, 1000, 1000, 1000, 500, use_sweep ) for circuit_with_pauli_expectations in circuits_with_pauli_expectations: @@ -257,19 +208,20 @@ def test_group_pauli_string_measurement_errors_no_noise_with_coefficient() -> No } -def test_pauli_string_measurement_errors_with_noise() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_pauli_string_measurement_errors_with_noise(use_sweep: bool) -> None: """Test that the mitigated expectation is close to the ideal expectation based on the Pauli string""" qubits = cirq.LineQubit.range(7) circuit = cirq.FrozenCircuit(_create_ghz(7, qubits)) - sampler = NoisySingleQubitReadoutSampler(p0=0.001, p1=0.005, seed=1234) + sampler = NoisySingleQubitReadoutSampler(p0=0.01, p1=0.005, seed=1234) simulator = cirq.Simulator() circuits_to_pauli: dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {} circuits_to_pauli[circuit] = [_generate_random_pauli_string(qubits) for _ in range(3)] circuits_with_pauli_expectations = measure_pauli_strings( - circuits_to_pauli, sampler, 1000, 1000, 1000, np.random.default_rng() + circuits_to_pauli, sampler, 1000, 1000, 1000, np.random.default_rng(), use_sweep ) for circuit_with_pauli_expectations in circuits_with_pauli_expectations: @@ -297,19 +249,20 @@ def test_pauli_string_measurement_errors_with_noise() -> None: for ( error ) in pauli_string_measurement_results.calibration_result.zero_state_errors.values(): - assert 0.0008 < error < 0.0012 + assert 0.008 < error < 0.012 for ( error ) in pauli_string_measurement_results.calibration_result.one_state_errors.values(): assert 0.0045 < error < 0.0055 -def test_group_pauli_string_measurement_errors_with_noise() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_group_pauli_string_measurement_errors_with_noise(use_sweep: bool) -> None: """Test that the mitigated expectation is close to the ideal expectation based on the group Pauli strings""" qubits = cirq.LineQubit.range(7) circuit = cirq.FrozenCircuit(_create_ghz(7, qubits)) - sampler = NoisySingleQubitReadoutSampler(p0=0.001, p1=0.005, seed=1234) + sampler = NoisySingleQubitReadoutSampler(p0=0.01, p1=0.005, seed=1234) simulator = cirq.Simulator() circuits_to_pauli: dict[cirq.FrozenCircuit, list[list[cirq.PauliString]]] = {} @@ -320,7 +273,7 @@ def test_group_pauli_string_measurement_errors_with_noise() -> None: ] circuits_with_pauli_expectations = measure_pauli_strings( - circuits_to_pauli, sampler, 800, 1000, 800, np.random.default_rng() + circuits_to_pauli, sampler, 1000, 1000, 1000, np.random.default_rng(), use_sweep ) for circuit_with_pauli_expectations in circuits_with_pauli_expectations: @@ -348,14 +301,15 @@ def test_group_pauli_string_measurement_errors_with_noise() -> None: for ( error ) in pauli_string_measurement_results.calibration_result.zero_state_errors.values(): - assert 0.0008 < error < 0.0012 + assert 0.008 < error < 0.012 for ( error ) in pauli_string_measurement_results.calibration_result.one_state_errors.values(): assert 0.0045 < error < 0.0055 -def test_many_circuits_input_measurement_with_noise() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_many_circuits_input_measurement_with_noise(use_sweep: bool) -> None: """Test that the mitigated expectation is close to the ideal expectation based on the Pauli string for multiple circuits""" qubits_1 = cirq.LineQubit.range(3) @@ -381,7 +335,7 @@ def test_many_circuits_input_measurement_with_noise() -> None: simulator = cirq.Simulator() circuits_with_pauli_expectations = measure_pauli_strings( - circuits_to_pauli, sampler, 1000, 1000, 1000, np.random.default_rng() + circuits_to_pauli, sampler, 1000, 1000, 1000, np.random.default_rng(), use_sweep ) for circuit_with_pauli_expectations in circuits_with_pauli_expectations: @@ -414,41 +368,12 @@ def test_many_circuits_input_measurement_with_noise() -> None: assert 0.0045 < error < 0.0055 -def test_allow_measurement_without_readout_mitigation() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_allow_group_pauli_measurement_without_readout_mitigation(use_sweep: bool) -> None: """Test that the function allows to measure without error mitigation""" qubits = cirq.LineQubit.range(7) circuit = cirq.FrozenCircuit(_create_ghz(7, qubits)) - sampler = NoisySingleQubitReadoutSampler(p0=0.001, p1=0.005, seed=1234) - - circuits_to_pauli: dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {} - circuits_to_pauli[circuit] = [ - _generate_random_pauli_string(qubits, True), - _generate_random_pauli_string(qubits), - _generate_random_pauli_string(qubits), - ] - - circuits_with_pauli_expectations = measure_pauli_strings( - circuits_to_pauli, sampler, 1000, 1000, 0, np.random.default_rng() - ) - - for circuit_with_pauli_expectations in circuits_with_pauli_expectations: - assert isinstance(circuit_with_pauli_expectations.circuit, cirq.FrozenCircuit) - - for pauli_string_measurement_results in circuit_with_pauli_expectations.results: - # Since there's no mitigation, the mitigated and unmitigated expectations - # should be the same - assert np.isclose( - pauli_string_measurement_results.mitigated_expectation, - pauli_string_measurement_results.unmitigated_expectation, - ) - assert pauli_string_measurement_results.calibration_result is None - - -def test_allow_group_pauli_measurement_without_readout_mitigation() -> None: - """Test that the function allows to measure without error mitigation""" - qubits = cirq.LineQubit.range(7) - circuit = cirq.FrozenCircuit(_create_ghz(7, qubits)) - sampler = NoisySingleQubitReadoutSampler(p0=0.001, p1=0.005, seed=1234) + sampler = NoisySingleQubitReadoutSampler(p0=0.01, p1=0.005, seed=1234) circuits_to_pauli: dict[cirq.FrozenCircuit, list[list[cirq.PauliString]]] = {} circuits_to_pauli[circuit] = [ @@ -458,7 +383,7 @@ def test_allow_group_pauli_measurement_without_readout_mitigation() -> None: ] circuits_with_pauli_expectations = measure_pauli_strings( - circuits_to_pauli, sampler, 100, 100, 0, np.random.default_rng() + circuits_to_pauli, sampler, 1000, 1000, 0, np.random.default_rng(), use_sweep ) for circuit_with_pauli_expectations in circuits_with_pauli_expectations: @@ -474,7 +399,13 @@ def test_allow_group_pauli_measurement_without_readout_mitigation() -> None: assert pauli_string_measurement_results.calibration_result is None -def test_many_circuits_with_coefficient() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +@pytest.mark.parametrize( + "insert_strategy", [cirq.InsertStrategy.INLINE, cirq.InsertStrategy.EARLIEST] +) +def test_many_circuits_with_coefficient( + use_sweep: bool, insert_strategy: cirq.InsertStrategy +) -> None: """Test that the mitigated expectation is close to the ideal expectation based on the Pauli string for multiple circuits""" qubits_1 = cirq.LineQubit.range(3) @@ -500,7 +431,14 @@ def test_many_circuits_with_coefficient() -> None: simulator = cirq.Simulator() circuits_with_pauli_expectations = measure_pauli_strings( - circuits_to_pauli, sampler, 1000, 1000, 1000, np.random.default_rng() + circuits_to_pauli, + sampler, + 1000, + 1000, + 1000, + np.random.default_rng(), + use_sweep, + insert_strategy, ) for circuit_with_pauli_expectations in circuits_with_pauli_expectations: @@ -533,7 +471,8 @@ def test_many_circuits_with_coefficient() -> None: assert 0.0045 < error < 0.0055 -def test_many_group_pauli_in_circuits_with_coefficient() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_many_group_pauli_in_circuits_with_coefficient(use_sweep: bool) -> None: """Test that the mitigated expectation is close to the ideal expectation based on the Pauli string for multiple circuits""" qubits_1 = cirq.LineQubit.range(3) @@ -571,7 +510,7 @@ def test_many_group_pauli_in_circuits_with_coefficient() -> None: simulator = cirq.Simulator() circuits_with_pauli_expectations = measure_pauli_strings( - circuits_to_pauli, sampler, 1000, 1000, 1000, np.random.default_rng() + circuits_to_pauli, sampler, 1000, 1000, 1000, np.random.default_rng(), use_sweep ) for circuit_with_pauli_expectations in circuits_with_pauli_expectations: @@ -734,7 +673,7 @@ def test_zero_pauli_repetitions() -> None: circuits_to_pauli: dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {} circuits_to_pauli[circuit] = [cirq.PauliString({q: cirq.X for q in qubits})] - with pytest.raises(ValueError, match="Must provide non-zero pauli_repetitions."): + with pytest.raises(ValueError, match="Must provide positive pauli_repetitions."): measure_pauli_strings( circuits_to_pauli, cirq.Simulator(), 0, 1000, 1000, np.random.default_rng() ) @@ -763,7 +702,7 @@ def test_zero_readout_repetitions() -> None: circuits_to_pauli: dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {} circuits_to_pauli[circuit] = [cirq.PauliString({q: cirq.X for q in qubits})] with pytest.raises( - ValueError, match="Must provide non-zero readout_repetitions for readout" + " calibration." + ValueError, match="Must provide positive readout_repetitions for readout" + " calibration." ): measure_pauli_strings( circuits_to_pauli, cirq.Simulator(), 1000, 0, 1000, np.random.default_rng() @@ -798,7 +737,7 @@ def test_pauli_type_mismatch() -> None: " ops.PauliStrings. Got instead.", ): measure_pauli_strings( - circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, "test" # type: ignore[arg-type] + circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, 1 # type: ignore[arg-type] ) @@ -869,34 +808,3 @@ def test_group_paulis_type_mismatch() -> None: measure_pauli_strings( circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, np.random.default_rng() ) - - -def test_process_pauli_measurement_results_raises_error_on_missing_calibration() -> None: - """Test that the function raises an error if the calibration result is missing.""" - qubits: Sequence[cirq.Qid] = cirq.LineQubit.range(5) - - measurement_op = cirq.measure(*qubits, key='m') - test_circuits: list[cirq.Circuit] = [_create_ghz(5, qubits) + measurement_op for _ in range(3)] - - pauli_strings = [_generate_random_pauli_string(qubits, True) for _ in range(3)] - sampler = cirq.Simulator() - - circuit_results = sampler.run_batch(test_circuits, repetitions=1000) - - pauli_strings_qubits = sorted( - set(itertools.chain.from_iterable(ps.qubits for ps in pauli_strings)) - ) - empty_calibration_result_dict = {tuple(pauli_strings_qubits): None} - - with pytest.raises( - ValueError, - match="Readout mitigation is enabled, but no calibration result was found for qubits", - ): - _process_pauli_measurement_results( - qubits, - [pauli_strings], - circuit_results[0], - empty_calibration_result_dict, # type: ignore[arg-type] - 1000, - 1.0, - ) 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 391b26d2021..dac7056f8c6 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 @@ -105,7 +105,7 @@ def _generate_readout_calibration_circuits( readout_calibration_circuits.append( circuits.Circuit( [bit_to_gate[bit](qubit) for bit, qubit in zip(bitstr, qubits)] - + [ops.M(qubits, key="m")] + + [ops.M(qubits, key="result")] ) ) return readout_calibration_circuits, random_bitstrings @@ -137,11 +137,12 @@ def _generate_parameterized_readout_calibration_circuit_with_sweep( 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") + [ops.X(qubit) ** exp for exp, qubit in zip(exp_symbols, qubits)], + ops.M(*qubits, key="result"), ) sweep_params = [] for bitstr in random_bitstrings: - sweep_params.append({exp: bit for exp, bit in zip(exp_symbols, bitstr)}) + sweep_params.append({str(exp): bit for exp, bit in zip(exp_symbols, bitstr)}) return parameterized_readout_calibration_circuit, sweep_params, random_bitstrings