From e755ad0549dec8b32936f3e42c70c15d77deaf67 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 13 Jan 2022 13:58:40 +0000 Subject: [PATCH] Fix parameter order in VQE, if the ansatz is resized (#7479) (#7526) * fix VQE param order if ansatz is resized * add test, rm trailing comment * fix construct circuit for params != circuit params * fix test_max_evals_grouped In this test, the ansatz was resized and thus the parameters ASCII-sorted. Now they are sorted by vector, which leads to a slightly different optimization (since the params are in a different order). Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> (cherry picked from commit d0b26c280d0d02d53a879e776e62505b8d0a8f75) Co-authored-by: Julien Gacon --- qiskit/algorithms/minimum_eigen_solvers/vqe.py | 18 +++++++----------- ...der-if-ansatz-resized-14634a7efff7c74f.yaml | 10 ++++++++++ test/python/algorithms/test_vqe.py | 18 +++++++++++++++++- 3 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/fix-vqe-paramorder-if-ansatz-resized-14634a7efff7c74f.yaml diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index a9bd46fa629c..83a09e9b9521 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -141,8 +141,6 @@ def __init__( self.expectation = expectation self._include_custom = include_custom - # set ansatz -- still supporting pre 0.18.0 sorting - self._ansatz_params = None self._ansatz = None self.ansatz = ansatz @@ -186,7 +184,6 @@ def ansatz(self, ansatz: Optional[QuantumCircuit]): ansatz = RealAmplitudes() self._ansatz = ansatz - self._ansatz_params = list(ansatz.parameters) @property def gradient(self) -> Optional[Union[GradientBase, Callable]]: @@ -278,7 +275,6 @@ def _check_operator_ansatz(self, operator: OperatorBase): # try to set the number of qubits on the ansatz, if possible try: self.ansatz.num_qubits = operator.num_qubits - self._ansatz_params = sorted(self.ansatz.parameters, key=lambda p: p.name) except AttributeError as ex: raise AlgorithmError( "The number of qubits of the ansatz does not match the " @@ -382,8 +378,7 @@ def construct_expectation( else: expectation = self.expectation - param_dict = dict(zip(self._ansatz_params, parameter)) # type: Dict - wave_function = self.ansatz.assign_parameters(param_dict) + wave_function = self.ansatz.assign_parameters(parameter) observable_meas = expectation.convert(StateFn(operator, is_measurement=True)) ansatz_circuit_op = CircuitStateFn(wave_function) @@ -525,8 +520,8 @@ def compute_minimum_eigenvalue( # optimization routine. if isinstance(self._gradient, GradientBase): gradient = self._gradient.gradient_wrapper( - ~StateFn(operator) @ StateFn(self._ansatz), - bind_params=self._ansatz_params, + ~StateFn(operator) @ StateFn(self.ansatz), + bind_params=list(self.ansatz.parameters), backend=self._quantum_instance, ) else: @@ -564,7 +559,7 @@ def compute_minimum_eigenvalue( result = VQEResult() result.optimal_point = opt_result.x - result.optimal_parameters = dict(zip(self._ansatz_params, opt_result.x)) + result.optimal_parameters = dict(zip(self.ansatz.parameters, opt_result.x)) result.optimal_value = opt_result.fun result.cost_function_evals = opt_result.nfev result.optimizer_time = eval_time @@ -615,14 +610,15 @@ def get_energy_evaluation( if num_parameters == 0: raise RuntimeError("The ansatz must be parameterized, but has 0 free parameters.") + ansatz_params = self.ansatz.parameters expect_op, expectation = self.construct_expectation( - self._ansatz_params, operator, return_expectation=True + ansatz_params, operator, return_expectation=True ) def energy_evaluation(parameters): parameter_sets = np.reshape(parameters, (-1, num_parameters)) # Create dict associating each parameter with the lists of parameterization values for it - param_bindings = dict(zip(self._ansatz_params, parameter_sets.transpose().tolist())) + param_bindings = dict(zip(ansatz_params, parameter_sets.transpose().tolist())) start_time = time() sampled_expect_op = self._circuit_sampler.convert(expect_op, params=param_bindings) diff --git a/releasenotes/notes/fix-vqe-paramorder-if-ansatz-resized-14634a7efff7c74f.yaml b/releasenotes/notes/fix-vqe-paramorder-if-ansatz-resized-14634a7efff7c74f.yaml new file mode 100644 index 000000000000..963879922bc5 --- /dev/null +++ b/releasenotes/notes/fix-vqe-paramorder-if-ansatz-resized-14634a7efff7c74f.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Fix a bug in :class:`~qiskit.algorithms.VQE` where the parameters of the ansatz were + still explicitly ASCII-sorted by their name if the ansatz was resized. That led to a + mismatching order of the optimized values in the ``optimal_point`` attribute of the result + object. + + This bug did in particular occur if no ansatz was set by the user and the VQE chose + a default with 11 or more free parameters. diff --git a/test/python/algorithms/test_vqe.py b/test/python/algorithms/test_vqe.py index a8b43d819929..c80656361a43 100644 --- a/test/python/algorithms/test_vqe.py +++ b/test/python/algorithms/test_vqe.py @@ -46,6 +46,7 @@ X, Z, ) +from qiskit.quantum_info import Statevector from qiskit.transpiler import PassManager, PassManagerConfig from qiskit.transpiler.preset_passmanagers import level_1_pass_manager from qiskit.utils import QuantumInstance, algorithm_globals, has_aer @@ -170,7 +171,7 @@ def test_missing_varform_params(self): @data( (SLSQP(maxiter=50), 5, 4), - (SPSA(maxiter=150), 3, 2), # max_evals_grouped=n or =2 if n>2 + (SPSA(maxiter=150), 2, 2), # max_evals_grouped=n or =2 if n>2 ) @unpack def test_max_evals_grouped(self, optimizer, places, max_evals_grouped): @@ -714,6 +715,21 @@ def test_2step_transpile(self): ] self.assertEqual([record.message for record in cm.records], expected) + def test_construct_eigenstate_from_optpoint(self): + """Test constructing the eigenstate from the optimal point, if the default ansatz is used.""" + + # use Hamiltonian yielding more than 11 parameters in the default ansatz + hamiltonian = Z ^ Z ^ Z + optimizer = SPSA(maxiter=1, learning_rate=0.01, perturbation=0.01) + quantum_instance = QuantumInstance( + backend=BasicAer.get_backend("statevector_simulator"), basis_gates=["u3", "cx"] + ) + vqe = VQE(optimizer=optimizer, quantum_instance=quantum_instance) + result = vqe.compute_minimum_eigenvalue(hamiltonian) + + optimal_circuit = vqe.ansatz.bind_parameters(result.optimal_point) + self.assertTrue(Statevector(result.eigenstate).equiv(optimal_circuit)) + if __name__ == "__main__": unittest.main()