Repository: Qiskit/qiskit

Issue:
Open CX not simulated correctly
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Informations

- **Qiskit Aer version**: 0.6.0
- **Python version**: 3.7.5
- **Operating system**: macOS Catalina , 10.15.4

### What is the current behavior?

The CX gate with an open-control does not get simulated correctly. This is a follow-up on Qiskit/qiskit-terra#4180 where this has first been discovered.

The output state of 
```
q_0: ──o──
     ┌─┴─┐
q_1: ┤ X ├
     └───┘
```
should be `|10>` (in Qiskit endianess) but the QASM and statevector simulator both produce `|00>`. The `quantum_info.Statevector` object however computes the correct result. 

This might be a gate-naming issue, since the CX gate is called `'cx'` both for open and closed controls. The same problem occurs for the CCX gate.

### Steps to reproduce the problem

Aer's simulators:
```
>>> from qiskit import Aer, QuantumCircuit, execute
>>> open_cx = QuantumCircuit(2)
>>> open_cx.cx(0, 1, ctrl_state='0')
>>> open_cx.draw()
q_0: ──o──
     ┌─┴─┐
q_1: ┤ X ├
     └───┘
>>> sv = execute(open_cx, Aer.get_backend('statevector_simulator')).result().get_statevector()
>>> sv
array([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j])   # expect [0 0 1 0]
>>> open_cx.measure_all()
>>> counts = execute(open_cx, Aer.get_backend('qasm_simulator')).result().get_counts()
>>> counts
{'00': 1024}    # expect '10'
```

Statevector object from quantum info:
```
>>> from qiskit.quantum_info import Statevector
>>> sv = Statevector.from_label('00').evolve(open_cx)
>>> sv
Statevector([0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],    # as expected
            dims=(2, 2))
```

### What is the expected behavior?

Produce `|10>`.

### Suggested solutions

Check the `ctrl_state` upon simulation for the gate names `'cx'` and `'ccx'`. But I haven't worked with Aer before so maybe this is also completely wrong! 



Discussion:
This is changing the qobj spec for "cx" which is not a parameterized gate. If you transpile and assemble for a backend that only supposed "cx" this is the resulting qobj from your example:

```python
qobj = assemble(transpile(open_cx, Aer.get_backend("qasm_simulator")))
qobj.experiments[0].instructions
[QasmQobjInstruction(name='cx', qubits=[0, 1])]
```

An open-controlled-X gate should be compiled to `x q0; cx q0, q1; x q0;` for correct execution on a backend.

Resolving patch:
diff --git a/qiskit/circuit/controlledgate.py b/qiskit/circuit/controlledgate.py
--- a/qiskit/circuit/controlledgate.py
+++ b/qiskit/circuit/controlledgate.py
@@ -108,26 +108,24 @@ def definition(self) -> List:
         definition is conjugated with X without changing the internal
         `_definition`.
         """
-        if not self._definition:
-            self._define()
-        # pylint: disable=cyclic-import
-        from qiskit.circuit.library.standard_gates import XGate, CXGate
-        bit_ctrl_state = bin(self.ctrl_state)[2:].zfill(self.num_ctrl_qubits)
-        # hacky way to get register assuming single register
-        if self._definition:
-            qreg = self._definition[0][1][0].register
-            definition = self._definition
-        elif isinstance(self, CXGate):
-            qreg = QuantumRegister(self.num_qubits, 'q')
-            definition = [(self, [qreg[0], qreg[1]], [])]
-        open_rules = []
-        for qind, val in enumerate(bit_ctrl_state[::-1]):
-            if val == '0':
-                open_rules.append([XGate(), [qreg[qind]], []])
-        if open_rules:
-            return open_rules + definition + open_rules
+        if self._open_ctrl:
+            closed_gate = self.copy()
+            closed_gate.ctrl_state = None
+            # pylint: disable=cyclic-import
+            from qiskit.circuit.library.standard_gates import XGate
+            bit_ctrl_state = bin(self.ctrl_state)[2:].zfill(self.num_ctrl_qubits)
+            qreg = QuantumRegister(self.num_qubits)
+            definition = [(closed_gate, qreg, [])]
+            open_rules = []
+            for qind, val in enumerate(bit_ctrl_state[::-1]):
+                if val == '0':
+                    open_rules.append([XGate(), [qreg[qind]], []])
+            if open_rules:
+                return open_rules + definition + open_rules
+            else:
+                return self._definition
         else:
-            return self._definition
+            return super().definition
 
     @definition.setter
     def definition(self, excited_def: List):
@@ -169,6 +167,11 @@ def ctrl_state(self, ctrl_state: Union[int, str, None]):
             raise CircuitError('invalid control state specification: {}'.format(
                 repr(ctrl_state)))
 
+    @property
+    def _open_ctrl(self) -> bool:
+        """Return whether gate has any open controls"""
+        return self.ctrl_state < 2**self.num_ctrl_qubits - 1
+
     def __eq__(self, other) -> bool:
         return (isinstance(other, ControlledGate) and
                 self.num_ctrl_qubits == other.num_ctrl_qubits and
diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py
--- a/qiskit/extensions/unitary.py
+++ b/qiskit/extensions/unitary.py
@@ -133,8 +133,10 @@ def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None):
         """
         cmat = _compute_control_matrix(self.to_matrix(), num_ctrl_qubits)
         iso = isometry.Isometry(cmat, 0, 0)
-        return ControlledGate('c-unitary', self.num_qubits + num_ctrl_qubits, cmat,
-                              definition=iso.definition, label=label)
+        cunitary = ControlledGate('c-unitary', self.num_qubits + num_ctrl_qubits, cmat,
+                                  definition=iso.definition, label=label)
+        cunitary.base_gate = self.copy()
+        return cunitary
 
     def qasm(self):
         """ The qasm for a custom unitary gate
diff --git a/qiskit/transpiler/passes/basis/unroller.py b/qiskit/transpiler/passes/basis/unroller.py
--- a/qiskit/transpiler/passes/basis/unroller.py
+++ b/qiskit/transpiler/passes/basis/unroller.py
@@ -17,6 +17,7 @@
 from qiskit.transpiler.basepasses import TransformationPass
 from qiskit.dagcircuit import DAGCircuit
 from qiskit.exceptions import QiskitError
+from qiskit.circuit import ControlledGate
 
 
 class Unroller(TransformationPass):
@@ -61,7 +62,10 @@ def run(self, dag):
                 #  backend reports "measure", for example.
                 continue
             if node.name in self.basis:  # If already a base, ignore.
-                continue
+                if isinstance(node.op, ControlledGate) and node.op._open_ctrl:
+                    pass
+                else:
+                    continue
 
             # TODO: allow choosing other possible decompositions
             try:


Test patch:
diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py
--- a/test/python/circuit/test_controlled_gate.py
+++ b/test/python/circuit/test_controlled_gate.py
@@ -667,6 +667,76 @@ def test_open_controlled_unitary_matrix(self, num_ctrl_qubits):
                          (output_target != cond_output)) or
                         output_ctrl != input_ctrl)
 
+    def test_open_control_cx_unrolling(self):
+        """test unrolling of open control gates when gate is in basis"""
+        qc = QuantumCircuit(2)
+        qc.cx(0, 1, ctrl_state=0)
+        dag = circuit_to_dag(qc)
+        unroller = Unroller(['u3', 'cx'])
+        uqc = dag_to_circuit(unroller.run(dag))
+
+        ref_circuit = QuantumCircuit(2)
+        ref_circuit.u3(np.pi, 0, np.pi, 0)
+        ref_circuit.cx(0, 1)
+        ref_circuit.u3(np.pi, 0, np.pi, 0)
+        self.assertEqual(uqc, ref_circuit)
+
+    def test_open_control_cy_unrolling(self):
+        """test unrolling of open control gates when gate is in basis"""
+        qc = QuantumCircuit(2)
+        qc.cy(0, 1, ctrl_state=0)
+        dag = circuit_to_dag(qc)
+        unroller = Unroller(['u3', 'cy'])
+        uqc = dag_to_circuit(unroller.run(dag))
+
+        ref_circuit = QuantumCircuit(2)
+        ref_circuit.u3(np.pi, 0, np.pi, 0)
+        ref_circuit.cy(0, 1)
+        ref_circuit.u3(np.pi, 0, np.pi, 0)
+        self.assertEqual(uqc, ref_circuit)
+
+    def test_open_control_cxx_unrolling(self):
+        """test unrolling of open control gates when gate is in basis"""
+        qreg = QuantumRegister(3)
+        qc = QuantumCircuit(qreg)
+        ccx = CCXGate(ctrl_state=0)
+        qc.append(ccx, [0, 1, 2])
+        dag = circuit_to_dag(qc)
+        unroller = Unroller(['x', 'ccx'])
+        unrolled_dag = unroller.run(dag)
+
+        ref_circuit = QuantumCircuit(qreg)
+        ref_circuit.x(qreg[0])
+        ref_circuit.x(qreg[1])
+        ref_circuit.ccx(qreg[0], qreg[1], qreg[2])
+        ref_circuit.x(qreg[0])
+        ref_circuit.x(qreg[1])
+        ref_dag = circuit_to_dag(ref_circuit)
+        self.assertEqual(unrolled_dag, ref_dag)
+
+    def test_open_control_composite_unrolling(self):
+        """test unrolling of open control gates when gate is in basis"""
+        # create composite gate
+        qreg = QuantumRegister(2)
+        qcomp = QuantumCircuit(qreg, name='bell')
+        qcomp.h(qreg[0])
+        qcomp.cx(qreg[0], qreg[1])
+        bell = qcomp.to_gate()
+        # create controlled composite gate
+        cqreg = QuantumRegister(3)
+        qc = QuantumCircuit(cqreg)
+        qc.append(bell.control(ctrl_state=0), qc.qregs[0][:])
+        dag = circuit_to_dag(qc)
+        unroller = Unroller(['x', 'u1', 'cbell'])
+        unrolled_dag = unroller.run(dag)
+        # create reference circuit
+        ref_circuit = QuantumCircuit(cqreg)
+        ref_circuit.x(cqreg[0])
+        ref_circuit.append(bell.control(), [cqreg[0], cqreg[1], cqreg[2]])
+        ref_circuit.x(cqreg[0])
+        ref_dag = circuit_to_dag(ref_circuit)
+        self.assertEqual(unrolled_dag, ref_dag)
+
     @data(*ControlledGate.__subclasses__())
     def test_base_gate_setting(self, gate_class):
         """Test all gates in standard extensions which are of type ControlledGate