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..d96eedd3b97 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,6 +22,7 @@ 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 @@ -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="m"), + 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="m") + + 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, @@ -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 = circuits_results[i] + else: + circuits_results_for_group = circuits_results[ + circuit_result_index : circuit_result_index + len(pauli_string_groups) + ] + circuit_result_index += len(pauli_string_groups) + pauli_measurement_results = _process_pauli_measurement_results( list(qubits_in_circuit), pauli_string_groups, - circuits_results[ - circuit_result_index : circuit_result_index + len(pauli_string_groups) - ], + circuits_results_for_group, calibration_results, pauli_repetitions, time.time(), @@ -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 6c42d317293..bba229ba8cc 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,7 +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 + circuits_to_pauli, sampler, 1000, 1000, 1000, 1000, use_sweep ) for circuit_with_pauli_expectations in circuits_with_pauli_expectations: @@ -155,7 +153,8 @@ def test_pauli_string_measurement_errors_no_noise() -> None: } -def test_pauli_string_measurement_errors_with_coefficient_no_noise() -> None: +@pytest.mark.parametrize("use_sweep", [True]) +def test_pauli_string_measurement_errors_with_coefficient_no_noise(use_sweep: bool) -> None: """Test that the mitigated expectation is close to the ideal expectation based on the Pauli string""" @@ -167,7 +166,7 @@ def test_pauli_string_measurement_errors_with_coefficient_no_noise() -> None: 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 +202,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 +214,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, 100, 100, 100, 100 + circuits_to_pauli, sampler, 1000, 1000, 1000, 500, use_sweep ) for circuit_with_pauli_expectations in circuits_with_pauli_expectations: @@ -257,19 +257,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.1, 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 +298,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.08 < error < 0.12 + 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.1, 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 +322,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 +350,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.08 < error < 0.12 + 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 +384,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,11 +417,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_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.1, p1=0.005, seed=1234) + sampler = NoisySingleQubitReadoutSampler(p0=0.01, p1=0.005, seed=1234) circuits_to_pauli: dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {} circuits_to_pauli[circuit] = [ @@ -428,7 +432,7 @@ def test_allow_measurement_without_readout_mitigation() -> None: ] circuits_with_pauli_expectations = measure_pauli_strings( - circuits_to_pauli, sampler, 1000, 1000, 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: @@ -444,11 +448,12 @@ def test_allow_measurement_without_readout_mitigation() -> None: assert pauli_string_measurement_results.calibration_result is None -def test_allow_group_pauli_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.1, 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 +463,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 +479,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 +511,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 +551,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 +590,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: @@ -604,7 +623,8 @@ def test_many_group_pauli_in_circuits_with_coefficient() -> None: assert 0.0045 < error < 0.0055 -def test_coefficient_not_real_number() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_coefficient_not_real_number(use_sweep: bool) -> None: """Test that the coefficient of input pauli string is not real. Should return error in this case""" qubits_1 = cirq.LineQubit.range(3) @@ -624,11 +644,18 @@ def test_coefficient_not_real_number() -> None: "non-Hermitian PauliString. Coefficient must be real.", ): measure_pauli_strings( - circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, np.random.default_rng() + circuits_to_pauli, + cirq.Simulator(), + 1000, + 1000, + 1000, + np.random.default_rng(), + use_sweep, ) -def test_empty_input_circuits_to_pauli_mapping() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_empty_input_circuits_to_pauli_mapping(use_sweep: bool) -> None: """Test that the input circuits are empty.""" with pytest.raises(ValueError, match="Input circuits must not be empty."): @@ -639,10 +666,12 @@ def test_empty_input_circuits_to_pauli_mapping() -> None: 1000, 1000, np.random.default_rng(), + use_sweep, ) -def test_invalid_input_circuit_type() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_invalid_input_circuit_type(use_sweep: bool) -> None: """Test that the input circuit type is not frozen circuit""" qubits = cirq.LineQubit.range(5) @@ -658,10 +687,12 @@ def test_invalid_input_circuit_type() -> None: 1000, 1000, np.random.default_rng(), + use_sweep, ) -def test_invalid_input_pauli_string_type() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_invalid_input_pauli_string_type(use_sweep: bool) -> None: """Test input circuit is not mapping to a paulistring""" qubits_1 = cirq.LineQubit.range(5) qubits_2 = [ @@ -691,10 +722,12 @@ def test_invalid_input_pauli_string_type() -> None: 1000, 1000, np.random.default_rng(), + use_sweep, ) -def test_all_pauli_strings_are_pauli_i() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_all_pauli_strings_are_pauli_i(use_sweep: bool) -> None: """Test that all input pauli are pauli I""" qubits_1 = cirq.LineQubit.range(5) qubits_2 = [ @@ -722,11 +755,18 @@ def test_all_pauli_strings_are_pauli_i() -> None: "valid input Pauli strings.", ): measure_pauli_strings( - circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, np.random.default_rng() + circuits_to_pauli, + cirq.Simulator(), + 1000, + 1000, + 1000, + np.random.default_rng(), + use_sweep, ) -def test_zero_pauli_repetitions() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_zero_pauli_repetitions(use_sweep: bool) -> None: """Test that the pauli repetitions are zero.""" qubits = cirq.LineQubit.range(5) @@ -734,13 +774,14 @@ 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() + circuits_to_pauli, cirq.Simulator(), 0, 1000, 1000, np.random.default_rng(), use_sweep ) -def test_negative_num_random_bitstrings() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_negative_num_random_bitstrings(use_sweep: bool) -> None: """Test that the number of random bitstrings is smaller than zero.""" qubits = cirq.LineQubit.range(5) @@ -750,11 +791,12 @@ def test_negative_num_random_bitstrings() -> None: circuits_to_pauli[circuit] = [cirq.PauliString({q: cirq.X for q in qubits})] with pytest.raises(ValueError, match="Must provide zero or more num_random_bitstrings."): measure_pauli_strings( - circuits_to_pauli, cirq.Simulator(), 1000, 1000, -1, np.random.default_rng() + circuits_to_pauli, cirq.Simulator(), 1000, 1000, -1, np.random.default_rng(), use_sweep ) -def test_zero_readout_repetitions() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_zero_readout_repetitions(use_sweep: bool) -> None: """Test that the readout repetitions is zero.""" qubits = cirq.LineQubit.range(5) @@ -763,14 +805,15 @@ 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() + circuits_to_pauli, cirq.Simulator(), 1000, 0, 1000, np.random.default_rng(), use_sweep ) -def test_rng_type_mismatch() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_rng_type_mismatch(use_sweep: bool) -> None: """Test that the rng is not a numpy random generator or a seed.""" qubits = cirq.LineQubit.range(5) @@ -780,11 +823,18 @@ def test_rng_type_mismatch() -> None: circuits_to_pauli[circuit] = [cirq.PauliString({q: cirq.X for q in qubits})] with pytest.raises(ValueError, match="Must provide a numpy random generator or a seed"): measure_pauli_strings( - circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, "test" # type: ignore[arg-type] + circuits_to_pauli, + cirq.Simulator(), + 1000, + 1000, + 1000, + "test", # type: ignore[arg-type] + use_sweep, ) -def test_pauli_type_mismatch() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_pauli_type_mismatch(use_sweep: bool) -> None: """Test that the input paulis are not a sequence of PauliStrings.""" qubits = cirq.LineQubit.range(5) @@ -798,11 +848,18 @@ 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, # type: ignore[arg-type] + cirq.Simulator(), + 1000, + 1000, + 1000, + 1, + use_sweep, ) -def test_group_paulis_are_not_qwc() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_group_paulis_are_not_qwc(use_sweep: bool) -> None: """Test that the group paulis are not qwc.""" qubits = cirq.LineQubit.range(5) @@ -817,11 +874,18 @@ def test_group_paulis_are_not_qwc() -> None: ValueError, match="The group of Pauli strings are not Qubit-Wise Commuting with each other." ): measure_pauli_strings( - circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, np.random.default_rng() + circuits_to_pauli, + cirq.Simulator(), + 1000, + 1000, + 1000, + np.random.default_rng(), + use_sweep, ) -def test_empty_group_paulis_not_allowed() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_empty_group_paulis_not_allowed(use_sweep: bool) -> None: """Test that the group paulis are empty""" qubits = cirq.LineQubit.range(5) @@ -831,11 +895,18 @@ def test_empty_group_paulis_not_allowed() -> None: circuits_to_pauli[circuit] = [[]] # type: ignore with pytest.raises(ValueError, match="Empty group of Pauli strings is not allowed"): measure_pauli_strings( - circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, np.random.default_rng() + circuits_to_pauli, + cirq.Simulator(), + 1000, + 1000, + 1000, + np.random.default_rng(), + use_sweep, ) -def test_group_paulis_type_mismatch() -> None: +@pytest.mark.parametrize("use_sweep", [True, False]) +def test_group_paulis_type_mismatch(use_sweep: bool) -> None: """Test that the group paulis type is not correct""" qubits_1 = cirq.LineQubit.range(3) qubits_2 = [ @@ -867,36 +938,11 @@ def test_group_paulis_type_mismatch() -> None: "but found .", ): 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] + circuits_to_pauli, + cirq.Simulator(), 1000, - 1.0, + 1000, + 1000, + np.random.default_rng(), + use_sweep, )