Repository: Qiskit/qiskit

Issue:
scipy minimal required version does not have unitary_group
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Informations

The changes included in #760 require a `scipy` version that provides `scipy.stats.unitary_group`, which is not available in the minimal version currently required for scipy:

```
scipy>=0.19
```

We should look for the minimal version of `scipy` that includes that function (seems to be 1.0) and update the dependencies accordingly - otherwise we risk installations with broken `qiskit.tools.qi.qi` in some cases.


Discussion:
@diego-plan9 can you just update the dependencies as we don't want to write this function. 
This is on its way to be fixed via #713 
@diego-plan9 @delapuente this issue still has not been fixed: requirements.txt still lists scipy>=0.19, and qiskit.tools.qi.qi still tries to import unitary_group from scipy.stats
Thanks for reporting, @dtmcclure - can you paste the version of `scipy` you have installed that is resulting in a problem?

 I'm reopening the issue so we can investigate further - and actually pinging  @ewinston as he might have more information. Based on https://github.com/Qiskit/qiskit-terra/pull/713/files#r214942849:

> For some reason scipy 0.19.1 doesn't implement unitary_group although it does before (0.19.0) and after (1.0).

But it could be the case that the exclusion of 0.19.1 might not be enough.
I thought this was in 0.19.0 but I couldn't find it. Maybe just change requirement to >=1.0.0 or maybe Jay has other idea?
Diego yeah I was using 0.19.0 and it could not find unitary_group. This is
consistent with the scipy source code:
Not present in 0.19.0:
https://github.com/scipy/scipy/blob/v0.19.0/scipy/stats/_multivariate.py
Present in 1.0.0:
https://github.com/scipy/scipy/blob/v1.0.0/scipy/stats/_multivariate.py

On Mon, Feb 4, 2019 at 3:50 PM ewinston <notifications@github.com> wrote:

> I thought this was in 0.19.0 but I couldn't find it. Maybe just change
> requirement to >=1.0.0?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/Qiskit/qiskit-terra/issues/829#issuecomment-460407827>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AanFzfKlSw3IWtd-09cjO2y00lJ75OtDks5vKJ0lgaJpZM4WPRGd>
> .
>


Resolving patch:
diff --git a/qiskit/quantum_info/states/_states.py b/qiskit/quantum_info/states/_states.py
--- a/qiskit/quantum_info/states/_states.py
+++ b/qiskit/quantum_info/states/_states.py
@@ -38,13 +38,13 @@ def basis_state(str_state, num):
         raise QiskitError('size of bitstring is greater than num.')
 
 
-def random_state(num, seed=None):
+def random_state(dim, seed=None):
     """
     Return a random quantum state from the uniform (Haar) measure on
     state space.
 
     Args:
-        num (int): the number of qubits
+        dim (int): the dim of the state spaxe
         seed (int): Optional. To set a random seed.
 
     Returns:
@@ -53,11 +53,11 @@ def random_state(num, seed=None):
     if seed is not None:
         np.random.seed(seed)
     # Random array over interval (0, 1]
-    x = np.random.random(1 << num)
+    x = np.random.random(dim)
     x += x == 0
     x = -np.log(x)
     sumx = sum(x)
-    phases = np.random.random(1 << num)*2.0*np.pi
+    phases = np.random.random(dim)*2.0*np.pi
     return np.sqrt(x/sumx)*np.exp(1j*phases)
 
 
diff --git a/qiskit/tools/qi/qi.py b/qiskit/tools/qi/qi.py
--- a/qiskit/tools/qi/qi.py
+++ b/qiskit/tools/qi/qi.py
@@ -20,12 +20,12 @@
 
 import numpy as np
 import scipy.linalg as la
-from scipy.stats import unitary_group
 
 
 from qiskit.exceptions import QiskitError
 from qiskit.quantum_info import pauli_group
 from qiskit.quantum_info import purity as new_purity
+from qiskit.quantum_info import random_state
 
 
 ###############################################################
@@ -336,19 +336,32 @@ def outer(vector1, vector2=None):
 # Random Matrices.
 ###############################################################
 
-def random_unitary_matrix(length, seed=None):
+def random_unitary_matrix(dim, seed=None):
     """
-    Return a random unitary ndarray.
+    Return a random unitary ndarray over num qubits.
 
     Args:
-        length (int): the length of the returned unitary.
+        dim (int): the dim of the state space.
         seed (int): Optional. To set a random seed.
     Returns:
-        ndarray: U (length, length) unitary ndarray.
+        ndarray: U (2**num, 2**num) unitary ndarray.
     """
-    if seed is not None:
-        np.random.seed(seed)
-    return unitary_group.rvs(length)
+    unitary = np.zeros([dim, dim], dtype=complex)
+    for j in range(dim):
+        if j == 0:
+            a = random_state(dim, seed)
+        else:
+            a = random_state(dim)
+        unitary[:, j] = np.copy(a)
+        # Grahm-Schmidt Orthogonalize
+        i = j-1
+        while i >= 0:
+            dc = np.vdot(unitary[:, i], a)
+            unitary[:, j] = unitary[:, j]-dc*unitary[:, i]
+            i = i - 1
+        # normalize
+        unitary[:, j] = unitary[:, j] * (1.0 / np.sqrt(np.vdot(unitary[:, j], unitary[:, j])))
+    return unitary
 
 
 def random_density_matrix(length, rank=None, method='Hilbert-Schmidt', seed=None):


Test patch:
diff --git a/test/python/quantum_info/test_states.py b/test/python/quantum_info/test_states.py
--- a/test/python/quantum_info/test_states.py
+++ b/test/python/quantum_info/test_states.py
@@ -84,12 +84,12 @@ def test_random_state(self):
         E_P0_last = 0
         for ii in range(number):
             state = basis_state(bin(3)[2:].zfill(3), 3)
-            E_P0 = (E_P0_last*ii)/(ii+1)+state_fidelity(state, random_state(3, seed=ii))/(ii+1)
+            E_P0 = (E_P0_last*ii)/(ii+1)+state_fidelity(state, random_state(2**3, seed=ii))/(ii+1)
             E_P0_last = E_P0
         self.assertAlmostEqual(E_P0, 1/8, places=2)
 
     def test_random_state_circuit(self):
-        state = random_state(3, seed=40)
+        state = random_state(2**3, seed=40)
         q = QuantumRegister(3)
         qc = QuantumCircuit(q)
         qc.initialize(state, [q[0], q[1], q[2]])