Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix parameter order in VQE, if the ansatz is resized (backport #7479) #7526

Merged
merged 1 commit into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()