Skip to content

Commit

Permalink
Fix parameter order in VQE, if the ansatz is resized (#7479) (#7526)
Browse files Browse the repository at this point in the history
* 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 d0b26c2)

Co-authored-by: Julien Gacon <gaconju@gmail.com>
  • Loading branch information
mergify[bot] and Cryoris authored Jan 13, 2022
1 parent b8a2bd7 commit e755ad0
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 12 deletions.
18 changes: 7 additions & 11 deletions qiskit/algorithms/minimum_eigen_solvers/vqe.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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]]:
Expand Down Expand Up @@ -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 "
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
18 changes: 17 additions & 1 deletion test/python/algorithms/test_vqe.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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()

0 comments on commit e755ad0

Please sign in to comment.