Repository: Qiskit/qiskit

Issue:
testsuite input should be deterministic
The use of the `random` module and `test/python/_random_circuit_generator.py` for creating input in the testsuite should be avoid. Ideally, the output of the tests in `test/python` should be deterministic and well define. I understand that, because the nature of QISKit, that's not always possible. 

## Expected Behavior
if I'm passing the tests locally, I should be quite confident that the same situation will hold in the future. Changing nothing and get tests failing is frustrating. Feed random input to tests is good and desired in a context of fuzzing. The random input can be moved to a different place with the goal of generating new tests. 

## Possible Solution
Remove as much as possible sources of non-determinism in the testsuite.



Discussion:
> This list is outdated. Here is the new list https://github.com/Qiskit/qiskit-terra/issues/205#issuecomment-462579107

So far, the tests that depend on random are:
 - `test/python/test_projectq_cpp_simulator.py`
 - `test/python/test_qi.py` 
 - `test/python/test_quantumprogram.py` (trivial, random is used to generate a random hostname)
 - `test/python/test_mapper.py`
 - `test/python/test_unitary_python_simulator.py`
 - `test/python/test_qasm_python_simulator.py`
 - `test/python/test_job_processor.py`
 - `test/python/test_local_qiskit_simulator.py` (the problem here looks deeper, in how the seed is consumed. Probably non-trivial.)
`test/python/test_qi.py` fixed by #258
@1ucian0, does this overlap somehow with your efforts on #697? I mention it because we've discussed several times that the tests attacking the mock servers should be as deterministic as possible.

As a side note, avoiding the `random` module is a solution but finding a way of fixing the seed of the random functions could be another and it might require fewer changes.
This issue is about test files that call `random` as part of the test. Situations like the one in `test/python/test_quantumprogram.py`:

```
    def test_offline(self):
        import string
        import random
        qp = QuantumProgram()
        FAKE_TOKEN = 'this_token_is_not_going_to_be_sent_nowhere'
        FAKE_URL = 'http://{0}.com'.format(
            ''.join(random.choice(string.ascii_lowercase) for _ in range(63))
        )
        # SDK will throw ConnectionError on every call that implies a connection
        self.assertRaises(QISKitError, qp.set_api, FAKE_TOKEN, FAKE_URL)
```

 The list of file is outdated tho. I can have a new look to it to see if this is still relevant.
As far as I know, all the random functions depend on the same random generator so if we get some way of [restoring the seed](https://docs.python.org/3.7/library/random.html#random.seed) before each test, we won't remove the randomness but we can ensure it is deterministic from run to run.
We should not fix the random seed in tests that rely on asserts. Namely tests that pass for any random seed, as long as the code is correct. The random seed should be fixed in the testsuite only for tests that fail otherwise.
A reminder, we are talking about random inserted just for testing, not about the intrinsic random used by Terra.

@delapuente: Using only constant-seeded generators is equivalent of using constants directly.

@yaelbh I disagree. Making sure that the tests pass with a big variety of inputs it's the job of a fuzzer, not the test suite. If I write code and the test suite fails, I want to make sure it is because my new code broke something, not because in that particular execution of the test suite the input covered a different path of existing buggy code.

Fuzzing Terra is something definitely worth trying, and it is something I did in the past. Let me know if you are interested in this effort.
Closed by error, reopening.
Updating the list:
- [ ] `test/python/test_visualization.py`:
  * The method `TestLatexSourceGenerator.random_circuit` generates a random circuit (used in `test_tiny_circuit`, `test_normal_circuit`, `test_deep_circuit`, and `test_huge_circuit`. 
- [ ] `test/python/test_apps.py`:
  * Numpy random is called
- [ ] `test/python/test_qasm_simulator_py.py`: 
  * The tests `profile_qasm_simulator` and `profile_nqubit_speed_grow_depth`  (currently both of them skiped) uses `RandomQasmGenerator`.
- [ ] `test/python/test_qi.py`:
  * The library `random_pauli` is being used in `test_random_pauli5`.
- [ ] `test/python/test_quaternions.py`:
  * The test `test_random_euler` calls Numpy random.
- [ ] `test/python/test_quantumprogram.py`:
  * trivial, random is used to generate a random hostname
- [ ] `test/python/test_unitary_simulator_py.py`:
  * The test `profile_unitary_simulator` uses `RandomQasmGenerator`.
@1ucian0 can you update the list here if it's still relevant? many of these tests no longer exist.

Resolving patch:
diff --git a/qiskit/quantum_info/operators/pauli.py b/qiskit/quantum_info/operators/pauli.py
--- a/qiskit/quantum_info/operators/pauli.py
+++ b/qiskit/quantum_info/operators/pauli.py
@@ -424,15 +424,17 @@ def delete_qubits(self, indices):
         return self
 
     @classmethod
-    def random(cls, num_qubits):
+    def random(cls, num_qubits, seed=None):
         """Return a random Pauli on number of qubits.
 
         Args:
             num_qubits (int): the number of qubits
-
+            seed (int): Optional. To set a random seed.
         Returns:
             Pauli: the random pauli
         """
+        if seed is not None:
+            np.random.seed(seed)
         z = np.random.randint(2, size=num_qubits).astype(np.bool)
         x = np.random.randint(2, size=num_qubits).astype(np.bool)
         return cls(z, x)
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,16 +38,20 @@ def basis_state(str_state, num):
         raise QiskitError('size of bitstring is greater than num.')
 
 
-def random_state(num):
+def random_state(num, seed=None):
     """
     Return a random quantum state from the uniform (Haar) measure on
     state space.
 
     Args:
         num (int): the number of qubits
+        seed (int): Optional. To set a random seed.
+
     Returns:
         ndarray:  state(2**num) a random quantum state.
     """
+    if seed is not None:
+        np.random.seed(seed)
     # Random array over interval (0, 1]
     x = np.random.random(1 << num)
     x += x == 0
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
@@ -335,19 +335,22 @@ def outer(vector1, vector2=None):
 # Random Matrices.
 ###############################################################
 
-def random_unitary_matrix(length):
+def random_unitary_matrix(length, seed=None):
     """
     Return a random unitary ndarray.
 
     Args:
         length (int): the length of the returned unitary.
+        seed (int): Optional. To set a random seed.
     Returns:
         ndarray: U (length, length) unitary ndarray.
     """
+    if seed is not None:
+        np.random.seed(seed)
     return unitary_group.rvs(length)
 
 
-def random_density_matrix(length, rank=None, method='Hilbert-Schmidt'):
+def random_density_matrix(length, rank=None, method='Hilbert-Schmidt', seed=None):
     """
     Generate a random density matrix rho.
 
@@ -358,40 +361,42 @@ def random_density_matrix(length, rank=None, method='Hilbert-Schmidt'):
         method (string): the method to use.
             'Hilbert-Schmidt': sample rho from the Hilbert-Schmidt metric.
             'Bures': sample rho from the Bures metric.
-
+        seed (int): Optional. To set a random seed.
     Returns:
         ndarray: rho (length, length) a density matrix.
     Raises:
         QiskitError: if the method is not valid.
     """
     if method == 'Hilbert-Schmidt':
-        return __random_density_hs(length, rank)
+        return __random_density_hs(length, rank, seed)
     elif method == 'Bures':
-        return __random_density_bures(length, rank)
+        return __random_density_bures(length, rank, seed)
     else:
         raise QiskitError('Error: unrecognized method {}'.format(method))
 
 
-def __ginibre_matrix(nrow, ncol=None):
+def __ginibre_matrix(nrow, ncol=None, seed=None):
     """
     Return a normally distributed complex random matrix.
 
     Args:
         nrow (int): number of rows in output matrix.
         ncol (int): number of columns in output matrix.
-
+        seed (int): Optional. To set a random seed.
     Returns:
         ndarray: A complex rectangular matrix where each real and imaginary
             entry is sampled from the normal distribution.
     """
     if ncol is None:
         ncol = nrow
+    if seed is not None:
+        np.random.seed(seed)
     G = np.random.normal(size=(nrow, ncol)) + \
         np.random.normal(size=(nrow, ncol)) * 1j
     return G
 
 
-def __random_density_hs(N, rank=None):
+def __random_density_hs(N, rank=None, seed=None):
     """
     Generate a random density matrix from the Hilbert-Schmidt metric.
 
@@ -399,15 +404,16 @@ def __random_density_hs(N, rank=None):
         N (int): the length of the density matrix.
         rank (int or None): the rank of the density matrix. The default
             value is full-rank.
+        seed (int): Optional. To set a random seed.
     Returns:
         ndarray: rho (N,N  a density matrix.
     """
-    G = __ginibre_matrix(N, rank)
+    G = __ginibre_matrix(N, rank, seed)
     G = G.dot(G.conj().T)
     return G / np.trace(G)
 
 
-def __random_density_bures(N, rank=None):
+def __random_density_bures(N, rank=None, seed=None):
     """
     Generate a random density matrix from the Bures metric.
 
@@ -415,11 +421,12 @@ def __random_density_bures(N, rank=None):
         N (int): the length of the density matrix.
         rank (int or None): the rank of the density matrix. The default
             value is full-rank.
+        seed (int): Optional. To set a random seed.
     Returns:
         ndarray: rho (N,N) a density matrix.
     """
     P = np.eye(N) + random_unitary_matrix(N)
-    G = P.dot(__ginibre_matrix(N, rank))
+    G = P.dot(__ginibre_matrix(N, rank, seed))
     G = G.dot(G.conj().T)
     return G / np.trace(G)
 


Test patch:
diff --git a/test/python/quantum_info/test_pauli.py b/test/python/quantum_info/test_pauli.py
--- a/test/python/quantum_info/test_pauli.py
+++ b/test/python/quantum_info/test_pauli.py
@@ -113,7 +113,7 @@ def test_repr(self):
     def test_random_pauli(self):
         """Test random pauli creation."""
         length = 4
-        q = Pauli.random(length)
+        q = Pauli.random(length, seed=42)
         self.log.info(q)
         self.assertEqual(q.numberofqubits, length)
         self.assertEqual(len(q.z), length)
diff --git a/test/python/quantum_info/test_quaternions.py b/test/python/quantum_info/test_quaternions.py
--- a/test/python/quantum_info/test_quaternions.py
+++ b/test/python/quantum_info/test_quaternions.py
@@ -18,59 +18,37 @@
 class TestQuaternions(QiskitTestCase):
     """Tests qiskit.quantum_info.operators.quaternion"""
 
+    def setUp(self):
+        axes = ['x', 'y', 'z']
+        rnd = np.array([-0.92545003, -2.19985357, 6.01761209])
+        idx = np.array([0, 2, 1])
+        self.mat1 = rotation_matrix(rnd[0], axes[idx[0]]).dot(
+            rotation_matrix(rnd[1], axes[idx[1]]).dot(
+                rotation_matrix(rnd[2], axes[idx[2]])))
+        axes_str = ''.join(axes[i] for i in idx)
+        quat = quaternion_from_euler(rnd, axes_str)
+        self.mat2 = quat.to_matrix()
+
     def test_random_euler(self):
-        """Quaternion from random Euler rotations."""
-        # Random angles and axes
-        axes = {0: 'x', 1: 'y', 2: 'z'}
-        for _ in range(1000):
-            rnd = 4*np.pi*(np.random.random(3)-0.5)
-            idx = np.random.randint(3, size=3)
-            mat1 = rotation_matrix(rnd[0], axes[idx[0]]).dot(
-                rotation_matrix(rnd[1], axes[idx[1]]).dot(
-                    rotation_matrix(rnd[2], axes[idx[2]])))
-            axes_str = ''.join(axes[i] for i in idx)
-            quat = quaternion_from_euler(rnd, axes_str)
-            mat2 = quat.to_matrix()
-            self.assertTrue(np.allclose(mat1, mat2))
+        """Quaternion from Euler rotations."""
+        self.assertTrue(np.allclose(self.mat1, self.mat2))
 
     def test_orthogonality(self):
         """Quaternion rotation matrix orthogonality"""
-        # Check orthogonality of generated rotation matrix
-        axes = {0: 'x', 1: 'y', 2: 'z'}
-        for _ in range(1000):
-            rnd = 4*np.pi*(np.random.random(3)-0.5)
-            idx = np.random.randint(3, size=3)
-            axes_str = ''.join(axes[i] for i in idx)
-            quat = quaternion_from_euler(rnd, axes_str)
-            mat = quat.to_matrix()
-            self.assertTrue(np.allclose(mat.dot(mat.T),
-                                        np.identity(3, dtype=float)))
+        self.assertTrue(np.allclose(self.mat2.dot(self.mat2.T), np.identity(3, dtype=float)))
 
     def test_det(self):
         """Quaternion det = 1"""
-        # Check det for rotation and not reflection
-        axes = {0: 'x', 1: 'y', 2: 'z'}
-        for _ in range(1000):
-            rnd = 4*np.pi*(np.random.random(3)-0.5)
-            idx = np.random.randint(3, size=3)
-            axes_str = ''.join(axes[i] for i in idx)
-            quat = quaternion_from_euler(rnd, axes_str)
-            mat = quat.to_matrix()
-            self.assertTrue(np.allclose(la.det(mat), 1))
+        self.assertTrue(np.allclose(la.det(self.mat2), 1))
 
     def test_equiv_quaternions(self):
         """Different Euler rotations give same quaternion, up to sign."""
         # Check if euler angles from to_zyz return same quaternion
         # up to a sign (2pi rotation)
-        rot = {0: 'xyz', 1: 'xyx', 2: 'xzy',
-               3: 'xzx', 4: 'yzx', 5: 'yzy',
-               6: 'yxz', 7: 'yxy', 8: 'zxy',
-               9: 'zxz', 10: 'zyx', 11: 'zyz'}
-
-        for _ in range(1000):
-            rnd = 4*np.pi*(np.random.random(3)-0.5)
-            idx = np.random.randint(12)
-            quat1 = quaternion_from_euler(rnd, rot[idx])
+        rot = ['xyz', 'xyx', 'xzy', 'xzx', 'yzx', 'yzy', 'yxz', 'yxy', 'zxy', 'zxz', 'zyx', 'zyz']
+        for value in rot:
+            rnd = np.array([-1.57657536, 5.66384302, 2.91532185])
+            quat1 = quaternion_from_euler(rnd, value)
             euler = quat1.to_zyz()
             quat2 = quaternion_from_euler(euler, 'zyz')
             self.assertTrue(np.allclose(abs(quat1.data.dot(quat2.data)), 1))
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
@@ -83,14 +83,13 @@ def test_random_state(self):
         number = 100000
         E_P0_last = 0
         for ii in range(number):
-            state = basis_state(bin(np.random.randint(0, 8))[2:].zfill(3), 3)
-            E_P0 = (E_P0_last*ii)/(ii+1)+state_fidelity(state,
-                                                        random_state(3))/(ii+1)
+            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_last = E_P0
         self.assertAlmostEqual(E_P0, 1/8, places=2)
 
     def test_random_state_circuit(self):
-        state = random_state(3)
+        state = random_state(3, seed=40)
         q = QuantumRegister(3)
         qc = QuantumCircuit(q)
         qc.initialize(state, [q[0], q[1], q[2]])
diff --git a/test/python/test_qi.py b/test/python/test_qi.py
--- a/test/python/test_qi.py
+++ b/test/python/test_qi.py
@@ -225,10 +225,9 @@ def test_choi_to_rauli(self):
         self.assertTrue(np.array_equal(expected, res))
 
     def test_random_density_matrix(self):
-        # Verify the shape because random data will change each call
-        random_hs_matrix = random_density_matrix(2)
+        random_hs_matrix = random_density_matrix(2, seed=42)
         self.assertEqual((2, 2), random_hs_matrix.shape)
-        random_bures_matrix = random_density_matrix(2, method='Bures')
+        random_bures_matrix = random_density_matrix(2, method='Bures', seed=40)
         self.assertEqual((2, 2), random_bures_matrix.shape)
 
     def test_random_density_matrix_invalid_method(self):