Repository: Qiskit/qiskit

Issue:
Allow passing in a Generator object for seed in QuantumVolume circuit
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected enhancement?

Allow `QuantumVolume` to be called with a generator object rather than only using a numerical seed. This is important for sharing RNG objects in applications using these circuits (such as in Ignis), and also internally in the QV circuit we can pass the RNG object to the `random_unitary` function which already accepts a generator for the seed kwarg.

Example: to generate a list of quantum volume circuits from a single seed value:
```python
seed = 101
rng = np.random.default_rng(seed)
circs = [QuantumVolume(10, 10, seed=rng) for _ in range(20)]
```



Discussion:

Resolving patch:
diff --git a/qiskit/circuit/library/quantum_volume.py b/qiskit/circuit/library/quantum_volume.py
--- a/qiskit/circuit/library/quantum_volume.py
+++ b/qiskit/circuit/library/quantum_volume.py
@@ -14,7 +14,7 @@
 
 """Quantum Volume model circuit."""
 
-from typing import Optional
+from typing import Optional, Union
 
 import numpy as np
 from qiskit.quantum_info.random import random_unitary
@@ -62,30 +62,37 @@ class QuantumVolume(QuantumCircuit):
     def __init__(self,
                  num_qubits: int,
                  depth: Optional[int] = None,
-                 seed: Optional[int] = None,
+                 seed: Optional[Union[int, np.random.Generator]] = None,
                  classical_permutation: bool = True) -> None:
         """Create quantum volume model circuit of size num_qubits x depth.
 
         Args:
             num_qubits: number of active qubits in model circuit.
             depth: layers of SU(4) operations in model circuit.
-            seed: randomization seed.
+            seed: Random number generator or generator seed.
             classical_permutation: use classical permutations at every layer,
                 rather than quantum.
         """
-        depth = depth or num_qubits  # how many layers of SU(4)
-        width = int(np.floor(num_qubits/2))  # how many SU(4)s fit in each layer
-
+        # Initialize RNG
         if seed is None:
             rng_set = np.random.default_rng()
             seed = rng_set.integers(low=1, high=1000)
+        if isinstance(seed, np.random.Generator):
+            rng = seed
+        else:
+            rng = np.random.default_rng(seed)
 
+        # Parameters
+        depth = depth or num_qubits  # how many layers of SU(4)
+        width = int(np.floor(num_qubits/2))  # how many SU(4)s fit in each layer
         name = "quantum_volume_" + str([num_qubits, depth, seed]).replace(' ', '')
-
         super().__init__(num_qubits, name=name)
 
-        rng = np.random.default_rng(seed)
-
+        # Generator random unitary seeds in advance.
+        # Note that this means we are constructing multiple new generator
+        # objects from low-entropy integer seeds rather than pass the shared
+        # generator object to the random_unitary function. This is done so
+        # that we can use the integer seed as a label for the generated gates.
         unitary_seeds = rng.integers(low=1, high=1000, size=[depth, width])
 
         # For each layer, generate a permutation of qubits


Test patch:
diff --git a/test/python/circuit/test_library.py b/test/python/circuit/test_library.py
--- a/test/python/circuit/test_library.py
+++ b/test/python/circuit/test_library.py
@@ -274,11 +274,20 @@ class TestQuantumVolumeLibrary(QiskitTestCase):
 
     def test_qv(self):
         """Test qv circuit."""
-        circuit = QuantumVolume(2, 2, seed=2, classical_permutation=False)
+        seed = 10203
+        rng1 = np.random.default_rng(seed)
+        rng2 = np.random.default_rng(seed)
+
+        depth = 2
+        width = 1
+        circuit = QuantumVolume(2, depth, seed=rng1, classical_permutation=False)
+
         expected = QuantumCircuit(2)
-        expected.swap(0, 1)
-        expected.append(random_unitary(4, seed=837), [0, 1])
-        expected.append(random_unitary(4, seed=262), [0, 1])
+        unitary_seeds = rng2.integers(low=1, high=1000, size=[depth, width])
+        for d in range(depth):
+            if rng2.permutation([0, 1]).tolist() == [1, 0]:
+                expected.swap(0, 1)
+            expected.append(random_unitary(4, seed=unitary_seeds[d][0]), [0, 1])
         expected = Operator(expected)
         simulated = Operator(circuit)
         self.assertTrue(expected.equiv(simulated))