Repository: Qiskit/qiskit-terra
Pull request #5678 (2021-01-21 23:58:23)

Updated QAOA implementation
<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->

### Summary
This is an update to the QAOA implementation as it has been moved to qiskit-terra.
Changes in this PR:
- A new class `QAOACircuit` is introduced that extends `BlueprintCircuit` and replaces `QAOAVarForm`.
- `QAOAVarForm` is now removed and is no longer in use in the implementation.
- QAOA tests are copied from qiskit-optimization and modified for use in qiskit-terra. Tests in optimization to be removed once they are accepted in terra.


stefan-woerner: @Cryoris good point, yes, should be moved to the circuit library. I am fine with `QAOAAnsatz` as well, if this is more consistent with the other names.
adekusar-drl: @Cryoris

> Could we change the name of `QAOACircuit`? None of the other ansatz circuits have the word "circuit" in them, but they're just called `TwoLocal RealAmplitudes PauliTwoDesign` etc. Maybe `QAOAAnsatz`? Also, I feel we should put this circuit in the circuit library with all other Ansatzes, it seems like the more natural place where people would look for it.

Renamed to `QAOAAnsatz`. 
What's the point of moving the circuit class and leaving the algorithm `QAOA` class where it is now? The algorithm does not make much sense without the circuit and vice versa. So, I believe they should stay together.

> 
> Could you also add some tests for the `QAOA<Circuit, Ansatz>`?

Sorry, but I don't understand this notation in context of python and QAOA implementation.
woodsp-ibm: > Also, I feel we should put this circuit in the circuit library with all other Ansatzes, it seems like the more natural place where people would look for it.

I think it would be a lot easier for people to see that library had ansatzes if that were the name of the folder - as it is I am still pointing out to people that we have these circuits there as its not so obvious to many.

> What's the point of moving the circuit class and leaving the algorithm QAOA class where it is now? The algorithm does not make much sense without the circuit and vice versa. So, I believe they should stay together.

I guess its a choice. We had chosen to keep the ansatz along with the algo since, unlike the others it was not dependent on a simple num_qubits but on the operator. As such I wonder whether we should allow the cost_operator to be set later in an analogous way to num_qubits. Though while VQE knows to set num_qubits it does not know about setting cost operator which is just done by QAOA. If things were done more this way then QAOA could look more like VQE in terms of setting var form params on the var form and being given one, i.e. we might not need to pass 'p' and 'mixer' via the algo to get it to the var form. Of course at this point, since it looks so much like VQE, if VQE supported passing an operator when needed to a var form, or setting num_qubits as needed, then QAOA would not be needed as such, though having it there as an algo makes it more visible even though today its a simple extension of VQE.

One other observation, if the ansatz is moved to the circuit library then I think the qaoa algo should be moved up a level and the folder eliminated as it makes little sense to have that structure there then.

Cryoris: The point of moving the circuit to the library is that we have all circuits in one place. So whenever you need an ansatz or a special circuit you go to the library instead of looking other submodules 🙂 

> The algorithm does not make much sense without the circuit and vice versa.

But it doesn't matter where the circuit lives, does it? We can as well do `from qiskit.circuit.library import` instead of `from .qaoa_ansatz import` in the algorithm.

@woodsp-ibm Yeah if we put QAOA there I agree it makes sense to have something better than `n_local`.
adekusar-drl: > The point of moving the circuit to the library is that we have all circuits in one place. So whenever you need an ansatz or a special circuit you go to the library instead of looking other submodules 🙂

OK, where should it be place in the library then? Yes, I will eliminate `qaoa` package and move the algo up in the structure.
Cryoris: Could you just put it in `qiskit.circuit.library.n_local` for now? Then we can further discuss if that folder needs restructuring/renaming. But since users anyways import from `qiskit.circuit.library` we can still do that later.
woodsp-ibm: In regards of this comment above to which I did not really see response/thought.

> I guess its a choice. We had chosen to keep the ansatz along with the algo since, unlike the others it was not dependent on a simple num_qubits but on the operator. As such I wonder whether we should allow the cost_operator to be set later in an analogous way to num_qubits. Though while VQE knows to set num_qubits it does not know about setting cost operator which is just done by QAOA. If things were done more this way then QAOA could look more like VQE in terms of setting var form params on the var form and being given one, i.e. we might not need to pass 'p' and 'mixer' via the algo to get it to the var form. Of course at this point, since it looks so much like VQE, if VQE supported passing an operator when needed to a var form, or setting num_qubits as needed, then QAOA would not be needed as such, though having it there as an algo makes it more visible even though today its a simple extension of VQE.

This change makes the ansatz an external object but its still created internally in QAOA with things like mixer and reps being passed through QAOA constructor in order to build out the ansatz. This is different than VQE where the Ansatz is built out and passed in. Here though the operator in its entirety needs to be passed to the var form though, not just the num_qubits as is done with VQE. Maybe we don't want to go as far as this in the refactor for now; and just leave QAOA building the ansatz out internally. Making it more visible and separately instantiatable it does seem like one is part way there.
Cryoris: Wrt this comment:
> I guess its a choice. We had chosen to keep the ansatz along with the algo since, unlike the others it was not dependent on a simple num_qubits but on the operator. As such I wonder whether we should allow the cost_operator to be set later in an analogous way to num_qubits. Though while VQE knows to set num_qubits it does not know about setting cost operator which is just done by QAOA. If things were done more this way then QAOA could look more like VQE in terms of setting var form params on the var form and being given one, i.e. we might not need to pass 'p' and 'mixer' via the algo to get it to the var form. Of course at this point, since it looks so much like VQE, if VQE supported passing an operator when needed to a var form, or setting num_qubits as needed, then QAOA would not be needed as such, though having it there as an algo makes it more visible even though today its a simple extension of VQE.

It's true that with the `QAOAAnsatz` as external object we should just be able to do something like
```python
qaoa = VQE(QAOAAnsatz(), optimizer)
qaoa.compute_minimum_eigenvalue(operator)
```

I personally think keeping an explicit QAOA class still makes sense for visibility and users that are not yet familiar with the connection between VQE and QAOA. However we can clearly point that out in the documentation.


Patch:
diff --git a/qiskit/algorithms/minimum_eigen_solvers/qaoa/qaoa.py b/qiskit/algorithms/minimum_eigen_solvers/qaoa.py
old mode 100755
new mode 100644
similarity index 91%
rename from qiskit/algorithms/minimum_eigen_solvers/qaoa/qaoa.py
rename to qiskit/algorithms/minimum_eigen_solvers/qaoa.py
index 87fdb7c10..7ee40cdb6
--- a/qiskit/algorithms/minimum_eigen_solvers/qaoa/qaoa.py
+++ b/qiskit/algorithms/minimum_eigen_solvers/qaoa.py
@@ -1,6 +1,6 @@
 # This code is part of Qiskit.
 #
-# (C) Copyright IBM 2018, 2020.
+# (C) Copyright IBM 2018, 2021.
 #
 # 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
@@ -12,29 +12,25 @@
 
 """ The Quantum Approximate Optimization Algorithm. """
 
-from typing import List, Callable, Optional, Union
 import logging
+from typing import List, Callable, Optional, Union
+
 import numpy as np
 
+from qiskit.algorithms.optimizers import Optimizer
 from qiskit.circuit import QuantumCircuit
-from qiskit.providers import BaseBackend
-from qiskit.providers import Backend
 from qiskit.opflow import OperatorBase, ExpectationBase
 from qiskit.opflow.gradients import GradientBase
-from qiskit.algorithms.optimizers import Optimizer
-from qiskit.utils.validation import validate_min
+from qiskit.providers import Backend
+from qiskit.providers import BaseBackend
 from qiskit.utils.quantum_instance import QuantumInstance
-from .var_form import QAOAVarForm
-from ..vqe import VQE
+from qiskit.utils.validation import validate_min
+from qiskit.circuit.library.n_local.qaoa_ansatz import QAOAAnsatz
+from qiskit.algorithms.minimum_eigen_solvers.vqe import VQE
 
 logger = logging.getLogger(__name__)
 
 
-# pylint: disable=invalid-name
-# disable check for operator setter because of pylint bug
-# pylint: disable=no-member
-
-
 class QAOA(VQE):
     """
     The Quantum Approximate Optimization Algorithm.
@@ -59,12 +55,11 @@ class QAOA(VQE):
     and in `this paper <https://arxiv.org/abs/1709.03489>`__ for QAOA,
     to run constrained optimization problems where the mixer constrains
     the evolution to a feasible subspace of the full Hilbert space.
-
     """
 
     def __init__(self,
                  optimizer: Optimizer = None,
-                 p: int = 1,
+                 reps: int = 1,
                  initial_state: Optional[QuantumCircuit] = None,
                  mixer: Union[QuantumCircuit, OperatorBase] = None,
                  initial_point: Optional[np.ndarray] = None,
@@ -79,7 +74,7 @@ class QAOA(VQE):
         """
         Args:
             optimizer: A classical optimizer.
-            p: the integer parameter p as specified in https://arxiv.org/abs/1411.4028,
+            reps: the integer parameter :math:`p` as specified in https://arxiv.org/abs/1411.4028,
                 Has a minimum valid value of 1.
             initial_state: An optional initial state to prepend the QAOA circuit with
             mixer: the mixer Hamiltonian to evolve with or a custom quantum circuit. Allows support
@@ -116,9 +111,9 @@ class QAOA(VQE):
                 variational form, the evaluated mean and the evaluated standard deviation.
             quantum_instance: Quantum Instance or Backend
         """
-        validate_min('p', p, 1)
+        validate_min('reps', reps, 1)
 
-        self._p = p
+        self._reps = reps
         self._mixer = mixer
         self._initial_state = initial_state
 
@@ -132,14 +127,13 @@ class QAOA(VQE):
                          callback=callback,
                          quantum_instance=quantum_instance)
 
-    def _check_operator(self,
-                        operator: OperatorBase) -> OperatorBase:
-        # Recreates var_form based on operator parameter.
+    def _check_operator(self, operator: OperatorBase) -> OperatorBase:
+        # Recreates a circuit based on operator parameter.
         if operator.num_qubits != self.var_form.num_qubits:
-            self.var_form = QAOAVarForm(operator,
-                                        self._p,
-                                        initial_state=self._initial_state,
-                                        mixer_operator=self._mixer)
+            self.var_form = QAOAAnsatz(operator,
+                                       self._reps,
+                                       initial_state=self._initial_state,
+                                       mixer_operator=self._mixer)
         operator = super()._check_operator(operator)
         return operator
 
diff --git a/qiskit/algorithms/minimum_eigen_solvers/qaoa/__init__.py b/qiskit/algorithms/minimum_eigen_solvers/qaoa/__init__.py
deleted file mode 100644
index 4fdb0e21e..000000000
--- a/qiskit/algorithms/minimum_eigen_solvers/qaoa/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# This code is part of Qiskit.
-#
-# (C) Copyright IBM 2018, 2020.
-#
-# 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.
-
-""" QAOA packages """
-
-from .qaoa import QAOA
-
-
-__all__ = ['QAOA']
diff --git a/qiskit/algorithms/minimum_eigen_solvers/qaoa/var_form.py b/qiskit/algorithms/minimum_eigen_solvers/qaoa/var_form.py
deleted file mode 100755
index 0e07dddf8..000000000
--- a/qiskit/algorithms/minimum_eigen_solvers/qaoa/var_form.py
+++ /dev/null
@@ -1,129 +0,0 @@
-# This code is part of Qiskit.
-#
-# (C) Copyright IBM 2018, 2020.
-#
-# 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.
-
-"""Global X phases and parameterized problem hamiltonian."""
-
-from typing import Optional, Union, cast
-
-import numpy as np
-
-from qiskit import QuantumCircuit
-from qiskit.opflow import (OperatorBase, X, I, H, CircuitStateFn,
-                           EvolutionFactory, CircuitOp)
-from qiskit.algorithms.variational_forms import VariationalForm
-
-# pylint: disable=invalid-name
-
-
-class QAOAVarForm(VariationalForm):
-    """Global X phases and parameterized problem hamiltonian."""
-
-    def __init__(self,
-                 cost_operator: OperatorBase,
-                 p: int,
-                 initial_state: Optional[QuantumCircuit] = None,
-                 mixer_operator: Optional[Union[QuantumCircuit, OperatorBase]] = None):
-        """
-        Constructor, following the QAOA paper https://arxiv.org/abs/1411.4028
-
-        Args:
-            cost_operator: The operator representing the cost of
-                            the optimization problem,
-                            denoted as U(B, gamma) in the original paper.
-            p: The integer parameter p, which determines the depth of the circuit,
-                as specified in the original paper.
-            initial_state: An optional initial state to use.
-            mixer_operator: An optional custom mixer to use instead of the global X-rotations,
-                            denoted as U(B, beta) in the original paper. Can be an operator or
-                            an optionally parameterized quantum circuit.
-        Raises:
-            TypeError: invalid input
-        """
-        super().__init__()
-        self._cost_operator = cost_operator
-        self._num_qubits = cost_operator.num_qubits
-        self._p = p
-        self._initial_state = initial_state
-
-        if isinstance(mixer_operator, QuantumCircuit):
-            self._num_parameters = (1 + mixer_operator.num_parameters) * p
-            self._bounds = [(None, None)] * p + [(None, None)] * p * mixer_operator.num_parameters
-            self._mixer = mixer_operator
-        elif isinstance(mixer_operator, OperatorBase):
-            self._num_parameters = 2 * p
-            self._bounds = [(None, None)] * p + [(None, None)] * p
-            self._mixer = mixer_operator
-        elif mixer_operator is None:
-            self._num_parameters = 2 * p
-            # next three lines are to avoid a mypy error (incorrect types, etc)
-            self._bounds = []
-            self._bounds.extend([(None, None)] * p)
-            self._bounds.extend([(0, 2 * np.pi)] * p)
-            # Mixer is just a sum of single qubit X's on each qubit. Evolving by this operator
-            # will simply produce rx's on each qubit.
-            num_qubits = self._cost_operator.num_qubits
-            mixer_terms = [(I ^ left) ^ X ^ (I ^ (num_qubits - left - 1))
-                           for left in range(num_qubits)]
-            self._mixer = sum(mixer_terms)
-
-        self.support_parameterized_circuit = True
-
-    def construct_circuit(self, parameters, q=None):
-        """ construct circuit """
-        if not len(parameters) == self.num_parameters:
-            raise ValueError('Incorrect number of angles: expecting {}, but {} given.'.format(
-                self.num_parameters, len(parameters)
-            ))
-
-        # initialize circuit, possibly based on given register/initial state
-        if isinstance(self._initial_state, QuantumCircuit):
-            circuit_op = CircuitStateFn(self._initial_state)
-        elif self._initial_state is not None:
-            circuit_op = CircuitStateFn(self._initial_state.construct_circuit('circuit'))
-        else:
-            circuit_op = (H ^ self._num_qubits)
-
-        # iterate over layers
-        for idx in range(self._p):
-            # the first [:self._p] parameters are used for the cost operator,
-            # so we apply them here
-            circuit_op = (self._cost_operator * parameters[idx]).exp_i().compose(circuit_op)
-            if isinstance(self._mixer, OperatorBase):
-                mixer = cast(OperatorBase, self._mixer)
-                # we apply beta parameter in case of operator based mixer.
-                circuit_op = (mixer * parameters[idx + self._p]).exp_i().compose(circuit_op)
-            else:
-                # mixer as a quantum circuit that can be parameterized
-                mixer = cast(QuantumCircuit, self._mixer)
-                num_params = mixer.num_parameters
-                # the remaining [self._p:] parameters are used for the mixer,
-                # there may be multiple layers, so parameters are grouped by layers.
-                param_values = parameters[self._p + num_params * idx:
-                                          self._p + num_params * (idx + 1)]
-                param_dict = dict(zip(mixer.parameters, param_values))
-                mixer = mixer.assign_parameters(param_dict)
-                circuit_op = CircuitOp(mixer).compose(circuit_op)
-
-        evolution = EvolutionFactory.build(self._cost_operator)
-        circuit_op = evolution.convert(circuit_op)
-        return circuit_op.to_circuit()
-
-    @property
-    def setting(self):
-        """ returns setting """
-        ret = "Variational Form: {}\n".format(self.__class__.__name__)
-        params = ""
-        for key, value in self.__dict__.items():
-            if key[0] == "_":
-                params += "-- {}: {}\n".format(key[1:], value)
-        ret += "{}".format(params)
-        return ret
diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py
index 6b08b3a5a..e29a4aca5 100644
--- a/qiskit/circuit/library/__init__.py
+++ b/qiskit/circuit/library/__init__.py
@@ -205,6 +205,7 @@ N-local circuits
    RealAmplitudes
    EfficientSU2
    ExcitationPreserving
+   QAOAAnsatz
 
 
 Data encoding circuits
@@ -318,6 +319,7 @@ from .n_local import (
     RealAmplitudes,
     EfficientSU2,
     ExcitationPreserving,
+    QAOAAnsatz
 )
 from .data_preparation import (
     PauliFeatureMap,
diff --git a/qiskit/circuit/library/blueprintcircuit.py b/qiskit/circuit/library/blueprintcircuit.py
index 2131e477b..92dd7292c 100644
--- a/qiskit/circuit/library/blueprintcircuit.py
+++ b/qiskit/circuit/library/blueprintcircuit.py
@@ -71,6 +71,7 @@ class BlueprintCircuit(QuantumCircuit, ABC):
         """Invalidate the current circuit build."""
         self._data = None
         self._parameter_table = ParameterTable()
+        self.global_phase = 0
 
     @property
     def qregs(self):
diff --git a/qiskit/circuit/library/n_local/__init__.py b/qiskit/circuit/library/n_local/__init__.py
index c81bbc206..b2e7b2fd6 100644
--- a/qiskit/circuit/library/n_local/__init__.py
+++ b/qiskit/circuit/library/n_local/__init__.py
@@ -18,6 +18,7 @@ from .pauli_two_design import PauliTwoDesign
 from .real_amplitudes import RealAmplitudes
 from .efficient_su2 import EfficientSU2
 from .excitation_preserving import ExcitationPreserving
+from .qaoa_ansatz import QAOAAnsatz
 
 __all__ = [
     'NLocal',
@@ -25,5 +26,6 @@ __all__ = [
     'RealAmplitudes',
     'PauliTwoDesign',
     'EfficientSU2',
-    'ExcitationPreserving'
+    'ExcitationPreserving',
+    'QAOAAnsatz'
 ]
diff --git a/qiskit/circuit/library/n_local/qaoa_ansatz.py b/qiskit/circuit/library/n_local/qaoa_ansatz.py
new file mode 100644
index 000000000..8a7ab6d22
--- /dev/null
+++ b/qiskit/circuit/library/n_local/qaoa_ansatz.py
@@ -0,0 +1,294 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2018, 2021.
+#
+# 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.
+
+"""A generalized QAOA quantum circuit with a support of custom initial states and mixers."""
+# pylint: disable=R0401
+from typing import Optional, cast, Set, List, Tuple
+
+import numpy as np
+
+from qiskit.circuit.parameter import Parameter
+from qiskit.circuit.parametervector import ParameterVector
+from qiskit.circuit.quantumcircuit import QuantumCircuit
+from qiskit.circuit.quantumregister import QuantumRegister
+from ..blueprintcircuit import BlueprintCircuit
+
+
+class QAOAAnsatz(BlueprintCircuit):
+    """A generalized QAOA quantum circuit with a support of custom initial states and mixers.
+
+    References:
+
+        [1]: Farhi et al., A Quantum Approximate Optimization Algorithm.
+            `arXiv:1411.4028 <https://arxiv.org/pdf/1411.4028>`_
+    """
+
+    def __init__(self,
+                 cost_operator=None,
+                 reps: int = 1,
+                 initial_state: Optional[QuantumCircuit] = None,
+                 mixer_operator=None,
+                 name: str = "qaoa"):
+        r"""
+        Args:
+            cost_operator (OperatorBase, optional): The operator representing the cost of
+                the optimization problem, denoted as :math:`U(C, \gamma)` in the original paper.
+                Must be set either in the constructor or via property setter.
+            reps (int): The integer parameter p, which determines the depth of the circuit,
+                as specified in the original paper, default is 1.
+            initial_state (QuantumCircuit, optional): An optional initial state to use.
+                If `None` is passed then a set of Hadamard gates is applied as an initial state
+                to all qubits.
+            mixer_operator (OperatorBase or QuantumCircuit, optional): An optional custom mixer
+                to use instead of the global X-rotations, denoted as :math:`U(B, \beta)`
+                in the original paper. Can be an operator or an optionally parameterized quantum
+                circuit.
+            name (str): A name of the circuit, default 'qaoa'
+        """
+        super().__init__(name=name)
+
+        self._cost_operator = None
+        self._reps = reps
+        self._initial_state = initial_state
+        self._mixer = mixer_operator
+
+        # set this circuit as a not-built circuit
+        self._num_parameters = 0
+        self._bounds = None
+
+        # store cost operator and set the registers if the operator is not None
+        self.cost_operator = cost_operator
+
+    def _check_configuration(self, raise_on_failure: bool = True) -> bool:
+        valid = True
+
+        if self._cost_operator is None:
+            valid = False
+            if raise_on_failure:
+                raise AttributeError("The operator representing the cost of "
+                                     "the optimization problem is not set")
+        if self._reps is None or self._reps < 1:
+            valid = False
+            if raise_on_failure:
+                raise AttributeError("The integer parameter reps, which determines the depth "
+                                     "of the circuit, needs to be >= 1 but has value {}"
+                                     .format(self._reps))
+
+        if self.initial_state is not None and self.initial_state.num_qubits != self.num_qubits:
+            valid = False
+            if raise_on_failure:
+                raise AttributeError("The number of qubits of the initial state {} does not match "
+                                     "the number of qubits of the cost operator {}"
+                                     .format(self.initial_state.num_qubits, self.num_qubits))
+
+        if self.mixer_operator is not None and self.mixer_operator.num_qubits != self.num_qubits:
+            valid = False
+            if raise_on_failure:
+                raise AttributeError("The number of qubits of the mixer {} does not match "
+                                     "the number of qubits of the cost operator {}"
+                                     .format(self.mixer_operator.num_qubits, self.num_qubits))
+
+        return valid
+
+    def _build(self) -> None:
+        """Build the circuit."""
+        if self._data:
+            return
+
+        self._check_configuration()
+        self._data = []
+
+        # calculate bounds, num_parameters, mixer
+        self._calculate_parameters()
+
+        # parametrize circuit and build it
+        param_vector = ParameterVector("θ", self._num_parameters)
+        circuit = self._construct_circuit(param_vector)
+
+        # append(replace) the circuit to this
+        self.compose(circuit, inplace=True)
+
+    def _reset_registers(self, num_qubits):
+        """Set the registers and qubits to the new size."""
+        self._qregs = []
+        self._qubits = []
+
+        if num_qubits > 0:
+            qr = QuantumRegister(num_qubits, 'q')
+            self.add_register(qr)
+
+    @property
+    def parameters(self) -> Set[Parameter]:
+        """Get the :class:`~qiskit.circuit.Parameter` objects in the circuit.
+
+        Returns:
+            A set containing the unbound circuit parameters.
+        """
+        self._build()
+        return super().parameters
+
+    @property
+    def parameter_bounds(self) -> List[Tuple[float, float]]:
+        """Parameter bounds.
+
+        Returns: A list of pairs indicating the bounds, as (lower, upper). None indicates
+            an unbounded parameter in the corresponding direction. If None is returned, problem is
+            fully unbounded or is not built yet.
+        """
+
+        return self._bounds
+
+    def _calculate_parameters(self):
+        """Calculated internal parameters of the circuit to be built."""
+        from qiskit.opflow import OperatorBase
+        if isinstance(self._mixer, QuantumCircuit):
+            self._num_parameters = (1 + self._mixer.num_parameters) * self._reps
+            self._bounds = [(None, None)] * self._reps + \
+                           [(None, None)] * self._reps * self._mixer.num_parameters
+        elif isinstance(self._mixer, OperatorBase):
+            self._num_parameters = 2 * self._reps
+            self._bounds = [(None, None)] * self._reps + [(None, None)] * self._reps
+        elif self._mixer is None:
+            self._num_parameters = 2 * self._reps
+            self._bounds = [(None, None)] * self._reps + [(0, 2 * np.pi)] * self._reps
+
+    def _construct_circuit(self, parameters) -> QuantumCircuit:
+        """Construct a parametrized circuit."""
+        if not len(parameters) == self._num_parameters:
+            raise ValueError('Incorrect number of angles: expecting {}, but {} given.'.format(
+                self._num_parameters, len(parameters)
+            ))
+
+        # local imports to avoid circular imports
+        from qiskit.opflow import CircuitStateFn
+        from qiskit.opflow import CircuitOp, EvolutionFactory
+        from qiskit.opflow import OperatorBase
+
+        circuit_op = CircuitStateFn(self.initial_state)
+
+        # iterate over layers
+        for idx in range(self._reps):
+            # the first [:self._reps] parameters are used for the cost operator,
+            # so we apply them here
+            circuit_op = (self._cost_operator * parameters[idx]).exp_i().compose(circuit_op)
+            mixer = self.mixer_operator
+            if isinstance(mixer, OperatorBase):
+                mixer = cast(OperatorBase, mixer)
+                # we apply beta parameter in case of operator based mixer.
+                circuit_op = (mixer * parameters[idx + self._reps]).exp_i().compose(circuit_op)
+            else:
+                # mixer as a quantum circuit that can be parameterized
+                mixer = cast(QuantumCircuit, mixer)
+                num_params = mixer.num_parameters
+                # the remaining [self._p:] parameters are used for the mixer,
+                # there may be multiple layers, so parameters are grouped by layers.
+                param_values = parameters[self._reps + num_params * idx:
+                                          self._reps + num_params * (idx + 1)]
+                param_dict = dict(zip(mixer.parameters, param_values))
+                mixer = mixer.assign_parameters(param_dict)
+                circuit_op = CircuitOp(mixer).compose(circuit_op)
+
+        evolution = EvolutionFactory.build(self._cost_operator)
+        circuit_op = evolution.convert(circuit_op)
+        return circuit_op.to_circuit()
+
+    @property
+    def cost_operator(self):
+        """Returns an operator representing the cost of the optimization problem.
+
+        Returns:
+            OperatorBase: cost operator.
+        """
+        return self._cost_operator
+
+    @cost_operator.setter
+    def cost_operator(self, cost_operator) -> None:
+        """Sets cost operator.
+
+        Args:
+            cost_operator (OperatorBase, optional): cost operator to set.
+        """
+        self._cost_operator = cost_operator
+        self._invalidate()
+
+        num_qubits = cost_operator.num_qubits if cost_operator else None
+        self._reset_registers(num_qubits)
+
+    @property
+    def reps(self) -> int:
+        """Returns the `reps` parameter, which determines the depth of the circuit."""
+        return self._reps
+
+    @reps.setter
+    def reps(self, reps: int) -> None:
+        """Sets the `reps` parameter."""
+        self._reps = reps
+        self._invalidate()
+
+    @property
+    def initial_state(self) -> Optional[QuantumCircuit]:
+        """Returns an optional initial state as a circuit"""
+        if self._initial_state is not None:
+            return self._initial_state
+
+        # if no initial state is passed and we know the number of qubits, then initialize it.
+        if self.num_qubits > 0:
+            initial_state = QuantumCircuit(self.num_qubits)
+            initial_state.h(range(self.num_qubits))
+            return initial_state
+
+        # otherwise we cannot provide a default
+        return None
+
+    @initial_state.setter
+    def initial_state(self, initial_state: Optional[QuantumCircuit]) -> None:
+        """Sets initial state."""
+        self._initial_state = initial_state
+        self._invalidate()
+
+    # we can't directly specify OperatorBase as a return type, it causes a circular import
+    # and pylint objects if return type is not documented
+    # pylint: disable=missing-return-type-doc
+    @property
+    def mixer_operator(self):
+        """Returns an optional mixer operator expressed as an operator or a quantum circuit.
+
+        Returns:
+            OperatorBase or QuantumCircuit, optional: mixer operator or circuit.
+        """
+        if self._mixer is not None:
+            return self._mixer
+
+        # if no mixer is passed and we know the number of qubits, then initialize it.
+        if self.num_qubits > 0:
+            # local imports to avoid circular imports
+            from qiskit.opflow import I, X
+            # Mixer is just a sum of single qubit X's on each qubit. Evolving by this operator
+            # will simply produce rx's on each qubit.
+            mixer_terms = [(I ^ left) ^ X ^ (I ^ (self.num_qubits - left - 1))
+                           for left in range(self.num_qubits)]
+            mixer = sum(mixer_terms)
+            return mixer
+
+        # otherwise we cannot provide a default
+        return None
+
+    @mixer_operator.setter
+    def mixer_operator(self, mixer_operator) -> None:
+        """Sets mixer operator.
+
+        Args:
+            mixer_operator (OperatorBase or QuantumCircuit, optional): mixer operator or circuit
+                to set.
+        """
+        self._mixer = mixer_operator
+        self._invalidate()


Test patch:
diff --git a/test/python/algorithms/test_qaoa.py b/test/python/algorithms/test_qaoa.py
new file mode 100644
index 000000000..2f2c847ef
--- /dev/null
+++ b/test/python/algorithms/test_qaoa.py
@@ -0,0 +1,375 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2018, 2021.
+#
+# 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.
+
+""" Test QAOA """
+
+import unittest
+from test.python.algorithms import QiskitAlgorithmsTestCase
+
+import math
+import networkx as nx
+import numpy as np
+from ddt import ddt, idata, unpack
+
+from qiskit.algorithms import QAOA
+from qiskit.algorithms.optimizers import COBYLA, NELDER_MEAD
+
+from qiskit.opflow import I, X, PauliSumOp
+
+from qiskit import BasicAer, QuantumCircuit, QuantumRegister, execute
+
+from qiskit.circuit import Parameter
+from qiskit.quantum_info import Pauli
+from qiskit.utils import QuantumInstance, algorithm_globals
+
+W1 = np.array([
+    [0, 1, 0, 1],
+    [1, 0, 1, 0],
+    [0, 1, 0, 1],
+    [1, 0, 1, 0]
+])
+P1 = 1
+M1 = (I ^ I ^ I ^ X) + (I ^ I ^ X ^ I) + (I ^ X ^ I ^ I) + (X ^ I ^ I ^ I)
+S1 = {'0101', '1010'}
+
+
+W2 = np.array([
+    [0., 8., -9., 0.],
+    [8., 0., 7., 9.],
+    [-9., 7., 0., -8.],
+    [0., 9., -8., 0.],
+])
+P2 = 1
+M2 = None
+S2 = {'1011', '0100'}
+
+CUSTOM_SUPERPOSITION = [1/math.sqrt(15)] * 15 + [0]
+
+
+@ddt
+class TestQAOA(QiskitAlgorithmsTestCase):
+    """Test QAOA with MaxCut."""
+    @idata([
+        [W1, P1, M1, S1, False],
+        [W2, P2, M2, S2, False],
+        [W1, P1, M1, S1, True],
+        [W2, P2, M2, S2, True],
+    ])
+    @unpack
+    def test_qaoa(self, w, prob, m, solutions, convert_to_matrix_op):
+        """ QAOA test """
+        seed = 0
+        algorithm_globals.random_seed = seed
+        self.log.debug('Testing %s-step QAOA with MaxCut on graph\n%s', prob, w)
+
+        backend = BasicAer.get_backend('statevector_simulator')
+        optimizer = COBYLA()
+        qubit_op, _ = self._get_operator(w)
+        if convert_to_matrix_op:
+            qubit_op = qubit_op.to_matrix_op()
+
+        q_i = QuantumInstance(backend, seed_simulator=seed, seed_transpiler=seed)
+        qaoa = QAOA(optimizer, prob, mixer=m, quantum_instance=q_i)
+
+        result = qaoa.compute_minimum_eigenvalue(operator=qubit_op)
+        x = self._sample_most_likely(result.eigenstate)
+        graph_solution = self._get_graph_solution(x)
+        self.assertIn(''.join([str(int(i)) for i in graph_solution]), solutions)
+
+    @idata([
+        [W1, P1, S1, False],
+        [W2, P2, S2, False],
+        [W1, P1, S1, True],
+        [W2, P2, S2, True],
+    ])
+    @unpack
+    def test_qaoa_qc_mixer(self, w, prob, solutions, convert_to_matrix_op):
+        """ QAOA test with a mixer as a parameterized circuit"""
+        seed = 0
+        algorithm_globals.random_seed = seed
+        self.log.debug('Testing %s-step QAOA with MaxCut on graph with '
+                       'a mixer as a parameterized circuit\n%s', prob, w)
+
+        backend = BasicAer.get_backend('statevector_simulator')
+        optimizer = COBYLA()
+        qubit_op, _ = self._get_operator(w)
+        if convert_to_matrix_op:
+            qubit_op = qubit_op.to_matrix_op()
+
+        num_qubits = qubit_op.num_qubits
+        mixer = QuantumCircuit(num_qubits)
+        theta = Parameter('θ')
+        mixer.rx(theta, range(num_qubits))
+
+        q_i = QuantumInstance(backend, seed_simulator=seed, seed_transpiler=seed)
+        qaoa = QAOA(optimizer, prob, mixer=mixer, quantum_instance=q_i)
+
+        result = qaoa.compute_minimum_eigenvalue(operator=qubit_op)
+        x = self._sample_most_likely(result.eigenstate)
+        graph_solution = self._get_graph_solution(x)
+        self.assertIn(''.join([str(int(i)) for i in graph_solution]), solutions)
+
+    def test_qaoa_qc_mixer_many_parameters(self):
+        """ QAOA test with a mixer as a parameterized circuit with the num of parameters > 1. """
+        seed = 0
+        algorithm_globals.random_seed = seed
+
+        optimizer = COBYLA()
+        qubit_op, _ = self._get_operator(W1)
+
+        num_qubits = qubit_op.num_qubits
+        mixer = QuantumCircuit(num_qubits)
+        for i in range(num_qubits):
+            theta = Parameter('θ' + str(i))
+            mixer.rx(theta, range(num_qubits))
+
+        backend = BasicAer.get_backend('statevector_simulator')
+        q_i = QuantumInstance(backend, seed_simulator=seed, seed_transpiler=seed)
+        qaoa = QAOA(optimizer=optimizer, reps=2, mixer=mixer, quantum_instance=q_i)
+        result = qaoa.compute_minimum_eigenvalue(operator=qubit_op)
+        x = self._sample_most_likely(result.eigenstate)
+        self.log.debug(x)
+        graph_solution = self._get_graph_solution(x)
+        self.assertIn(''.join([str(int(i)) for i in graph_solution]), S1)
+
+    def test_qaoa_qc_mixer_no_parameters(self):
+        """ QAOA test with a mixer as a parameterized circuit with zero parameters. """
+        seed = 0
+        algorithm_globals.random_seed = seed
+
+        qubit_op, _ = self._get_operator(W1)
+
+        num_qubits = qubit_op.num_qubits
+        mixer = QuantumCircuit(num_qubits)
+        # just arbitrary circuit
+        mixer.rx(np.pi/2, range(num_qubits))
+
+        backend = BasicAer.get_backend('statevector_simulator')
+        q_i = QuantumInstance(backend, seed_simulator=seed, seed_transpiler=seed)
+        qaoa = QAOA(optimizer=COBYLA(), reps=1, mixer=mixer, quantum_instance=q_i)
+        result = qaoa.compute_minimum_eigenvalue(operator=qubit_op)
+        # we just assert that we get a result, it is not meaningful.
+        self.assertIsNotNone(result.eigenstate)
+
+    def test_change_operator_size(self):
+        """ QAOA change operator size test """
+
+        algorithm_globals.random_seed = 0
+        qubit_op, _ = self._get_operator(
+            np.array([
+                [0, 1, 0, 1],
+                [1, 0, 1, 0],
+                [0, 1, 0, 1],
+                [1, 0, 1, 0]
+            ]))
+        q_i = QuantumInstance(BasicAer.get_backend('statevector_simulator'),
+                              seed_simulator=algorithm_globals.random_seed,
+                              seed_transpiler=algorithm_globals.random_seed)
+        qaoa = QAOA(COBYLA(), 1, quantum_instance=q_i)
+        result = qaoa.compute_minimum_eigenvalue(operator=qubit_op)
+        x = self._sample_most_likely(result.eigenstate)
+        graph_solution = self._get_graph_solution(x)
+        with self.subTest(msg='QAOA 4x4'):
+            self.assertIn(''.join([str(int(i)) for i in graph_solution]), {'0101', '1010'})
+
+        try:
+            qubit_op, _ = self._get_operator(
+                np.array([
+                    [0, 1, 0, 1, 0, 1],
+                    [1, 0, 1, 0, 1, 0],
+                    [0, 1, 0, 1, 0, 1],
+                    [1, 0, 1, 0, 1, 0],
+                    [0, 1, 0, 1, 0, 1],
+                    [1, 0, 1, 0, 1, 0],
+                ]))
+        except Exception as ex:  # pylint: disable=broad-except
+            self.fail("Failed to change operator. Error: '{}'".format(str(ex)))
+
+        result = qaoa.compute_minimum_eigenvalue(operator=qubit_op)
+        x = self._sample_most_likely(result.eigenstate)
+        graph_solution = self._get_graph_solution(x)
+        with self.subTest(msg='QAOA 6x6'):
+            self.assertIn(''.join([str(int(i)) for i in graph_solution]), {'010101', '101010'})
+
+    @idata([
+        [W2, S2, None],
+        [W2, S2, [0.0, 0.0]],
+        [W2, S2, [1.0, 0.8]]
+    ])
+    @unpack
+    def test_qaoa_initial_point(self, w, solutions, init_pt):
+        """ Check first parameter value used is initial point as expected """
+        algorithm_globals.random_seed = 10598
+        optimizer = COBYLA()
+        qubit_op, _ = self._get_operator(w)
+
+        first_pt = []
+
+        def cb_callback(eval_count, parameters, mean, std):
+            nonlocal first_pt
+            if eval_count == 1:
+                first_pt = list(parameters)
+
+        quantum_instance = QuantumInstance(BasicAer.get_backend('statevector_simulator'),
+                                           seed_simulator=algorithm_globals.random_seed,
+                                           seed_transpiler=algorithm_globals.random_seed)
+        qaoa = QAOA(optimizer, initial_point=init_pt, callback=cb_callback,
+                    quantum_instance=quantum_instance)
+
+        result = qaoa.compute_minimum_eigenvalue(operator=qubit_op)
+        x = self._sample_most_likely(result.eigenstate)
+        graph_solution = self._get_graph_solution(x)
+
+        with self.subTest('Initial Point'):
+            # If None the preferred random initial point of QAOA variational form
+            if init_pt is None:
+                np.testing.assert_almost_equal([-0.2398, 0.3378], first_pt, decimal=4)
+            else:
+                self.assertListEqual(init_pt, first_pt)
+
+        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, _ = self._get_operator(w)
+
+        init_pt = np.asarray([0.0, 0.0])  # Avoid generating random initial point
+
+        if init_state is None:
+            initial_state = None
+        else:
+            initial_state = QuantumCircuit(QuantumRegister(4, "q"))
+            initial_state.initialize(init_state, initial_state.qubits)
+
+        quantum_instance = QuantumInstance(BasicAer.get_backend('statevector_simulator'))
+        zero_init_state = QuantumCircuit(QuantumRegister(qubit_op.num_qubits, "q"))
+        qaoa_zero_init_state = QAOA(optimizer=optimizer,
+                                    initial_state=zero_init_state,
+                                    initial_point=init_pt,
+                                    quantum_instance=quantum_instance)
+        qaoa = QAOA(optimizer=optimizer,
+                    initial_state=initial_state,
+                    initial_point=init_pt,
+                    quantum_instance=quantum_instance)
+
+        zero_circuits = qaoa_zero_init_state.construct_circuit(init_pt, qubit_op)
+        custom_circuits = qaoa.construct_circuit(init_pt, qubit_op)
+
+        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 = QuantumCircuit(custom_circ.num_qubits)
+            custom_init_qc.data = custom_circ.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
+
+            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())
+
+    def test_qaoa_random_initial_point(self):
+        """ QAOA random initial point """
+        algorithm_globals.random_seed = 10598
+        w = nx.adjacency_matrix(
+            nx.fast_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed)).toarray()
+        qubit_op, _ = self._get_operator(w)
+        q_i = QuantumInstance(BasicAer.get_backend('qasm_simulator'),
+                              seed_simulator=algorithm_globals.random_seed,
+                              seed_transpiler=algorithm_globals.random_seed,
+                              shots=4096)
+        qaoa = QAOA(optimizer=NELDER_MEAD(disp=True), reps=1, quantum_instance=q_i)
+        _ = qaoa.compute_minimum_eigenvalue(operator=qubit_op)
+
+        np.testing.assert_almost_equal([-0.237, 0.342], qaoa.optimal_params, decimal=3)
+
+    def _get_operator(self, weight_matrix):
+        """Generate Hamiltonian for the max-cut problem of a graph.
+
+        Args:
+            weight_matrix (numpy.ndarray) : adjacency matrix.
+
+        Returns:
+            PauliSumOp: operator for the Hamiltonian
+            float: a constant shift for the obj function.
+
+        """
+        num_nodes = weight_matrix.shape[0]
+        pauli_list = []
+        shift = 0
+        for i in range(num_nodes):
+            for j in range(i):
+                if weight_matrix[i, j] != 0:
+                    x_p = np.zeros(num_nodes, dtype=np.bool)
+                    z_p = np.zeros(num_nodes, dtype=np.bool)
+                    z_p[i] = True
+                    z_p[j] = True
+                    pauli_list.append([0.5 * weight_matrix[i, j], Pauli((z_p, x_p))])
+                    shift -= 0.5 * weight_matrix[i, j]
+        opflow_list = [(pauli[1].to_label(), pauli[0]) for pauli in pauli_list]
+        return PauliSumOp.from_list(opflow_list), shift
+
+    def _get_graph_solution(self, x: np.ndarray) -> np.ndarray:
+        """Get graph solution from binary string.
+
+        Args:
+            x : binary string as numpy array.
+
+        Returns:
+            a graph solution as binary numpy array.
+        """
+
+        return 1 - x
+
+    def _sample_most_likely(self, state_vector):
+        """Compute the most likely binary string from state vector.
+        Args:
+            state_vector (numpy.ndarray or dict): state vector or counts.
+
+        Returns:
+            numpy.ndarray: binary string as numpy.ndarray of ints.
+        """
+        n = int(np.log2(state_vector.shape[0]))
+        k = np.argmax(np.abs(state_vector))
+        x = np.zeros(n)
+        for i in range(n):
+            x[i] = k % 2
+            k >>= 1
+        return x
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/test/python/circuit/library/test_qaoa_ansatz.py b/test/python/circuit/library/test_qaoa_ansatz.py
new file mode 100644
index 000000000..bb7012985
--- /dev/null
+++ b/test/python/circuit/library/test_qaoa_ansatz.py
@@ -0,0 +1,98 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2021.
+#
+# 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.
+
+"""Test QAOA ansatz from the library."""
+from qiskit.circuit.quantumcircuit import QuantumCircuit
+from qiskit.circuit.library.n_local.qaoa_ansatz import QAOAAnsatz
+from qiskit.circuit.library import HGate, RXGate, YGate, RYGate, RZGate
+from qiskit.opflow import I, Y, Z
+from qiskit.test import QiskitTestCase
+
+
+class TestQAOAAnsatz(QiskitTestCase):
+    """Test QAOAAnsatz."""
+    def test_default_qaoa(self):
+        """Test construction of the default circuit."""
+        circuit = QAOAAnsatz(I, 1)
+
+        parameters = circuit.parameters
+
+        self.assertEqual(1, len(parameters))
+        self.assertIsInstance(circuit.data[0][0], HGate)
+        self.assertIsInstance(circuit.data[1][0], RXGate)
+
+    def test_custom_initial_state(self):
+        """Test circuit with a custom initial state."""
+        initial_state = QuantumCircuit(1)
+        initial_state.y(0)
+        circuit = QAOAAnsatz(initial_state=initial_state, cost_operator=I, reps=1)
+
+        parameters = circuit.parameters
+        self.assertEqual(1, len(parameters))
+        self.assertIsInstance(circuit.data[0][0], YGate)
+        self.assertIsInstance(circuit.data[1][0], RXGate)
+
+    def test_custom_circuit_mixer(self):
+        """Test circuit with a custom mixer as a circuit"""
+        mixer = QuantumCircuit(1)
+        mixer.ry(1, 0)
+        circuit = QAOAAnsatz(cost_operator=I, reps=1, mixer_operator=mixer)
+
+        parameters = circuit.parameters
+        self.assertEqual(0, len(parameters))
+        self.assertIsInstance(circuit.data[0][0], HGate)
+        self.assertIsInstance(circuit.data[1][0], RYGate)
+
+    def test_custom_operator_mixer(self):
+        """Test circuit with a custom mixer as an operator."""
+        mixer = Y
+        circuit = QAOAAnsatz(cost_operator=I, reps=1, mixer_operator=mixer)
+
+        parameters = circuit.parameters
+        self.assertEqual(1, len(parameters))
+        self.assertIsInstance(circuit.data[0][0], HGate)
+        self.assertIsInstance(circuit.data[1][0], RYGate)
+
+    def test_all_custom_parameters(self):
+        """Test circuit with all custom parameters."""
+        initial_state = QuantumCircuit(1)
+        initial_state.y(0)
+        mixer = Z
+
+        circuit = QAOAAnsatz(cost_operator=I, reps=2, initial_state=initial_state,
+                             mixer_operator=mixer)
+
+        parameters = circuit.parameters
+        self.assertEqual(2, len(parameters))
+        self.assertIsInstance(circuit.data[0][0], YGate)
+        self.assertIsInstance(circuit.data[1][0], RZGate)
+        self.assertIsInstance(circuit.data[2][0], RZGate)
+
+    def test_configuration(self):
+        """Test configuration checks."""
+        mixer = QuantumCircuit(2)
+        circuit = QAOAAnsatz(cost_operator=I, reps=1, mixer_operator=mixer)
+
+        self.assertRaises(AttributeError, lambda: circuit.parameters)
+
+    def test_rebuild(self):
+        """Test how a circuit can be rebuilt."""
+        circuit = QAOAAnsatz(cost_operator=Z ^ I)  # circuit with 2 qubits
+        # force circuit to be built
+        _ = circuit.parameters
+
+        circuit.cost_operator = Z  # now it only has 1 qubit
+        circuit.reps = 5  # and now 5 repetitions
+        # rebuild the circuit
+        _ = circuit.parameters
+        self.assertEqual(1, circuit.num_qubits)
+        self.assertEqual(10, circuit.num_parameters)