From 529c5da6f71e0dc7430b061e57274d33266fc742 Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Fri, 9 Aug 2024 11:28:10 +0200 Subject: [PATCH 1/9] Modified phase estimators to work with V2 primitives and ISA circuits --- .../hamiltonian_phase_estimation.py | 10 +- qiskit_algorithms/phase_estimators/ipe.py | 20 +- .../phase_estimators/phase_estimation.py | 23 +- .../phase_estimators/phase_estimator.py | 6 +- test/test_phase_estimator.py | 214 +++++++++++------- 5 files changed, 169 insertions(+), 104 deletions(-) diff --git a/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py index bfb36e9a..94f30f96 100644 --- a/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2020, 2023. +# (C) Copyright IBM 2020, 2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -17,7 +17,8 @@ from qiskit import QuantumCircuit from qiskit.circuit.library import PauliEvolutionGate -from qiskit.primitives import BaseSampler +from qiskit.passmanager import BasePassManager +from qiskit.primitives import BaseSamplerV2 from qiskit.quantum_info import SparsePauliOp, Statevector, Pauli from qiskit.synthesis import EvolutionSynthesis @@ -83,17 +84,20 @@ class HamiltonianPhaseEstimation: def __init__( self, num_evaluation_qubits: int, - sampler: BaseSampler | None = None, + sampler: BaseSamplerV2 | None = None, + pass_manager: BasePassManager | None = None, ) -> None: r""" Args: num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will be estimated as a binary string with this many bits. sampler: The sampler primitive on which the circuit will be sampled. + pass_manager: A pass manager to use to transpile the circuits. """ self._phase_estimation = PhaseEstimation( num_evaluation_qubits=num_evaluation_qubits, sampler=sampler, + pass_manager=pass_manager, ) def _get_scale(self, hamiltonian, bound=None) -> PhaseEstimationScale: diff --git a/qiskit_algorithms/phase_estimators/ipe.py b/qiskit_algorithms/phase_estimators/ipe.py index 7c797182..d1485d6c 100644 --- a/qiskit_algorithms/phase_estimators/ipe.py +++ b/qiskit_algorithms/phase_estimators/ipe.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2021, 2023. +# (C) Copyright IBM 2021, 2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -19,7 +19,8 @@ from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.circuit.classicalregister import ClassicalRegister -from qiskit.primitives import BaseSampler +from qiskit.passmanager import BasePassManager +from qiskit.primitives import BaseSamplerV2 from qiskit_algorithms.exceptions import AlgorithmError @@ -40,12 +41,14 @@ class IterativePhaseEstimation(PhaseEstimator): def __init__( self, num_iterations: int, - sampler: BaseSampler | None = None, + sampler: BaseSamplerV2 | None = None, + pass_manager: BasePassManager | None = None, ) -> None: r""" Args: num_iterations: The number of iterations (rounds) of the phase estimation to run. sampler: The sampler primitive on which the circuit will be sampled. + pass_manager: A pass manager to use to transpile the circuits. Raises: ValueError: if num_iterations is not greater than zero. @@ -58,6 +61,7 @@ def __init__( raise ValueError("`num_iterations` must be greater than zero.") self._num_iterations = num_iterations self._sampler = sampler + self._pass_manager = pass_manager def construct_circuit( self, @@ -125,9 +129,17 @@ def _estimate_phase_iteratively(self, unitary, state_preparation): qc = self.construct_circuit( unitary, state_preparation, k, -2 * numpy.pi * omega_coef, True ) + + if self._pass_manager is not None: + qc = self._pass_manager.run(qc) + try: sampler_job = self._sampler.run([qc]) - result = sampler_job.result().quasi_dists[0] + result = sampler_job.result()[0].data.c + result = { + label: value / result.num_shots + for label, value in result.get_int_counts().items() + } except Exception as exc: raise AlgorithmError("The primitive job failed!") from exc x = 1 if result.get(1, 0) > result.get(0, 0) else 0 diff --git a/qiskit_algorithms/phase_estimators/phase_estimation.py b/qiskit_algorithms/phase_estimators/phase_estimation.py index bf84b736..efb9c31d 100644 --- a/qiskit_algorithms/phase_estimators/phase_estimation.py +++ b/qiskit_algorithms/phase_estimators/phase_estimation.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2020, 2023. +# (C) Copyright IBM 2020, 2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -20,7 +20,8 @@ from qiskit import circuit from qiskit.circuit import QuantumCircuit from qiskit.circuit.classicalregister import ClassicalRegister -from qiskit.primitives import BaseSampler +from qiskit.passmanager import BasePassManager +from qiskit.primitives import BaseSamplerV2 from qiskit.result import Result from qiskit_algorithms.exceptions import AlgorithmError @@ -82,13 +83,15 @@ class PhaseEstimation(PhaseEstimator): def __init__( self, num_evaluation_qubits: int, - sampler: BaseSampler | None = None, + sampler: BaseSamplerV2 | None = None, + pass_manager: BasePassManager | None = None, ) -> None: r""" Args: num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will be estimated as a binary string with this many bits. sampler: The sampler primitive on which the circuit will be sampled. + pass_manager: A pass manager to use to transpile the circuits. Raises: AlgorithmError: If a sampler is not provided @@ -101,6 +104,7 @@ def __init__( self._num_evaluation_qubits = num_evaluation_qubits self._sampler = sampler + self._pass_manager = pass_manager def construct_circuit( self, unitary: QuantumCircuit, state_preparation: QuantumCircuit | None = None @@ -189,6 +193,9 @@ def estimate_from_pe_circuit(self, pe_circuit: QuantumCircuit) -> PhaseEstimatio AlgorithmError: Primitive job failed. """ + if self._pass_manager is not None: + pe_circuit = self._pass_manager.run(pe_circuit) + self._add_measurement_if_required(pe_circuit) try: @@ -196,11 +203,13 @@ def estimate_from_pe_circuit(self, pe_circuit: QuantumCircuit) -> PhaseEstimatio circuit_result = circuit_job.result() except Exception as exc: raise AlgorithmError("The primitive job failed!") from exc - phases = circuit_result.quasi_dists[0] + phases = circuit_result[0].data.meas.get_counts() + # Ensure we still return the measurement strings in sorted order, which SamplerV2 doesn't + # guarantee + measurement_labels = sorted(phases.keys()) phases_bitstrings = {} - for key, phase in phases.items(): - bitstring_key = self._get_reversed_bitstring(self._num_evaluation_qubits, key) - phases_bitstrings[bitstring_key] = phase + for key in measurement_labels: + phases_bitstrings[key[::-1]] = phases[key] / circuit_result[0].data.meas.num_shots phases = phases_bitstrings return PhaseEstimationResult( diff --git a/qiskit_algorithms/phase_estimators/phase_estimator.py b/qiskit_algorithms/phase_estimators/phase_estimator.py index 1f0f5002..2a78f7f8 100644 --- a/qiskit_algorithms/phase_estimators/phase_estimator.py +++ b/qiskit_algorithms/phase_estimators/phase_estimator.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2020, 2023. +# (C) Copyright IBM 2020, 2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -38,10 +38,6 @@ def estimate( """Estimate the phase.""" raise NotImplementedError - @staticmethod - def _get_reversed_bitstring(length: int, number: int) -> str: - return f"{number:b}".zfill(length)[::-1] - class PhaseEstimatorResult(AlgorithmResult): """Phase Estimator Result.""" diff --git a/test/test_phase_estimator.py b/test/test_phase_estimator.py index 1b7ca114..4d19a97a 100644 --- a/test/test_phase_estimator.py +++ b/test/test_phase_estimator.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2018, 2023. +# (C) Copyright IBM 2018, 2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -11,22 +11,24 @@ # that they have been altered from the originals. """Test phase estimation""" - import unittest +from itertools import product from test import QiskitAlgorithmsTestCase -from ddt import ddt, data, unpack + import numpy as np -from qiskit.circuit.library import ZGate, XGate, HGate, IGate -from qiskit.quantum_info import Pauli, SparsePauliOp, Statevector, Operator -from qiskit.synthesis import MatrixExponential, SuzukiTrotter -from qiskit.primitives import Sampler +from ddt import ddt, data, unpack from qiskit import QuantumCircuit +from qiskit.circuit.library import HGate, XGate, IGate, ZGate +from qiskit.primitives import StatevectorSampler as Sampler +from qiskit.quantum_info import SparsePauliOp, Pauli, Statevector, Operator +from qiskit.synthesis import MatrixExponential, SuzukiTrotter +from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager -from qiskit_algorithms import PhaseEstimationScale -from qiskit_algorithms.phase_estimators import ( - PhaseEstimation, +from qiskit_algorithms import ( HamiltonianPhaseEstimation, IterativePhaseEstimation, + PhaseEstimation, + PhaseEstimationScale, ) @@ -34,6 +36,8 @@ class TestHamiltonianPhaseEstimation(QiskitAlgorithmsTestCase): """Tests for obtaining eigenvalues from phase estimation""" + pm = generate_preset_pass_manager(optimization_level=1, seed_transpiler=42) + # sampler tests def hamiltonian_pe_sampler( self, @@ -42,11 +46,12 @@ def hamiltonian_pe_sampler( num_evaluation_qubits=6, evolution=None, bound=None, + pass_manager=None, ): """Run HamiltonianPhaseEstimation and return result with all phases.""" - sampler = Sampler() + sampler = Sampler(default_shots=10_000, seed=42) phase_est = HamiltonianPhaseEstimation( - num_evaluation_qubits=num_evaluation_qubits, sampler=sampler + num_evaluation_qubits=num_evaluation_qubits, sampler=sampler, pass_manager=pass_manager ) result = phase_est.estimate( hamiltonian=hamiltonian, @@ -56,13 +61,16 @@ def hamiltonian_pe_sampler( ) return result - @data(MatrixExponential(), SuzukiTrotter(reps=4)) - def test_pauli_sum_1_sampler(self, evolution): + @data(*product((MatrixExponential(), SuzukiTrotter(reps=4)), (None, pm))) + @unpack + def test_pauli_sum_1_sampler(self, evolution, pass_manager): """Two eigenvalues from Pauli sum with X, Z""" hamiltonian = SparsePauliOp.from_list([("X", 0.5), ("Z", 1)]) state_preparation = QuantumCircuit(1).compose(HGate()) - result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=evolution) + result = self.hamiltonian_pe_sampler( + hamiltonian, state_preparation, evolution=evolution, pass_manager=pass_manager + ) phase_dict = result.filter_phases(0.162, as_float=True) phases = list(phase_dict.keys()) phases.sort() @@ -70,13 +78,16 @@ def test_pauli_sum_1_sampler(self, evolution): self.assertAlmostEqual(phases[0], -1.125, delta=0.001) self.assertAlmostEqual(phases[1], 1.125, delta=0.001) - @data(MatrixExponential(), SuzukiTrotter(reps=3)) - def test_pauli_sum_2_sampler(self, evolution): + @data(*product((MatrixExponential(), SuzukiTrotter(reps=3)), (None, pm))) + @unpack + def test_pauli_sum_2_sampler(self, evolution, pass_manager): """Two eigenvalues from Pauli sum with X, Y, Z""" hamiltonian = SparsePauliOp.from_list([("X", 0.5), ("Z", 1), ("Y", 1)]) state_preparation = None - result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=evolution) + result = self.hamiltonian_pe_sampler( + hamiltonian, state_preparation, evolution=evolution, pass_manager=pass_manager + ) phase_dict = result.filter_phases(0.1, as_float=True) phases = list(phase_dict.keys()) phases.sort() @@ -84,12 +95,15 @@ def test_pauli_sum_2_sampler(self, evolution): self.assertAlmostEqual(phases[0], -1.484, delta=0.001) self.assertAlmostEqual(phases[1], 1.484, delta=0.001) - def test_single_pauli_op_sampler(self): + @data(None, pm) + def test_single_pauli_op_sampler(self, pass_manager): """Two eigenvalues from Pauli sum with X, Y, Z""" hamiltonian = SparsePauliOp(Pauli("Z")) state_preparation = None - result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=None) + result = self.hamiltonian_pe_sampler( + hamiltonian, state_preparation, evolution=None, pass_manager=pass_manager + ) eigv = result.most_likely_eigenvalue with self.subTest("First eigenvalue"): self.assertAlmostEqual(eigv, 1.0, delta=0.001) @@ -102,10 +116,16 @@ def test_single_pauli_op_sampler(self): self.assertAlmostEqual(eigv, -0.98, delta=0.01) @data( - (Statevector(QuantumCircuit(2).compose(IGate()).compose(HGate()))), - (QuantumCircuit(2).compose(IGate()).compose(HGate())), + *product( + ( + (Statevector(QuantumCircuit(2).compose(IGate()).compose(HGate()))), + (QuantumCircuit(2).compose(IGate()).compose(HGate())), + ), + (None, pm), + ) ) - def test_H2_hamiltonian_sampler(self, state_preparation): + @unpack + def test_H2_hamiltonian_sampler(self, state_preparation, pass_manager): """Test H2 hamiltonian""" hamiltonian = SparsePauliOp.from_list( @@ -119,7 +139,9 @@ def test_H2_hamiltonian_sampler(self, state_preparation): ) evo = SuzukiTrotter(reps=4) - result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=evo) + result = self.hamiltonian_pe_sampler( + hamiltonian, state_preparation, evolution=evo, pass_manager=pass_manager + ) with self.subTest("Most likely eigenvalues"): self.assertAlmostEqual(result.most_likely_eigenvalue, -1.855, delta=0.001) with self.subTest("Most likely phase"): @@ -131,14 +153,15 @@ def test_H2_hamiltonian_sampler(self, state_preparation): self.assertAlmostEqual(phases[1], -1.2376, delta=0.001) self.assertAlmostEqual(phases[2], -0.8979, delta=0.001) - def test_matrix_evolution_sampler(self): + @data(None, pm) + def test_matrix_evolution_sampler(self, pass_manager): """1Q Hamiltonian with MatrixEvolution""" # hamiltonian = PauliSumOp(SparsePauliOp.from_list([("X", 0.5), ("Y", 0.6), ("I", 0.7)])) hamiltonian = SparsePauliOp.from_list([("X", 0.5), ("Y", 0.6), ("I", 0.7)]) state_preparation = None result = self.hamiltonian_pe_sampler( - hamiltonian, state_preparation, evolution=MatrixExponential() + hamiltonian, state_preparation, evolution=MatrixExponential(), pass_manager=pass_manager ) phase_dict = result.filter_phases(0.2, as_float=True) phases = sorted(phase_dict.keys()) @@ -150,6 +173,8 @@ def test_matrix_evolution_sampler(self): class TestPhaseEstimation(QiskitAlgorithmsTestCase): """Evolution tests.""" + pm = generate_preset_pass_manager(optimization_level=1, seed_transpiler=42) + # sampler tests def one_phase_sampler( self, @@ -158,21 +183,26 @@ def one_phase_sampler( phase_estimator=None, num_iterations=6, shots=None, + pass_manager=None, ): """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, `state_preparation`. Return the estimated phase as a value in :math:`[0,1)`. """ + if shots is not None: - options = {"shots": shots} + sampler = Sampler(default_shots=shots, seed=42) else: - options = {} - sampler = Sampler(options=options) + sampler = Sampler(seed=42) if phase_estimator is None: phase_estimator = IterativePhaseEstimation if phase_estimator == IterativePhaseEstimation: - p_est = IterativePhaseEstimation(num_iterations=num_iterations, sampler=sampler) + p_est = IterativePhaseEstimation( + num_iterations=num_iterations, sampler=sampler, pass_manager=pass_manager + ) elif phase_estimator == PhaseEstimation: - p_est = PhaseEstimation(num_evaluation_qubits=6, sampler=sampler) + p_est = PhaseEstimation( + num_evaluation_qubits=6, sampler=sampler, pass_manager=pass_manager + ) else: raise ValueError("Unrecognized phase_estimator") result = p_est.estimate(unitary=unitary_circuit, state_preparation=state_preparation) @@ -180,102 +210,111 @@ def one_phase_sampler( return phase @data( - (QuantumCircuit(1).compose(XGate()), 0.5, None, IterativePhaseEstimation), - (QuantumCircuit(1).compose(XGate()), 0.5, 1000, IterativePhaseEstimation), - (None, 0.0, 1000, IterativePhaseEstimation), - (QuantumCircuit(1).compose(XGate()), 0.5, 1000, PhaseEstimation), - (None, 0.0, 1000, PhaseEstimation), - (QuantumCircuit(1).compose(XGate()), 0.5, None, PhaseEstimation), + *product( + ((None, 0.0), (QuantumCircuit(1).compose(XGate()), 0.5)), + (None, 1000), + (IterativePhaseEstimation, PhaseEstimation), + (None, pm), + ) ) @unpack - def test_qpe_Z_sampler(self, state_preparation, expected_phase, shots, phase_estimator): + def test_qpe_Z_sampler( + self, state_preparation_and_expected_phase, shots, phase_estimator, pass_manager + ): """eigenproblem Z, |0> and |1>""" + state_preparation, expected_phase = state_preparation_and_expected_phase unitary_circuit = QuantumCircuit(1).compose(ZGate()) phase = self.one_phase_sampler( unitary_circuit, state_preparation=state_preparation, phase_estimator=phase_estimator, shots=shots, + pass_manager=pass_manager, ) self.assertEqual(phase, expected_phase) @data( - (QuantumCircuit(1).compose(HGate()), 0.0, IterativePhaseEstimation), - (QuantumCircuit(1).compose(HGate()).compose(ZGate()), 0.5, IterativePhaseEstimation), - (QuantumCircuit(1).compose(HGate()), 0.0, PhaseEstimation), - (QuantumCircuit(1).compose(HGate()).compose(ZGate()), 0.5, PhaseEstimation), + *product( + ( + (QuantumCircuit(1).compose(HGate()), 0.0), + (QuantumCircuit(1).compose(HGate()).compose(ZGate()), 0.5), + ), + (IterativePhaseEstimation, PhaseEstimation), + (None, pm), + ) ) @unpack - def test_qpe_X_plus_minus_sampler(self, state_preparation, expected_phase, phase_estimator): + def test_qpe_X_plus_minus_sampler( + self, state_preparation_and_expected_phase, phase_estimator, pass_manager + ): """eigenproblem X, (|+>, |->)""" + state_preparation, expected_phase = state_preparation_and_expected_phase unitary_circuit = QuantumCircuit(1).compose(XGate()) phase = self.one_phase_sampler( - unitary_circuit, - state_preparation, - phase_estimator, + unitary_circuit, state_preparation, phase_estimator, pass_manager=pass_manager ) self.assertEqual(phase, expected_phase) @data( - (QuantumCircuit(1).compose(XGate()), 0.125, IterativePhaseEstimation), - (QuantumCircuit(1).compose(IGate()), 0.875, IterativePhaseEstimation), - (QuantumCircuit(1).compose(XGate()), 0.125, PhaseEstimation), - (QuantumCircuit(1).compose(IGate()), 0.875, PhaseEstimation), + *product( + ( + (QuantumCircuit(1).compose(XGate()), 0.125), + (QuantumCircuit(1).compose(IGate()), 0.875), + ), + (IterativePhaseEstimation, PhaseEstimation), + (None, pm), + ) ) @unpack - def test_qpe_RZ_sampler(self, state_preparation, expected_phase, phase_estimator): + def test_qpe_RZ_sampler( + self, state_preparation_and_expected_phase, phase_estimator, pass_manager + ): """eigenproblem RZ, (|0>, |1>)""" + state_preparation, expected_phase = state_preparation_and_expected_phase alpha = np.pi / 2 unitary_circuit = QuantumCircuit(1) unitary_circuit.rz(alpha, 0) phase = self.one_phase_sampler( - unitary_circuit, - state_preparation, - phase_estimator, + unitary_circuit, state_preparation, phase_estimator, pass_manager=pass_manager ) self.assertEqual(phase, expected_phase) @data( - ( - QuantumCircuit(2).compose(XGate(), qubits=[0]).compose(XGate(), qubits=[1]), - 0.25, - IterativePhaseEstimation, - ), - ( - QuantumCircuit(2).compose(IGate(), qubits=[0]).compose(XGate(), qubits=[1]), - 0.125, - IterativePhaseEstimation, - ), - ( - QuantumCircuit(2).compose(XGate(), qubits=[0]).compose(XGate(), qubits=[1]), - 0.25, - PhaseEstimation, - ), - ( - QuantumCircuit(2).compose(IGate(), qubits=[0]).compose(XGate(), qubits=[1]), - 0.125, - PhaseEstimation, - ), + *product( + ( + (QuantumCircuit(2).compose(XGate(), qubits=[0]).compose(XGate(), qubits=[1]), 0.25), + ( + QuantumCircuit(2).compose(IGate(), qubits=[0]).compose(XGate(), qubits=[1]), + 0.125, + ), + ), + (IterativePhaseEstimation, PhaseEstimation), + (None, pm), + ) ) @unpack - def test_qpe_two_qubit_unitary(self, state_preparation, expected_phase, phase_estimator): + def test_qpe_two_qubit_unitary( + self, state_preparation_and_expected_phase, phase_estimator, pass_manager + ): """two qubit unitary T ^ T""" + state_preparation, expected_phase = state_preparation_and_expected_phase unitary_circuit = QuantumCircuit(2) unitary_circuit.t(0) unitary_circuit.t(1) phase = self.one_phase_sampler( - unitary_circuit, - state_preparation, - phase_estimator, + unitary_circuit, state_preparation, phase_estimator, pass_manager=pass_manager ) self.assertEqual(phase, expected_phase) - def test_check_num_iterations_sampler(self): + @data(None, pm) + def test_check_num_iterations_sampler(self, pass_manager): """test check for num_iterations greater than zero""" unitary_circuit = QuantumCircuit(1).compose(XGate()) state_preparation = None with self.assertRaises(ValueError): - self.one_phase_sampler(unitary_circuit, state_preparation, num_iterations=-1) + self.one_phase_sampler( + unitary_circuit, state_preparation, num_iterations=-1, pass_manager=pass_manager + ) def test_phase_estimation_scale_from_operator(self): """test that PhaseEstimationScale from_pauli_sum works with Operator""" @@ -291,11 +330,14 @@ def phase_estimation_sampler( state_preparation=None, num_evaluation_qubits=6, construct_circuit=False, + pass_manager=None, ): """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, `state_preparation`. Return all results """ - phase_est = PhaseEstimation(num_evaluation_qubits=num_evaluation_qubits, sampler=sampler) + phase_est = PhaseEstimation( + num_evaluation_qubits=num_evaluation_qubits, sampler=sampler, pass_manager=pass_manager + ) if construct_circuit: pe_circuit = phase_est.construct_circuit(unitary_circuit, state_preparation) result = phase_est.estimate_from_pe_circuit(pe_circuit) @@ -305,17 +347,19 @@ def phase_estimation_sampler( ) return result - @data(True, False) - def test_qpe_Zplus_sampler(self, construct_circuit): + @data(*product((True, False), (None, pm))) + @unpack + def test_qpe_Zplus_sampler(self, construct_circuit, pass_manager): """superposition eigenproblem Z, |+>""" unitary_circuit = QuantumCircuit(1).compose(ZGate()) state_preparation = QuantumCircuit(1).compose(HGate()) # prepare |+> - sampler = Sampler() + sampler = Sampler(default_shots=10_000, seed=42) result = self.phase_estimation_sampler( unitary_circuit, sampler, state_preparation, construct_circuit=construct_circuit, + pass_manager=pass_manager, ) phases = result.filter_phases(1e-15, as_float=True) @@ -323,7 +367,7 @@ def test_qpe_Zplus_sampler(self, construct_circuit): self.assertEqual(list(phases.keys()), [0.0, 0.5]) with self.subTest("test phases has correct probabilities"): - np.testing.assert_allclose(list(phases.values()), [0.5, 0.5]) + np.testing.assert_allclose(list(phases.values()), [0.5, 0.5], atol=1e-2, rtol=1e-2) with self.subTest("test bitstring representation"): phases = result.filter_phases(1e-15, as_float=False) From da404254f8bc4f250a916055bb671922e56016e8 Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Mon, 12 Aug 2024 20:26:28 +0200 Subject: [PATCH 2/9] Changed PassManager to more generic transpiler --- qiskit_algorithms/custom_types.py | 27 +++++++++++++++++ .../hamiltonian_phase_estimation.py | 13 ++++---- qiskit_algorithms/phase_estimators/ipe.py | 10 ++++--- .../phase_estimators/phase_estimation.py | 10 ++++--- test/test_phase_estimator.py | 30 ++++++++++++------- 5 files changed, 65 insertions(+), 25 deletions(-) create mode 100644 qiskit_algorithms/custom_types.py diff --git a/qiskit_algorithms/custom_types.py b/qiskit_algorithms/custom_types.py new file mode 100644 index 00000000..b865873e --- /dev/null +++ b/qiskit_algorithms/custom_types.py @@ -0,0 +1,27 @@ +# This code is part of a Qiskit project. +# +# (C) Copyright IBM 2024. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Types used by the qiskit-algorithms package.""" + +from typing import Any, Protocol, Union + +from qiskit import QuantumCircuit + +_Circuits = Union[list[QuantumCircuit], QuantumCircuit] + + +class Transpiler(Protocol): + """A Generic type to represent a transpiler.""" + + def run(self, circuits: _Circuits, **options: Any) -> _Circuits: + """Transpile a circuit or a list of quantum circuits.""" + pass diff --git a/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py index 94f30f96..058a884b 100644 --- a/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -14,17 +14,16 @@ from __future__ import annotations - from qiskit import QuantumCircuit from qiskit.circuit.library import PauliEvolutionGate -from qiskit.passmanager import BasePassManager from qiskit.primitives import BaseSamplerV2 from qiskit.quantum_info import SparsePauliOp, Statevector, Pauli from qiskit.synthesis import EvolutionSynthesis -from .phase_estimation import PhaseEstimation from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult +from .phase_estimation import PhaseEstimation from .phase_estimation_scale import PhaseEstimationScale +from ..custom_types import Transpiler class HamiltonianPhaseEstimation: @@ -85,19 +84,21 @@ def __init__( self, num_evaluation_qubits: int, sampler: BaseSamplerV2 | None = None, - pass_manager: BasePassManager | None = None, + transpiler: Transpiler | None = None, ) -> None: r""" Args: num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will be estimated as a binary string with this many bits. sampler: The sampler primitive on which the circuit will be sampled. - pass_manager: A pass manager to use to transpile the circuits. + transpiler: An optional object with a `run` method allowing to transpile the circuits + that are produced within this algorithm. If set to `None`, these won't be + transpiled. """ self._phase_estimation = PhaseEstimation( num_evaluation_qubits=num_evaluation_qubits, sampler=sampler, - pass_manager=pass_manager, + transpiler=transpiler, ) def _get_scale(self, hamiltonian, bound=None) -> PhaseEstimationScale: diff --git a/qiskit_algorithms/phase_estimators/ipe.py b/qiskit_algorithms/phase_estimators/ipe.py index d1485d6c..42b88e0b 100644 --- a/qiskit_algorithms/phase_estimators/ipe.py +++ b/qiskit_algorithms/phase_estimators/ipe.py @@ -19,13 +19,13 @@ from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.circuit.classicalregister import ClassicalRegister -from qiskit.passmanager import BasePassManager from qiskit.primitives import BaseSamplerV2 from qiskit_algorithms.exceptions import AlgorithmError from .phase_estimator import PhaseEstimator from .phase_estimator import PhaseEstimatorResult +from ..custom_types import Transpiler class IterativePhaseEstimation(PhaseEstimator): @@ -42,13 +42,15 @@ def __init__( self, num_iterations: int, sampler: BaseSamplerV2 | None = None, - pass_manager: BasePassManager | None = None, + transpiler: Transpiler | None = None, ) -> None: r""" Args: num_iterations: The number of iterations (rounds) of the phase estimation to run. sampler: The sampler primitive on which the circuit will be sampled. - pass_manager: A pass manager to use to transpile the circuits. + transpiler: An optional object with a `run` method allowing to transpile the circuits + that are produced within this algorithm. If set to `None`, these won't be + transpiled. Raises: ValueError: if num_iterations is not greater than zero. @@ -61,7 +63,7 @@ def __init__( raise ValueError("`num_iterations` must be greater than zero.") self._num_iterations = num_iterations self._sampler = sampler - self._pass_manager = pass_manager + self._pass_manager = transpiler def construct_circuit( self, diff --git a/qiskit_algorithms/phase_estimators/phase_estimation.py b/qiskit_algorithms/phase_estimators/phase_estimation.py index efb9c31d..930fed65 100644 --- a/qiskit_algorithms/phase_estimators/phase_estimation.py +++ b/qiskit_algorithms/phase_estimators/phase_estimation.py @@ -20,7 +20,6 @@ from qiskit import circuit from qiskit.circuit import QuantumCircuit from qiskit.circuit.classicalregister import ClassicalRegister -from qiskit.passmanager import BasePassManager from qiskit.primitives import BaseSamplerV2 from qiskit.result import Result @@ -28,6 +27,7 @@ from .phase_estimation_result import PhaseEstimationResult, _sort_phases from .phase_estimator import PhaseEstimator +from ..custom_types import Transpiler class PhaseEstimation(PhaseEstimator): @@ -84,14 +84,16 @@ def __init__( self, num_evaluation_qubits: int, sampler: BaseSamplerV2 | None = None, - pass_manager: BasePassManager | None = None, + transpiler: Transpiler | None = None, ) -> None: r""" Args: num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will be estimated as a binary string with this many bits. sampler: The sampler primitive on which the circuit will be sampled. - pass_manager: A pass manager to use to transpile the circuits. + transpiler: An optional object with a `run` method allowing to transpile the circuits + that are produced within this algorithm. If set to `None`, these won't be + transpiled. Raises: AlgorithmError: If a sampler is not provided @@ -104,7 +106,7 @@ def __init__( self._num_evaluation_qubits = num_evaluation_qubits self._sampler = sampler - self._pass_manager = pass_manager + self._pass_manager = transpiler def construct_circuit( self, unitary: QuantumCircuit, state_preparation: QuantumCircuit | None = None diff --git a/test/test_phase_estimator.py b/test/test_phase_estimator.py index 4d19a97a..ebed64fb 100644 --- a/test/test_phase_estimator.py +++ b/test/test_phase_estimator.py @@ -36,8 +36,6 @@ class TestHamiltonianPhaseEstimation(QiskitAlgorithmsTestCase): """Tests for obtaining eigenvalues from phase estimation""" - pm = generate_preset_pass_manager(optimization_level=1, seed_transpiler=42) - # sampler tests def hamiltonian_pe_sampler( self, @@ -51,7 +49,7 @@ def hamiltonian_pe_sampler( """Run HamiltonianPhaseEstimation and return result with all phases.""" sampler = Sampler(default_shots=10_000, seed=42) phase_est = HamiltonianPhaseEstimation( - num_evaluation_qubits=num_evaluation_qubits, sampler=sampler, pass_manager=pass_manager + num_evaluation_qubits=num_evaluation_qubits, sampler=sampler, transpiler=pass_manager ) result = phase_est.estimate( hamiltonian=hamiltonian, @@ -61,7 +59,12 @@ def hamiltonian_pe_sampler( ) return result - @data(*product((MatrixExponential(), SuzukiTrotter(reps=4)), (None, pm))) + @data( + *product( + (MatrixExponential(), SuzukiTrotter(reps=4)), + (None, generate_preset_pass_manager(optimization_level=1, seed_transpiler=42)), + ) + ) @unpack def test_pauli_sum_1_sampler(self, evolution, pass_manager): """Two eigenvalues from Pauli sum with X, Z""" @@ -78,7 +81,12 @@ def test_pauli_sum_1_sampler(self, evolution, pass_manager): self.assertAlmostEqual(phases[0], -1.125, delta=0.001) self.assertAlmostEqual(phases[1], 1.125, delta=0.001) - @data(*product((MatrixExponential(), SuzukiTrotter(reps=3)), (None, pm))) + @data( + *product( + (MatrixExponential(), SuzukiTrotter(reps=3)), + (None, generate_preset_pass_manager(optimization_level=1, seed_transpiler=42)), + ) + ) @unpack def test_pauli_sum_2_sampler(self, evolution, pass_manager): """Two eigenvalues from Pauli sum with X, Y, Z""" @@ -95,7 +103,7 @@ def test_pauli_sum_2_sampler(self, evolution, pass_manager): self.assertAlmostEqual(phases[0], -1.484, delta=0.001) self.assertAlmostEqual(phases[1], 1.484, delta=0.001) - @data(None, pm) + @data(None, generate_preset_pass_manager(optimization_level=1, seed_transpiler=42)) def test_single_pauli_op_sampler(self, pass_manager): """Two eigenvalues from Pauli sum with X, Y, Z""" hamiltonian = SparsePauliOp(Pauli("Z")) @@ -121,7 +129,7 @@ def test_single_pauli_op_sampler(self, pass_manager): (Statevector(QuantumCircuit(2).compose(IGate()).compose(HGate()))), (QuantumCircuit(2).compose(IGate()).compose(HGate())), ), - (None, pm), + (None, generate_preset_pass_manager(optimization_level=1, seed_transpiler=42)), ) ) @unpack @@ -153,7 +161,7 @@ def test_H2_hamiltonian_sampler(self, state_preparation, pass_manager): self.assertAlmostEqual(phases[1], -1.2376, delta=0.001) self.assertAlmostEqual(phases[2], -0.8979, delta=0.001) - @data(None, pm) + @data(None, generate_preset_pass_manager(optimization_level=1, seed_transpiler=42)) def test_matrix_evolution_sampler(self, pass_manager): """1Q Hamiltonian with MatrixEvolution""" # hamiltonian = PauliSumOp(SparsePauliOp.from_list([("X", 0.5), ("Y", 0.6), ("I", 0.7)])) @@ -197,11 +205,11 @@ def one_phase_sampler( phase_estimator = IterativePhaseEstimation if phase_estimator == IterativePhaseEstimation: p_est = IterativePhaseEstimation( - num_iterations=num_iterations, sampler=sampler, pass_manager=pass_manager + num_iterations=num_iterations, sampler=sampler, transpiler=pass_manager ) elif phase_estimator == PhaseEstimation: p_est = PhaseEstimation( - num_evaluation_qubits=6, sampler=sampler, pass_manager=pass_manager + num_evaluation_qubits=6, sampler=sampler, transpiler=pass_manager ) else: raise ValueError("Unrecognized phase_estimator") @@ -336,7 +344,7 @@ def phase_estimation_sampler( `state_preparation`. Return all results """ phase_est = PhaseEstimation( - num_evaluation_qubits=num_evaluation_qubits, sampler=sampler, pass_manager=pass_manager + num_evaluation_qubits=num_evaluation_qubits, sampler=sampler, transpiler=pass_manager ) if construct_circuit: pe_circuit = phase_est.construct_circuit(unitary_circuit, state_preparation) From d83408c0336cf3256384ba13a4157b0ea3210dfd Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Mon, 12 Aug 2024 20:30:52 +0200 Subject: [PATCH 3/9] Changed custom types to support Python 3.8 --- qiskit_algorithms/custom_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_algorithms/custom_types.py b/qiskit_algorithms/custom_types.py index b865873e..b1e6b668 100644 --- a/qiskit_algorithms/custom_types.py +++ b/qiskit_algorithms/custom_types.py @@ -12,11 +12,11 @@ """Types used by the qiskit-algorithms package.""" -from typing import Any, Protocol, Union +from typing import Any, List, Protocol, Union from qiskit import QuantumCircuit -_Circuits = Union[list[QuantumCircuit], QuantumCircuit] +_Circuits = Union[List[QuantumCircuit], QuantumCircuit] class Transpiler(Protocol): From 0e6e6f03f5251a98d30f79caa4110bf8bca5b97e Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Mon, 12 Aug 2024 20:36:56 +0200 Subject: [PATCH 4/9] Added transpiler to .pylintdict --- .pylintdict | 1 + 1 file changed, 1 insertion(+) diff --git a/.pylintdict b/.pylintdict index 245f309e..6846bce4 100644 --- a/.pylintdict +++ b/.pylintdict @@ -362,6 +362,7 @@ trainability transpilation transpile transpiled +transpiler trotterization trotterized uncompute From 0e8ac74cfa7157fd10b2068e95f72766448be9ad Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Mon, 12 Aug 2024 22:16:52 +0200 Subject: [PATCH 5/9] Adapted Grover to V2 primitives --- .../amplitude_amplifiers/grover.py | 55 +++++---- test/test_grover.py | 108 +++++++----------- 2 files changed, 74 insertions(+), 89 deletions(-) diff --git a/qiskit_algorithms/amplitude_amplifiers/grover.py b/qiskit_algorithms/amplitude_amplifiers/grover.py index 56fd2ad1..762294b1 100644 --- a/qiskit_algorithms/amplitude_amplifiers/grover.py +++ b/qiskit_algorithms/amplitude_amplifiers/grover.py @@ -20,7 +20,7 @@ import numpy as np from qiskit import ClassicalRegister, QuantumCircuit -from qiskit.primitives import BaseSampler +from qiskit.primitives import BaseSamplerV2 from qiskit.quantum_info import Statevector from qiskit_algorithms.exceptions import AlgorithmError @@ -28,6 +28,7 @@ from .amplification_problem import AmplificationProblem from .amplitude_amplifier import AmplitudeAmplifier, AmplitudeAmplifierResult +from ..custom_types import Transpiler class Grover(AmplitudeAmplifier): @@ -116,7 +117,9 @@ def __init__( iterations: list[int] | Iterator[int] | int | None = None, growth_rate: float | None = None, sample_from_iterations: bool = False, - sampler: BaseSampler | None = None, + sampler: BaseSamplerV2 | None = None, + transpiler: Transpiler | None = None, + transpiler_options: dict[str, Any] | None = None, ) -> None: r""" Args: @@ -136,6 +139,11 @@ def __init__( powers of the Grover operator, a random integer sample between 0 and smaller value than the iteration is used as a power, see [1], Section 4. sampler: A Sampler to use for sampling the results of the circuits. + transpiler: An optional object with a `run` method allowing to transpile the circuits + that are produced within this algorithm. If set to `None`, these won't be + transpiled. + transpiler_options: A dictionary of options to be passed to the transpiler's `run` + method as keyword arguments. Raises: ValueError: If ``growth_rate`` is a float but not larger than 1. @@ -165,9 +173,11 @@ def __init__( self._sampler = sampler self._sample_from_iterations = sample_from_iterations self._iterations_arg = iterations + self._transpiler = transpiler + self._transpiler_options = transpiler_options if transpiler_options is not None else {} @property - def sampler(self) -> BaseSampler | None: + def sampler(self) -> BaseSamplerV2 | None: """Get the sampler. Returns: @@ -176,7 +186,7 @@ def sampler(self) -> BaseSampler | None: return self._sampler @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: + def sampler(self, sampler: BaseSamplerV2) -> None: """Set the sampler. Args: @@ -234,23 +244,28 @@ def amplify(self, amplification_problem: AmplificationProblem) -> "GroverResult" # sample from [0, power) if specified if self._sample_from_iterations: power = algorithm_globals.random.integers(power) + # Run a grover experiment for a given power of the Grover operator. - if self._sampler is not None: - qc = self.construct_circuit(amplification_problem, power, measurement=True) - job = self._sampler.run([qc]) - - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Sampler job failed.") from exc - - num_bits = len(amplification_problem.objective_qubits) - circuit_results: dict[str, Any] | Statevector | np.ndarray = { - np.binary_repr(k, num_bits): v for k, v in results.quasi_dists[0].items() - } - top_measurement, max_probability = max( - circuit_results.items(), key=lambda x: x[1] # type: ignore[union-attr] - ) + qc = self.construct_circuit(amplification_problem, power, measurement=True) + + if self._transpiler is not None: + qc = self._transpiler.run(qc, **self._transpiler_options) + + job = self._sampler.run([qc]) + + try: + results = job.result() + except Exception as exc: + raise AlgorithmError("Sampler job failed.") from exc + + circuit_results: dict[str, Any] = getattr(results[0].data, qc.cregs[0].name) + circuit_results = { + label: value / circuit_results.num_shots + for label, value in circuit_results.get_counts().items() + } + top_measurement, max_probability = max( + circuit_results.items(), key=lambda x: x[1] # type: ignore[union-attr] + ) all_circuit_results.append(circuit_results) diff --git a/test/test_grover.py b/test/test_grover.py index 966c69a6..978008a1 100644 --- a/test/test_grover.py +++ b/test/test_grover.py @@ -14,18 +14,17 @@ import itertools import unittest -from test import QiskitAlgorithmsTestCase import numpy as np -from ddt import data, ddt, idata, unpack - +from ddt import data, ddt from qiskit import QuantumCircuit from qiskit.circuit.library import GroverOperator, PhaseOracle -from qiskit.primitives import Sampler +from qiskit.primitives import StatevectorSampler as Sampler from qiskit.quantum_info import Operator, Statevector from qiskit.utils.optionals import HAS_TWEEDLEDUM from qiskit_algorithms import AmplificationProblem, Grover +from test import QiskitAlgorithmsTestCase @ddt @@ -91,50 +90,44 @@ class TestGrover(QiskitAlgorithmsTestCase): def setUp(self): super().setUp() - self._sampler = Sampler() - self._sampler_with_shots = Sampler(options={"shots": 1024, "seed": 123}) + self._sampler = Sampler(seed=123) @unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum required for this test") - @data("ideal", "shots") - def test_implicit_phase_oracle_is_good_state(self, use_sampler): + def test_implicit_phase_oracle_is_good_state(self): """Test implicit default for is_good_state with PhaseOracle.""" - grover = self._prepare_grover(use_sampler) + grover = self._prepare_grover() oracle = PhaseOracle("x & y") problem = AmplificationProblem(oracle) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "11") - @idata(itertools.product(["ideal", "shots"], [[1, 2, 3], None, 2])) - @unpack - def test_iterations_with_good_state(self, use_sampler, iterations): + @data([1, 2, 3], None, 2) + def test_iterations_with_good_state(self, iterations): """Test the algorithm with different iteration types and with good state""" - grover = self._prepare_grover(use_sampler, iterations) + grover = self._prepare_grover(iterations) problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "111") - @idata(itertools.product(["shots"], [[1, 2, 3], None, 2])) - @unpack - def test_iterations_with_good_state_sample_from_iterations(self, use_sampler, iterations): + @data([1, 2, 3], None, 2) + def test_iterations_with_good_state_sample_from_iterations(self, iterations): """Test the algorithm with different iteration types and with good state""" - grover = self._prepare_grover(use_sampler, iterations, sample_from_iterations=True) + grover = self._prepare_grover(iterations, sample_from_iterations=True) problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "111") - @data("ideal", "shots") - def test_fixed_iterations_without_good_state(self, use_sampler): + def test_fixed_iterations_without_good_state(self): """Test the algorithm with iterations as an int and without good state""" - grover = self._prepare_grover(use_sampler, iterations=2) + grover = self._prepare_grover(iterations=2) problem = AmplificationProblem(Statevector.from_label("111")) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "111") - @idata(itertools.product(["ideal", "shots"], [[1, 2, 3], None])) - @unpack - def test_iterations_without_good_state(self, use_sampler, iterations): + @data([1, 2, 3], None) + def test_iterations_without_good_state(self, iterations): """Test the correct error is thrown for none/list of iterations and without good state""" - grover = self._prepare_grover(use_sampler, iterations=iterations) + grover = self._prepare_grover(iterations=iterations) problem = AmplificationProblem(Statevector.from_label("111")) with self.assertRaisesRegex( @@ -142,8 +135,7 @@ def test_iterations_without_good_state(self, use_sampler, iterations): ): grover.amplify(problem) - @data("ideal", "shots") - def test_iterator(self, use_sampler): + def test_iterator(self): """Test running the algorithm on an iterator.""" # step-function iterator @@ -155,63 +147,57 @@ def iterator(): if count % wait == 0: value += 1 - grover = self._prepare_grover(use_sampler, iterations=iterator()) + grover = self._prepare_grover(iterations=iterator()) problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "111") - @data("ideal", "shots") - def test_growth_rate(self, use_sampler): + def test_growth_rate(self): """Test running the algorithm on a growth rate""" - grover = self._prepare_grover(use_sampler, growth_rate=8 / 7) + grover = self._prepare_grover(growth_rate=8 / 7) problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "111") - @data("ideal", "shots") - def test_max_num_iterations(self, use_sampler): + def test_max_num_iterations(self): """Test the iteration stops when the maximum number of iterations is reached.""" def zero(): while True: yield 0 - grover = self._prepare_grover(use_sampler, iterations=zero()) + grover = self._prepare_grover(iterations=zero()) n = 5 problem = AmplificationProblem(Statevector.from_label("1" * n), is_good_state=["1" * n]) result = grover.amplify(problem) self.assertEqual(len(result.iterations), 2**n) - @data("ideal", "shots") - def test_max_power(self, use_sampler): + def test_max_power(self): """Test the iteration stops when the maximum power is reached.""" lam = 10.0 - grover = self._prepare_grover(use_sampler, growth_rate=lam) + grover = self._prepare_grover(growth_rate=lam) problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) result = grover.amplify(problem) self.assertEqual(len(result.iterations), 0) - @data("ideal", "shots") - def test_run_circuit_oracle(self, use_sampler): + def test_run_circuit_oracle(self): """Test execution with a quantum circuit oracle""" oracle = QuantumCircuit(2) oracle.cz(0, 1) problem = AmplificationProblem(oracle, is_good_state=["11"]) - grover = self._prepare_grover(use_sampler) + grover = self._prepare_grover() result = grover.amplify(problem) self.assertIn(result.top_measurement, ["11"]) - @data("ideal", "shots") - def test_run_state_vector_oracle(self, use_sampler): + def test_run_state_vector_oracle(self): """Test execution with a state vector oracle""" mark_state = Statevector.from_label("11") problem = AmplificationProblem(mark_state, is_good_state=["11"]) - grover = self._prepare_grover(use_sampler) + grover = self._prepare_grover() result = grover.amplify(problem) self.assertIn(result.top_measurement, ["11"]) - @data("ideal", "shots") - def test_run_custom_grover_operator(self, use_sampler): + def test_run_custom_grover_operator(self): """Test execution with a grover operator oracle""" oracle = QuantumCircuit(2) oracle.cz(0, 1) @@ -219,7 +205,7 @@ def test_run_custom_grover_operator(self, use_sampler): problem = AmplificationProblem( oracle=oracle, grover_operator=grover_op, is_good_state=["11"] ) - grover = self._prepare_grover(use_sampler) + grover = self._prepare_grover() result = grover.amplify(problem) self.assertIn(result.top_measurement, ["11"]) @@ -247,14 +233,13 @@ def test_construct_circuit(self): self.assertTrue(Operator(constructed).equiv(Operator(expected))) - @data("ideal", "shots") - def test_circuit_result(self, use_sampler): + def test_circuit_result(self): """Test circuit_result""" oracle = QuantumCircuit(2) oracle.cz(0, 1) # is_good_state=['00'] is intentionally selected to obtain a list of results problem = AmplificationProblem(oracle, is_good_state=["00"]) - grover = self._prepare_grover(use_sampler, iterations=[1, 2, 3, 4]) + grover = self._prepare_grover(iterations=[1, 2, 3, 4]) result = grover.amplify(problem) @@ -267,23 +252,21 @@ def test_circuit_result(self, use_sampler): self.assertTupleEqual(keys, ("00", "01", "10", "11")) np.testing.assert_allclose(values, [0.25, 0.25, 0.25, 0.25], atol=0.2) - @data("ideal", "shots") - def test_max_probability(self, use_sampler): + def test_max_probability(self): """Test max_probability""" oracle = QuantumCircuit(2) oracle.cz(0, 1) problem = AmplificationProblem(oracle, is_good_state=["11"]) - grover = self._prepare_grover(use_sampler) + grover = self._prepare_grover() result = grover.amplify(problem) self.assertAlmostEqual(result.max_probability, 1.0) @unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum required for this test") - @data("ideal", "shots") - def test_oracle_evaluation(self, use_sampler): + def test_oracle_evaluation(self): """Test oracle_evaluation for PhaseOracle""" oracle = PhaseOracle("x1 & x2 & (not x3)") problem = AmplificationProblem(oracle, is_good_state=oracle.evaluate_bitstring) - grover = self._prepare_grover(use_sampler) + grover = self._prepare_grover() result = grover.amplify(problem) self.assertTrue(result.oracle_evaluation) self.assertEqual("011", result.top_measurement) @@ -294,27 +277,14 @@ def test_sampler_setter(self): grover.sampler = self._sampler self.assertEqual(grover.sampler, self._sampler) - def _prepare_grover( - self, use_sampler, iterations=None, growth_rate=None, sample_from_iterations=False - ): + def _prepare_grover(self, iterations=None, growth_rate=None, sample_from_iterations=False): """Prepare Grover instance for test""" - if use_sampler == "ideal": - grover = Grover( + return Grover( sampler=self._sampler, iterations=iterations, growth_rate=growth_rate, sample_from_iterations=sample_from_iterations, ) - elif use_sampler == "shots": - grover = Grover( - sampler=self._sampler_with_shots, - iterations=iterations, - growth_rate=growth_rate, - sample_from_iterations=sample_from_iterations, - ) - else: - raise RuntimeError("Unexpected `use_sampler` value {use_sampler}") - return grover if __name__ == "__main__": From 3d79bb50fd357fdd2945189d790fd4f327003904 Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Fri, 16 Aug 2024 19:33:45 +0200 Subject: [PATCH 6/9] Changed custom types using __future__ annotations --- qiskit_algorithms/custom_types.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit_algorithms/custom_types.py b/qiskit_algorithms/custom_types.py index b1e6b668..3d98fd3e 100644 --- a/qiskit_algorithms/custom_types.py +++ b/qiskit_algorithms/custom_types.py @@ -11,12 +11,13 @@ # that they have been altered from the originals. """Types used by the qiskit-algorithms package.""" +from __future__ import annotations -from typing import Any, List, Protocol, Union +from typing import Any, Protocol, Union from qiskit import QuantumCircuit -_Circuits = Union[List[QuantumCircuit], QuantumCircuit] +_Circuits = Union[list[QuantumCircuit], QuantumCircuit] class Transpiler(Protocol): From eae6cff9d29579c0cd2ebaf21f4dda3949e3b07c Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Fri, 16 Aug 2024 20:10:36 +0200 Subject: [PATCH 7/9] Adapated VQD to V2 primitives --- qiskit_algorithms/eigensolvers/vqd.py | 19 ++++++++----------- qiskit_algorithms/observables_evaluator.py | 16 +++++++--------- test/eigensolvers/test_vqd.py | 19 ++++++++----------- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/qiskit_algorithms/eigensolvers/vqd.py b/qiskit_algorithms/eigensolvers/vqd.py index 8fee8b76..ff8ac75f 100644 --- a/qiskit_algorithms/eigensolvers/vqd.py +++ b/qiskit_algorithms/eigensolvers/vqd.py @@ -25,7 +25,7 @@ import numpy as np from qiskit.circuit import QuantumCircuit -from qiskit.primitives import BaseEstimator +from qiskit.primitives import BaseEstimatorV2 from qiskit.quantum_info.operators.base_operator import BaseOperator from qiskit.quantum_info import SparsePauliOp @@ -88,7 +88,7 @@ class VQD(VariationalAlgorithm, Eigensolver): updated once the VQD object has been constructed. Attributes: - estimator (BaseEstimator): The primitive instance used to perform the expectation + estimator (BaseEstimatorV2): The primitive instance used to perform the expectation estimation as indicated in the VQD paper. fidelity (BaseStateFidelity): The fidelity class instance used to compute the overlap estimation as indicated in the VQD paper. @@ -112,7 +112,7 @@ class VQD(VariationalAlgorithm, Eigensolver): def __init__( self, - estimator: BaseEstimator, + estimator: BaseEstimatorV2, fidelity: BaseStateFidelity, ansatz: QuantumCircuit, optimizer: Optimizer | Minimizer | Sequence[Optimizer | Minimizer], @@ -389,9 +389,7 @@ def evaluate_energy(parameters: np.ndarray) -> float | np.ndarray: parameters = np.reshape(parameters, (-1, num_parameters)) batch_size = len(parameters) - estimator_job = self.estimator.run( - batch_size * [self.ansatz], batch_size * [operator], parameters - ) + estimator_job = self.estimator.run([(self.ansatz, operator, parameters)]) total_cost = np.zeros(batch_size) @@ -410,18 +408,17 @@ def evaluate_energy(parameters: np.ndarray) -> float | np.ndarray: total_cost += np.real(betas[state] * cost) try: - estimator_result = estimator_job.result() + estimator_result = estimator_job.result()[0] except Exception as exc: raise AlgorithmError("The primitive job to evaluate the energy failed!") from exc - values = estimator_result.values + total_cost + values = estimator_result.data.evs + total_cost if self.callback is not None: - metadata = estimator_result.metadata - for params, value, meta in zip(parameters, values, metadata): + for params, value in zip(parameters, values): self._eval_count += 1 - self.callback(self._eval_count, params, value, meta, step) + self.callback(self._eval_count, params, value, estimator_result.metadata, step) else: self._eval_count += len(values) diff --git a/qiskit_algorithms/observables_evaluator.py b/qiskit_algorithms/observables_evaluator.py index ae125bfb..74323218 100644 --- a/qiskit_algorithms/observables_evaluator.py +++ b/qiskit_algorithms/observables_evaluator.py @@ -20,7 +20,7 @@ from qiskit import QuantumCircuit from qiskit.quantum_info import SparsePauliOp -from qiskit.primitives import BaseEstimator +from qiskit.primitives import BaseEstimatorV2 from qiskit.quantum_info.operators.base_operator import BaseOperator from .exceptions import AlgorithmError @@ -28,7 +28,7 @@ def estimate_observables( - estimator: BaseEstimator, + estimator: BaseEstimatorV2, quantum_state: QuantumCircuit, observables: ListOrDict[BaseOperator], parameter_values: Sequence[float] | None = None, @@ -63,21 +63,19 @@ def estimate_observables( if len(observables_list) > 0: observables_list = _handle_zero_ops(observables_list) - quantum_state = [quantum_state] * len(observables) parameter_values_: Sequence[float] | Sequence[Sequence[float]] | None = parameter_values - if parameter_values is not None: - parameter_values_ = [parameter_values] * len(observables) try: - estimator_job = estimator.run(quantum_state, observables_list, parameter_values_) - expectation_values = estimator_job.result().values + estimator_job = estimator.run([(quantum_state, observables_list, parameter_values_)]) + estimator_result = estimator_job.result()[0] + expectation_values = estimator_result.data.evs except Exception as exc: raise AlgorithmError("The primitive job failed!") from exc - metadata = estimator_job.result().metadata + metadata = estimator_result.metadata # Discard values below threshold observables_means = expectation_values * (np.abs(expectation_values) > threshold) # zip means and metadata into tuples - observables_results = list(zip(observables_means, metadata)) + observables_results = list(zip(observables_means, [metadata] * len(observables_means))) else: observables_results = [] diff --git a/test/eigensolvers/test_vqd.py b/test/eigensolvers/test_vqd.py index 78b398c6..0098d1ea 100644 --- a/test/eigensolvers/test_vqd.py +++ b/test/eigensolvers/test_vqd.py @@ -20,7 +20,7 @@ from qiskit import QuantumCircuit from qiskit.circuit.library import TwoLocal, RealAmplitudes -from qiskit.primitives import Sampler, Estimator +from qiskit.primitives import Sampler, StatevectorEstimator as Estimator from qiskit.quantum_info import SparsePauliOp from qiskit_algorithms.eigensolvers import VQD, VQDResult @@ -57,8 +57,7 @@ def setUp(self): ) self.ry_wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") - self.estimator = Estimator() - self.estimator_shots = Estimator(options={"shots": 1024, "seed": self.seed}) + self.estimator = Estimator(seed=self.seed) self.fidelity = ComputeUncompute(Sampler()) self.betas = [50, 50] @@ -91,12 +90,10 @@ def test_basic_operator(self, op): self.assertIsNotNone(result.optimizer_times) with self.subTest(msg="assert return ansatz is set"): - job = self.estimator.run( - result.optimal_circuits, - [op] * len(result.optimal_points), - result.optimal_points, - ) - np.testing.assert_array_almost_equal(job.result().values, result.eigenvalues, 6) + job = self.estimator.run([(circuits, op, optimal_points) for (circuits, optimal_points) in zip(result.optimal_circuits, result.optimal_points)]) + job_result = job.result() + eigenvalues = np.array([job_result[i].data.evs for i in range(len(result.eigenvalues))]) + np.testing.assert_array_almost_equal(eigenvalues, result.eigenvalues, 6) with self.subTest(msg="assert returned values are eigenvalues"): np.testing.assert_array_almost_equal( @@ -117,7 +114,7 @@ def test_beta_autoeval(self, op): with self.assertLogs(level="INFO") as logs: vqd = VQD( - self.estimator_shots, self.fidelity, self.ryrz_wavefunction, optimizer=L_BFGS_B() + self.estimator, self.fidelity, self.ryrz_wavefunction, optimizer=L_BFGS_B() ) _ = vqd.compute_eigenvalues(op) @@ -172,7 +169,7 @@ def store_intermediate_result(eval_count, parameters, mean, metadata, step): wavefunction = self.ry_wavefunction vqd = VQD( - estimator=self.estimator_shots, + estimator=self.estimator, fidelity=self.fidelity, ansatz=wavefunction, optimizer=optimizer, From df69ea7ca3aea2a1d9f755bdb5102bc2d0379ecc Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Fri, 16 Aug 2024 19:31:24 +0200 Subject: [PATCH 8/9] Adapted tests for Grover --- .../amplitude_amplifiers/grover.py | 3 +- test/test_grover.py | 113 ++++++++++++++---- 2 files changed, 91 insertions(+), 25 deletions(-) diff --git a/qiskit_algorithms/amplitude_amplifiers/grover.py b/qiskit_algorithms/amplitude_amplifiers/grover.py index 762294b1..5d79fb1a 100644 --- a/qiskit_algorithms/amplitude_amplifiers/grover.py +++ b/qiskit_algorithms/amplitude_amplifiers/grover.py @@ -258,11 +258,12 @@ def amplify(self, amplification_problem: AmplificationProblem) -> "GroverResult" except Exception as exc: raise AlgorithmError("Sampler job failed.") from exc - circuit_results: dict[str, Any] = getattr(results[0].data, qc.cregs[0].name) + circuit_results = getattr(results[0].data, qc.cregs[0].name) circuit_results = { label: value / circuit_results.num_shots for label, value in circuit_results.get_counts().items() } + top_measurement, max_probability = max( circuit_results.items(), key=lambda x: x[1] # type: ignore[union-attr] ) diff --git a/test/test_grover.py b/test/test_grover.py index 978008a1..787d6e16 100644 --- a/test/test_grover.py +++ b/test/test_grover.py @@ -12,15 +12,16 @@ """Test Grover's algorithm.""" -import itertools import unittest +from itertools import product import numpy as np -from ddt import data, ddt +from ddt import data, ddt, idata, unpack from qiskit import QuantumCircuit from qiskit.circuit.library import GroverOperator, PhaseOracle from qiskit.primitives import StatevectorSampler as Sampler from qiskit.quantum_info import Operator, Statevector +from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.utils.optionals import HAS_TWEEDLEDUM from qiskit_algorithms import AmplificationProblem, Grover @@ -71,9 +72,7 @@ def is_good_state(bitstr): # same as ``bitstr in ['01', '11']`` return bitstr[1] == "1" - possible_states = [ - "".join(list(map(str, item))) for item in itertools.product([0, 1], repeat=2) - ] + possible_states = ["".join(list(map(str, item))) for item in product([0, 1], repeat=2)] oracle = QuantumCircuit(2) problem = AmplificationProblem(oracle, is_good_state=is_good_state) @@ -93,41 +92,98 @@ def setUp(self): self._sampler = Sampler(seed=123) @unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum required for this test") - def test_implicit_phase_oracle_is_good_state(self): + @data( + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + ) + @unpack + def test_implicit_phase_oracle_is_good_state(self, transpiler, transpiler_options): """Test implicit default for is_good_state with PhaseOracle.""" - grover = self._prepare_grover() + grover = self._prepare_grover(transpiler=transpiler, transpiler_options=transpiler_options) oracle = PhaseOracle("x & y") problem = AmplificationProblem(oracle) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "11") - @data([1, 2, 3], None, 2) - def test_iterations_with_good_state(self, iterations): + @idata( + product( + [[1, 2, 3], None, 2], + [ + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + ], + ) + ) + @unpack + def test_iterations_with_good_state(self, iterations, transpiler_and_options): """Test the algorithm with different iteration types and with good state""" - grover = self._prepare_grover(iterations) + transpiler, transpiler_options = transpiler_and_options + grover = self._prepare_grover( + iterations, transpiler=transpiler, transpiler_options=transpiler_options + ) problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "111") - @data([1, 2, 3], None, 2) - def test_iterations_with_good_state_sample_from_iterations(self, iterations): + @idata( + product( + [[1, 2, 3], None, 2], + [ + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + ], + ) + ) + @unpack + def test_iterations_with_good_state_sample_from_iterations(self, iterations, transpiler_and_options): """Test the algorithm with different iteration types and with good state""" - grover = self._prepare_grover(iterations, sample_from_iterations=True) + transpiler, transpiler_options = transpiler_and_options + grover = self._prepare_grover(iterations, sample_from_iterations=True, transpiler=transpiler, transpiler_options=transpiler_options) problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "111") - def test_fixed_iterations_without_good_state(self): + @data( + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + ) + @unpack + def test_fixed_iterations_without_good_state(self, transpiler, transpiler_options): """Test the algorithm with iterations as an int and without good state""" - grover = self._prepare_grover(iterations=2) + grover = self._prepare_grover(iterations=2, transpiler=transpiler, transpiler_options=transpiler_options) problem = AmplificationProblem(Statevector.from_label("111")) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "111") - @data([1, 2, 3], None) - def test_iterations_without_good_state(self, iterations): + @idata( + product( + [[1, 2, 3], None], + [ + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + ], + ) + ) + @unpack + def test_iterations_without_good_state(self, iterations, transpiler_and_options): """Test the correct error is thrown for none/list of iterations and without good state""" - grover = self._prepare_grover(iterations=iterations) + transpiler, transpiler_options = transpiler_and_options + grover = self._prepare_grover(iterations=iterations, transpiler=transpiler, transpiler_options=transpiler_options) problem = AmplificationProblem(Statevector.from_label("111")) with self.assertRaisesRegex( @@ -277,14 +333,23 @@ def test_sampler_setter(self): grover.sampler = self._sampler self.assertEqual(grover.sampler, self._sampler) - def _prepare_grover(self, iterations=None, growth_rate=None, sample_from_iterations=False): + def _prepare_grover( + self, + iterations=None, + growth_rate=None, + sample_from_iterations=False, + transpiler=None, + transpiler_options=None, + ): """Prepare Grover instance for test""" return Grover( - sampler=self._sampler, - iterations=iterations, - growth_rate=growth_rate, - sample_from_iterations=sample_from_iterations, - ) + sampler=self._sampler, + iterations=iterations, + growth_rate=growth_rate, + sample_from_iterations=sample_from_iterations, + transpiler=transpiler, + transpiler_options=transpiler_options, + ) if __name__ == "__main__": From 111188bce5520b587b15157f59ceeccb34b4126b Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Fri, 16 Aug 2024 20:24:14 +0200 Subject: [PATCH 9/9] Fixed styling --- .../amplitude_amplifiers/grover.py | 3 --- test/eigensolvers/test_vqd.py | 13 ++++++++---- test/test_grover.py | 21 ++++++++++++++----- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/qiskit_algorithms/amplitude_amplifiers/grover.py b/qiskit_algorithms/amplitude_amplifiers/grover.py index 5d79fb1a..aaab79d6 100644 --- a/qiskit_algorithms/amplitude_amplifiers/grover.py +++ b/qiskit_algorithms/amplitude_amplifiers/grover.py @@ -18,14 +18,11 @@ from typing import Any import numpy as np - from qiskit import ClassicalRegister, QuantumCircuit from qiskit.primitives import BaseSamplerV2 -from qiskit.quantum_info import Statevector from qiskit_algorithms.exceptions import AlgorithmError from qiskit_algorithms.utils import algorithm_globals - from .amplification_problem import AmplificationProblem from .amplitude_amplifier import AmplitudeAmplifier, AmplitudeAmplifierResult from ..custom_types import Transpiler diff --git a/test/eigensolvers/test_vqd.py b/test/eigensolvers/test_vqd.py index 0098d1ea..aec32483 100644 --- a/test/eigensolvers/test_vqd.py +++ b/test/eigensolvers/test_vqd.py @@ -90,7 +90,14 @@ def test_basic_operator(self, op): self.assertIsNotNone(result.optimizer_times) with self.subTest(msg="assert return ansatz is set"): - job = self.estimator.run([(circuits, op, optimal_points) for (circuits, optimal_points) in zip(result.optimal_circuits, result.optimal_points)]) + job = self.estimator.run( + [ + (circuits, op, optimal_points) + for (circuits, optimal_points) in zip( + result.optimal_circuits, result.optimal_points + ) + ] + ) job_result = job.result() eigenvalues = np.array([job_result[i].data.evs for i in range(len(result.eigenvalues))]) np.testing.assert_array_almost_equal(eigenvalues, result.eigenvalues, 6) @@ -113,9 +120,7 @@ def test_beta_autoeval(self, op): """Test beta auto-evaluation for different operator types.""" with self.assertLogs(level="INFO") as logs: - vqd = VQD( - self.estimator, self.fidelity, self.ryrz_wavefunction, optimizer=L_BFGS_B() - ) + vqd = VQD(self.estimator, self.fidelity, self.ryrz_wavefunction, optimizer=L_BFGS_B()) _ = vqd.compute_eigenvalues(op) # the first log message shows the value of beta[0] diff --git a/test/test_grover.py b/test/test_grover.py index 787d6e16..93080d3b 100644 --- a/test/test_grover.py +++ b/test/test_grover.py @@ -14,6 +14,7 @@ import unittest from itertools import product +from test import QiskitAlgorithmsTestCase import numpy as np from ddt import data, ddt, idata, unpack @@ -25,7 +26,6 @@ from qiskit.utils.optionals import HAS_TWEEDLEDUM from qiskit_algorithms import AmplificationProblem, Grover -from test import QiskitAlgorithmsTestCase @ddt @@ -144,10 +144,17 @@ def test_iterations_with_good_state(self, iterations, transpiler_and_options): ) ) @unpack - def test_iterations_with_good_state_sample_from_iterations(self, iterations, transpiler_and_options): + def test_iterations_with_good_state_sample_from_iterations( + self, iterations, transpiler_and_options + ): """Test the algorithm with different iteration types and with good state""" transpiler, transpiler_options = transpiler_and_options - grover = self._prepare_grover(iterations, sample_from_iterations=True, transpiler=transpiler, transpiler_options=transpiler_options) + grover = self._prepare_grover( + iterations, + sample_from_iterations=True, + transpiler=transpiler, + transpiler_options=transpiler_options, + ) problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "111") @@ -162,7 +169,9 @@ def test_iterations_with_good_state_sample_from_iterations(self, iterations, tra @unpack def test_fixed_iterations_without_good_state(self, transpiler, transpiler_options): """Test the algorithm with iterations as an int and without good state""" - grover = self._prepare_grover(iterations=2, transpiler=transpiler, transpiler_options=transpiler_options) + grover = self._prepare_grover( + iterations=2, transpiler=transpiler, transpiler_options=transpiler_options + ) problem = AmplificationProblem(Statevector.from_label("111")) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "111") @@ -183,7 +192,9 @@ def test_fixed_iterations_without_good_state(self, transpiler, transpiler_option def test_iterations_without_good_state(self, iterations, transpiler_and_options): """Test the correct error is thrown for none/list of iterations and without good state""" transpiler, transpiler_options = transpiler_and_options - grover = self._prepare_grover(iterations=iterations, transpiler=transpiler, transpiler_options=transpiler_options) + grover = self._prepare_grover( + iterations=iterations, transpiler=transpiler, transpiler_options=transpiler_options + ) problem = AmplificationProblem(Statevector.from_label("111")) with self.assertRaisesRegex(