Repository: Qiskit/qiskit

Issue:
Controlled HamiltonianGate seems to be wrong
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: master @ 49baa6ee5222bc497951423afa0fc7cbdb434702
- **Python version**: 3.7.9
- **Operating system**: macOS Big Sur

### What is the current behavior?

Controlled the `HamiltonianGate` seems to produce wrong results, whereas manually exponentiating and then using the `UnitaryGate` works fine.

### Steps to reproduce the problem

```python
import numpy as np
import scipy as sc

from qiskit import QuantumCircuit
from qiskit.quantum_info import Operator

np.set_printoptions(precision=2)

matrix = np.array([[2, 1], [1, 2]])
evolved = sc.linalg.expm(1j * matrix)

u = QuantumCircuit(1)
u.unitary(evolved, [0])

h = QuantumCircuit(1)
h.hamiltonian(matrix, -1, [0])

print('Unitary of e^(-matrix)')
print(Operator(u).data)

print('Hamiltonian of matrix')
print(Operator(h).data)

print('Controlled unitary of e^(-matrix)')
# so that the matrix of the controlled gate uses the "physics order", reverse bits
print(Operator(u.control().reverse_bits()).data)  

print('Controlled hamiltonian of matrix')
print(Operator(h.control().reverse_bits()).data)
```

This yields:
```
Unitary of e^(-matrix)
[[-0.22+0.49j -0.77-0.35j]
 [-0.77-0.35j -0.22+0.49j]]
Hamiltonian of matrix
[[-0.22+0.49j -0.77-0.35j]
 [-0.77-0.35j -0.22+0.49j]]
Controlled unitary of e^(-matrix)
[[ 1.  +0.j    0.  +0.j    0.  +0.j    0.  +0.j  ]
 [ 0.  +0.j    1.  +0.j    0.  +0.j    0.  +0.j  ]
 [ 0.  +0.j    0.  +0.j   -0.22+0.49j -0.77-0.35j]
 [ 0.  +0.j    0.  +0.j   -0.77-0.35j -0.22+0.49j]]
Controlled hamiltonian of matrix
[[ 1.00e+00+0.00e+00j  0.00e+00+0.00e+00j  0.00e+00+0.00e+00j 0.00e+00+0.00e+00j]
 [ 0.00e+00+0.00e+00j  1.00e+00+0.00e+00j  0.00e+00+0.00e+00j 0.00e+00+0.00e+00j]
 [ 0.00e+00+0.00e+00j  0.00e+00+0.00e+00j  5.40e-01+0.00e+00j 1.55e-16+8.41e-01j]
 [ 0.00e+00+0.00e+00j  0.00e+00+0.00e+00j -1.35e-16+8.41e-01j 5.40e-01-1.32e-16j]]
```


### What is the expected behavior?

Controlling the `HamiltonianGate` (and reversing bits) should produce this matrix:
```
[[ 1.  +0.j    0.  +0.j    0.  +0.j    0.  +0.j  ]
 [ 0.  +0.j    1.  +0.j    0.  +0.j    0.  +0.j  ]
 [ 0.  +0.j    0.  +0.j   -0.22+0.49j -0.77-0.35j]
 [ 0.  +0.j    0.  +0.j   -0.77-0.35j -0.22+0.49j]]
 ```

### Suggested solutions

Check what's going on in `HamiltonianGate.control`, maybe it's a relative phase that's introduced.



Discussion:
@Cryoris Under `extensions/hamiltonian_gate.py`, I don't see a `.control` method for the `HamiltonianGate` class. Any ideas where I can look into this?
@paniash The `control` method is implemented in the parent `Gate` class.
I noticed that when you create a HamiltonianGate directly it looks ok,
```python
        evo = scipy.linalg.expm(-1j * hami * tdelta)
        cevo = _compute_control_matrix(evo, 1)
        print('-'*5 + 'hamiltonian')
        print(hami)
        print('-'*5 + 'evolution operator')
        print(evo)
        print('-'*5 + 'controlled evolution operator')        
        print(cevo)


        print('-'*5 + 'HamiltonianGate as QuantumCircuit')
        hami_circ = QuantumCircuit(1, name='hami_circ')
        hami_circ.hamiltonian(hami, -1, [0])
        chami_circ = hami_circ.control()
        print(Operator(hami_circ).data)
        print(Operator(chami_circ).data)

        print('-'*5 + 'HamiltonianGate as Gate appended to QuantumCircuit')
        hami_gate = HamiltonianGate(hami, tdelta)
        chami_gate = hami_gate.control()
        chami_circuit = QuantumCircuit(2, name='hami_circ')
        chami_circuit.append(chami_gate, [0, 1])
        print(Operator(chami_gate).data)
        print(Operator(chami_circuit).data)   
```
prints;
```
-----hamiltonian
[[2 1]
 [1 2]]
-----evolution operator
[[-0.22-0.49j -0.77+0.35j]
 [-0.77+0.35j -0.22-0.49j]]
-----controlled evolution operator
[[ 1.  +0.j    0.  +0.j    0.  +0.j    0.  +0.j  ]
 [ 0.  +0.j   -0.22-0.49j  0.  +0.j   -0.77+0.35j]
 [ 0.  +0.j    0.  +0.j    1.  +0.j    0.  +0.j  ]
 [ 0.  +0.j   -0.77+0.35j  0.  +0.j   -0.22-0.49j]]

-----HamiltonianGate as QuantumCircuit
hami_circ
                              
control_0: ─────────■─────────
           ┌────────┴────────┐
 target_0: ┤ U(2,π/2,3π/2,0) ├
           └─────────────────┘
[[-0.22+0.49j -0.77-0.35j]
 [-0.77-0.35j -0.22+0.49j]]
[[1.  +0.j   0.  +0.j   0.  +0.j   0.  +0.j  ]
 [0.  +0.j   0.54+0.j   0.  +0.j   0.  +0.84j]
 [0.  +0.j   0.  +0.j   1.  +0.j   0.  +0.j  ]
 [0.  +0.j   0.  +0.84j 0.  +0.j   0.54-0.j  ]]

-----HamiltonianGate as Gate appended to QuantumCircuit
gate hamiltonian has phase of -1.9999999999999998
hamiltonian
                              ┌───────┐
control_0: ─────────■─────────┤ P(-2) ├
           ┌────────┴────────┐└───────┘
 target_0: ┤ U(2,3π/2,π/2,0) ├─────────
           └─────────────────┘         
[[ 1.  +0.j    0.  +0.j    0.  +0.j    0.  +0.j  ]
 [ 0.  +0.j   -0.22-0.49j  0.  +0.j   -0.77+0.35j]
 [ 0.  +0.j    0.  +0.j    1.  +0.j    0.  +0.j  ]
 [ 0.  +0.j   -0.77+0.35j  0.  +0.j   -0.22-0.49j]]
[[ 1.  +0.j    0.  +0.j    0.  +0.j    0.  +0.j  ]
 [ 0.  +0.j   -0.22-0.49j  0.  +0.j   -0.77+0.35j]
 [ 0.  +0.j    0.  +0.j    1.  +0.j    0.  +0.j  ]
 [ 0.  +0.j   -0.77+0.35j  0.  +0.j   -0.22-0.49j]]

This may actually be an issue with the Unroller preserving global phase;
```python
        from qiskit.transpiler.passes.basis import Unroller
        from qiskit.transpiler import PassManager
        hami = np.array([[2, 1], [1, 2]])
        hami_gate = HamiltonianGate(hami, 1)
        hami_circ = QuantumCircuit(1)
        hami_circ.append(hami_gate, [0])
        pm = PassManager()
        pm.append(Unroller(['u']))
        unrolled_hami_circ = pm.run(hami_circ)
        np.set_printoptions(precision=2, linewidth=200, suppress=True)
        print(Operator(hami_circ).data)
        print(Operator(unrolled_hami_circ).data)        
```
yields,
```
[[-0.22-0.49j -0.77+0.35j]
 [-0.77+0.35j -0.22-0.49j]]

[[ 0.54+0.j   -0.  -0.84j]
 [-0.  -0.84j  0.54-0.j  ]]
```

Resolving patch:
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
@@ -70,6 +70,7 @@ def run(self, dag):
 
             # TODO: allow choosing other possible decompositions
             try:
+                phase = node.op.definition.global_phase
                 rule = node.op.definition.data
             except TypeError as err:
                 raise QiskitError(f'Error decomposing node {node.name}: {err}') from err
@@ -80,11 +81,11 @@ def run(self, dag):
             # different that the width of the node.
             while rule and len(rule) == 1 and len(node.qargs) == len(rule[0][1]) == 1:
                 if rule[0][0].name in self.basis:
-                    if node.op.definition and node.op.definition.global_phase:
-                        dag.global_phase += node.op.definition.global_phase
+                    dag.global_phase += phase
                     dag.substitute_node(node, rule[0][0], inplace=True)
                     break
                 try:
+                    phase += rule[0][0].definition.global_phase
                     rule = rule[0][0].definition.data
                 except (TypeError, AttributeError) as err:
                     raise QiskitError(f'Error decomposing node {node.name}: {err}') from err
@@ -93,6 +94,7 @@ def run(self, dag):
                 if not rule:
                     if rule == []:  # empty node
                         dag.remove_op_node(node)
+                        dag.global_phase += phase
                         continue
                     # opaque node
                     raise QiskitError("Cannot unroll the circuit to the given basis, %s. "


Test patch:
diff --git a/test/python/transpiler/test_unroller.py b/test/python/transpiler/test_unroller.py
--- a/test/python/transpiler/test_unroller.py
+++ b/test/python/transpiler/test_unroller.py
@@ -284,6 +284,24 @@ def test_unrolling_global_phase_1q(self):
 
         self.assertEqual(Operator(qc), Operator(qcd))
 
+    def test_unrolling_global_phase_nested_gates(self):
+        """Test unrolling a nested gate preseveres global phase."""
+        qc = QuantumCircuit(1, global_phase=pi)
+        qc.x(0)
+        gate = qc.to_gate()
+
+        qc = QuantumCircuit(1)
+        qc.append(gate, [0])
+        gate = qc.to_gate()
+
+        qc = QuantumCircuit(1)
+        qc.append(gate, [0])
+        dag = circuit_to_dag(qc)
+        out_dag = Unroller(['x', 'u']).run(dag)
+        qcd = dag_to_circuit(out_dag)
+
+        self.assertEqual(Operator(qc), Operator(qcd))
+
 
 class TestUnrollAllInstructions(QiskitTestCase):
     """Test unrolling a circuit containing all standard instructions."""