Skip to content
This repository has been archived by the owner on Dec 7, 2021. It is now read-only.

Don't add Hadamards to a user-defined initial state in QAOA #1362

Merged
merged 10 commits into from
Oct 23, 2020
9 changes: 4 additions & 5 deletions qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/var_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import numpy as np

from qiskit.aqua.operators import (OperatorBase, X, I, H, Zero, CircuitStateFn,
from qiskit.aqua.operators import (OperatorBase, X, I, H, CircuitStateFn,
EvolutionFactory, LegacyBaseOperator)
from qiskit.aqua.components.variational_forms import VariationalForm
from qiskit.aqua.components.initial_states import InitialState
Expand Down Expand Up @@ -80,13 +80,12 @@ def construct_circuit(self, parameters, q=None):
self.num_parameters, len(parameters)
))

circuit = (H ^ self._num_qubits)
# initialize circuit, possibly based on given register/initial state
if self._initial_state is not None:
init_state = CircuitStateFn(self._initial_state.construct_circuit('circuit'))
stateVector = CircuitStateFn(self._initial_state.construct_circuit('circuit'))
circuit = stateVector.to_circuit_op()
else:
init_state = Zero
circuit = circuit.compose(init_state)
circuit = (H ^ self._num_qubits)

for idx in range(self._p):
circuit = (self._cost_operator * parameters[idx]).exp_i().compose(circuit)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
fixes:
- |
A bug that mixes custom ``initial_state`` in ``QAOA`` with Hadamard gates has been fixed. This doesn't change functionality of QAOA if no initial_state is provided by the user. Attention should be taken if your implementation uses QAOA with cusom ``initial_state`` parameter as the optimization results might differ.
critical:
- |
Be aware that ``initial_state`` parameter in ``QAOA`` has now different implementation as a result of a bug fix. The previous implementation wrongly mixed the user provided ``initial_state`` with Hadamard gates. The issue is fixed now. No attention needed if your code does not make use of the user provided ``initial_state`` parameter.
63 changes: 62 additions & 1 deletion test/optimization/test_qaoa.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
import unittest
from test.optimization import QiskitOptimizationTestCase

import math
import numpy as np
from ddt import ddt, idata, unpack
from qiskit import BasicAer
from qiskit import BasicAer, QuantumCircuit, execute

from qiskit.optimization.applications.ising import max_cut
from qiskit.optimization.applications.ising.common import sample_most_likely
from qiskit.aqua.components.optimizers import COBYLA
from qiskit.aqua.components.initial_states import Custom, Zero
from qiskit.aqua.algorithms import QAOA
from qiskit.aqua import QuantumInstance, aqua_globals
from qiskit.aqua.operators import X, I
Expand All @@ -47,6 +49,8 @@
M2 = None
S2 = {'1011', '0100'}

CUSTOM_SUPERPOSITION = [1/math.sqrt(15)] * 15 + [0]


@ddt
class TestQAOA(QiskitOptimizationTestCase):
Expand Down Expand Up @@ -161,6 +165,63 @@ def cb_callback(eval_count, parameters, mean, std):
with self.subTest('Solution'):
self.assertIn(''.join([str(int(i)) for i in graph_solution]), solutions)

@idata([
[W2, None],
[W2, [1.0] + 15*[0.0]],
[W2, CUSTOM_SUPERPOSITION]
])
@unpack
def test_qaoa_initial_state(self, w, init_state):
""" QAOA initial state test """

optimizer = COBYLA()
qubit_op, _ = max_cut.get_operator(w)

init_pt = [0.0, 0.0] # Avoid generating random initial point

if init_state is None:
initial_state = None
else:
initial_state = Custom(num_qubits=4, state_vector=init_state)

quantum_instance = QuantumInstance(BasicAer.get_backend('statevector_simulator'))
qaoa_zero_init_state = QAOA(qubit_op, optimizer, initial_point=init_pt,
initial_state=Zero(qubit_op.num_qubits),
quantum_instance=quantum_instance)
qaoa = QAOA(qubit_op, optimizer, initial_point=init_pt,
initial_state=initial_state, quantum_instance=quantum_instance)

zero_circuits = qaoa_zero_init_state.construct_circuit(init_pt)
custom_circuits = qaoa.construct_circuit(init_pt)

self.assertEqual(len(zero_circuits), len(custom_circuits))

backend = BasicAer.get_backend('statevector_simulator')
for zero_circ, custom_circ in zip(zero_circuits, custom_circuits):

z_length = len(zero_circ.data)
c_length = len(custom_circ.data)

self.assertGreaterEqual(c_length, z_length)
self.assertTrue(zero_circ.data == custom_circ.data[-z_length:])

custom_init_qc = custom_circ.copy()
custom_init_qc.data = custom_init_qc.data[0:c_length-z_length]

if initial_state is None:
original_init_qc = QuantumCircuit(qubit_op.num_qubits)
original_init_qc.h(range(qubit_op.num_qubits))
else:
original_init_qc = initial_state.construct_circuit()

job_init_state = execute(original_init_qc, backend)
job_qaoa_init_state = execute(custom_init_qc, backend)

statevector_original = job_init_state.result().get_statevector(original_init_qc)
statevector_custom = job_qaoa_init_state.result().get_statevector(custom_init_qc)

self.assertEqual(statevector_original.tolist(), statevector_custom.tolist())


if __name__ == '__main__':
unittest.main()