Repository: Qiskit/qiskit

Issue:
Two qubit UnitaryGate produces non-deterministic decomposition
<!-- ⚠️ 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**: 0.11.0
- **Python version**: 3.6.8
- **Operating system**: Windows 10

### What is the current behavior?
When generating a controlled version of a unitary gate, the resulting gate does not seem to be deterministically generated. This can be seen by checking two gates generated from the same unitary for equality. Sometimes this will return True and sometimes False, seemingly at random. The reason seems to be differing definitions of the controlled versions, which is checked by the \_\_eq\_\_ function.


### Steps to reproduce the problem
Generate a new unitary, for example with
```
uni = UnitaryGate([[1, 0, 0, 0],
                   [0, 1, 0, 0],
                   [0, 0, 1, 0],
                   [0, 0, 0, 1j]])
```
and repeatedly generate controlled versions of it, comparing them for equality:
```
for i in range(10):
    uni.control() == uni.control()
```

### What is the expected behavior?
Shouldn't the generated gate definitions be deterministic? At least I would expect two controlled gates to be identified as equal when generated from the same object.


### Suggested solutions
During the gate.control() call, add_control() will call _define() on unitary gates, which seems to produce different decompositions of the unitary every time it is called, before the controlled version is even generated.



Discussion:
@dime10 Thanks for bringing this up. I was not able to reproduce this issue. I also tried with 1000 iterations and random unitaries which was also successful. I tested on:

terra: 0.11.0.dev0+b7d6a90
python: 3.6.9
OS: Linux

I'll see if I can find someone with windows to test there. Could you try with the latest on master?
Hmm I just tested it again on a fresh master install (0.11.0.dev0+b7d6a90) with the same results.

To be fair maybe I should specify the OS as WSL (Ubuntu 18.04.3 LTS) on Windows 10 (Build 18362).

EDIT: retried on Ubuntu inside a VM to be sure, Python 3.6.9, still the same
I was able to replicate this on osx 10.14 with python 3.5 at 1f0c716a.

```
>>> uni = UnitaryGate([[1, 0, 0, 0],
...                    [0, 1, 0, 0],
...                    [0, 0, 1, 0],
...                    [0, 0, 0, 1j]])
>>> uni.control() == uni.control()
False
>>> uni.control() == uni.control()
True
```

The fix here is probably that controlled gates should use the `__eq__` method of the base gate instead of equality of the definition.
> 
> The fix here is probably that controlled gates should use the `__eq__` method of the base gate instead of equality of the definition.

While that could fix the comparison, it won't affect the underlying "issue" that definitions generated for the controlled gate can differ even if they came from the same object (if we care about that).

(However most basic gates don't implement their own \_\_eq\_\_ currently, and so still rely on the definition, although I proposed changing this in #3542)

> While that could fix the comparison, it won't affect the underlying "issue" that definitions generated for the controlled gate can differ even if they came from the same object (if we care about that).

I agree with this. However, the non-deterministic behavior seems to come from the comparison.

```
for i in range(100):
    uni1 = uni.control()
    uni2 = uni.control()
    if uni1 == uni2:
	    print(i, uni == uni2)  # False is printed, after being True in previous line
```
There are two issues at play here:
1. The definition of 2Q `UnitaryGates` built by `two_qubit_cnot_decompose` is non-deterministic.
2. `ControlledGate.__eq__` doesn't use a specialized `__eq__` of `base_gate`.

I'll rename this issue to cover 1. and open a new issue for 2.


Can you add an example of the decomposition?
> 
> 
> > While that could fix the comparison, it won't affect the underlying "issue" that definitions generated for the controlled gate can differ even if they came from the same object (if we care about that).
> 
> I agree with this. However, the non-deterministic behavior seems to come from the comparison.
> 
> ```
> for i in range(100):
>     uni1 = uni.control()
>     uni2 = uni.control()
>     if uni1 == uni2:
> 	    print(i, uni == uni2)  # False is printed, after being True in previous line
> ```

I think the False here is due to using 'uni' instead of 'uni1' in your comparison.
> Can you add an example of the decomposition?

```
           ┌─────────────┐         ┌───────────────────┐      ┌─────────────────────────┐
q_0: |0>───┤ U3(0,0,-pi) ├─────■───┤ U3(pi/1,pi,-pi/2) ├───■──┤ U3(pi/1,-2.6671,1.2599) ├
        ┌──┴─────────────┴──┐┌─┴─┐┌┴───────────────────┴┐┌─┴─┐└┬───────────────────────┬┘
q_1: |0>┤ U3(pi,-pi/2,pi/2) ├┤ X ├┤ U3(pi/2,pi/2,-pi/4) ├┤ X ├─┤ U3(pi/2,-pi/4,-3pi/2) ├─
        └───────────────────┘└───┘└─────────────────────┘└───┘ └───────────────────────┘

          ┌───────────────┐        ┌───────────────────┐      ┌───────────────────────┐
q_0: |0>──┤ U3(pi/1,pi,0) ├────■───┤ U3(pi/1,pi,-pi/2) ├───■──┤ U3(0,-1.7788,0.99339) ├
        ┌─┴───────────────┴─┐┌─┴─┐┌┴───────────────────┴┐┌─┴─┐└┬──────────────────────┤
q_1: |0>┤ U3(0,-pi/2,-pi/2) ├┤ X ├┤ U3(pi/2,pi/2,-pi/4) ├┤ X ├─┤ U3(pi/2,-pi/4,3pi/2) ├
        └───────────────────┘└───┘└─────────────────────┘└───┘ └──────────────────────┘
```
> I think the False here is due to using 'uni' instead of 'uni1' in your comparison.

You are right.

Resolving patch:
diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py
--- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py
+++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py
@@ -161,8 +161,8 @@ def __init__(self, unitary_matrix):
         # M2 is a symmetric complex matrix. We need to decompose it as M2 = P D P^T where
         # P ∈ SO(4), D is diagonal with unit-magnitude elements.
         # D, P = la.eig(M2)  # this can fail for certain kinds of degeneracy
-        for _ in range(100):  # FIXME: this randomized algorithm is horrendous
-            M2real = np.random.randn()*M2.real + np.random.randn()*M2.imag
+        for i in range(-50, 50):
+            M2real = (i/50)*M2.real + (i/25)*M2.imag
             _, P = la.eigh(M2real)
             D = P.T.dot(M2).dot(P).diagonal()
             if np.allclose(P.dot(np.diag(D)).dot(P.T), M2, rtol=1.0e-13, atol=1.0e-13):


Test patch: