From 634dc693aaa83ae26ef1af8006c585a4ec971369 Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Wed, 7 Aug 2024 13:58:16 +0200 Subject: [PATCH 01/31] Added TODO --- TODO.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..a5b5de32 --- /dev/null +++ b/TODO.md @@ -0,0 +1,42 @@ +Tristan: +[] phase_estimators/hamiltonian_phase_estimation.py +[] phase_estimators/ipe.py +[] phase_estimators/phase_estimation.py +[] eigensolvers/vqd.py +[] amplitude_amplifiers/grover.py +[] time_evolvers/pvqd/utils.py +[] time_evolvers/pvqd/pvqd.py +[] time_evolvers/trotterization/trotter_qrte.py +[] time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py +[] time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py +[] time_evolvers/variational/var_qite.py +[] time_evolvers/variational/var_qrte.py +[] time_evolvers/variational/var_qte.py +[] state_fidelities/compute_uncompute.py +[] optimizers/qnspsa.py +[] optimizers/umda.py +[] optimizers/spsa.py +[] observables_evaluator.py + +Léna: +[] gradients/reverse/reverse_gradient.py +[] gradients/reverse/reverse_qgt.py +[] gradients/finite_diff/finite_diff_estimator_gradient.py +[] gradients/finite_diff/finite_diff_sampler_gradient.py +[] gradients/spsa/spsa_estimator_gradient.py +[] gradients/spsa/spsa_sampler_gradient.py +[] gradients/lin_comb/lin_comb_sampler_gradient.py +[] gradients/lin_comb/lin_comb_estimator_gradient.py +[] gradients/lin_comb/lin_comb_qgt.py +[] gradients/base/base_sampler_gradient.py +[] gradients/base/base_qgt.py +[] gradients/base/base_estimator_gradient.py +[] minimum_eigensolvers/vqe.py +[] minimum_eigensolvers/adapt_vqe.py +[] minimum_eigensolvers/qaoa.py +[] minimum_eigensolvers/diagonal_estimator.py +[] minimum_eigensolvers/sampling_vqe.py +[] amplitude_estimators/mlae.py +[] amplitude_estimators/fae.py +[] amplitude_estimators/iae.py +[] amplitude_estimators/ae.py From ecee0c6ecf60ab2c81633e4ae1cb5a97142a0395 Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Wed, 7 Aug 2024 14:18:32 +0200 Subject: [PATCH 02/31] Started to adapt to V2 primitives --- qiskit_algorithms/minimum_eigensolvers/vqe.py | 20 ++++---- test/minimum_eigensolvers/test_vqe.py | 48 +++++++++---------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/qiskit_algorithms/minimum_eigensolvers/vqe.py b/qiskit_algorithms/minimum_eigensolvers/vqe.py index b0e85a67..3fedd55e 100644 --- a/qiskit_algorithms/minimum_eigensolvers/vqe.py +++ b/qiskit_algorithms/minimum_eigensolvers/vqe.py @@ -22,7 +22,8 @@ import numpy as np from qiskit.circuit import QuantumCircuit -from qiskit.primitives import BaseEstimator +# from qiskit.primitives import BaseEstimator +from qiskit.primitives import BaseEstimatorV2 from qiskit.quantum_info.operators.base_operator import BaseOperator from qiskit_algorithms.gradients import BaseEstimatorGradient @@ -94,7 +95,7 @@ def my_minimizer(fun, x0, jac=None, bounds=None) -> OptimizerResult: the VQE object has been constructed. Attributes: - estimator (BaseEstimator): The estimator primitive to compute the expectation value of the + estimator (BaseEstimatorV2): The estimator primitive to compute the expectation value of the Hamiltonian operator. ansatz (QuantumCircuit): A parameterized quantum circuit to prepare the trial state. optimizer (Optimizer | Minimizer): A classical optimizer to find the minimum energy. This @@ -114,7 +115,7 @@ def my_minimizer(fun, x0, jac=None, bounds=None) -> OptimizerResult: def __init__( self, - estimator: BaseEstimator, + estimator: BaseEstimatorV2, ansatz: QuantumCircuit, optimizer: Optimizer | Minimizer, *, @@ -258,16 +259,19 @@ def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: nonlocal eval_count # handle broadcasting: ensure parameters is of shape [array, array, ...] - parameters = np.reshape(parameters, (-1, num_parameters)).tolist() - batch_size = len(parameters) + # parameters = np.reshape(parameters, (-1, num_parameters)).tolist() + # batch_size = len(parameters) try: - job = self.estimator.run(batch_size * [ansatz], batch_size * [operator], parameters) - estimator_result = job.result() + job = self.estimator.run([(ansatz, operator, parameters)]) + estimator_result = job.result()[0] except Exception as exc: raise AlgorithmError("The primitive job to evaluate the energy failed!") from exc - values = estimator_result.values + values = estimator_result.data.evs + + if not values.shape: + values = values.reshape(1) if self.callback is not None: metadata = estimator_result.metadata diff --git a/test/minimum_eigensolvers/test_vqe.py b/test/minimum_eigensolvers/test_vqe.py index 053cfd05..a0a7b2c2 100644 --- a/test/minimum_eigensolvers/test_vqe.py +++ b/test/minimum_eigensolvers/test_vqe.py @@ -23,7 +23,7 @@ from qiskit import QuantumCircuit from qiskit.circuit.library import RealAmplitudes, TwoLocal from qiskit.quantum_info import SparsePauliOp, Operator, Pauli -from qiskit.primitives import Estimator, Sampler +from qiskit.primitives import Estimator, Sampler, StatevectorEstimator, StatevectorSampler from qiskit_algorithms import AlgorithmError from qiskit_algorithms.gradients import ParamShiftEstimatorGradient @@ -83,7 +83,7 @@ def setUp(self): @data(L_BFGS_B(), COBYLA()) def test_using_ref_estimator(self, optimizer): """Test VQE using reference Estimator.""" - vqe = VQE(Estimator(), self.ryrz_wavefunction, optimizer) + vqe = VQE(StatevectorEstimator(), self.ryrz_wavefunction, optimizer) result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) @@ -109,9 +109,9 @@ def test_using_ref_estimator(self, optimizer): self.assertAlmostEqual(result.optimizer_result.fun, self.h2_energy, places=5) with self.subTest(msg="assert return ansatz is set"): - estimator = Estimator() - job = estimator.run(result.optimal_circuit, self.h2_op, result.optimal_point) - np.testing.assert_array_almost_equal(job.result().values, result.eigenvalue, 6) + estimator = StatevectorEstimator() + job = estimator.run([(result.optimal_circuit, self.h2_op, result.optimal_point)]) + np.testing.assert_array_almost_equal(job.result()[0].data.evs, result.eigenvalue, 6) def test_invalid_initial_point(self): """Test the proper error is raised when the initial point has the wrong size.""" @@ -119,7 +119,7 @@ def test_invalid_initial_point(self): initial_point = np.array([1]) vqe = VQE( - Estimator(), + StatevectorEstimator(), ansatz, SLSQP(), initial_point=initial_point, @@ -131,7 +131,7 @@ def test_invalid_initial_point(self): def test_ansatz_resize(self): """Test the ansatz is properly resized if it's a blueprint circuit.""" ansatz = RealAmplitudes(1, reps=1) - vqe = VQE(Estimator(), ansatz, SLSQP()) + vqe = VQE(StatevectorEstimator(), ansatz, SLSQP()) result = vqe.compute_minimum_eigenvalue(self.h2_op) self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) @@ -139,7 +139,7 @@ def test_invalid_ansatz_size(self): """Test an error is raised if the ansatz has the wrong number of qubits.""" ansatz = QuantumCircuit(1) ansatz.compose(RealAmplitudes(1, reps=2)) - vqe = VQE(Estimator(), ansatz, SLSQP()) + vqe = VQE(StatevectorEstimator(), ansatz, SLSQP()) with self.assertRaises(AlgorithmError): _ = vqe.compute_minimum_eigenvalue(operator=self.h2_op) @@ -147,7 +147,7 @@ def test_invalid_ansatz_size(self): def test_missing_ansatz_params(self): """Test specifying an ansatz with no parameters raises an error.""" ansatz = QuantumCircuit(self.h2_op.num_qubits) - vqe = VQE(Estimator(), ansatz, SLSQP()) + vqe = VQE(StatevectorEstimator(), ansatz, SLSQP()) with self.assertRaises(AlgorithmError): vqe.compute_minimum_eigenvalue(operator=self.h2_op) @@ -155,7 +155,7 @@ def test_max_evals_grouped(self): """Test with SLSQP with max_evals_grouped.""" optimizer = SLSQP(maxiter=50, max_evals_grouped=5) vqe = VQE( - Estimator(), + StatevectorEstimator(), self.ryrz_wavefunction, optimizer, ) @@ -171,7 +171,7 @@ def test_max_evals_grouped(self): ) def test_with_gradient(self, optimizer): """Test VQE using gradient primitive.""" - estimator = Estimator() + estimator = StatevectorEstimator() vqe = VQE( estimator, self.ry_wavefunction, @@ -184,7 +184,7 @@ def test_with_gradient(self, optimizer): def test_gradient_passed(self): """Test the gradient is properly passed into the optimizer.""" inputs = {} - estimator = Estimator() + estimator = StatevectorEstimator() vqe = VQE( estimator, RealAmplitudes(), @@ -197,7 +197,7 @@ def test_gradient_passed(self): def test_gradient_run(self): """Test using the gradient to calculate the minimum.""" - estimator = Estimator() + estimator = StatevectorEstimator() vqe = VQE( estimator, RealAmplitudes(), @@ -220,7 +220,7 @@ def store_intermediate_result(eval_count, parameters, mean, metadata): optimizer = COBYLA(maxiter=3) wavefunction = self.ry_wavefunction - estimator = Estimator() + estimator = StatevectorEstimator() vqe = VQE( estimator, @@ -239,7 +239,7 @@ def store_intermediate_result(eval_count, parameters, mean, metadata): def test_reuse(self): """Test re-using a VQE algorithm instance.""" ansatz = TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - vqe = VQE(Estimator(), ansatz, SLSQP(maxiter=300)) + vqe = VQE(StatevectorEstimator(), ansatz, SLSQP(maxiter=300)) with self.subTest(msg="assert VQE works once all info is available"): result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) @@ -254,7 +254,7 @@ def test_reuse(self): def test_vqe_optimizer_reuse(self): """Test running same VQE twice to re-use optimizer, then switch optimizer""" vqe = VQE( - Estimator(), + StatevectorEstimator(), self.ryrz_wavefunction, SLSQP(), ) @@ -276,8 +276,8 @@ def test_default_batch_evaluation_on_spsa(self): """Test the default batching works.""" ansatz = TwoLocal(2, rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - wrapped_estimator = Estimator() - inner_estimator = Estimator() + wrapped_estimator = StatevectorEstimator() + inner_estimator = StatevectorEstimator() callcount = {"estimator": 0} @@ -308,8 +308,8 @@ def test_batch_evaluate_with_qnspsa(self): wrapped_sampler = Sampler() inner_sampler = Sampler() - wrapped_estimator = Estimator() - inner_estimator = Estimator() + wrapped_estimator = StatevectorEstimator() + inner_estimator = StatevectorEstimator() callcount = {"sampler": 0, "estimator": 0} @@ -353,7 +353,7 @@ def fidelity_callable(left, right): def test_optimizer_scipy_callable(self): """Test passing a SciPy optimizer directly as callable.""" vqe = VQE( - Estimator(), + StatevectorEstimator(), self.ryrz_wavefunction, partial(scipy_minimize, method="L-BFGS-B", options={"maxiter": 10}), ) @@ -363,13 +363,13 @@ def test_optimizer_scipy_callable(self): def test_optimizer_callable(self): """Test passing a optimizer directly as callable.""" ansatz = RealAmplitudes(1, reps=1) - vqe = VQE(Estimator(), ansatz, _mock_optimizer) + vqe = VQE(StatevectorEstimator(), ansatz, _mock_optimizer) result = vqe.compute_minimum_eigenvalue(SparsePauliOp("Z")) self.assertTrue(np.all(result.optimal_point == np.zeros(ansatz.num_parameters))) def test_aux_operators_list(self): """Test list-based aux_operators.""" - vqe = VQE(Estimator(), self.ry_wavefunction, SLSQP(maxiter=300)) + vqe = VQE(StatevectorEstimator(), self.ry_wavefunction, SLSQP(maxiter=300)) with self.subTest("Test with an empty list."): result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=[]) @@ -408,7 +408,7 @@ def test_aux_operators_list(self): def test_aux_operators_dict(self): """Test dictionary compatibility of aux_operators""" - vqe = VQE(Estimator(), self.ry_wavefunction, SLSQP(maxiter=300)) + vqe = VQE(StatevectorEstimator(), self.ry_wavefunction, SLSQP(maxiter=300)) with self.subTest("Test with an empty dictionary."): result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators={}) From 529c5da6f71e0dc7430b061e57274d33266fc742 Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Fri, 9 Aug 2024 11:28:10 +0200 Subject: [PATCH 03/31] 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 04/31] 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 05/31] 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 06/31] 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 07/31] 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 08/31] 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 09/31] 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 10/31] 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 11/31] 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( From 93af7744b77438e434ad4ca6a197e123cf2be444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9na=20P=C3=A9renn=C3=A8s?= Date: Mon, 26 Aug 2024 19:02:12 +0200 Subject: [PATCH 12/31] removed useless comments in vqe and changed SamplingVQE to match v2 primitives --- .../minimum_eigensolvers/sampling_vqe.py | 28 +++++++++++-------- qiskit_algorithms/minimum_eigensolvers/vqe.py | 1 - 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/qiskit_algorithms/minimum_eigensolvers/sampling_vqe.py b/qiskit_algorithms/minimum_eigensolvers/sampling_vqe.py index 3d007452..dd0f7137 100644 --- a/qiskit_algorithms/minimum_eigensolvers/sampling_vqe.py +++ b/qiskit_algorithms/minimum_eigensolvers/sampling_vqe.py @@ -22,7 +22,7 @@ import numpy as np from qiskit.circuit import QuantumCircuit -from qiskit.primitives import BaseSampler +from qiskit.primitives import BaseSamplerV2 from qiskit.result import QuasiDistribution from qiskit.quantum_info.operators.base_operator import BaseOperator @@ -92,7 +92,7 @@ def my_minimizer(fun, x0, jac=None, bounds=None) -> OptimizerResult: the ``SamplingVQE`` object has been constructed. Attributes: - sampler (BaseSampler): The sampler primitive to sample the circuits. + sampler (BaseSamplerV2): The sampler primitive to sample the circuits. ansatz (QuantumCircuit): A parameterized quantum circuit to prepare the trial state. optimizer (Optimizer | Minimizer): A classical optimizer to find the minimum energy. This can either be an :class:`.Optimizer` or a callable implementing the @@ -116,7 +116,7 @@ def my_minimizer(fun, x0, jac=None, bounds=None) -> OptimizerResult: def __init__( self, - sampler: BaseSampler, + sampler: BaseSamplerV2, ansatz: QuantumCircuit, optimizer: Optimizer | Minimizer, *, @@ -240,7 +240,12 @@ def compute_minimum_eigenvalue( optimizer_result.x, ) - final_state = self.sampler.run([self.ansatz], [optimizer_result.x]).result().quasi_dists[0] + final_res = self.sampler.run([(self.ansatz, optimizer_result.x)]).result() + final_state = getattr(final_res[0].data, self.ansatz.cregs[0].name) + final_state = { + label: value / final_state.num_shots + for label, value in final_state.get_counts().items() + } if aux_operators is not None: aux_operators_evaluated = estimate_observables( @@ -313,13 +318,14 @@ def store_best_measurement(best): def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: nonlocal eval_count # handle broadcasting: ensure parameters is of shape [array, array, ...] - parameters = np.reshape(parameters, (-1, num_parameters)).tolist() - batch_size = len(parameters) - - estimator_result = estimator.run( - batch_size * [ansatz], batch_size * [operator], parameters - ).result() - values = estimator_result.values + # parameters = np.reshape(parameters, (-1, num_parameters)).tolist() + # batch_size = len(parameters) + + job = self.estimator.run([(ansatz, operator, parameters)]) + estimator_result = job.result()[0] + values = estimator_result.data.evs + if not values.shape: + values = values.reshape(1) if self.callback is not None: metadata = estimator_result.metadata diff --git a/qiskit_algorithms/minimum_eigensolvers/vqe.py b/qiskit_algorithms/minimum_eigensolvers/vqe.py index 3fedd55e..c449da4f 100644 --- a/qiskit_algorithms/minimum_eigensolvers/vqe.py +++ b/qiskit_algorithms/minimum_eigensolvers/vqe.py @@ -22,7 +22,6 @@ 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 63332e992f8763de395df6a889b1548b0ec44e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9na=20P=C3=A9renn=C3=A8s?= Date: Mon, 26 Aug 2024 19:27:40 +0200 Subject: [PATCH 13/31] changed AdaptVQE --- qiskit_algorithms/minimum_eigensolvers/adapt_vqe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_algorithms/minimum_eigensolvers/adapt_vqe.py b/qiskit_algorithms/minimum_eigensolvers/adapt_vqe.py index 895a8fae..bdd22936 100644 --- a/qiskit_algorithms/minimum_eigensolvers/adapt_vqe.py +++ b/qiskit_algorithms/minimum_eigensolvers/adapt_vqe.py @@ -62,7 +62,7 @@ class AdaptVQE(VariationalAlgorithm, MinimumEigensolver): from qiskit_algorithms.minimum_eigensolvers import AdaptVQE, VQE from qiskit_algorithms.optimizers import SLSQP - from qiskit.primitives import Estimator + from qiskit.primitives import StatevectorEstimator from qiskit.circuit.library import EvolvedOperatorAnsatz # get your Hamiltonian @@ -71,7 +71,7 @@ class AdaptVQE(VariationalAlgorithm, MinimumEigensolver): # construct your ansatz ansatz = EvolvedOperatorAnsatz(...) - vqe = VQE(Estimator(), ansatz, SLSQP()) + vqe = VQE(StatevectorEstimator(), ansatz, SLSQP()) adapt_vqe = AdaptVQE(vqe) From 1b58074817bac68d4c77585583200c16130aa382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9na=20P=C3=A9renn=C3=A8s?= Date: Mon, 26 Aug 2024 20:15:41 +0200 Subject: [PATCH 14/31] changed diagonal_estimators from v1 to v2 primitives --- .../diagonal_estimator.py | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/qiskit_algorithms/minimum_eigensolvers/diagonal_estimator.py b/qiskit_algorithms/minimum_eigensolvers/diagonal_estimator.py index aaabf3b8..f0034bc9 100644 --- a/qiskit_algorithms/minimum_eigensolvers/diagonal_estimator.py +++ b/qiskit_algorithms/minimum_eigensolvers/diagonal_estimator.py @@ -21,7 +21,7 @@ import numpy as np from qiskit.circuit import QuantumCircuit -from qiskit.primitives import BaseSampler, BaseEstimator, EstimatorResult +from qiskit.primitives import BaseSamplerV2, BaseEstimatorV2, PubResult from qiskit.primitives.utils import init_observable, _circuit_key from qiskit.quantum_info import SparsePauliOp from qiskit.quantum_info.operators.base_operator import BaseOperator @@ -30,19 +30,19 @@ @dataclass(frozen=True) -class _DiagonalEstimatorResult(EstimatorResult): +class _DiagonalEstimatorResult(PubResult): """A result from an expectation of a diagonal observable.""" # TODO make each measurement a dataclass rather than a dict best_measurements: Sequence[Mapping[str, Any]] | None = None -class _DiagonalEstimator(BaseEstimator): +class _DiagonalEstimator(BaseEstimatorV2): """An estimator for diagonal observables.""" def __init__( self, - sampler: BaseSampler, + sampler: BaseSamplerV2, aggregation: float | Callable[[Iterable[tuple[float, float]]], float] | None = None, callback: Callable[[Sequence[Mapping[str, Any]]], None] | None = None, **options, @@ -114,13 +114,22 @@ def _call( parameter_values: Sequence[Sequence[float]], **run_options, ) -> _DiagonalEstimatorResult: + + pubs = list(zip(circuits, parameter_values)) job = self.sampler.run( - [self._circuits[i] for i in circuits], - parameter_values, + pubs, **run_options, ) - sampler_result = job.result() - samples = sampler_result.quasi_dists + results = job.result() + samples = [] + for i, pubres in enumerate(results): + qc = pubs[i][0] + sampler_result = getattr(results[i].data, qc.cregs[0].name) + sample = { + label: value / sampler_result.num_shots + for label, value in sampler_result.get_counts().items() + } + samples.append(sample) # a list of dictionaries containing: {state: (measurement probability, value)} evaluations: list[dict[int, tuple[float, float]]] = [ From b48c66d83b359e1970aa7d3681a0dc0274e1b24f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9na=20P=C3=A9renn=C3=A8s?= Date: Fri, 30 Aug 2024 16:24:36 +0200 Subject: [PATCH 15/31] updated TODO --- TODO.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index a5b5de32..55192d42 100644 --- a/TODO.md +++ b/TODO.md @@ -31,11 +31,11 @@ Léna: [] gradients/base/base_sampler_gradient.py [] gradients/base/base_qgt.py [] gradients/base/base_estimator_gradient.py -[] minimum_eigensolvers/vqe.py -[] minimum_eigensolvers/adapt_vqe.py +[x] minimum_eigensolvers/vqe.py +[x] minimum_eigensolvers/adapt_vqe.py [] minimum_eigensolvers/qaoa.py -[] minimum_eigensolvers/diagonal_estimator.py -[] minimum_eigensolvers/sampling_vqe.py +[x] minimum_eigensolvers/diagonal_estimator.py +[x] minimum_eigensolvers/sampling_vqe.py [] amplitude_estimators/mlae.py [] amplitude_estimators/fae.py [] amplitude_estimators/iae.py From 1cd76ff22ef4582ec8ca62a49800d76a44e444bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9na=20P=C3=A9renn=C3=A8s?= Date: Fri, 30 Aug 2024 16:44:30 +0200 Subject: [PATCH 16/31] changed qaoa to match v2 and isa --- TODO.md | 2 +- .../minimum_eigensolvers/qaoa.py | 25 ++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index 55192d42..328ae4f6 100644 --- a/TODO.md +++ b/TODO.md @@ -33,7 +33,7 @@ Léna: [] gradients/base/base_estimator_gradient.py [x] minimum_eigensolvers/vqe.py [x] minimum_eigensolvers/adapt_vqe.py -[] minimum_eigensolvers/qaoa.py +[x] minimum_eigensolvers/qaoa.py [x] minimum_eigensolvers/diagonal_estimator.py [x] minimum_eigensolvers/sampling_vqe.py [] amplitude_estimators/mlae.py diff --git a/qiskit_algorithms/minimum_eigensolvers/qaoa.py b/qiskit_algorithms/minimum_eigensolvers/qaoa.py index 8953b795..8f29d1d6 100644 --- a/qiskit_algorithms/minimum_eigensolvers/qaoa.py +++ b/qiskit_algorithms/minimum_eigensolvers/qaoa.py @@ -20,12 +20,13 @@ from qiskit.circuit import QuantumCircuit from qiskit.circuit.library.n_local.qaoa_ansatz import QAOAAnsatz from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.primitives import BaseSampler +from qiskit.primitives import BaseSamplerV2 from qiskit_algorithms.utils.validation import validate_min from qiskit_algorithms.optimizers import Minimizer, Optimizer from .sampling_vqe import SamplingVQE +from ..custom_types import Transpiler class QAOA(SamplingVQE): @@ -54,7 +55,7 @@ class QAOA(SamplingVQE): the QAOA object has been constructed. Attributes: - sampler (BaseSampler): The sampler primitive to sample the circuits. + sampler (BaseSamplerV2): The sampler primitive to sample the circuits. optimizer (Optimizer | Minimizer): A classical optimizer to find the minimum energy. This can either be an :class:`.Optimizer` or a callable implementing the :class:`.Minimizer` protocol. @@ -71,6 +72,11 @@ class QAOA(SamplingVQE): that can access the intermediate data at each optimization step. These data are: the evaluation count, the optimizer parameters for the ansatz, the evaluated value, and the metadata dictionary. + 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. + References: [1]: Farhi, E., Goldstone, J., Gutmann, S., "A Quantum Approximate Optimization Algorithm" @@ -85,7 +91,7 @@ class QAOA(SamplingVQE): def __init__( self, - sampler: BaseSampler, + sampler: BaseSamplerV2, optimizer: Optimizer | Minimizer, *, reps: int = 1, @@ -94,6 +100,8 @@ def __init__( initial_point: np.ndarray | None = None, aggregation: float | Callable[[list[float]], float] | None = None, callback: Callable[[int, np.ndarray, float, dict[str, Any]], None] | None = None, + transpiler: Transpiler | None = None, + transpiler_options: dict[str, Any] | None = None, ) -> None: r""" Args: @@ -117,6 +125,10 @@ def __init__( callback: A callback that can access the intermediate data at each optimization step. These data are: the evaluation count, the optimizer parameters for the ansatz, the evaluated value, the metadata dictionary. + 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. """ validate_min("reps", reps, 1) @@ -124,6 +136,8 @@ def __init__( self.mixer = mixer self.initial_state = initial_state self._cost_operator = None + self._transpiler = transpiler + self._transpiler_options = transpiler_options if transpiler_options is not None else {} super().__init__( sampler=sampler, @@ -136,6 +150,9 @@ def __init__( def _check_operator_ansatz(self, operator: BaseOperator): # Recreates a circuit based on operator parameter. - self.ansatz = QAOAAnsatz( + ansatz = QAOAAnsatz( operator, self.reps, initial_state=self.initial_state, mixer_operator=self.mixer ).decompose() # TODO remove decompose once #6674 is fixed + if self._transpiler is not None: + ansatz = self._transpiler.run(ansatz, **self._transpiler_options) + self.ansatz=ansatz From 4f734d9eea0d059a5c748e1d958edbb7b6620baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9na=20P=C3=A9renn=C3=A8s?= Date: Fri, 30 Aug 2024 18:21:18 +0200 Subject: [PATCH 17/31] modified all associated tests for min_eigen classes --- test/minimum_eigensolvers/test_adapt_vqe.py | 18 +++++----- test/minimum_eigensolvers/test_qaoa.py | 4 +-- .../minimum_eigensolvers/test_sampling_vqe.py | 33 +++++++++---------- test/minimum_eigensolvers/test_vqe.py | 8 ++--- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/test/minimum_eigensolvers/test_adapt_vqe.py b/test/minimum_eigensolvers/test_adapt_vqe.py index 4a13c9ca..011a3c93 100644 --- a/test/minimum_eigensolvers/test_adapt_vqe.py +++ b/test/minimum_eigensolvers/test_adapt_vqe.py @@ -21,7 +21,7 @@ from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.circuit.library import EvolvedOperatorAnsatz -from qiskit.primitives import Estimator +from qiskit.primitives import StatevectorEstimator from qiskit.quantum_info import SparsePauliOp from qiskit_algorithms.minimum_eigensolvers import VQE @@ -83,7 +83,7 @@ def setUp(self): def test_default(self): """Default execution""" - calc = AdaptVQE(VQE(Estimator(), self.ansatz, self.optimizer)) + calc = AdaptVQE(VQE(StatevectorEstimator(), self.ansatz, self.optimizer)) res = calc.compute_minimum_eigenvalue(operator=self.h2_op) @@ -98,7 +98,7 @@ def test_with_quantum_info(self): self.excitation_pool, initial_state=self.initial_state, ) - calc = AdaptVQE(VQE(Estimator(), ansatz, self.optimizer)) + calc = AdaptVQE(VQE(StatevectorEstimator(), ansatz, self.optimizer)) res = calc.compute_minimum_eigenvalue(operator=self.h2_op) @@ -110,7 +110,7 @@ def test_with_quantum_info(self): def test_converged(self): """Test to check termination criteria""" calc = AdaptVQE( - VQE(Estimator(), self.ansatz, self.optimizer), + VQE(StatevectorEstimator(), self.ansatz, self.optimizer), gradient_threshold=1e-3, ) res = calc.compute_minimum_eigenvalue(operator=self.h2_op) @@ -120,7 +120,7 @@ def test_converged(self): def test_maximum(self): """Test to check termination criteria""" calc = AdaptVQE( - VQE(Estimator(), self.ansatz, self.optimizer), + VQE(StatevectorEstimator(), self.ansatz, self.optimizer), max_iterations=1, ) res = calc.compute_minimum_eigenvalue(operator=self.h2_op) @@ -144,7 +144,7 @@ def test_eigenvalue_threshold(self): ) calc = AdaptVQE( - VQE(Estimator(), ansatz, self.optimizer), + VQE(StatevectorEstimator(), ansatz, self.optimizer), eigenvalue_threshold=1, ) res = calc.compute_minimum_eigenvalue(operator) @@ -185,14 +185,14 @@ def test_cyclicity(self, seq, is_cycle): def test_vqe_solver(self): """Test to check if the VQE solver remains the same or not""" - solver = VQE(Estimator(), self.ansatz, self.optimizer) + solver = VQE(StatevectorEstimator(), self.ansatz, self.optimizer) calc = AdaptVQE(solver) _ = calc.compute_minimum_eigenvalue(operator=self.h2_op) self.assertEqual(solver.ansatz, calc.solver.ansatz) def test_gradient_calculation(self): """Test to check if the gradient calculation""" - solver = VQE(Estimator(), QuantumCircuit(1), self.optimizer) + solver = VQE(StatevectorEstimator(), QuantumCircuit(1), self.optimizer) calc = AdaptVQE(solver) calc._excitation_pool = [SparsePauliOp("X")] res = calc._compute_gradients(operator=SparsePauliOp("Y"), theta=[]) @@ -201,7 +201,7 @@ def test_gradient_calculation(self): def test_supports_aux_operators(self): """Test that auxiliary operators are supported""" - calc = AdaptVQE(VQE(Estimator(), self.ansatz, self.optimizer)) + calc = AdaptVQE(VQE(StatevectorEstimator(), self.ansatz, self.optimizer)) res = calc.compute_minimum_eigenvalue(operator=self.h2_op, aux_operators=[self.h2_op]) expected_eigenvalue = -1.85727503 diff --git a/test/minimum_eigensolvers/test_qaoa.py b/test/minimum_eigensolvers/test_qaoa.py index 17a15773..503cfa3e 100644 --- a/test/minimum_eigensolvers/test_qaoa.py +++ b/test/minimum_eigensolvers/test_qaoa.py @@ -23,7 +23,7 @@ from qiskit import QuantumCircuit from qiskit.circuit import Parameter -from qiskit.primitives import Sampler +from qiskit.primitives import StatevectorSampler from qiskit.quantum_info import Pauli, SparsePauliOp from qiskit.result import QuasiDistribution @@ -67,7 +67,7 @@ def setUp(self): super().setUp() self.seed = 10598 algorithm_globals.random_seed = self.seed - self.sampler = Sampler() + self.sampler = StatevectorSampler() @idata( [ diff --git a/test/minimum_eigensolvers/test_sampling_vqe.py b/test/minimum_eigensolvers/test_sampling_vqe.py index 35b84a3a..7fb5bda6 100644 --- a/test/minimum_eigensolvers/test_sampling_vqe.py +++ b/test/minimum_eigensolvers/test_sampling_vqe.py @@ -23,7 +23,7 @@ from qiskit.circuit import ParameterVector, QuantumCircuit from qiskit.circuit.library import RealAmplitudes, TwoLocal -from qiskit.primitives import Sampler +from qiskit.primitives import StatevectorSampler from qiskit.quantum_info import Pauli, SparsePauliOp from qiskit_algorithms import AlgorithmError @@ -79,7 +79,7 @@ def test_exact_sampler(self, op): initial_point = np.zeros(ansatz.num_parameters) initial_point[-ansatz.num_qubits :] = np.pi / 2 - vqe = SamplingVQE(Sampler(), ansatz, optimizer, initial_point=initial_point) + vqe = SamplingVQE(StatevectorSampler(), ansatz, optimizer, initial_point=initial_point) result = vqe.compute_minimum_eigenvalue(operator=op) with self.subTest(msg="test eigenvalue"): @@ -107,7 +107,7 @@ def test_invalid_initial_point(self, op): ansatz = RealAmplitudes(2, reps=1) initial_point = np.array([1]) - vqe = SamplingVQE(Sampler(), ansatz, SLSQP(), initial_point=initial_point) + vqe = SamplingVQE(StatevectorSampler(), ansatz, SLSQP(), initial_point=initial_point) with self.assertRaises(ValueError): _ = vqe.compute_minimum_eigenvalue(operator=op) @@ -116,7 +116,7 @@ def test_invalid_initial_point(self, op): def test_ansatz_resize(self, op): """Test the ansatz is properly resized if it's a blueprint circuit.""" ansatz = RealAmplitudes(1, reps=1) - vqe = SamplingVQE(Sampler(), ansatz, SLSQP()) + vqe = SamplingVQE(StatevectorSampler(), ansatz, SLSQP()) result = vqe.compute_minimum_eigenvalue(operator=op) self.assertAlmostEqual(result.eigenvalue, self.optimal_value, places=5) @@ -125,7 +125,7 @@ def test_invalid_ansatz_size(self, op): """Test an error is raised if the ansatz has the wrong number of qubits.""" ansatz = QuantumCircuit(1) ansatz.compose(RealAmplitudes(1, reps=2)) - vqe = SamplingVQE(Sampler(), ansatz, SLSQP()) + vqe = SamplingVQE(StatevectorSampler(), ansatz, SLSQP()) with self.assertRaises(AlgorithmError): _ = vqe.compute_minimum_eigenvalue(operator=op) @@ -134,7 +134,7 @@ def test_invalid_ansatz_size(self, op): def test_missing_varform_params(self, op): """Test specifying a variational form with no parameters raises an error.""" circuit = QuantumCircuit(op.num_qubits) - vqe = SamplingVQE(Sampler(), circuit, SLSQP()) + vqe = SamplingVQE(StatevectorSampler(), circuit, SLSQP()) with self.assertRaises(AlgorithmError): vqe.compute_minimum_eigenvalue(operator=op) @@ -142,7 +142,7 @@ def test_missing_varform_params(self, op): def test_batch_evaluate_slsqp(self, op): """Test batching with SLSQP (as representative of SciPyOptimizer).""" optimizer = SLSQP(max_evals_grouped=10) - vqe = SamplingVQE(Sampler(), RealAmplitudes(), optimizer) + vqe = SamplingVQE(StatevectorSampler(), RealAmplitudes(), optimizer) result = vqe.compute_minimum_eigenvalue(operator=op) self.assertAlmostEqual(result.eigenvalue, self.optimal_value, places=5) @@ -150,8 +150,8 @@ def test_batch_evaluate_with_qnspsa(self): """Test batch evaluating with QNSPSA works.""" ansatz = TwoLocal(2, rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - wrapped_sampler = Sampler() - inner_sampler = Sampler() + wrapped_sampler = StatevectorSampler() + inner_sampler = StatevectorSampler() callcount = {"count": 0} @@ -164,8 +164,7 @@ def wrapped_run(*args, **kwargs): fidelity = ComputeUncompute(wrapped_sampler) def fidelity_callable(left, right): - batchsize = np.asarray(left).shape[0] - job = fidelity.run(batchsize * [ansatz], batchsize * [ansatz], left, right) + job = fidelity.run(ansatz, ansatz, left, right) return job.result().fidelities qnspsa = QNSPSA(fidelity_callable, maxiter=5) @@ -183,7 +182,7 @@ def fidelity_callable(left, right): def test_optimizer_scipy_callable(self): """Test passing a SciPy optimizer directly as callable.""" vqe = SamplingVQE( - Sampler(), + StatevectorSampler(), RealAmplitudes(), partial(scipy_minimize, method="COBYLA", options={"maxiter": 2}), ) @@ -193,7 +192,7 @@ def test_optimizer_scipy_callable(self): def test_optimizer_callable(self): """Test passing a optimizer directly as callable.""" ansatz = RealAmplitudes(1, reps=1) - vqe = SamplingVQE(Sampler(), ansatz, _mock_optimizer) + vqe = SamplingVQE(StatevectorSampler(), ansatz, _mock_optimizer) result = vqe.compute_minimum_eigenvalue(Pauli("Z")) self.assertTrue(np.all(result.optimal_point == np.zeros(ansatz.num_parameters))) @@ -201,7 +200,7 @@ def test_optimizer_callable(self): def test_auxops(self, op): """Test passing auxiliary operators.""" ansatz = RealAmplitudes(2, reps=1) - vqe = SamplingVQE(Sampler(), ansatz, SLSQP()) + vqe = SamplingVQE(StatevectorSampler(), ansatz, SLSQP()) as_list = [Pauli("ZZ"), Pauli("II")] with self.subTest(auxops=as_list): @@ -220,7 +219,7 @@ def test_auxops(self, op): def test_nondiag_observable_raises(self): """Test passing a non-diagonal observable raises an error.""" - vqe = SamplingVQE(Sampler(), RealAmplitudes(), SLSQP()) + vqe = SamplingVQE(StatevectorSampler(), RealAmplitudes(), SLSQP()) with self.assertRaises(ValueError): _ = vqe.compute_minimum_eigenvalue(Pauli("X")) @@ -242,7 +241,7 @@ def store_intermediate_result(eval_count, parameters, mean, metadata): history["metadata"].append(metadata) sampling_vqe = SamplingVQE( - Sampler(), + StatevectorSampler(), RealAmplitudes(2, reps=1), SLSQP(), callback=store_intermediate_result, @@ -271,7 +270,7 @@ def best_measurement(measurements): for aggregation in [alpha, best_measurement]: with self.subTest(aggregation=aggregation): - vqe = SamplingVQE(Sampler(), ansatz, _mock_optimizer, aggregation=best_measurement) + vqe = SamplingVQE(StatevectorSampler(), ansatz, _mock_optimizer, aggregation=best_measurement) result = vqe.compute_minimum_eigenvalue(Pauli("Z")) # evaluation at x0=0 samples -1 and 1 with 50% probability, and our aggregation diff --git a/test/minimum_eigensolvers/test_vqe.py b/test/minimum_eigensolvers/test_vqe.py index a0a7b2c2..a1c64a8e 100644 --- a/test/minimum_eigensolvers/test_vqe.py +++ b/test/minimum_eigensolvers/test_vqe.py @@ -23,7 +23,7 @@ from qiskit import QuantumCircuit from qiskit.circuit.library import RealAmplitudes, TwoLocal from qiskit.quantum_info import SparsePauliOp, Operator, Pauli -from qiskit.primitives import Estimator, Sampler, StatevectorEstimator, StatevectorSampler +from qiskit.primitives import StatevectorEstimator, StatevectorSampler from qiskit_algorithms import AlgorithmError from qiskit_algorithms.gradients import ParamShiftEstimatorGradient @@ -305,8 +305,8 @@ def test_batch_evaluate_with_qnspsa(self): """Test batch evaluating with QNSPSA works.""" ansatz = TwoLocal(2, rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - wrapped_sampler = Sampler() - inner_sampler = Sampler() + wrapped_sampler = StatevectorSampler() + inner_sampler = StatevectorSampler() wrapped_estimator = StatevectorEstimator() inner_estimator = StatevectorEstimator() @@ -328,7 +328,7 @@ def wrapped_sampler_run(*args, **kwargs): def fidelity_callable(left, right): batchsize = np.asarray(left).shape[0] - job = fidelity.run(batchsize * [ansatz], batchsize * [ansatz], left, right) + job = fidelity.run(ansatz, ansatz, left, right) return job.result().fidelities qnspsa = QNSPSA(fidelity_callable, maxiter=5) From d7b671f42c68096d92411e17abd49a8e98d39962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9na=20P=C3=A9renn=C3=A8s?= Date: Fri, 30 Aug 2024 18:26:35 +0200 Subject: [PATCH 18/31] changed todo --- TODO.md | 78 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/TODO.md b/TODO.md index 328ae4f6..d9deb547 100644 --- a/TODO.md +++ b/TODO.md @@ -1,42 +1,42 @@ Tristan: -[] phase_estimators/hamiltonian_phase_estimation.py -[] phase_estimators/ipe.py -[] phase_estimators/phase_estimation.py -[] eigensolvers/vqd.py -[] amplitude_amplifiers/grover.py -[] time_evolvers/pvqd/utils.py -[] time_evolvers/pvqd/pvqd.py -[] time_evolvers/trotterization/trotter_qrte.py -[] time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py -[] time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py -[] time_evolvers/variational/var_qite.py -[] time_evolvers/variational/var_qrte.py -[] time_evolvers/variational/var_qte.py -[] state_fidelities/compute_uncompute.py -[] optimizers/qnspsa.py -[] optimizers/umda.py -[] optimizers/spsa.py -[] observables_evaluator.py +- [] phase_estimators/hamiltonian_phase_estimation.py +- [] phase_estimators/ipe.py +- [] phase_estimators/phase_estimation.py +- [] eigensolvers/vqd.py +- [] amplitude_amplifiers/grover.py +- [] time_evolvers/pvqd/utils.py +- [] time_evolvers/pvqd/pvqd.py +- [] time_evolvers/trotterization/trotter_qrte.py +- [] time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py +- [] time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py +- [] time_evolvers/variational/var_qite.py +- [] time_evolvers/variational/var_qrte.py +- [] time_evolvers/variational/var_qte.py +- [] state_fidelities/compute_uncompute.py +- [] optimizers/qnspsa.py +- [] optimizers/umda.py +- [] optimizers/spsa.py +- [] observables_evaluator.py Léna: -[] gradients/reverse/reverse_gradient.py -[] gradients/reverse/reverse_qgt.py -[] gradients/finite_diff/finite_diff_estimator_gradient.py -[] gradients/finite_diff/finite_diff_sampler_gradient.py -[] gradients/spsa/spsa_estimator_gradient.py -[] gradients/spsa/spsa_sampler_gradient.py -[] gradients/lin_comb/lin_comb_sampler_gradient.py -[] gradients/lin_comb/lin_comb_estimator_gradient.py -[] gradients/lin_comb/lin_comb_qgt.py -[] gradients/base/base_sampler_gradient.py -[] gradients/base/base_qgt.py -[] gradients/base/base_estimator_gradient.py -[x] minimum_eigensolvers/vqe.py -[x] minimum_eigensolvers/adapt_vqe.py -[x] minimum_eigensolvers/qaoa.py -[x] minimum_eigensolvers/diagonal_estimator.py -[x] minimum_eigensolvers/sampling_vqe.py -[] amplitude_estimators/mlae.py -[] amplitude_estimators/fae.py -[] amplitude_estimators/iae.py -[] amplitude_estimators/ae.py +- [] gradients/reverse/reverse_gradient.py +- [] gradients/reverse/reverse_qgt.py +- [] gradients/finite_diff/finite_diff_estimator_gradient.py +- [] gradients/finite_diff/finite_diff_sampler_gradient.py +- [] gradients/spsa/spsa_estimator_gradient.py +- [] gradients/spsa/spsa_sampler_gradient.py +- [] gradients/lin_comb/lin_comb_sampler_gradient.py +- [] gradients/lin_comb/lin_comb_estimator_gradient.py +- [] gradients/lin_comb/lin_comb_qgt.py +- [] gradients/base/base_sampler_gradient.py +- [] gradients/base/base_qgt.py +- [] gradients/base/base_estimator_gradient.py +- [x] minimum_eigensolvers/vqe.py +- [x] minimum_eigensolvers/adapt_vqe.py +- [x] minimum_eigensolvers/qaoa.py +- [x] minimum_eigensolvers/diagonal_estimator.py +- [x] minimum_eigensolvers/sampling_vqe.py +- [] amplitude_estimators/mlae.py +- [] amplitude_estimators/fae.py +- [] amplitude_estimators/iae.py +- [] amplitude_estimators/ae.py From d2bc2db7bfcadf56b9c665195a4014285ccd7afa Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Tue, 3 Sep 2024 01:12:26 +0200 Subject: [PATCH 19/31] ComputeUncompute done --- qiskit_algorithms/algorithm_job.py | 22 +- .../gradients/base/base_estimator_gradient.py | 2 +- qiskit_algorithms/gradients/base/base_qgt.py | 2 +- .../gradients/base/base_sampler_gradient.py | 2 +- qiskit_algorithms/gradients/qfi.py | 2 +- .../diagonal_estimator.py | 2 +- .../hamiltonian_phase_estimation.py | 6 + .../phase_estimators/phase_estimation.py | 5 + .../state_fidelities/base_state_fidelity.py | 47 ++- .../state_fidelities/compute_uncompute.py | 129 ++++---- .../state_fidelities/state_fidelity_result.py | 8 +- .../test_compute_uncompute.py | 313 +++++++++++++----- 12 files changed, 359 insertions(+), 181 deletions(-) diff --git a/qiskit_algorithms/algorithm_job.py b/qiskit_algorithms/algorithm_job.py index b1215574..0ec841bc 100644 --- a/qiskit_algorithms/algorithm_job.py +++ b/qiskit_algorithms/algorithm_job.py @@ -20,26 +20,6 @@ class AlgorithmJob(PrimitiveJob): """ This class is introduced for typing purposes and provides no additional function beyond that inherited from its parents. - - Update: :meth:`AlgorithmJob.submit()` method added. See its - documentation for more info. """ - def submit(self) -> None: - """ - Submit the job for execution. - - For V1 primitives, Qiskit ``PrimitiveJob`` subclassed JobV1 and defined ``submit()``. - ``PrimitiveJob`` was updated for V2 primitives, no longer subclasses ``JobV1``, and - now has a private ``_submit()`` method, with ``submit()`` being deprecated as of - Qiskit version 0.46. This maintains the ``submit()`` for ``AlgorithmJob`` here as - it's called in many places for such a job. An alternative could be to make - 0.46 the required minimum version and alter all algorithm's call sites to use - ``_submit()`` and make this an empty class again as it once was. For now this - way maintains compatibility with the current min version of 0.44. - """ - # TODO: Considering changing this in the future - see above docstring. - try: - super()._submit() - except AttributeError: - super().submit() + pass diff --git a/qiskit_algorithms/gradients/base/base_estimator_gradient.py b/qiskit_algorithms/gradients/base/base_estimator_gradient.py index f7ea927b..bfc79836 100644 --- a/qiskit_algorithms/gradients/base/base_estimator_gradient.py +++ b/qiskit_algorithms/gradients/base/base_estimator_gradient.py @@ -149,7 +149,7 @@ def run( job = AlgorithmJob( self._run, circuits, observables, parameter_values, parameters, **opts.__dict__ ) - job.submit() + job._submit() return job @abstractmethod diff --git a/qiskit_algorithms/gradients/base/base_qgt.py b/qiskit_algorithms/gradients/base/base_qgt.py index 2e254a8f..24e2e2d1 100644 --- a/qiskit_algorithms/gradients/base/base_qgt.py +++ b/qiskit_algorithms/gradients/base/base_qgt.py @@ -161,7 +161,7 @@ def run( opts = copy(self._default_options) opts.update_options(**options) job = AlgorithmJob(self._run, circuits, parameter_values, parameters, **opts.__dict__) - job.submit() + job._submit() return job @abstractmethod diff --git a/qiskit_algorithms/gradients/base/base_sampler_gradient.py b/qiskit_algorithms/gradients/base/base_sampler_gradient.py index 1114b5f0..f17da118 100644 --- a/qiskit_algorithms/gradients/base/base_sampler_gradient.py +++ b/qiskit_algorithms/gradients/base/base_sampler_gradient.py @@ -107,7 +107,7 @@ def run( opts = copy(self._default_options) opts.update_options(**options) job = AlgorithmJob(self._run, circuits, parameter_values, parameters, **opts.__dict__) - job.submit() + job._submit() return job @abstractmethod diff --git a/qiskit_algorithms/gradients/qfi.py b/qiskit_algorithms/gradients/qfi.py index 4a53b20d..4bb5623a 100644 --- a/qiskit_algorithms/gradients/qfi.py +++ b/qiskit_algorithms/gradients/qfi.py @@ -103,7 +103,7 @@ def run( opts = copy(self._default_options) opts.update_options(**options) job = AlgorithmJob(self._run, circuits, parameter_values, parameters, **opts.__dict__) - job.submit() + job._submit() return job def _run( diff --git a/qiskit_algorithms/minimum_eigensolvers/diagonal_estimator.py b/qiskit_algorithms/minimum_eigensolvers/diagonal_estimator.py index f0034bc9..80b7ef62 100644 --- a/qiskit_algorithms/minimum_eigensolvers/diagonal_estimator.py +++ b/qiskit_algorithms/minimum_eigensolvers/diagonal_estimator.py @@ -104,7 +104,7 @@ def _run( job = AlgorithmJob( self._call, circuit_indices, observable_indices, parameter_values, **run_options ) - job.submit() + job._submit() return job def _call( diff --git a/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py index 058a884b..16471f61 100644 --- a/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -14,6 +14,8 @@ from __future__ import annotations +from typing import Any + from qiskit import QuantumCircuit from qiskit.circuit.library import PauliEvolutionGate from qiskit.primitives import BaseSamplerV2 @@ -85,6 +87,7 @@ def __init__( num_evaluation_qubits: int, sampler: BaseSamplerV2 | None = None, transpiler: Transpiler | None = None, + transpiler_options: dict[str, Any] | None = None, ) -> None: r""" Args: @@ -94,11 +97,14 @@ def __init__( 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. """ self._phase_estimation = PhaseEstimation( num_evaluation_qubits=num_evaluation_qubits, sampler=sampler, transpiler=transpiler, + transpiler_options=transpiler_options ) def _get_scale(self, hamiltonian, bound=None) -> PhaseEstimationScale: diff --git a/qiskit_algorithms/phase_estimators/phase_estimation.py b/qiskit_algorithms/phase_estimators/phase_estimation.py index 930fed65..e24c10f5 100644 --- a/qiskit_algorithms/phase_estimators/phase_estimation.py +++ b/qiskit_algorithms/phase_estimators/phase_estimation.py @@ -15,6 +15,8 @@ from __future__ import annotations +from typing import Any + import numpy import qiskit from qiskit import circuit @@ -85,6 +87,7 @@ def __init__( num_evaluation_qubits: int, sampler: BaseSamplerV2 | None = None, transpiler: Transpiler | None = None, + transpiler_options: dict[str, Any] | None = None, ) -> None: r""" Args: @@ -94,6 +97,8 @@ def __init__( 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: AlgorithmError: If a sampler is not provided diff --git a/qiskit_algorithms/state_fidelities/base_state_fidelity.py b/qiskit_algorithms/state_fidelities/base_state_fidelity.py index 5c1199c4..344849c2 100644 --- a/qiskit_algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit_algorithms/state_fidelities/base_state_fidelity.py @@ -16,7 +16,7 @@ from __future__ import annotations from abc import ABC, abstractmethod from collections.abc import MutableMapping -from typing import cast, Sequence, List +from typing import cast, Sequence, List, Any import numpy as np from qiskit import QuantumCircuit @@ -24,6 +24,7 @@ from qiskit.primitives.utils import _circuit_key from ..algorithm_job import AlgorithmJob +from ..custom_types import Transpiler class BaseStateFidelity(ABC): @@ -42,10 +43,19 @@ class BaseStateFidelity(ABC): """ - def __init__(self) -> None: - + def __init__(self, transpiler: Transpiler | None = None, transpiler_options: dict[str, Any] | None = None,) -> None: + r""" + Args: + 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. + """ # use cache for preventing unnecessary circuit compositions self._circuit_cache: MutableMapping[tuple[int, int], QuantumCircuit] = {} + self._transpiler = transpiler + self._transpiler_options = transpiler_options if transpiler_options is not None else {} @staticmethod def _preprocess_values( @@ -195,6 +205,9 @@ def _construct_circuits( # update cache self._circuit_cache[_circuit_key(circuit_1), _circuit_key(circuit_2)] = circuit + if self._transpiler is not None: + return self._transpiler.run(circuits, **self._transpiler_options) + return circuits def _construct_value_list( @@ -245,7 +258,7 @@ def _run( circuits_2: QuantumCircuit | Sequence[QuantumCircuit], values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, - **options, + shots: int | Sequence[int] | None = None, ) -> AlgorithmJob: r""" Computes the state overlap (fidelity) calculation between two @@ -257,10 +270,12 @@ def _run( circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. values_1: Numerical parameters to be bound to the first set of circuits values_2: Numerical parameters to be bound to the second set of circuits. - options: Primitive backend runtime options used for circuit execution. The order - of priority is\: options in ``run`` method > fidelity's default - options > primitive's default setting. - Higher priority setting overrides lower priority setting. + shots: Number of shots to be used by the underlying sampler. If a single integer is + provided, this number will be used for all circuits. If a sequence of integers is + provided, they will be used on a per-circuit basis. If none is provided, the + fidelity's default number of shots will be used for all circuits. If this number is + also set to None, the underlying primitive's default number of shots will be used + for all circuits. Returns: A newly constructed algorithm job instance to get the fidelity result. @@ -273,7 +288,7 @@ def run( circuits_2: QuantumCircuit | Sequence[QuantumCircuit], values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, - **options, + shots: int | Sequence[int] | None = None, ) -> AlgorithmJob: r""" Runs asynchronously the state overlap (fidelity) calculation between two @@ -286,18 +301,20 @@ def run( circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. values_1: Numerical parameters to be bound to the first set of circuits. values_2: Numerical parameters to be bound to the second set of circuits. - options: Primitive backend runtime options used for circuit execution. The order - of priority is\: options in ``run`` method > fidelity's default - options > primitive's default setting. - Higher priority setting overrides lower priority setting. + shots: Number of shots to be used by the underlying sampler. If a single integer is + provided, this number will be used for all circuits. If a sequence of integers is + provided, they will be used on a per-circuit basis. If none is provided, the + fidelity's default number of shots will be used for all circuits. If this number is + also set to None, the underlying primitive's default number of shots will be used + for all circuits. Returns: Primitive job for the fidelity calculation. The job's result is an instance of :class:`.StateFidelityResult`. """ - job = self._run(circuits_1, circuits_2, values_1, values_2, **options) + job = self._run(circuits_1, circuits_2, values_1, values_2, shots) - job.submit() + job._submit() return job @staticmethod diff --git a/qiskit_algorithms/state_fidelities/compute_uncompute.py b/qiskit_algorithms/state_fidelities/compute_uncompute.py index b16e4011..83143f43 100644 --- a/qiskit_algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit_algorithms/state_fidelities/compute_uncompute.py @@ -14,18 +14,20 @@ """ from __future__ import annotations + from collections.abc import Sequence -from copy import copy +from typing import Any from qiskit import QuantumCircuit -from qiskit.primitives import BaseSampler +from qiskit.primitives import BaseSamplerV2 +from qiskit.primitives.containers.sampler_pub import SamplerPub from qiskit.primitives.primitive_job import PrimitiveJob -from qiskit.providers import Options -from ..exceptions import AlgorithmError from .base_state_fidelity import BaseStateFidelity from .state_fidelity_result import StateFidelityResult from ..algorithm_job import AlgorithmJob +from ..custom_types import Transpiler +from ..exceptions import AlgorithmError class ComputeUncompute(BaseStateFidelity): @@ -53,16 +55,18 @@ class ComputeUncompute(BaseStateFidelity): def __init__( self, - sampler: BaseSampler, - options: Options | None = None, + sampler: BaseSamplerV2, + shots: int | None = None, local: bool = False, + transpiler: Transpiler | None = None, + transpiler_options: dict[str, Any] | None = None, ) -> None: r""" Args: sampler: Sampler primitive instance. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > fidelity's - default options > primitive's default setting. + shots: Number of shots to be used by the underlying sampler. + The order of priority is: number of shots in ``run`` method > fidelity's + number of shots > primitive's default number of shots. Higher priority setting overrides lower priority setting. local: If set to ``True``, the fidelity is averaged over single-qubit projectors @@ -75,20 +79,23 @@ def __init__( This coincides with the standard (global) fidelity in the limit of the fidelity approaching 1. Might be used to increase the variance to improve trainability in algorithms such as :class:`~.time_evolvers.PVQD`. + 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 the sampler is not an instance of ``BaseSampler``. + ValueError: If the sampler is not an instance of ``BaseSamplerV2``. """ - if not isinstance(sampler, BaseSampler): + if not isinstance(sampler, BaseSamplerV2): raise ValueError( - f"The sampler should be an instance of BaseSampler, " f"but got {type(sampler)}" + f"The sampler should be an instance of BaseSamplerV2, " f"but got {type(sampler)}" ) - self._sampler: BaseSampler = sampler + self._sampler: BaseSamplerV2 = sampler self._local = local - self._default_options = Options() - if options is not None: - self._default_options.update_options(**options) - super().__init__() + self._shots = shots + super().__init__(transpiler, transpiler_options) def create_fidelity_circuit( self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit @@ -119,7 +126,7 @@ def _run( circuits_2: QuantumCircuit | Sequence[QuantumCircuit], values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, - **options, + shots: int | Sequence[int] | None = None, ) -> AlgorithmJob: r""" Computes the state overlap (fidelity) calculation between two @@ -131,10 +138,12 @@ def _run( circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. values_1: Numerical parameters to be bound to the first circuits. values_2: Numerical parameters to be bound to the second circuits. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > fidelity's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting. + shots: Number of shots to be used by the underlying sampler. If a single integer is + provided, this number will be used for all circuits. If a sequence of integers is + provided, they will be used on a per-circuit basis. If none is provided, the + fidelity's default number of shots will be used for all circuits. If this number is + also set to None, the underlying primitive's default number of shots will be used + for all circuits. Returns: An AlgorithmJob for the fidelity calculation. @@ -151,79 +160,79 @@ def _run( ) values = self._construct_value_list(circuits_1, circuits_2, values_1, values_2) - # The priority of run options is as follows: - # options in `evaluate` method > fidelity's default options > - # primitive's default options. - opts = copy(self._default_options) - opts.update_options(**options) + # The priority of number of shots options is as follows: + # number in `run` method > fidelity's default number of shots > + # primitive's default number of shots. + if not isinstance(shots, Sequence): + if shots is None: + shots = self.shots + coerced_pubs = [SamplerPub.coerce((circuit, value), shots) for circuit, value in zip(circuits, values)] + else: + coerced_pubs = [SamplerPub.coerce((circuit, value), shots_number) for circuit, value, shots_number in zip(circuits, values, shots)] - sampler_job = self._sampler.run(circuits=circuits, parameter_values=values, **opts.__dict__) + job = self._sampler.run(coerced_pubs) - local_opts = self._get_local_options(opts.__dict__) - return AlgorithmJob(ComputeUncompute._call, sampler_job, circuits, self._local, local_opts) + return AlgorithmJob(ComputeUncompute._call, job, circuits, self._local) @staticmethod def _call( - job: PrimitiveJob, circuits: Sequence[QuantumCircuit], local: bool, local_opts: Options + job: PrimitiveJob, circuits: Sequence[QuantumCircuit], local: bool ) -> StateFidelityResult: try: result = job.result() except Exception as exc: raise AlgorithmError("Sampler job failed!") from exc + pub_results_data = [getattr(pub_result.data, circuit.cregs[0].name) for pub_result, circuit in zip(result, circuits)] + quasi_dists = [ + { + label: value / prob_dist.num_shots + for label, value in prob_dist.get_int_counts().items() + } + for prob_dist in pub_results_data + ] + if local: raw_fidelities = [ ComputeUncompute._get_local_fidelity(prob_dist, circuit.num_qubits) - for prob_dist, circuit in zip(result.quasi_dists, circuits) + for prob_dist, circuit in zip(quasi_dists, circuits) ] else: raw_fidelities = [ - ComputeUncompute._get_global_fidelity(prob_dist) for prob_dist in result.quasi_dists + ComputeUncompute._get_global_fidelity(prob_dist) for prob_dist in quasi_dists ] fidelities = ComputeUncompute._truncate_fidelities(raw_fidelities) + shots = [pub_result_data.num_shots for pub_result_data in pub_results_data] + + if len(shots) == 1: + shots = shots[0] return StateFidelityResult( fidelities=fidelities, raw_fidelities=raw_fidelities, metadata=result.metadata, - options=local_opts, + shots=shots, ) @property - def options(self) -> Options: - """Return the union of estimator options setting and fidelity default options, - where, if the same field is set in both, the fidelity's default options override - the primitive's default setting. + def shots(self) -> int | None: + """Return the number of shots used by the `run` method of the Sampler primitive. If None, + the default number of shots of the primitive is used. Returns: - The fidelity default + estimator options. + The default number of shots. """ - return self._get_local_options(self._default_options.__dict__) + return self._shots - def update_default_options(self, **options): - """Update the fidelity's default options setting. + @shots.setter + def shots(self, shots: int | None): + """Update the fidelity's default number of shots setting. Args: - **options: The fields to update the default options. + shots: The new default number of shots. """ - self._default_options.update_options(**options) - - def _get_local_options(self, options: Options) -> Options: - """Return the union of the primitive's default setting, - the fidelity default options, and the options in the ``run`` method. - The order of priority is: options in ``run`` method > fidelity's - default options > primitive's default setting. - - Args: - options: The fields to update the options - - Returns: - The fidelity default + estimator + run options. - """ - opts = copy(self._sampler.options) - opts.update_options(**options) - return opts + self._shots = shots @staticmethod def _get_global_fidelity(probability_distribution: dict[int, float]) -> float: diff --git a/qiskit_algorithms/state_fidelities/state_fidelity_result.py b/qiskit_algorithms/state_fidelities/state_fidelity_result.py index 6dc26dbf..c0f7bd9a 100644 --- a/qiskit_algorithms/state_fidelities/state_fidelity_result.py +++ b/qiskit_algorithms/state_fidelities/state_fidelity_result.py @@ -16,10 +16,8 @@ from __future__ import annotations from collections.abc import Sequence, Mapping -from typing import Any from dataclasses import dataclass - -from qiskit.providers import Options +from typing import Any @dataclass(frozen=True) @@ -33,5 +31,5 @@ class StateFidelityResult: depending on the error mitigation method used.""" metadata: Sequence[Mapping[str, Any]] """Additional information about the fidelity calculation.""" - options: Options - """Primitive runtime options for the execution of the fidelity job.""" + shots: int | Sequence[int] + """Primitive number of shots options for the execution of the fidelity job.""" diff --git a/test/state_fidelities/test_compute_uncompute.py b/test/state_fidelities/test_compute_uncompute.py index a8cfe8f3..73ec7aa1 100644 --- a/test/state_fidelities/test_compute_uncompute.py +++ b/test/state_fidelities/test_compute_uncompute.py @@ -13,17 +13,22 @@ """Tests for Fidelity.""" import unittest +from itertools import product + +from ddt import data, ddt, unpack, idata +from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager + from test import QiskitAlgorithmsTestCase import numpy as np from qiskit.circuit import QuantumCircuit, ParameterVector from qiskit.circuit.library import RealAmplitudes -from qiskit.primitives import Sampler +from qiskit.primitives import StatevectorSampler as Sampler from qiskit_algorithms.state_fidelities import ComputeUncompute - +@ddt class TestComputeUncompute(QiskitAlgorithmsTestCase): """Test Compute-Uncompute Fidelity class""" @@ -49,52 +54,114 @@ def setUp(self): rx_rotation.h(1) self._circuit = [rx_rotations, ry_rotations, plus, zero, rx_rotation] - self._sampler = Sampler() + self._sampler = Sampler(seed=123) self._left_params = np.array([[0, 0], [np.pi / 2, 0], [0, np.pi / 2], [np.pi, np.pi]]) self._right_params = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) - def test_1param_pair(self): - """test for fidelity with one pair of parameters""" - fidelity = ComputeUncompute(self._sampler) - job = fidelity.run( - self._circuit[0], self._circuit[1], self._left_params[0], self._right_params[0] + @idata( + product( + [False, True], + [ + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + None, + ], + ], ) - result = job.result() - np.testing.assert_allclose(result.fidelities, np.array([1.0])) - - def test_1param_pair_local(self): + ) + @unpack + def test_1param_pair(self, local, transpiler_and_options): """test for fidelity with one pair of parameters""" - fidelity = ComputeUncompute(self._sampler, local=True) + transpiler, transpiler_options = transpiler_and_options + fidelity = ComputeUncompute(self._sampler, local=local, transpiler=transpiler, transpiler_options=transpiler_options) job = fidelity.run( self._circuit[0], self._circuit[1], self._left_params[0], self._right_params[0] ) result = job.result() np.testing.assert_allclose(result.fidelities, np.array([1.0])) - def test_local(self): + @idata( + product( + [ + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + None, + ], + ], + [ + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + None, + ], + ], + ) + ) + @unpack + def test_local(self, transpiler_and_options_1, transpiler_and_options_2): """test difference between local and global fidelity""" - fidelity_global = ComputeUncompute(self._sampler, local=False) - fidelity_local = ComputeUncompute(self._sampler, local=True) + transpiler_1, transpiler_options_1 = transpiler_and_options_1 + transpiler_2, transpiler_options_2 = transpiler_and_options_2 + fidelity_global = ComputeUncompute(self._sampler, local=False, transpiler=transpiler_1, transpiler_options=transpiler_options_1) + fidelity_local = ComputeUncompute(self._sampler, local=True, transpiler=transpiler_2, transpiler_options=transpiler_options_2) fidelities = [] for fidelity in [fidelity_global, fidelity_local]: job = fidelity.run(self._circuit[2], self._circuit[3]) result = job.result() fidelities.append(result.fidelities[0]) - np.testing.assert_allclose(fidelities, np.array([0.25, 0.5]), atol=1e-16) - - def test_4param_pairs(self): + np.testing.assert_allclose(fidelities, np.array([0.25, 0.5]), atol=1e-2, rtol=1e-2) + + @data( + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + None, + ], + ) + @unpack + def test_4param_pairs(self, transpiler, transpiler_options): """test for fidelity with four pairs of parameters""" - fidelity = ComputeUncompute(self._sampler) + fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) n = len(self._left_params) job = fidelity.run( [self._circuit[0]] * n, [self._circuit[1]] * n, self._left_params, self._right_params ) results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) - - def test_symmetry(self): + np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-2, rtol=1e-2) + + @data( + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + None, + ], + ) + @unpack + def test_symmetry(self, transpiler, transpiler_options): """test for fidelity with the same circuit""" - fidelity = ComputeUncompute(self._sampler) + fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) n = len(self._left_params) job_1 = fidelity.run( [self._circuit[0]] * n, [self._circuit[0]] * n, self._left_params, self._right_params @@ -104,42 +171,90 @@ def test_symmetry(self): ) results_1 = job_1.result() results_2 = job_2.result() - np.testing.assert_allclose(results_1.fidelities, results_2.fidelities, atol=1e-16) - - def test_no_params(self): + np.testing.assert_allclose(results_1.fidelities, results_2.fidelities, atol=1e-2, rtol=1e-2) + + @data( + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + None, + ], + ) + @unpack + def test_no_params(self, transpiler, transpiler_options): """test for fidelity without parameters""" - fidelity = ComputeUncompute(self._sampler) + fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) job = fidelity.run([self._circuit[2]], [self._circuit[3]]) results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([0.25]), atol=1e-16) + np.testing.assert_allclose(results.fidelities, np.array([0.25]), atol=1e-2, rtol=1e-2) job = fidelity.run([self._circuit[2]], [self._circuit[3]], [], []) results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([0.25]), atol=1e-16) - - def test_left_param(self): + np.testing.assert_allclose(results.fidelities, np.array([0.25]), atol=1e-2, rtol=1e-2) + + @data( + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + None, + ], + ) + @unpack + def test_left_param(self, transpiler, transpiler_options): """test for fidelity with only left parameters""" - fidelity = ComputeUncompute(self._sampler) + fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) n = len(self._left_params) job = fidelity.run( [self._circuit[1]] * n, [self._circuit[3]] * n, values_1=self._left_params ) results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) - - def test_right_param(self): + np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-2, rtol=1e-2) + + @data( + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + None, + ], + ) + @unpack + def test_right_param(self, transpiler, transpiler_options): """test for fidelity with only right parameters""" - fidelity = ComputeUncompute(self._sampler) + fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) n = len(self._left_params) job = fidelity.run( [self._circuit[3]] * n, [self._circuit[1]] * n, values_2=self._left_params ) results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) - - def test_not_set_circuits(self): + np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-2, rtol=1e-2) + + @data( + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + None, + ], + ) + @unpack + def test_not_set_circuits(self, transpiler, transpiler_options): """test for fidelity with no circuits.""" - fidelity = ComputeUncompute(self._sampler) + fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) with self.assertRaises(TypeError): job = fidelity.run( circuits_1=None, @@ -149,9 +264,21 @@ def test_not_set_circuits(self): ) job.result() - def test_circuit_mismatch(self): + @data( + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + None, + ], + ) + @unpack + def test_circuit_mismatch(self, transpiler, transpiler_options): """test for fidelity with different number of left/right circuits.""" - fidelity = ComputeUncompute(self._sampler) + fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) n = len(self._left_params) with self.assertRaises(ValueError): job = fidelity.run( @@ -162,23 +289,47 @@ def test_circuit_mismatch(self): ) job.result() - def test_asymmetric_params(self): + @data( + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + None, + ], + ) + @unpack + def test_asymmetric_params(self, transpiler, transpiler_options): """test for fidelity when the 2 circuits have different number of left/right parameters.""" - fidelity = ComputeUncompute(self._sampler) + fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) n = len(self._left_params) right_params = [[p] for p in self._right_params[:, 0]] job = fidelity.run( [self._circuit[0]] * n, [self._circuit[4]] * n, self._left_params, right_params ) result = job.result() - np.testing.assert_allclose(result.fidelities, np.array([0.5, 0.25, 0.25, 0.0]), atol=1e-16) - - def test_input_format(self): + np.testing.assert_allclose(result.fidelities, np.array([0.5, 0.25, 0.25, 0.0]), atol=1e-2, rtol=1e-2) + + @data( + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + None, + ], + ) + @unpack + def test_input_format(self, transpiler, transpiler_options): """test for different input format variations""" - fidelity = ComputeUncompute(self._sampler) + fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) circuit = RealAmplitudes(2) values = np.random.random(circuit.num_parameters) shift = np.ones_like(values) * 0.01 @@ -201,13 +352,25 @@ def test_input_format(self): job = fidelity.run(circuit, circuit, values, values + shift) result_4 = job.result() - np.testing.assert_allclose(result_1.fidelities, result_2.fidelities, atol=1e-16) - np.testing.assert_allclose(result_1.fidelities, result_3.fidelities, atol=1e-16) - np.testing.assert_allclose(result_1.fidelities, result_4.fidelities, atol=1e-16) - - def test_input_measurements(self): + np.testing.assert_allclose(result_1.fidelities, result_2.fidelities, atol=1e-2, rtol=1e-2) + np.testing.assert_allclose(result_1.fidelities, result_3.fidelities, atol=1e-2, rtol=1e-2) + np.testing.assert_allclose(result_1.fidelities, result_4.fidelities, atol=1e-2, rtol=1e-2) + + @data( + [None, None], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + {"num_processes": None}, + ], + [ + generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + None, + ], + ) + @unpack + def test_input_measurements(self, transpiler, transpiler_options): """test for fidelity with measurements on input circuits""" - fidelity = ComputeUncompute(self._sampler) + fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) circuit_1 = self._circuit[0] circuit_1.measure_all() circuit_2 = self._circuit[1] @@ -217,48 +380,48 @@ def test_input_measurements(self): result = job.result() np.testing.assert_allclose(result.fidelities, np.array([1.0])) - def test_options(self): - """Test fidelity's run options""" - sampler_shots = Sampler(options={"shots": 1024}) + def test_shots(self): + """Test fidelity's run shots setting""" + sampler_shots = Sampler(default_shots=1024) with self.subTest("sampler"): # Only options in sampler fidelity = ComputeUncompute(sampler_shots) - options = fidelity.options + shots = fidelity.shots job = fidelity.run(self._circuit[2], self._circuit[3]) result = job.result() - self.assertEqual(options.__dict__, {"shots": 1024}) - self.assertEqual(result.options.__dict__, {"shots": 1024}) + self.assertEqual(shots, None) + self.assertEqual(result.shots, 1024) with self.subTest("fidelity init"): # Fidelity default options override sampler # options and add new fields - fidelity = ComputeUncompute(sampler_shots, options={"shots": 2048, "dummy": 100}) - options = fidelity.options + fidelity = ComputeUncompute(sampler_shots, shots=2048) + shots = fidelity.shots job = fidelity.run(self._circuit[2], self._circuit[3]) result = job.result() - self.assertEqual(options.__dict__, {"shots": 2048, "dummy": 100}) - self.assertEqual(result.options.__dict__, {"shots": 2048, "dummy": 100}) + self.assertEqual(shots, 2048) + self.assertEqual(result.shots, 2048) with self.subTest("fidelity update"): # Update fidelity options - fidelity = ComputeUncompute(sampler_shots, options={"shots": 2048, "dummy": 100}) - fidelity.update_default_options(shots=100) - options = fidelity.options + fidelity = ComputeUncompute(sampler_shots, shots=2048) + fidelity.shots = 100 + shots = fidelity.shots job = fidelity.run(self._circuit[2], self._circuit[3]) result = job.result() - self.assertEqual(options.__dict__, {"shots": 100, "dummy": 100}) - self.assertEqual(result.options.__dict__, {"shots": 100, "dummy": 100}) + self.assertEqual(shots, 100) + self.assertEqual(result.shots, 100) with self.subTest("fidelity run"): # Run options override fidelity options - fidelity = ComputeUncompute(sampler_shots, options={"shots": 2048, "dummy": 100}) - job = fidelity.run(self._circuit[2], self._circuit[3], shots=50, dummy=None) - options = fidelity.options + fidelity = ComputeUncompute(sampler_shots, shots=2048) + job = fidelity.run(self._circuit[2], self._circuit[3], shots=50) + shots = fidelity.shots result = job.result() # Only default + sampler options. Not run. - self.assertEqual(options.__dict__, {"shots": 2048, "dummy": 100}) - self.assertEqual(result.options.__dict__, {"shots": 50, "dummy": None}) + self.assertEqual(shots, 2048) + self.assertEqual(result.shots, 50) if __name__ == "__main__": From cae37b2a1a8f8aab15bd540eb4a5012ec1f57e2e Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Tue, 3 Sep 2024 01:51:03 +0200 Subject: [PATCH 20/31] QNSPSA done --- qiskit_algorithms/optimizers/qnspsa.py | 11 ++++++----- test/optimizers/test_spsa.py | 21 ++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/qiskit_algorithms/optimizers/qnspsa.py b/qiskit_algorithms/optimizers/qnspsa.py index cc9c0d63..3df5f2f0 100644 --- a/qiskit_algorithms/optimizers/qnspsa.py +++ b/qiskit_algorithms/optimizers/qnspsa.py @@ -20,7 +20,7 @@ import numpy as np from qiskit.circuit import QuantumCircuit -from qiskit.primitives import BaseSampler +from qiskit.primitives import BaseSamplerV2 from qiskit_algorithms.state_fidelities import ComputeUncompute from .spsa import SPSA, CALLBACK, TERMINATIONCHECKER, _batch_evaluate @@ -63,7 +63,8 @@ class QNSPSA(SPSA): import numpy as np from qiskit_algorithms.optimizers import QNSPSA from qiskit.circuit.library import PauliTwoDesign - from qiskit.primitives import Estimator, Sampler + from qiskit.primitives import StatevectorEstimator as Estimator,\ + StatevectorSampler as Sampler from qiskit.quantum_info import Pauli # problem setup @@ -75,8 +76,8 @@ class QNSPSA(SPSA): estimator = Estimator() def loss(x): - result = estimator.run([ansatz], [observable], [x]).result() - return np.real(result.values[0]) + result = estimator.run([(ansatz, observable, x)]).result()[0] + return np.real(result.data.evs[0]) # fidelity for estimation of the geometric tensor sampler = Sampler() @@ -231,7 +232,7 @@ def settings(self) -> dict[str, Any]: def get_fidelity( circuit: QuantumCircuit, *, - sampler: BaseSampler | None = None, + sampler: BaseSamplerV2 | None = None, ) -> Callable[[np.ndarray, np.ndarray], float]: r"""Get a function to compute the fidelity of ``circuit`` with itself. diff --git a/test/optimizers/test_spsa.py b/test/optimizers/test_spsa.py index c5622eff..9d86e797 100644 --- a/test/optimizers/test_spsa.py +++ b/test/optimizers/test_spsa.py @@ -18,7 +18,8 @@ import numpy as np from qiskit.circuit.library import PauliTwoDesign -from qiskit.primitives import Estimator, Sampler +from qiskit.primitives import StatevectorEstimator as Estimator, StatevectorSampler as Sampler + from qiskit.quantum_info import SparsePauliOp, Statevector from qiskit_algorithms.optimizers import SPSA, QNSPSA @@ -57,7 +58,7 @@ def objective(x): settings["regularization"] = 0.01 expected_nfev = settings["maxiter"] * 5 + 1 elif method == "qnspsa": - settings["fidelity"] = QNSPSA.get_fidelity(circuit, sampler=Sampler()) + settings["fidelity"] = QNSPSA.get_fidelity(circuit, sampler=Sampler(seed=123)) settings["regularization"] = 0.001 settings["learning_rate"] = 0.05 settings["perturbation"] = 0.05 @@ -203,29 +204,27 @@ def test_qnspsa_fidelity_primitives(self): initial_point = np.random.random(ansatz.num_parameters) with self.subTest(msg="pass as kwarg"): - fidelity = QNSPSA.get_fidelity(ansatz, sampler=Sampler()) + fidelity = QNSPSA.get_fidelity(ansatz, sampler=Sampler(seed=123)) result = fidelity(initial_point, initial_point) self.assertAlmostEqual(result[0], 1) def test_qnspsa_max_evals_grouped(self): """Test using max_evals_grouped with QNSPSA.""" - circuit = PauliTwoDesign(3, reps=1, seed=1) - num_parameters = circuit.num_parameters + circuit = PauliTwoDesign(3, reps=1, seed=123) obs = SparsePauliOp("ZZI") # Z^Z^I - estimator = Estimator(options={"seed": 12}) + estimator = Estimator(seed=123) initial_point = np.array( [0.82311034, 0.02611798, 0.21077064, 0.61842177, 0.09828447, 0.62013131] ) def objective(x): - x = np.reshape(x, (-1, num_parameters)).tolist() - n = len(x) - return estimator.run(n * [circuit], n * [obs], x).result().values.real + results = estimator.run([(circuit, obs, x)]).result() + return np.array([res.data.evs for res in results]).real.reshape(-1) - fidelity = QNSPSA.get_fidelity(circuit, sampler=Sampler()) + fidelity = QNSPSA.get_fidelity(circuit, sampler=Sampler(seed=123)) optimizer = QNSPSA(fidelity) optimizer.maxiter = 1 optimizer.learning_rate = 0.05 @@ -235,7 +234,7 @@ def objective(x): result = optimizer.minimize(objective, initial_point) with self.subTest("check final accuracy"): - self.assertAlmostEqual(result.fun[0], 0.473, places=3) + self.assertAlmostEqual(result.fun[0], 0.298, places=3) with self.subTest("check number of function calls"): expected_nfev = 8 # 7 * maxiter + 1 From 84128a83d2a46dbf21a58874ce9eed258060b225 Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Tue, 10 Sep 2024 21:38:13 +0200 Subject: [PATCH 21/31] Restored SPSA test to previous values --- test/optimizers/test_spsa.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/optimizers/test_spsa.py b/test/optimizers/test_spsa.py index 9d86e797..2bd99e84 100644 --- a/test/optimizers/test_spsa.py +++ b/test/optimizers/test_spsa.py @@ -211,10 +211,10 @@ def test_qnspsa_fidelity_primitives(self): def test_qnspsa_max_evals_grouped(self): """Test using max_evals_grouped with QNSPSA.""" - circuit = PauliTwoDesign(3, reps=1, seed=123) + circuit = PauliTwoDesign(3, reps=1, seed=1) obs = SparsePauliOp("ZZI") # Z^Z^I - estimator = Estimator(seed=123) + estimator = Estimator(seed=12) initial_point = np.array( [0.82311034, 0.02611798, 0.21077064, 0.61842177, 0.09828447, 0.62013131] @@ -224,7 +224,7 @@ def objective(x): results = estimator.run([(circuit, obs, x)]).result() return np.array([res.data.evs for res in results]).real.reshape(-1) - fidelity = QNSPSA.get_fidelity(circuit, sampler=Sampler(seed=123)) + fidelity = QNSPSA.get_fidelity(circuit, sampler=Sampler(seed=12, default_shots=10_000)) optimizer = QNSPSA(fidelity) optimizer.maxiter = 1 optimizer.learning_rate = 0.05 @@ -234,7 +234,7 @@ def objective(x): result = optimizer.minimize(objective, initial_point) with self.subTest("check final accuracy"): - self.assertAlmostEqual(result.fun[0], 0.298, places=3) + self.assertAlmostEqual(result.fun[0], 0.473, places=3) with self.subTest("check number of function calls"): expected_nfev = 8 # 7 * maxiter + 1 From b3c32a34e469a77d0ab514f6ce4306778959a152 Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Wed, 11 Sep 2024 00:08:25 +0200 Subject: [PATCH 22/31] Refactored test for transpilers, make linter happy, test_vqd not passing --- .../minimum_eigensolvers/qaoa.py | 4 +- qiskit_algorithms/observables_evaluator.py | 2 +- qiskit_algorithms/optimizers/qnspsa.py | 12 +- qiskit_algorithms/optimizers/spsa.py | 6 +- qiskit_algorithms/optimizers/umda.py | 2 +- .../hamiltonian_phase_estimation.py | 2 +- qiskit_algorithms/phase_estimators/ipe.py | 12 +- .../phase_estimators/phase_estimation.py | 7 +- .../phase_estimation_result.py | 2 +- .../state_fidelities/base_state_fidelity.py | 6 +- .../state_fidelities/compute_uncompute.py | 15 +- .../minimum_eigensolvers/test_sampling_vqe.py | 4 +- test/optimizers/test_optimizers.py | 12 +- .../test_compute_uncompute.py | 278 +++++------------- test/test_grover.py | 124 +++----- test/test_phase_estimator.py | 237 +++++++-------- 16 files changed, 282 insertions(+), 443 deletions(-) diff --git a/qiskit_algorithms/minimum_eigensolvers/qaoa.py b/qiskit_algorithms/minimum_eigensolvers/qaoa.py index 8f29d1d6..fbb7a4e6 100644 --- a/qiskit_algorithms/minimum_eigensolvers/qaoa.py +++ b/qiskit_algorithms/minimum_eigensolvers/qaoa.py @@ -154,5 +154,5 @@ def _check_operator_ansatz(self, operator: BaseOperator): operator, self.reps, initial_state=self.initial_state, mixer_operator=self.mixer ).decompose() # TODO remove decompose once #6674 is fixed if self._transpiler is not None: - ansatz = self._transpiler.run(ansatz, **self._transpiler_options) - self.ansatz=ansatz + ansatz = self._transpiler.run(ansatz, **self._transpiler_options) + self.ansatz = ansatz diff --git a/qiskit_algorithms/observables_evaluator.py b/qiskit_algorithms/observables_evaluator.py index 74323218..a455c78e 100644 --- a/qiskit_algorithms/observables_evaluator.py +++ b/qiskit_algorithms/observables_evaluator.py @@ -42,7 +42,7 @@ def estimate_observables( Args: estimator: An estimator primitive used for calculations. quantum_state: A (parameterized) quantum circuit preparing a quantum state that expectation - values are computed against. + values are computed against. It is expected to be an ISA circuit. observables: A list or a dictionary of operators whose expectation values are to be calculated. parameter_values: Optional list of parameters values to evaluate the quantum circuit on. diff --git a/qiskit_algorithms/optimizers/qnspsa.py b/qiskit_algorithms/optimizers/qnspsa.py index 3df5f2f0..0614b8c0 100644 --- a/qiskit_algorithms/optimizers/qnspsa.py +++ b/qiskit_algorithms/optimizers/qnspsa.py @@ -24,6 +24,7 @@ from qiskit_algorithms.state_fidelities import ComputeUncompute from .spsa import SPSA, CALLBACK, TERMINATIONCHECKER, _batch_evaluate +from ..custom_types import Transpiler # the function to compute the fidelity FIDELITY = Callable[[np.ndarray, np.ndarray], float] @@ -233,6 +234,8 @@ def get_fidelity( circuit: QuantumCircuit, *, sampler: BaseSamplerV2 | None = None, + transpiler: Transpiler | None = None, + transpiler_options: dict[str, Any] | None = None, ) -> Callable[[np.ndarray, np.ndarray], float]: r"""Get a function to compute the fidelity of ``circuit`` with itself. @@ -250,12 +253,19 @@ def get_fidelity( Args: circuit: The circuit preparing the parameterized ansatz. sampler: A sampler primitive to sample from a quantum state. + transpiler: An optional object with a `run` method allowing to transpile the circuits + that are produced by the fidelity object. 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. Returns: A handle to the function :math:`F`. """ - fid = ComputeUncompute(sampler) + fid = ComputeUncompute( + sampler, transpiler=transpiler, transpiler_options=transpiler_options + ) num_parameters = circuit.num_parameters diff --git a/qiskit_algorithms/optimizers/spsa.py b/qiskit_algorithms/optimizers/spsa.py index 62b124d4..ad33d1b2 100644 --- a/qiskit_algorithms/optimizers/spsa.py +++ b/qiskit_algorithms/optimizers/spsa.py @@ -90,7 +90,7 @@ class SPSA(Optimizer): import numpy as np from qiskit_algorithms.optimizers import SPSA from qiskit.circuit.library import PauliTwoDesign - from qiskit.primitives import Estimator + from qiskit.primitives import StatevectorEstimator as Estimator from qiskit.quantum_info import SparsePauliOp ansatz = PauliTwoDesign(2, reps=1, seed=2) @@ -99,8 +99,8 @@ class SPSA(Optimizer): estimator = Estimator() def loss(x): - job = estimator.run([ansatz], [observable], [x]) - return job.result().values[0] + job = estimator.run([(ansatz, observable, x)]) + return job.result()[0].data.evs spsa = SPSA(maxiter=300) result = spsa.minimize(loss, x0=initial_point) diff --git a/qiskit_algorithms/optimizers/umda.py b/qiskit_algorithms/optimizers/umda.py index f420e82d..2ad7967d 100644 --- a/qiskit_algorithms/optimizers/umda.py +++ b/qiskit_algorithms/optimizers/umda.py @@ -74,7 +74,7 @@ class UMDA(Optimizer): from qiskit_algorithms.optimizers import UMDA from qiskit_algorithms import QAOA from qiskit.quantum_info import Pauli - from qiskit.primitives import Sampler + from qiskit.primitives import StatevectorSampler as Sampler X = Pauli("X") I = Pauli("I") diff --git a/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py index 16471f61..35711762 100644 --- a/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit_algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -104,7 +104,7 @@ def __init__( num_evaluation_qubits=num_evaluation_qubits, sampler=sampler, transpiler=transpiler, - transpiler_options=transpiler_options + transpiler_options=transpiler_options, ) 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 42b88e0b..9a035a50 100644 --- a/qiskit_algorithms/phase_estimators/ipe.py +++ b/qiskit_algorithms/phase_estimators/ipe.py @@ -15,6 +15,8 @@ from __future__ import annotations +from typing import Any + import numpy from qiskit.circuit import QuantumCircuit, QuantumRegister @@ -43,6 +45,7 @@ def __init__( num_iterations: int, sampler: BaseSamplerV2 | None = None, transpiler: Transpiler | None = None, + transpiler_options: dict[str, Any] | None = None, ) -> None: r""" Args: @@ -51,6 +54,8 @@ def __init__( 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 num_iterations is not greater than zero. @@ -63,7 +68,8 @@ def __init__( raise ValueError("`num_iterations` must be greater than zero.") self._num_iterations = num_iterations self._sampler = sampler - self._pass_manager = transpiler + self._transpiler = transpiler + self._transpiler_options = transpiler_options if transpiler_options is not None else {} def construct_circuit( self, @@ -132,8 +138,8 @@ def _estimate_phase_iteratively(self, unitary, state_preparation): unitary, state_preparation, k, -2 * numpy.pi * omega_coef, True ) - if self._pass_manager is not None: - qc = self._pass_manager.run(qc) + if self._transpiler is not None: + qc = self._transpiler.run(qc, **self._transpiler_options) try: sampler_job = self._sampler.run([qc]) diff --git a/qiskit_algorithms/phase_estimators/phase_estimation.py b/qiskit_algorithms/phase_estimators/phase_estimation.py index e24c10f5..32cc2812 100644 --- a/qiskit_algorithms/phase_estimators/phase_estimation.py +++ b/qiskit_algorithms/phase_estimators/phase_estimation.py @@ -111,7 +111,8 @@ def __init__( self._num_evaluation_qubits = num_evaluation_qubits self._sampler = sampler - self._pass_manager = transpiler + self._transpiler = transpiler + self._transpiler_options = transpiler_options if transpiler_options is not None else {} def construct_circuit( self, unitary: QuantumCircuit, state_preparation: QuantumCircuit | None = None @@ -200,8 +201,8 @@ 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) + if self._transpiler is not None: + pe_circuit = self._transpiler.run(pe_circuit, **self._transpiler_options) self._add_measurement_if_required(pe_circuit) diff --git a/qiskit_algorithms/phase_estimators/phase_estimation_result.py b/qiskit_algorithms/phase_estimators/phase_estimation_result.py index bf57c800..31df327c 100644 --- a/qiskit_algorithms/phase_estimators/phase_estimation_result.py +++ b/qiskit_algorithms/phase_estimators/phase_estimation_result.py @@ -23,7 +23,7 @@ class PhaseEstimationResult(PhaseEstimatorResult): This class is instantiated by the ``PhaseEstimation`` class, not via user code. The ``PhaseEstimation`` class generates a list of phases and corresponding weights. Upon - completion it returns the results as an instance of this class. The main method for + completion, it returns the results as an instance of this class. The main method for accessing the results is `filter_phases`. The canonical phase satisfying the ``PhaseEstimator`` interface, returned by the diff --git a/qiskit_algorithms/state_fidelities/base_state_fidelity.py b/qiskit_algorithms/state_fidelities/base_state_fidelity.py index 344849c2..d6a0a864 100644 --- a/qiskit_algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit_algorithms/state_fidelities/base_state_fidelity.py @@ -43,7 +43,11 @@ class BaseStateFidelity(ABC): """ - def __init__(self, transpiler: Transpiler | None = None, transpiler_options: dict[str, Any] | None = None,) -> None: + def __init__( + self, + transpiler: Transpiler | None = None, + transpiler_options: dict[str, Any] | None = None, + ) -> None: r""" Args: transpiler: An optional object with a `run` method allowing to transpile the circuits diff --git a/qiskit_algorithms/state_fidelities/compute_uncompute.py b/qiskit_algorithms/state_fidelities/compute_uncompute.py index 83143f43..eb2d5377 100644 --- a/qiskit_algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit_algorithms/state_fidelities/compute_uncompute.py @@ -166,9 +166,15 @@ def _run( if not isinstance(shots, Sequence): if shots is None: shots = self.shots - coerced_pubs = [SamplerPub.coerce((circuit, value), shots) for circuit, value in zip(circuits, values)] + coerced_pubs = [ + SamplerPub.coerce((circuit, value), shots) + for circuit, value in zip(circuits, values) + ] else: - coerced_pubs = [SamplerPub.coerce((circuit, value), shots_number) for circuit, value, shots_number in zip(circuits, values, shots)] + coerced_pubs = [ + SamplerPub.coerce((circuit, value), shots_number) + for circuit, value, shots_number in zip(circuits, values, shots) + ] job = self._sampler.run(coerced_pubs) @@ -183,7 +189,10 @@ def _call( except Exception as exc: raise AlgorithmError("Sampler job failed!") from exc - pub_results_data = [getattr(pub_result.data, circuit.cregs[0].name) for pub_result, circuit in zip(result, circuits)] + pub_results_data = [ + getattr(pub_result.data, circuit.cregs[0].name) + for pub_result, circuit in zip(result, circuits) + ] quasi_dists = [ { label: value / prob_dist.num_shots diff --git a/test/minimum_eigensolvers/test_sampling_vqe.py b/test/minimum_eigensolvers/test_sampling_vqe.py index 7fb5bda6..581b1b7e 100644 --- a/test/minimum_eigensolvers/test_sampling_vqe.py +++ b/test/minimum_eigensolvers/test_sampling_vqe.py @@ -270,7 +270,9 @@ def best_measurement(measurements): for aggregation in [alpha, best_measurement]: with self.subTest(aggregation=aggregation): - vqe = SamplingVQE(StatevectorSampler(), ansatz, _mock_optimizer, aggregation=best_measurement) + vqe = SamplingVQE( + StatevectorSampler(), ansatz, _mock_optimizer, aggregation=best_measurement + ) result = vqe.compute_minimum_eigenvalue(Pauli("Z")) # evaluation at x0=0 samples -1 and 1 with 50% probability, and our aggregation diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py index c3b9e4cb..595b9d25 100644 --- a/test/optimizers/test_optimizers.py +++ b/test/optimizers/test_optimizers.py @@ -13,6 +13,9 @@ """Test Optimizers""" import unittest + +from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager + from test import QiskitAlgorithmsTestCase from typing import Optional, List, Tuple @@ -23,7 +26,7 @@ from qiskit.circuit.library import RealAmplitudes from qiskit.exceptions import MissingOptionalLibraryError from qiskit.utils import optionals -from qiskit.primitives import Sampler +from qiskit.primitives import StatevectorSampler as Sampler from qiskit_algorithms.optimizers import ( ADAM, @@ -407,7 +410,12 @@ def steps(): def test_qnspsa(self): """Test QN-SPSA optimizer is serializable.""" ansatz = RealAmplitudes(1) - fidelity = QNSPSA.get_fidelity(ansatz, sampler=Sampler()) + fidelity = QNSPSA.get_fidelity( + ansatz, + sampler=Sampler(seed=123), + transpiler=generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), + transpiler_options={"callable": lambda x: x}, + ) options = { "fidelity": fidelity, "maxiter": 100, diff --git a/test/state_fidelities/test_compute_uncompute.py b/test/state_fidelities/test_compute_uncompute.py index 73ec7aa1..c95fef6f 100644 --- a/test/state_fidelities/test_compute_uncompute.py +++ b/test/state_fidelities/test_compute_uncompute.py @@ -28,6 +28,7 @@ from qiskit_algorithms.state_fidelities import ComputeUncompute + @ddt class TestComputeUncompute(QiskitAlgorithmsTestCase): """Test Compute-Uncompute Fidelity class""" @@ -54,70 +55,32 @@ def setUp(self): rx_rotation.h(1) self._circuit = [rx_rotations, ry_rotations, plus, zero, rx_rotation] - self._sampler = Sampler(seed=123) + self._sampler = Sampler(seed=123, default_shots=10_000) self._left_params = np.array([[0, 0], [np.pi / 2, 0], [0, np.pi / 2], [np.pi, np.pi]]) self._right_params = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) - @idata( - product( - [False, True], - [ - [None, None], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - {"num_processes": None}, - ], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - None, - ], - ], - ) - ) - @unpack - def test_1param_pair(self, local, transpiler_and_options): + def test_1param_pair(self): """test for fidelity with one pair of parameters""" - transpiler, transpiler_options = transpiler_and_options - fidelity = ComputeUncompute(self._sampler, local=local, transpiler=transpiler, transpiler_options=transpiler_options) + fidelity = ComputeUncompute(self._sampler) job = fidelity.run( self._circuit[0], self._circuit[1], self._left_params[0], self._right_params[0] ) result = job.result() np.testing.assert_allclose(result.fidelities, np.array([1.0])) - @idata( - product( - [ - [None, None], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - {"num_processes": None}, - ], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - None, - ], - ], - [ - [None, None], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - {"num_processes": None}, - ], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - None, - ], - ], + def test_1param_pair_local(self): + """test for fidelity with one pair of parameters""" + fidelity = ComputeUncompute(self._sampler, local=True) + job = fidelity.run( + self._circuit[0], self._circuit[1], self._left_params[0], self._right_params[0] ) - ) - @unpack - def test_local(self, transpiler_and_options_1, transpiler_and_options_2): + result = job.result() + np.testing.assert_allclose(result.fidelities, np.array([1.0])) + + def test_local(self): """test difference between local and global fidelity""" - transpiler_1, transpiler_options_1 = transpiler_and_options_1 - transpiler_2, transpiler_options_2 = transpiler_and_options_2 - fidelity_global = ComputeUncompute(self._sampler, local=False, transpiler=transpiler_1, transpiler_options=transpiler_options_1) - fidelity_local = ComputeUncompute(self._sampler, local=True, transpiler=transpiler_2, transpiler_options=transpiler_options_2) + fidelity_global = ComputeUncompute(self._sampler, local=False) + fidelity_local = ComputeUncompute(self._sampler, local=True) fidelities = [] for fidelity in [fidelity_global, fidelity_local]: job = fidelity.run(self._circuit[2], self._circuit[3]) @@ -125,43 +88,21 @@ def test_local(self, transpiler_and_options_1, transpiler_and_options_2): fidelities.append(result.fidelities[0]) np.testing.assert_allclose(fidelities, np.array([0.25, 0.5]), atol=1e-2, rtol=1e-2) - @data( - [None, None], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - {"num_processes": None}, - ], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - None, - ], - ) - @unpack - def test_4param_pairs(self, transpiler, transpiler_options): + def test_4param_pairs(self): """test for fidelity with four pairs of parameters""" - fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) + fidelity = ComputeUncompute(self._sampler) n = len(self._left_params) job = fidelity.run( [self._circuit[0]] * n, [self._circuit[1]] * n, self._left_params, self._right_params ) results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-2, rtol=1e-2) - - @data( - [None, None], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - {"num_processes": None}, - ], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - None, - ], - ) - @unpack - def test_symmetry(self, transpiler, transpiler_options): + np.testing.assert_allclose( + results.fidelities, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-2, rtol=1e-2 + ) + + def test_symmetry(self): """test for fidelity with the same circuit""" - fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) + fidelity = ComputeUncompute(self._sampler) n = len(self._left_params) job_1 = fidelity.run( [self._circuit[0]] * n, [self._circuit[0]] * n, self._left_params, self._right_params @@ -171,23 +112,11 @@ def test_symmetry(self, transpiler, transpiler_options): ) results_1 = job_1.result() results_2 = job_2.result() - np.testing.assert_allclose(results_1.fidelities, results_2.fidelities, atol=1e-2, rtol=1e-2) - - @data( - [None, None], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - {"num_processes": None}, - ], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - None, - ], - ) - @unpack - def test_no_params(self, transpiler, transpiler_options): + np.testing.assert_allclose(results_1.fidelities, results_2.fidelities, atol=1e-16) + + def test_no_params(self): """test for fidelity without parameters""" - fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) + fidelity = ComputeUncompute(self._sampler) job = fidelity.run([self._circuit[2]], [self._circuit[3]]) results = job.result() np.testing.assert_allclose(results.fidelities, np.array([0.25]), atol=1e-2, rtol=1e-2) @@ -196,65 +125,33 @@ def test_no_params(self, transpiler, transpiler_options): results = job.result() np.testing.assert_allclose(results.fidelities, np.array([0.25]), atol=1e-2, rtol=1e-2) - @data( - [None, None], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - {"num_processes": None}, - ], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - None, - ], - ) - @unpack - def test_left_param(self, transpiler, transpiler_options): + def test_left_param(self): """test for fidelity with only left parameters""" - fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) + fidelity = ComputeUncompute(self._sampler) n = len(self._left_params) job = fidelity.run( [self._circuit[1]] * n, [self._circuit[3]] * n, values_1=self._left_params ) results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-2, rtol=1e-2) - - @data( - [None, None], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - {"num_processes": None}, - ], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - None, - ], - ) - @unpack - def test_right_param(self, transpiler, transpiler_options): + np.testing.assert_allclose( + results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-2, rtol=1e-2 + ) + + def test_right_param(self): """test for fidelity with only right parameters""" - fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) + fidelity = ComputeUncompute(self._sampler) n = len(self._left_params) job = fidelity.run( [self._circuit[3]] * n, [self._circuit[1]] * n, values_2=self._left_params ) results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-2, rtol=1e-2) - - @data( - [None, None], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - {"num_processes": None}, - ], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - None, - ], - ) - @unpack - def test_not_set_circuits(self, transpiler, transpiler_options): + np.testing.assert_allclose( + results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-2, rtol=1e-2 + ) + + def test_not_set_circuits(self): """test for fidelity with no circuits.""" - fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) + fidelity = ComputeUncompute(self._sampler) with self.assertRaises(TypeError): job = fidelity.run( circuits_1=None, @@ -264,21 +161,9 @@ def test_not_set_circuits(self, transpiler, transpiler_options): ) job.result() - @data( - [None, None], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - {"num_processes": None}, - ], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - None, - ], - ) - @unpack - def test_circuit_mismatch(self, transpiler, transpiler_options): + def test_circuit_mismatch(self): """test for fidelity with different number of left/right circuits.""" - fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) + fidelity = ComputeUncompute(self._sampler) n = len(self._left_params) with self.assertRaises(ValueError): job = fidelity.run( @@ -289,47 +174,25 @@ def test_circuit_mismatch(self, transpiler, transpiler_options): ) job.result() - @data( - [None, None], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - {"num_processes": None}, - ], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - None, - ], - ) - @unpack - def test_asymmetric_params(self, transpiler, transpiler_options): + def test_asymmetric_params(self): """test for fidelity when the 2 circuits have different number of left/right parameters.""" - fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) + fidelity = ComputeUncompute(self._sampler) n = len(self._left_params) right_params = [[p] for p in self._right_params[:, 0]] job = fidelity.run( [self._circuit[0]] * n, [self._circuit[4]] * n, self._left_params, right_params ) result = job.result() - np.testing.assert_allclose(result.fidelities, np.array([0.5, 0.25, 0.25, 0.0]), atol=1e-2, rtol=1e-2) - - @data( - [None, None], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - {"num_processes": None}, - ], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - None, - ], - ) - @unpack - def test_input_format(self, transpiler, transpiler_options): + np.testing.assert_allclose( + result.fidelities, np.array([0.5, 0.25, 0.25, 0.0]), atol=1e-2, rtol=1e-2 + ) + + def test_input_format(self): """test for different input format variations""" - fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) + fidelity = ComputeUncompute(self._sampler) circuit = RealAmplitudes(2) values = np.random.random(circuit.num_parameters) shift = np.ones_like(values) * 0.01 @@ -352,25 +215,13 @@ def test_input_format(self, transpiler, transpiler_options): job = fidelity.run(circuit, circuit, values, values + shift) result_4 = job.result() - np.testing.assert_allclose(result_1.fidelities, result_2.fidelities, atol=1e-2, rtol=1e-2) - np.testing.assert_allclose(result_1.fidelities, result_3.fidelities, atol=1e-2, rtol=1e-2) - np.testing.assert_allclose(result_1.fidelities, result_4.fidelities, atol=1e-2, rtol=1e-2) - - @data( - [None, None], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - {"num_processes": None}, - ], - [ - generate_preset_pass_manager(optimization_level=1, seed_transpiler=42), - None, - ], - ) - @unpack - def test_input_measurements(self, transpiler, transpiler_options): + np.testing.assert_allclose(result_1.fidelities, result_2.fidelities, atol=1e-16) + np.testing.assert_allclose(result_1.fidelities, result_3.fidelities, atol=1e-16) + np.testing.assert_allclose(result_1.fidelities, result_4.fidelities, atol=1e-16) + + def test_input_measurements(self): """test for fidelity with measurements on input circuits""" - fidelity = ComputeUncompute(self._sampler, transpiler=transpiler, transpiler_options=transpiler_options) + fidelity = ComputeUncompute(self._sampler) circuit_1 = self._circuit[0] circuit_1.measure_all() circuit_2 = self._circuit[1] @@ -423,6 +274,21 @@ def test_shots(self): self.assertEqual(shots, 2048) self.assertEqual(result.shots, 50) + def test_transpiler(self): + """Test that the transpiler is called""" + pass_manager = generate_preset_pass_manager(optimization_level=1, seed_transpiler=42) + counts = [0] + + def callback(**kwargs): + counts[0] = kwargs["count"] + + fidelity = ComputeUncompute( + Sampler(), transpiler=pass_manager, transpiler_options={"callback": callback} + ) + fidelity._construct_circuits(QuantumCircuit(1), QuantumCircuit(1)) + + self.assertEqual(counts[0], 15) + if __name__ == "__main__": unittest.main() diff --git a/test/test_grover.py b/test/test_grover.py index 93080d3b..62f94dc2 100644 --- a/test/test_grover.py +++ b/test/test_grover.py @@ -14,10 +14,9 @@ import unittest from itertools import product -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 StatevectorSampler as Sampler @@ -26,6 +25,7 @@ from qiskit.utils.optionals import HAS_TWEEDLEDUM from qiskit_algorithms import AmplificationProblem, Grover +from test import QiskitAlgorithmsTestCase @ddt @@ -92,109 +92,41 @@ def setUp(self): self._sampler = Sampler(seed=123) @unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum required for this test") - @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): + def test_implicit_phase_oracle_is_good_state(self): """Test implicit default for is_good_state with PhaseOracle.""" - grover = self._prepare_grover(transpiler=transpiler, transpiler_options=transpiler_options) + grover = self._prepare_grover() oracle = PhaseOracle("x & y") problem = AmplificationProblem(oracle) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "11") - @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): + @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""" - transpiler, transpiler_options = transpiler_and_options - grover = self._prepare_grover( - iterations, transpiler=transpiler, transpiler_options=transpiler_options - ) + 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( - 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 - ): + @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""" - 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) problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "111") - @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): + def test_fixed_iterations_without_good_state(self): """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) problem = AmplificationProblem(Statevector.from_label("111")) result = grover.amplify(problem) self.assertEqual(result.top_measurement, "111") - @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): + @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""" - transpiler, transpiler_options = transpiler_and_options - grover = self._prepare_grover( - iterations=iterations, transpiler=transpiler, transpiler_options=transpiler_options - ) + grover = self._prepare_grover(iterations=iterations) problem = AmplificationProblem(Statevector.from_label("111")) with self.assertRaisesRegex( @@ -344,13 +276,33 @@ def test_sampler_setter(self): grover.sampler = self._sampler self.assertEqual(grover.sampler, self._sampler) + def test_transpiler(self): + """Test that the transpiler is called""" + pass_manager = generate_preset_pass_manager(optimization_level=1, seed_transpiler=42) + counts = [0] + + def callback(**kwargs): + counts[0] = kwargs["count"] + + oracle = QuantumCircuit(2) + oracle.cz(0, 1) + # is_good_state=['00'] is intentionally selected to obtain a list of results + problem = AmplificationProblem(oracle) + + Grover( + iterations=1, + sampler=Sampler(), + transpiler=pass_manager, + transpiler_options={"callback": callback}, + ).amplify(problem) + + self.assertEqual(counts[0], 15) + 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( @@ -358,8 +310,6 @@ def _prepare_grover( iterations=iterations, growth_rate=growth_rate, sample_from_iterations=sample_from_iterations, - transpiler=transpiler, - transpiler_options=transpiler_options, ) diff --git a/test/test_phase_estimator.py b/test/test_phase_estimator.py index ebed64fb..f206b89d 100644 --- a/test/test_phase_estimator.py +++ b/test/test_phase_estimator.py @@ -12,8 +12,6 @@ """Test phase estimation""" import unittest -from itertools import product -from test import QiskitAlgorithmsTestCase import numpy as np from ddt import ddt, data, unpack @@ -30,6 +28,7 @@ PhaseEstimation, PhaseEstimationScale, ) +from test import QiskitAlgorithmsTestCase @ddt @@ -44,12 +43,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(default_shots=10_000, seed=42) phase_est = HamiltonianPhaseEstimation( - num_evaluation_qubits=num_evaluation_qubits, sampler=sampler, transpiler=pass_manager + num_evaluation_qubits=num_evaluation_qubits, + sampler=sampler, ) result = phase_est.estimate( hamiltonian=hamiltonian, @@ -59,21 +58,13 @@ def hamiltonian_pe_sampler( ) return result - @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): + @data(MatrixExponential(), SuzukiTrotter(reps=4)) + def test_pauli_sum_1_sampler(self, evolution): """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, pass_manager=pass_manager - ) + result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=evolution) phase_dict = result.filter_phases(0.162, as_float=True) phases = list(phase_dict.keys()) phases.sort() @@ -81,21 +72,13 @@ 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, generate_preset_pass_manager(optimization_level=1, seed_transpiler=42)), - ) - ) - @unpack - def test_pauli_sum_2_sampler(self, evolution, pass_manager): + @data(MatrixExponential(), SuzukiTrotter(reps=3)) + def test_pauli_sum_2_sampler(self, evolution): """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, pass_manager=pass_manager - ) + result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=evolution) phase_dict = result.filter_phases(0.1, as_float=True) phases = list(phase_dict.keys()) phases.sort() @@ -103,15 +86,12 @@ 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, generate_preset_pass_manager(optimization_level=1, seed_transpiler=42)) - def test_single_pauli_op_sampler(self, pass_manager): + def test_single_pauli_op_sampler(self): """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, pass_manager=pass_manager - ) + result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=None) eigv = result.most_likely_eigenvalue with self.subTest("First eigenvalue"): self.assertAlmostEqual(eigv, 1.0, delta=0.001) @@ -123,17 +103,26 @@ def test_single_pauli_op_sampler(self, pass_manager): with self.subTest("Second eigenvalue"): self.assertAlmostEqual(eigv, -0.98, delta=0.01) - @data( - *product( - ( - (Statevector(QuantumCircuit(2).compose(IGate()).compose(HGate()))), - (QuantumCircuit(2).compose(IGate()).compose(HGate())), - ), - (None, generate_preset_pass_manager(optimization_level=1, seed_transpiler=42)), + def test_single_pauli_op_sampler_with_transpiler(self): + """Check that the transpilation does happen""" + pass_manager = generate_preset_pass_manager(optimization_level=1, seed_transpiler=42) + counts = [0] + + def callback(**kwargs): + counts[0] = kwargs["count"] + + phase_est = HamiltonianPhaseEstimation( + 1, Sampler(), transpiler=pass_manager, transpiler_options={"callback": callback} ) + phase_est.estimate(hamiltonian=SparsePauliOp(Pauli("Z"))) + + self.assertEqual(counts[0], 15) + + @data( + (Statevector(QuantumCircuit(2).compose(IGate()).compose(HGate()))), + (QuantumCircuit(2).compose(IGate()).compose(HGate())), ) - @unpack - def test_H2_hamiltonian_sampler(self, state_preparation, pass_manager): + def test_H2_hamiltonian_sampler(self, state_preparation): """Test H2 hamiltonian""" hamiltonian = SparsePauliOp.from_list( @@ -147,9 +136,7 @@ def test_H2_hamiltonian_sampler(self, state_preparation, pass_manager): ) evo = SuzukiTrotter(reps=4) - result = self.hamiltonian_pe_sampler( - hamiltonian, state_preparation, evolution=evo, pass_manager=pass_manager - ) + result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=evo) with self.subTest("Most likely eigenvalues"): self.assertAlmostEqual(result.most_likely_eigenvalue, -1.855, delta=0.001) with self.subTest("Most likely phase"): @@ -161,15 +148,14 @@ 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, generate_preset_pass_manager(optimization_level=1, seed_transpiler=42)) - def test_matrix_evolution_sampler(self, pass_manager): + def test_matrix_evolution_sampler(self): """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(), pass_manager=pass_manager + hamiltonian, state_preparation, evolution=MatrixExponential() ) phase_dict = result.filter_phases(0.2, as_float=True) phases = sorted(phase_dict.keys()) @@ -181,8 +167,6 @@ def test_matrix_evolution_sampler(self, pass_manager): class TestPhaseEstimation(QiskitAlgorithmsTestCase): """Evolution tests.""" - pm = generate_preset_pass_manager(optimization_level=1, seed_transpiler=42) - # sampler tests def one_phase_sampler( self, @@ -191,7 +175,6 @@ 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)`. @@ -204,13 +187,9 @@ def one_phase_sampler( if phase_estimator is None: phase_estimator = IterativePhaseEstimation if phase_estimator == IterativePhaseEstimation: - p_est = IterativePhaseEstimation( - num_iterations=num_iterations, sampler=sampler, transpiler=pass_manager - ) + p_est = IterativePhaseEstimation(num_iterations=num_iterations, sampler=sampler) elif phase_estimator == PhaseEstimation: - p_est = PhaseEstimation( - num_evaluation_qubits=6, sampler=sampler, transpiler=pass_manager - ) + p_est = PhaseEstimation(num_evaluation_qubits=6, sampler=sampler) else: raise ValueError("Unrecognized phase_estimator") result = p_est.estimate(unitary=unitary_circuit, state_preparation=state_preparation) @@ -218,111 +197,90 @@ def one_phase_sampler( return phase @data( - *product( - ((None, 0.0), (QuantumCircuit(1).compose(XGate()), 0.5)), - (None, 1000), - (IterativePhaseEstimation, PhaseEstimation), - (None, pm), - ) + (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), ) @unpack - def test_qpe_Z_sampler( - self, state_preparation_and_expected_phase, shots, phase_estimator, pass_manager - ): + def test_qpe_Z_sampler(self, state_preparation, expected_phase, shots, phase_estimator): """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( - *product( - ( - (QuantumCircuit(1).compose(HGate()), 0.0), - (QuantumCircuit(1).compose(HGate()).compose(ZGate()), 0.5), - ), - (IterativePhaseEstimation, PhaseEstimation), - (None, pm), - ) + (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), ) @unpack - def test_qpe_X_plus_minus_sampler( - self, state_preparation_and_expected_phase, phase_estimator, pass_manager - ): + def test_qpe_X_plus_minus_sampler(self, state_preparation, expected_phase, phase_estimator): """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, pass_manager=pass_manager - ) + phase = self.one_phase_sampler(unitary_circuit, state_preparation, phase_estimator) self.assertEqual(phase, expected_phase) @data( - *product( - ( - (QuantumCircuit(1).compose(XGate()), 0.125), - (QuantumCircuit(1).compose(IGate()), 0.875), - ), - (IterativePhaseEstimation, PhaseEstimation), - (None, pm), - ) + (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), ) @unpack - def test_qpe_RZ_sampler( - self, state_preparation_and_expected_phase, phase_estimator, pass_manager - ): + def test_qpe_RZ_sampler(self, state_preparation, expected_phase, phase_estimator): """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, pass_manager=pass_manager - ) + phase = self.one_phase_sampler(unitary_circuit, state_preparation, phase_estimator) self.assertEqual(phase, expected_phase) @data( - *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), - ) + ( + 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, + ), ) @unpack - def test_qpe_two_qubit_unitary( - self, state_preparation_and_expected_phase, phase_estimator, pass_manager - ): + def test_qpe_two_qubit_unitary(self, state_preparation, expected_phase, phase_estimator): """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, pass_manager=pass_manager - ) + phase = self.one_phase_sampler(unitary_circuit, state_preparation, phase_estimator) self.assertEqual(phase, expected_phase) - @data(None, pm) - def test_check_num_iterations_sampler(self, pass_manager): + def test_check_num_iterations_sampler(self): """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, pass_manager=pass_manager - ) + self.one_phase_sampler(unitary_circuit, state_preparation, num_iterations=-1) def test_phase_estimation_scale_from_operator(self): """test that PhaseEstimationScale from_pauli_sum works with Operator""" @@ -331,6 +289,36 @@ def test_phase_estimation_scale_from_operator(self): scale = PhaseEstimationScale.from_pauli_sum(op) self.assertEqual(scale._bound, 4.0) + @data((PhaseEstimation, 15), (IterativePhaseEstimation, 22)) + @unpack + def test_transpiler(self, phase_estimator, expected_counts): + """Test that the transpiler is called""" + pass_manager = generate_preset_pass_manager(optimization_level=1, seed_transpiler=42) + counts = [0] + + def callback(**kwargs): + counts[0] = kwargs["count"] + + if phase_estimator == IterativePhaseEstimation: + p_est = IterativePhaseEstimation( + num_iterations=6, + sampler=Sampler(), + transpiler=pass_manager, + transpiler_options={"callback": callback}, + ) + elif phase_estimator == PhaseEstimation: + p_est = PhaseEstimation( + num_evaluation_qubits=6, + sampler=Sampler(), + transpiler=pass_manager, + transpiler_options={"callback": callback}, + ) + else: + raise ValueError("Unrecognized phase_estimator") + + p_est.estimate(unitary=QuantumCircuit(1), state_preparation=None) + self.assertEqual(counts[0], expected_counts) + def phase_estimation_sampler( self, unitary_circuit, @@ -338,14 +326,11 @@ 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, transpiler=pass_manager - ) + phase_est = PhaseEstimation(num_evaluation_qubits=num_evaluation_qubits, sampler=sampler) if construct_circuit: pe_circuit = phase_est.construct_circuit(unitary_circuit, state_preparation) result = phase_est.estimate_from_pe_circuit(pe_circuit) @@ -355,9 +340,8 @@ def phase_estimation_sampler( ) return result - @data(*product((True, False), (None, pm))) - @unpack - def test_qpe_Zplus_sampler(self, construct_circuit, pass_manager): + @data(True, False) + def test_qpe_Zplus_sampler(self, construct_circuit): """superposition eigenproblem Z, |+>""" unitary_circuit = QuantumCircuit(1).compose(ZGate()) state_preparation = QuantumCircuit(1).compose(HGate()) # prepare |+> @@ -367,7 +351,6 @@ def test_qpe_Zplus_sampler(self, construct_circuit, pass_manager): sampler, state_preparation, construct_circuit=construct_circuit, - pass_manager=pass_manager, ) phases = result.filter_phases(1e-15, as_float=True) From dc075d03d720d6f207056ce860b234cad6848a10 Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Wed, 11 Sep 2024 00:08:38 +0200 Subject: [PATCH 23/31] Updated TODO --- TODO.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/TODO.md b/TODO.md index d9deb547..b438cfb5 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,9 @@ Tristan: -- [] phase_estimators/hamiltonian_phase_estimation.py -- [] phase_estimators/ipe.py -- [] phase_estimators/phase_estimation.py +- [x] phase_estimators/hamiltonian_phase_estimation.py +- [x] phase_estimators/ipe.py +- [x] phase_estimators/phase_estimation.py - [] eigensolvers/vqd.py -- [] amplitude_amplifiers/grover.py +- [x] amplitude_amplifiers/grover.py - [] time_evolvers/pvqd/utils.py - [] time_evolvers/pvqd/pvqd.py - [] time_evolvers/trotterization/trotter_qrte.py @@ -12,11 +12,11 @@ Tristan: - [] time_evolvers/variational/var_qite.py - [] time_evolvers/variational/var_qrte.py - [] time_evolvers/variational/var_qte.py -- [] state_fidelities/compute_uncompute.py -- [] optimizers/qnspsa.py -- [] optimizers/umda.py -- [] optimizers/spsa.py -- [] observables_evaluator.py +- [x] state_fidelities/compute_uncompute.py +- [x] optimizers/qnspsa.py +- [x] optimizers/umda.py +- [x] optimizers/spsa.py +- [x] observables_evaluator.py Léna: - [] gradients/reverse/reverse_gradient.py From 0790c53ee42c4c361653477d56ca90903900d89a Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Wed, 11 Sep 2024 00:42:30 +0200 Subject: [PATCH 24/31] PVQD done --- TODO.md | 8 ++++---- qiskit_algorithms/time_evolvers/pvqd/pvqd.py | 6 +++--- qiskit_algorithms/time_evolvers/pvqd/utils.py | 16 ++++------------ test/time_evolvers/test_pvqd.py | 2 +- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/TODO.md b/TODO.md index b438cfb5..d1fcf803 100644 --- a/TODO.md +++ b/TODO.md @@ -2,10 +2,10 @@ Tristan: - [x] phase_estimators/hamiltonian_phase_estimation.py - [x] phase_estimators/ipe.py - [x] phase_estimators/phase_estimation.py -- [] eigensolvers/vqd.py -- [x] amplitude_amplifiers/grover.py -- [] time_evolvers/pvqd/utils.py -- [] time_evolvers/pvqd/pvqd.py +- [] eigensolvers/vqd.py (test doesn't work) +- [x] amplitude_amplifiers/grover.py (test doesn't work but should probably be refactored or removed) +- [x] time_evolvers/pvqd/utils.py +- [x] time_evolvers/pvqd/pvqd.py - [] time_evolvers/trotterization/trotter_qrte.py - [] time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py - [] time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py diff --git a/qiskit_algorithms/time_evolvers/pvqd/pvqd.py b/qiskit_algorithms/time_evolvers/pvqd/pvqd.py index 03c7f3ab..bdfafc5b 100644 --- a/qiskit_algorithms/time_evolvers/pvqd/pvqd.py +++ b/qiskit_algorithms/time_evolvers/pvqd/pvqd.py @@ -20,7 +20,7 @@ from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit from qiskit.circuit.library import PauliEvolutionGate -from qiskit.primitives import BaseEstimator +from qiskit.primitives import BaseEstimatorV2 from qiskit.quantum_info.operators.base_operator import BaseOperator from qiskit.synthesis import EvolutionSynthesis, LieTrotter from qiskit_algorithms.utils import algorithm_globals @@ -74,7 +74,7 @@ class PVQD(RealTimeEvolver): from qiskit_algorithms.state_fidelities import ComputeUncompute from qiskit_algorithms.time_evolvers import TimeEvolutionProblem, PVQD - from qiskit.primitives import Estimator, Sampler + from qiskit.primitives import StatevectorEstimator as Estimator, StatevectorSampler as Sampler from qiskit.circuit.library import EfficientSU2 from qiskit.quantum_info import SparsePauliOp, Pauli from qiskit_algorithms.optimizers import L_BFGS_B @@ -120,7 +120,7 @@ def __init__( fidelity: BaseStateFidelity, ansatz: QuantumCircuit, initial_parameters: np.ndarray, - estimator: BaseEstimator | None = None, + estimator: BaseEstimatorV2 | None = None, optimizer: Optimizer | Minimizer | None = None, num_timesteps: int | None = None, evolution: EvolutionSynthesis | None = None, diff --git a/qiskit_algorithms/time_evolvers/pvqd/utils.py b/qiskit_algorithms/time_evolvers/pvqd/utils.py index b571a792..840626a3 100644 --- a/qiskit_algorithms/time_evolvers/pvqd/utils.py +++ b/qiskit_algorithms/time_evolvers/pvqd/utils.py @@ -21,7 +21,7 @@ from qiskit.circuit import QuantumCircuit, Parameter, ParameterExpression from qiskit.compiler import transpile from qiskit.exceptions import QiskitError -from qiskit.primitives import BaseEstimator +from qiskit.primitives import BaseEstimatorV2 from qiskit.quantum_info.operators.base_operator import BaseOperator from qiskit_algorithms.gradients import ParamShiftSamplerGradient as ParamShift @@ -75,7 +75,7 @@ def _is_gradient_supported(ansatz: QuantumCircuit) -> bool: def _get_observable_evaluator( ansatz: QuantumCircuit, observables: BaseOperator | list[BaseOperator], - estimator: BaseEstimator, + estimator: BaseEstimatorV2, ) -> Callable[[np.ndarray], float | list[float]]: """Get a callable to evaluate a (list of) observable(s) for given circuit parameters.""" @@ -91,18 +91,10 @@ def evaluate_observables(theta: np.ndarray) -> float | list[float]: Raises: AlgorithmError: If a primitive job fails. """ - if isinstance(observables, list): - num_observables = len(observables) - obs = observables - else: - num_observables = 1 - obs = [observables] - states = [ansatz] * num_observables - parameter_values = [theta] * num_observables try: - estimator_job = estimator.run(states, obs, parameter_values=parameter_values) - results = estimator_job.result().values + estimator_job = estimator.run([(ansatz, observables, theta)]) + results = estimator_job.result()[0].data.evs except Exception as exc: raise AlgorithmError("The primitive job failed!") from exc diff --git a/test/time_evolvers/test_pvqd.py b/test/time_evolvers/test_pvqd.py index 740ac69f..c8b02241 100644 --- a/test/time_evolvers/test_pvqd.py +++ b/test/time_evolvers/test_pvqd.py @@ -20,7 +20,7 @@ from qiskit.circuit import Gate, Parameter, QuantumCircuit from qiskit.circuit.library import EfficientSU2 -from qiskit.primitives import Estimator, Sampler +from qiskit.primitives import StatevectorEstimator as Estimator, StatevectorSampler as Sampler from qiskit.quantum_info import Pauli, SparsePauliOp from qiskit_algorithms import AlgorithmError From 7ff3ab64ce2cd847c21dca6f2b8b77a35a1b555d Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Wed, 18 Sep 2024 07:48:23 +0200 Subject: [PATCH 25/31] Slight modifications of VQD, still isn't working --- docs/tutorials/04_vqd.ipynb | 2 +- qiskit_algorithms/eigensolvers/vqd.py | 2 +- test/eigensolvers/test_vqd.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/04_vqd.ipynb b/docs/tutorials/04_vqd.ipynb index ecd0696f..91e8b6c8 100644 --- a/docs/tutorials/04_vqd.ipynb +++ b/docs/tutorials/04_vqd.ipynb @@ -15,7 +15,7 @@ "source": [ "## Introduction\n", "\n", - "VQD is a quantum algorithm that uses a variational technique to find the *k* eigenvalues of the Hamiltonian *H* of a given system.\n", + "VQD is a quantum algorithm that uses a variational technique to find the *k* lowest eigenvalues of the Hamiltonian *H* of a given system.\n", "\n", "The algorithm computes excited state energies of generalized hamiltonians by optimizing over a modified cost function. Each successive eigenvalue is calculated iteratively by introducing an overlap term with all the previously computed eigenstates that must be minimized. This ensures that higher energy eigenstates are found." ] diff --git a/qiskit_algorithms/eigensolvers/vqd.py b/qiskit_algorithms/eigensolvers/vqd.py index ff8ac75f..909e669a 100644 --- a/qiskit_algorithms/eigensolvers/vqd.py +++ b/qiskit_algorithms/eigensolvers/vqd.py @@ -50,7 +50,7 @@ class VQD(VariationalAlgorithm, Eigensolver): `VQD `__ is a quantum algorithm that uses a variational technique to find - the k eigenvalues of the Hamiltonian :math:`H` of a given system. + the k lowest eigenvalues of the Hamiltonian :math:`H` of a given system. The algorithm computes excited state energies of generalised hamiltonians by optimizing over a modified cost function where each successive eigenvalue diff --git a/test/eigensolvers/test_vqd.py b/test/eigensolvers/test_vqd.py index aec32483..8b86606d 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, StatevectorEstimator as Estimator +from qiskit.primitives import StatevectorSampler as Sampler, StatevectorEstimator as Estimator from qiskit.quantum_info import SparsePauliOp from qiskit_algorithms.eigensolvers import VQD, VQDResult @@ -58,7 +58,7 @@ def setUp(self): self.ry_wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") self.estimator = Estimator(seed=self.seed) - self.fidelity = ComputeUncompute(Sampler()) + self.fidelity = ComputeUncompute(Sampler(seed=50)) self.betas = [50, 50] @data(H2_SPARSE_PAULI) From bf6a9318f2c30fda3b13e27b24b77c7e487260ca Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Fri, 16 Aug 2024 20:37:44 +0200 Subject: [PATCH 26/31] Adapted PVQD to EstimatorV2 # Conflicts: # qiskit_algorithms/time_evolvers/pvqd/pvqd.py # qiskit_algorithms/time_evolvers/pvqd/utils.py # test/time_evolvers/test_pvqd.py --- docs/tutorials/10_pvqd.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/10_pvqd.ipynb b/docs/tutorials/10_pvqd.ipynb index 239276fe..463a3174 100644 --- a/docs/tutorials/10_pvqd.ipynb +++ b/docs/tutorials/10_pvqd.ipynb @@ -125,7 +125,7 @@ "metadata": {}, "outputs": [], "source": [ - "from qiskit.primitives import Sampler, Estimator\n", + "from qiskit.primitives import Sampler, StatevectorEstimator as Estimator\n", "from qiskit_algorithms.state_fidelities import ComputeUncompute\n", "\n", "# the fidelity is used to evaluate the objective: the overlap of the variational form and the trotter step\n", From dea4bd04c1e99e8b06f8584941b9c1ebcf2a4e13 Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Fri, 16 Aug 2024 20:42:52 +0200 Subject: [PATCH 27/31] Adapted Trotter QRTE to EstimatorV2 --- .../time_evolvers/trotterization/trotter_qrte.py | 10 +++++----- test/time_evolvers/test_trotter_qrte.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py b/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py index 893cd985..a1073ed1 100644 --- a/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py +++ b/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py @@ -18,7 +18,7 @@ from qiskit.circuit.library import PauliEvolutionGate from qiskit.circuit.parametertable import ParameterView -from qiskit.primitives import BaseEstimator +from qiskit.primitives import BaseEstimatorV2 from qiskit.quantum_info import Pauli, SparsePauliOp from qiskit.synthesis import ProductFormula, LieTrotter @@ -41,7 +41,7 @@ class TrotterQRTE(RealTimeEvolver): from qiskit.quantum_info import Pauli, SparsePauliOp from qiskit import QuantumCircuit from qiskit_algorithms import TrotterQRTE, TimeEvolutionProblem - from qiskit.primitives import Estimator + from qiskit.primitives import StatevectorEstimator as Estimator operator = SparsePauliOp([Pauli("X"), Pauli("Z")]) initial_state = QuantumCircuit(1) @@ -56,7 +56,7 @@ class TrotterQRTE(RealTimeEvolver): def __init__( self, product_formula: ProductFormula | None = None, - estimator: BaseEstimator | None = None, + estimator: BaseEstimatorV2 | None = None, num_timesteps: int = 1, *, insert_barriers: bool = False, @@ -96,14 +96,14 @@ def product_formula(self, product_formula: ProductFormula | None): self._product_formula = product_formula @property - def estimator(self) -> BaseEstimator | None: + def estimator(self) -> BaseEstimatorV2 | None: """ Returns an estimator. """ return self._estimator @estimator.setter - def estimator(self, estimator: BaseEstimator) -> None: + def estimator(self, estimator: BaseEstimatorV2) -> None: """ Sets an estimator. """ diff --git a/test/time_evolvers/test_trotter_qrte.py b/test/time_evolvers/test_trotter_qrte.py index bd7ca48c..b582fe52 100644 --- a/test/time_evolvers/test_trotter_qrte.py +++ b/test/time_evolvers/test_trotter_qrte.py @@ -23,7 +23,7 @@ from qiskit.circuit.library import ZGate from qiskit.quantum_info import Statevector, Pauli, SparsePauliOp from qiskit.circuit import Parameter -from qiskit.primitives import Estimator +from qiskit.primitives import StatevectorEstimator as Estimator from qiskit.synthesis import SuzukiTrotter, QDrift from qiskit_algorithms import TimeEvolutionProblem, TrotterQRTE From 8d9a54cb87c381b894c044ba3b127f96c6d96b21 Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Wed, 18 Sep 2024 08:10:11 +0200 Subject: [PATCH 28/31] Trotter QRTE --- .../trotterization/trotter_qrte.py | 20 +++++++++++++++ test/time_evolvers/test_trotter_qrte.py | 25 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py b/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py index a1073ed1..15c9750b 100644 --- a/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py +++ b/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py @@ -14,6 +14,8 @@ from __future__ import annotations +from typing import Any + from qiskit import QuantumCircuit from qiskit.circuit.library import PauliEvolutionGate @@ -22,6 +24,7 @@ from qiskit.quantum_info import Pauli, SparsePauliOp from qiskit.synthesis import ProductFormula, LieTrotter +from qiskit_algorithms.custom_types import Transpiler from qiskit_algorithms.time_evolvers.time_evolution_problem import TimeEvolutionProblem from qiskit_algorithms.time_evolvers.time_evolution_result import TimeEvolutionResult from qiskit_algorithms.time_evolvers.real_time_evolver import RealTimeEvolver @@ -58,6 +61,8 @@ def __init__( product_formula: ProductFormula | None = None, estimator: BaseEstimatorV2 | None = None, num_timesteps: int = 1, + transpiler: Transpiler | None = None, + transpiler_options: dict[str, Any] | None = None, *, insert_barriers: bool = False, ) -> None: @@ -73,6 +78,11 @@ def __init__( ``TimeEvolutionProblem.aux_operators``. num_timesteps: The number of time-steps the full evolution time is divided into (repetitions of ``product_formula``). + 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. insert_barriers: If True, insert a barrier after the initial state and after each Trotter step. """ @@ -81,6 +91,8 @@ def __init__( self.num_timesteps = num_timesteps self.estimator = estimator self._insert_barriers = insert_barriers + self._transpiler = transpiler + self._transpiler_options = transpiler_options if transpiler_options is not None else {} @property def product_formula(self) -> ProductFormula: @@ -197,6 +209,10 @@ def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult evolved_state = QuantumCircuit(initial_state.num_qubits) evolved_state.append(initial_state, evolved_state.qubits) + + if self._transpiler is not None: + evolved_state = self._transpiler.run(evolved_state, **self._transpiler_options) + if self._insert_barriers: evolved_state.barrier() @@ -235,6 +251,10 @@ def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult synthesis=self.product_formula, ) evolved_state.append(single_step_evolution_gate, evolved_state.qubits) + + if self._transpiler is not None: + evolved_state = self._transpiler.run(evolved_state, **self._transpiler_options) + if self._insert_barriers: evolved_state.barrier() diff --git a/test/time_evolvers/test_trotter_qrte.py b/test/time_evolvers/test_trotter_qrte.py index b582fe52..f752c6ed 100644 --- a/test/time_evolvers/test_trotter_qrte.py +++ b/test/time_evolvers/test_trotter_qrte.py @@ -13,6 +13,9 @@ """Test TrotterQRTE.""" import unittest + +from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager + from test import QiskitAlgorithmsTestCase from ddt import ddt, data, unpack import numpy as np @@ -239,6 +242,28 @@ def test_barriers(self, insert_barrier): expected_circuit.decompose(reps=3), evolution_result.evolved_state.decompose(reps=5) ) + def test_transpiler(self): + """Test that the transpiler is called""" + pass_manager = generate_preset_pass_manager(optimization_level=1, seed_transpiler=42) + counts = [0] + + def callback(**kwargs): + counts[0] = kwargs["count"] + + operator = SparsePauliOp([Pauli("X"), Pauli("Z")]) + initial_state = QuantumCircuit(1) + time = 1 + evolution_problem = TimeEvolutionProblem(operator, time, initial_state) + + trotter_qrte = TrotterQRTE( + estimator=Estimator(), + transpiler=pass_manager, + transpiler_options={"callback": callback} + ) + trotter_qrte.evolve(evolution_problem) + + self.assertEqual(counts[0], 15) + @staticmethod def _run_error_test(initial_state, operator, aux_ops, estimator, t_param, param_value_dict): time = 1 From 8c87b8d4565022be003b33aac4c7f26c2b728a88 Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Thu, 5 Dec 2024 20:08:51 +0100 Subject: [PATCH 29/31] Time evolvers --- TODO.md | 12 +- .../time_evolvers/variational/var_qite.py | 6 +- .../time_evolvers/variational/var_qrte.py | 6 +- .../time_evolvers/variational/var_qte.py | 4 +- .../imaginary_mc_lachlan_principle.py | 2 +- .../real_mc_lachlan_principle.py | 6 +- .../variational/test_var_qite.py | 120 +++++------------- .../test_imaginary_mc_lachlan_principle.py | 4 +- .../real/test_real_mc_lachlan_principle.py | 4 +- 9 files changed, 57 insertions(+), 107 deletions(-) diff --git a/TODO.md b/TODO.md index d1fcf803..70033479 100644 --- a/TODO.md +++ b/TODO.md @@ -6,12 +6,12 @@ Tristan: - [x] amplitude_amplifiers/grover.py (test doesn't work but should probably be refactored or removed) - [x] time_evolvers/pvqd/utils.py - [x] time_evolvers/pvqd/pvqd.py -- [] time_evolvers/trotterization/trotter_qrte.py -- [] time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py -- [] time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py -- [] time_evolvers/variational/var_qite.py -- [] time_evolvers/variational/var_qrte.py -- [] time_evolvers/variational/var_qte.py +- [x] time_evolvers/trotterization/trotter_qrte.py +- [x] time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py +- [x] time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py +- [x] time_evolvers/variational/var_qite.py +- [x] time_evolvers/variational/var_qrte.py +- [x] time_evolvers/variational/var_qte.py - [x] state_fidelities/compute_uncompute.py - [x] optimizers/qnspsa.py - [x] optimizers/umda.py diff --git a/qiskit_algorithms/time_evolvers/variational/var_qite.py b/qiskit_algorithms/time_evolvers/variational/var_qite.py index c44219ce..c0611dec 100644 --- a/qiskit_algorithms/time_evolvers/variational/var_qite.py +++ b/qiskit_algorithms/time_evolvers/variational/var_qite.py @@ -21,7 +21,7 @@ from qiskit import QuantumCircuit from qiskit.circuit import Parameter -from qiskit.primitives import BaseEstimator +from qiskit.primitives import BaseEstimatorV2 from .solvers.ode.forward_euler_solver import ForwardEulerSolver @@ -42,7 +42,7 @@ class VarQITE(VarQTE, ImaginaryTimeEvolver): from qiskit_algorithms.time_evolvers.variational import ImaginaryMcLachlanPrinciple from qiskit.circuit.library import EfficientSU2 from qiskit.quantum_info import SparsePauliOp, Pauli - from qiskit.primitives import Estimator + from qiskit.primitives import StatevectorEstimator as Estimator observable = SparsePauliOp.from_list( [ @@ -77,7 +77,7 @@ def __init__( ansatz: QuantumCircuit, initial_parameters: Mapping[Parameter, float] | Sequence[float], variational_principle: ImaginaryVariationalPrinciple | None = None, - estimator: BaseEstimator | None = None, + estimator: BaseEstimatorV2 | None = None, ode_solver: Type[OdeSolver] | str = ForwardEulerSolver, lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, num_timesteps: int | None = None, diff --git a/qiskit_algorithms/time_evolvers/variational/var_qrte.py b/qiskit_algorithms/time_evolvers/variational/var_qrte.py index 21f8cce0..89561c38 100644 --- a/qiskit_algorithms/time_evolvers/variational/var_qrte.py +++ b/qiskit_algorithms/time_evolvers/variational/var_qrte.py @@ -21,7 +21,7 @@ from qiskit import QuantumCircuit from qiskit.circuit import Parameter -from qiskit.primitives import BaseEstimator +from qiskit.primitives import BaseEstimatorV2 from .solvers.ode.forward_euler_solver import ForwardEulerSolver @@ -43,7 +43,7 @@ class VarQRTE(VarQTE, RealTimeEvolver): from qiskit_algorithms.time_evolvers.variational import RealMcLachlanPrinciple from qiskit.quantum_info import SparsePauliOp from qiskit.quantum_info import SparsePauliOp, Pauli - from qiskit.primitives import Estimator + from qiskit.primitives import StatevectorEstimator as Estimator observable = SparsePauliOp.from_list( [ @@ -78,7 +78,7 @@ def __init__( ansatz: QuantumCircuit, initial_parameters: Mapping[Parameter, float] | Sequence[float], variational_principle: RealVariationalPrinciple | None = None, - estimator: BaseEstimator | None = None, + estimator: BaseEstimatorV2 | None = None, ode_solver: Type[OdeSolver] | str = ForwardEulerSolver, lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, num_timesteps: int | None = None, diff --git a/qiskit_algorithms/time_evolvers/variational/var_qte.py b/qiskit_algorithms/time_evolvers/variational/var_qte.py index 42e50b8b..ddfbefeb 100644 --- a/qiskit_algorithms/time_evolvers/variational/var_qte.py +++ b/qiskit_algorithms/time_evolvers/variational/var_qte.py @@ -22,7 +22,7 @@ from qiskit import QuantumCircuit from qiskit.circuit import Parameter -from qiskit.primitives import BaseEstimator +from qiskit.primitives import BaseEstimatorV2 from qiskit.quantum_info.operators.base_operator import BaseOperator from .solvers.ode.forward_euler_solver import ForwardEulerSolver @@ -76,7 +76,7 @@ def __init__( ansatz: QuantumCircuit, initial_parameters: Mapping[Parameter, float] | Sequence[float], variational_principle: VariationalPrinciple, - estimator: BaseEstimator, + estimator: BaseEstimatorV2, ode_solver: Type[OdeSolver] | str = ForwardEulerSolver, lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, num_timesteps: int | None = None, diff --git a/qiskit_algorithms/time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py b/qiskit_algorithms/time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py index bfa29efb..0560c5bd 100644 --- a/qiskit_algorithms/time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py +++ b/qiskit_algorithms/time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py @@ -21,7 +21,7 @@ from qiskit import QuantumCircuit from qiskit.circuit import Parameter -from qiskit.primitives import Estimator +from qiskit.primitives import StatevectorEstimator as Estimator from qiskit.quantum_info.operators.base_operator import BaseOperator from .imaginary_variational_principle import ImaginaryVariationalPrinciple diff --git a/qiskit_algorithms/time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py b/qiskit_algorithms/time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py index 822e6cc9..9a39277c 100644 --- a/qiskit_algorithms/time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py +++ b/qiskit_algorithms/time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py @@ -22,7 +22,7 @@ from qiskit import QuantumCircuit from qiskit.circuit import Parameter -from qiskit.primitives import Estimator +from qiskit.primitives import StatevectorEstimator as Estimator from qiskit.quantum_info import SparsePauliOp from qiskit.quantum_info.operators.base_operator import BaseOperator @@ -103,8 +103,8 @@ def evolution_gradient( """ try: - estimator_job = self.gradient._estimator.run([ansatz], [hamiltonian], [param_values]) - energy = estimator_job.result().values[0] + estimator_job = self.gradient._estimator.run([(ansatz, hamiltonian, param_values)]) + energy = estimator_job.result()[0].data.evs except Exception as exc: raise AlgorithmError("The primitive job failed!") from exc diff --git a/test/time_evolvers/variational/test_var_qite.py b/test/time_evolvers/variational/test_var_qite.py index e25b3743..de4878c0 100644 --- a/test/time_evolvers/variational/test_var_qite.py +++ b/test/time_evolvers/variational/test_var_qite.py @@ -19,7 +19,7 @@ from qiskit import QuantumCircuit from qiskit.circuit import Parameter -from qiskit.primitives import Estimator +from qiskit.primitives import StatevectorEstimator as Estimator from qiskit.quantum_info import SparsePauliOp, Pauli from qiskit.circuit.library import EfficientSU2 from qiskit.quantum_info import Statevector @@ -90,60 +90,32 @@ def test_run_d_1_with_aux_ops(self): 1.939353810908912, ] - with self.subTest(msg="Test exact backend."): - algorithm_globals.random_seed = self.seed - estimator = Estimator() - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator) - var_principle = ImaginaryMcLachlanPrinciple(qgt, gradient) + algorithm_globals.random_seed = self.seed - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=25 - ) - evolution_result = var_qite.evolve(evolution_problem) - - aux_ops = evolution_result.aux_ops_evaluated - - parameter_values = evolution_result.parameter_values[-1] - - expected_aux_ops = (-0.2177982985749799, 0.2556790598588627) + estimator = Estimator(seed=self.seed) + qgt = LinCombQGT(estimator) + gradient = LinCombEstimatorGradient(estimator) + var_principle = ImaginaryMcLachlanPrinciple(qgt, gradient) - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected[i], decimal=2 - ) + var_qite = VarQITE( + ansatz, init_param_values, var_principle, estimator, num_timesteps=25 + ) + evolution_result = var_qite.evolve(evolution_problem) - np.testing.assert_array_almost_equal( - [result[0] for result in aux_ops], expected_aux_ops - ) + aux_ops = evolution_result.aux_ops_evaluated - with self.subTest(msg="Test shot-based backend."): - algorithm_globals.random_seed = self.seed + parameter_values = evolution_result.parameter_values[-1] - estimator = Estimator(options={"shots": 4096, "seed": self.seed}) - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator) - var_principle = ImaginaryMcLachlanPrinciple(qgt, gradient) + expected_aux_ops = (-0.24629853310903974, 0.2518122871921184) - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=25 + for i, parameter_value in enumerate(parameter_values): + np.testing.assert_almost_equal( + float(parameter_value), thetas_expected_shots[i], decimal=2 ) - evolution_result = var_qite.evolve(evolution_problem) - - aux_ops = evolution_result.aux_ops_evaluated - parameter_values = evolution_result.parameter_values[-1] - - expected_aux_ops = (-0.24629853310903974, 0.2518122871921184) - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected_shots[i], decimal=2 - ) - - np.testing.assert_array_almost_equal( - [result[0] for result in aux_ops], expected_aux_ops - ) + np.testing.assert_array_almost_equal( + [result[0] for result in aux_ops], expected_aux_ops + ) def test_run_d_1_t_7(self): """Test VarQITE for d = 1 and t = 7 with RK45 ODE solver.""" @@ -263,51 +235,29 @@ def test_run_d_1_time_dependent(self): state_expected = Statevector([0.34849948 + 0.0j, 0.93730897 + 0.0j]).to_dict() # the expected final state is Statevector([0.34849948+0.j, 0.93730897+0.j]) - with self.subTest(msg="Test exact backend."): - algorithm_globals.random_seed = self.seed - estimator = Estimator() - var_principle = ImaginaryMcLachlanPrinciple() - - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=100 - ) - evolution_result = var_qite.evolve(evolution_problem) - evolved_state = evolution_result.evolved_state - parameter_values = evolution_result.parameter_values[-1] - - for key, evolved_value in Statevector(evolved_state).to_dict().items(): - # np.allclose works with complex numbers - self.assertTrue(np.allclose(evolved_value, state_expected[key], 1e-02)) + algorithm_globals.random_seed = self.seed - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected[i], decimal=2 - ) - - with self.subTest(msg="Test shot-based backend."): - algorithm_globals.random_seed = self.seed - - estimator = Estimator(options={"shots": 4 * 4096, "seed": self.seed}) - var_principle = ImaginaryMcLachlanPrinciple() + estimator = Estimator(seed=self.seed) + var_principle = ImaginaryMcLachlanPrinciple() - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=100 - ) + var_qite = VarQITE( + ansatz, init_param_values, var_principle, estimator, num_timesteps=100 + ) - evolution_result = var_qite.evolve(evolution_problem) + evolution_result = var_qite.evolve(evolution_problem) - evolved_state = evolution_result.evolved_state + evolved_state = evolution_result.evolved_state - parameter_values = evolution_result.parameter_values[-1] + parameter_values = evolution_result.parameter_values[-1] - for key, evolved_value in Statevector(evolved_state).to_dict().items(): - # np.allclose works with complex numbers - self.assertTrue(np.allclose(evolved_value, state_expected[key], 1e-02)) + for key, evolved_value in Statevector(evolved_state).to_dict().items(): + # np.allclose works with complex numbers + self.assertTrue(np.allclose(evolved_value, state_expected[key], 1e-02)) - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected_shots[i], decimal=2 - ) + for i, parameter_value in enumerate(parameter_values): + np.testing.assert_almost_equal( + float(parameter_value), thetas_expected_shots[i], decimal=2 + ) def _test_helper(self, observable, thetas_expected, time, var_qite, decimal): evolution_problem = TimeEvolutionProblem(observable, time) diff --git a/test/time_evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py b/test/time_evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py index 9d8c54fa..b16460ed 100644 --- a/test/time_evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py +++ b/test/time_evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py @@ -23,7 +23,7 @@ from qiskit.quantum_info import SparsePauliOp from qiskit.circuit.library import EfficientSU2 -from qiskit.primitives import Estimator +from qiskit.primitives import StatevectorEstimator as Estimator from qiskit_algorithms.gradients import LinCombEstimatorGradient, DerivativeType from qiskit_algorithms.time_evolvers.variational import ( @@ -103,7 +103,7 @@ def test_calc_calc_evolution_gradient(self): def test_gradient_setting(self): """Test reactions to wrong gradient settings..""" - estimator = Estimator() + estimator = Estimator(seed=123) gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) with self.assertWarns(Warning): diff --git a/test/time_evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py b/test/time_evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py index 40195d16..60cef1ae 100644 --- a/test/time_evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py +++ b/test/time_evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py @@ -24,7 +24,7 @@ from qiskit.quantum_info import SparsePauliOp from qiskit.circuit.library import EfficientSU2 -from qiskit.primitives import Estimator +from qiskit.primitives import StatevectorEstimator as Estimator from qiskit_algorithms.gradients import LinCombEstimatorGradient, DerivativeType from qiskit_algorithms.time_evolvers.variational import ( @@ -108,7 +108,7 @@ def test_calc_evolution_gradient(self): def test_gradient_setting(self): """Test reactions to wrong gradient settings..""" - estimator = Estimator() + estimator = Estimator(seed=123) gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.REAL) with self.assertWarns(Warning): From 3fcc2654136f0a71e5faeef54d70f5d166534579 Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Fri, 6 Dec 2024 00:20:49 +0100 Subject: [PATCH 30/31] Fixed linting and VQD test --- qiskit_algorithms/eigensolvers/vqd.py | 6 +++--- .../state_fidelities/compute_uncompute.py | 1 - .../time_evolvers/trotterization/trotter_qrte.py | 2 +- test/eigensolvers/test_vqd.py | 16 ++++++++++++++-- test/time_evolvers/test_trotter_qrte.py | 2 +- test/time_evolvers/variational/test_var_qite.py | 12 +++--------- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/qiskit_algorithms/eigensolvers/vqd.py b/qiskit_algorithms/eigensolvers/vqd.py index fcc3e1b1..a0d4d420 100644 --- a/qiskit_algorithms/eigensolvers/vqd.py +++ b/qiskit_algorithms/eigensolvers/vqd.py @@ -363,9 +363,9 @@ def compute_eigenvalues( raise AlgorithmError( f"Convergence threshold is set to {self.convergence_threshold} but an " - f"average fidelity of {average_fidelity:.5f} with the previous eigenstates" - f"has been observed during the evaluation of the {step}{suffix} lowest" - f"eigenvalue." + f"average (weighted by the betas) fidelity of {average_fidelity:.5f} with " + f"the previous eigenstates has been observed during the evaluation of the " + f"{step}{suffix} lowest eigenvalue." ) logger.info( ( diff --git a/qiskit_algorithms/state_fidelities/compute_uncompute.py b/qiskit_algorithms/state_fidelities/compute_uncompute.py index eb2d5377..8bdba579 100644 --- a/qiskit_algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit_algorithms/state_fidelities/compute_uncompute.py @@ -152,7 +152,6 @@ def _run( ValueError: At least one pair of circuits must be defined. AlgorithmError: If the sampler job is not completed successfully. """ - circuits = self._construct_circuits(circuits_1, circuits_2) if len(circuits) == 0: raise ValueError( diff --git a/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py b/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py index 15c9750b..4d216975 100644 --- a/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py +++ b/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py @@ -211,7 +211,7 @@ def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult evolved_state.append(initial_state, evolved_state.qubits) if self._transpiler is not None: - evolved_state = self._transpiler.run(evolved_state, **self._transpiler_options) + evolved_state = self._transpiler.run(evolved_state, **self._transpiler_options) if self._insert_barriers: evolved_state.barrier() diff --git a/test/eigensolvers/test_vqd.py b/test/eigensolvers/test_vqd.py index ac305dec..33ad560f 100644 --- a/test/eigensolvers/test_vqd.py +++ b/test/eigensolvers/test_vqd.py @@ -58,7 +58,7 @@ def setUp(self): self.ry_wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") self.estimator = Estimator(seed=self.seed) - self.fidelity = ComputeUncompute(Sampler(seed=self.seed, default_shots=100_000)) + self.fidelity = ComputeUncompute(Sampler(seed=self.seed, default_shots=10_000)) self.betas = [3] @data(H2_SPARSE_PAULI) @@ -457,6 +457,18 @@ def test_convergence_threshold(self): SLSQP(), k=2, betas=self.betas, + initial_point=np.array( + [ + 2.15707009, + -2.6128808, + 1.40478697, + -1.73909435, + -2.89100903, + 1.75289926, + -0.14760479, + -2.00011645, + ] + ), convergence_threshold=1e-3, ) with self.subTest("Failed convergence"): @@ -467,7 +479,7 @@ def test_convergence_threshold(self): vqd.convergence_threshold = 1e-1 result = vqd.compute_eigenvalues(operator=H2_SPARSE_PAULI) np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=1 + result.eigenvalues.real, self.h2_energy_excited[:2], decimal=2 ) diff --git a/test/time_evolvers/test_trotter_qrte.py b/test/time_evolvers/test_trotter_qrte.py index a0826995..9341420e 100644 --- a/test/time_evolvers/test_trotter_qrte.py +++ b/test/time_evolvers/test_trotter_qrte.py @@ -258,7 +258,7 @@ def callback(**kwargs): trotter_qrte = TrotterQRTE( estimator=Estimator(), transpiler=pass_manager, - transpiler_options={"callback": callback} + transpiler_options={"callback": callback}, ) trotter_qrte.evolve(evolution_problem) diff --git a/test/time_evolvers/variational/test_var_qite.py b/test/time_evolvers/variational/test_var_qite.py index ba5971e4..f058b5eb 100644 --- a/test/time_evolvers/variational/test_var_qite.py +++ b/test/time_evolvers/variational/test_var_qite.py @@ -97,9 +97,7 @@ def test_run_d_1_with_aux_ops(self): gradient = LinCombEstimatorGradient(estimator) var_principle = ImaginaryMcLachlanPrinciple(qgt, gradient) - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=25 - ) + var_qite = VarQITE(ansatz, init_param_values, var_principle, estimator, num_timesteps=25) evolution_result = var_qite.evolve(evolution_problem) aux_ops = evolution_result.aux_ops_evaluated @@ -113,9 +111,7 @@ def test_run_d_1_with_aux_ops(self): float(parameter_value), thetas_expected_shots[i], decimal=2 ) - np.testing.assert_array_almost_equal( - [result[0] for result in aux_ops], expected_aux_ops - ) + np.testing.assert_array_almost_equal([result[0] for result in aux_ops], expected_aux_ops) def test_run_d_1_t_7(self): """Test VarQITE for d = 1 and t = 7 with RK45 ODE solver.""" @@ -240,9 +236,7 @@ def test_run_d_1_time_dependent(self): estimator = Estimator(seed=self.seed) var_principle = ImaginaryMcLachlanPrinciple() - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=100 - ) + var_qite = VarQITE(ansatz, init_param_values, var_principle, estimator, num_timesteps=100) evolution_result = var_qite.evolve(evolution_problem) From a1171dccd0b97d7a6bf6b914cf37d5bfb57302dc Mon Sep 17 00:00:00 2001 From: Tristan NEMOZ Date: Fri, 6 Dec 2024 00:23:14 +0100 Subject: [PATCH 31/31] Updated TODO --- TODO.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index 70033479..a29cf8f3 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,7 @@ -Tristan: - [x] phase_estimators/hamiltonian_phase_estimation.py - [x] phase_estimators/ipe.py - [x] phase_estimators/phase_estimation.py -- [] eigensolvers/vqd.py (test doesn't work) +- [x] eigensolvers/vqd.py - [x] amplitude_amplifiers/grover.py (test doesn't work but should probably be refactored or removed) - [x] time_evolvers/pvqd/utils.py - [x] time_evolvers/pvqd/pvqd.py @@ -17,8 +16,6 @@ Tristan: - [x] optimizers/umda.py - [x] optimizers/spsa.py - [x] observables_evaluator.py - -Léna: - [] gradients/reverse/reverse_gradient.py - [] gradients/reverse/reverse_qgt.py - [] gradients/finite_diff/finite_diff_estimator_gradient.py