Repository: PennyLaneAI/pennylane

Issue:
Cache note/warning in RandomLayers
The [RandomLayers](https://pennylane.readthedocs.io/en/latest/code/api/pennylane.templates.layers.RandomLayers.html) documentation page has a note and a warning: 
![image](https://user-images.githubusercontent.com/49409390/78779293-b77ae400-796a-11ea-87b7-8be874a2a487.png)
However, the `cache` option for a QNode seems to have been removed in v0.8, so these boxes do not make sense anymore.

It would also be good to check if the removal of the `cache` option changes the behaviour of the random layers.




Discussion:
@mariaschuld @trbromley: this option remains, it just changed name. `cache=False` (default value) just became `mutable=True` (default value).
I'm making a PR where I also add a test. It's bad that there was none catching this!


In the long run it would be more user-friendly to have as default behaviour that every call to a qnode (mutable or not) uses the same random circuit...on our radar!


EDIT: I just realised that this IS default behaviour. I will add more information in the docstring.
Thanks @josh146 @mariaschuld!

Resolving patch:
diff --git a/pennylane/templates/layers/random.py b/pennylane/templates/layers/random.py
--- a/pennylane/templates/layers/random.py
+++ b/pennylane/templates/layers/random.py
@@ -63,6 +63,10 @@ def RandomLayers(weights, wires, ratio_imprim=0.3, imprimitive=CNOT, rotations=N
     r"""Layers of randomly chosen single qubit rotations and 2-qubit entangling gates, acting
     on randomly chosen qubits.
 
+    .. warning::
+        This template uses random number generation inside qnodes. Find more
+        details about how to invoke the desired random behaviour in the "Usage Details" section below.
+
     The argument ``weights`` contains the weights for each layer. The number of layers :math:`L` is therefore derived
     from the first dimension of ``weights``.
 
@@ -82,15 +86,6 @@ def RandomLayers(weights, wires, ratio_imprim=0.3, imprimitive=CNOT, rotations=N
         :width: 60%
         :target: javascript:void(0);
 
-    .. note::
-        Using the default seed (or any other fixed integer seed) generates one and the same circuit in every
-        quantum node. To generate different circuit architectures, either use a different random seed, or use ``seed=None``
-        together with the ``cache=False`` option when creating a quantum node.
-
-    .. warning::
-        When using a random number generator anywhere inside the quantum function without the ``cache=False`` option,
-        a new random circuit architecture will be created every time the quantum node is evaluated.
-
     Args:
         weights (array[float]): array of weights of shape ``(L, k)``,
         wires (Sequence[int]): sequence of qubit indices that the template acts on
@@ -99,10 +94,115 @@ def RandomLayers(weights, wires, ratio_imprim=0.3, imprimitive=CNOT, rotations=N
         rotations (list[pennylane.ops.Operation]): List of Pauli-X, Pauli-Y and/or Pauli-Z gates. The frequency
             determines how often a particular rotation type is used. Defaults to the use of all three
             rotations with equal frequency.
-        seed (int): seed to generate random architecture
+        seed (int): seed to generate random architecture, defaults to 42
 
     Raises:
         ValueError: if inputs do not have the correct format
+
+    .. UsageDetails::
+
+        **Default seed**
+
+        ``RandomLayers`` always uses a seed to initialize the construction of a random circuit. This means
+        that the template creates the same circuit every time it is called. If no seed is provided, the default
+        seed of ``42`` is used.
+
+        .. code-block:: python
+
+            import pennylane as qml
+            import numpy as np
+            from pennylane.templates.layers import RandomLayers
+
+            dev = qml.device("default.qubit", wires=2)
+            weights = [[0.1, -2.1, 1.4]]
+
+            @qml.qnode(dev)
+            def circuit1(weights):
+                RandomLayers(weights=weights, wires=range(2))
+                return qml.expval(qml.PauliZ(0))
+
+            @qml.qnode(dev)
+            def circuit2(weights):
+                RandomLayers(weights=weights, wires=range(2))
+                return qml.expval(qml.PauliZ(0))
+
+        >>> np.allclose(circuit1(weights), circuit2(weights))
+        >>> True
+
+        You can verify this by drawing the circuits.
+
+            >>> print(circuit1.draw())
+            >>>  0: ──RX(0.1)──RX(-2.1)──╭X──╭X───────────┤ ⟨Z⟩
+            ...  1: ─────────────────────╰C──╰C──RZ(1.4)──┤
+
+            >>> print(circuit2.draw())
+            >>>  0: ──RX(0.1)──RX(-2.1)──╭X──╭X───────────┤ ⟨Z⟩
+            ...  1: ─────────────────────╰C──╰C──RZ(1.4)──┤
+
+        **Changing the seed**
+
+        To change the randomly generated circuit architecture, you have to change the seed passed to the template.
+        For example, these two calls of ``RandomLayers`` *do not* create the same circuit:
+
+        .. code-block:: python
+
+            @qml.qnode(dev)
+            def circuit_9(weights):
+                RandomLayers(weights=weights, wires=range(2), seed=9)
+                return qml.expval(qml.PauliZ(0))
+
+            @qml.qnode(dev)
+            def circuit_12(weights):
+                RandomLayers(weights=weights, wires=range(2), seed=12)
+                return qml.expval(qml.PauliZ(0))
+
+        >>> np.allclose(circuit_9(weights), circuit_12(weights))
+        >>> False
+
+        >>> print(circuit_9.draw())
+        >>>  0: ──╭X──RY(-2.1)──RX(1.4)──┤ ⟨Z⟩
+        ...  1: ──╰C──RX(0.1)────────────┤
+
+        >>> print(circuit_12.draw())
+        >>>  0: ──╭X──RX(-2.1)──╭C──╭X──RZ(1.4)──┤ ⟨Z⟩
+        ...  1: ──╰C──RZ(0.1)───╰X──╰C───────────┤
+
+
+        **Automatically creating random circuits**
+
+        To automate the process of creating different circuits with ``RandomLayers``,
+        you can set ``seed=None`` to avoid specifying a seed. However, in this case care needs
+        to be taken. In the default setting, a quantum node is **mutable**, which means that the quantum function is
+        re-evaluated every time it is called. This means that the circuit is re-constructed from scratch
+        each time you call the qnode:
+
+        .. code-block:: python
+
+            @qml.qnode(dev)
+            def circuit_rnd(weights):
+                RandomLayers(weights=weights, wires=range(2), seed=None)
+                return qml.expval(qml.PauliZ(0))
+
+            first_call = circuit_rnd(weights)
+            second_call = circuit_rnd(weights)
+
+        >>> np.allclose(first_call, second_call)
+        >>> False
+
+        This can be rectified by making the quantum node **immutable**.
+
+        .. code-block:: python
+
+            @qml.qnode(dev, mutable=False)
+            def circuit_rnd(weights):
+                RandomLayers(weights=weights, wires=range(2), seed=None)
+                return qml.expval(qml.PauliZ(0))
+
+            first_call = circuit_rnd(weights)
+            second_call = circuit_rnd(weights)
+
+        >>> np.allclose(first_call, second_call)
+        >>> True
     """
     if seed is not None:
         np.random.seed(seed)


Test patch:
diff --git a/tests/templates/test_layers.py b/tests/templates/test_layers.py
--- a/tests/templates/test_layers.py
+++ b/tests/templates/test_layers.py
@@ -298,64 +298,67 @@ def impr(self, request):
     def rots(self, request):
         return request.param
 
-    def test_random_layers_deterministic_seed(self, n_layers, tol, seed):
-        """Test that RandomLayers() acts deterministically when using fixed seed."""
-        n_rots = 1
-        n_wires = 2
-        dev = qml.device("default.qubit", wires=n_wires)
-        weights = np.random.randn(n_layers, n_rots)
+    def test_same_circuit_for_same_seed(self, tol, seed):
+        """Test that RandomLayers() creates the same circuit when using the same seed."""
+        dev = qml.device("default.qubit", wires=2)
+        weights = [[0.1, 0.2, 0.3]]
 
-        def circuit1(weights):
-            RandomLayers(weights=weights, wires=range(n_wires), seed=seed)
-            return qml.expval(qml.PauliZ(0))
-
-        def circuit2(weights):
-            RandomLayers(weights=weights, wires=range(n_wires), seed=seed)
+        def circuit(weights):
+            RandomLayers(weights=weights, wires=range(2), seed=seed)
             return qml.expval(qml.PauliZ(0))
 
-        qnode1 = qml.QNode(circuit1, dev)
-        qnode2 = qml.QNode(circuit2, dev)
+        qnode1 = qml.QNode(circuit, dev)
+        qnode2 = qml.QNode(circuit, dev)
         assert np.allclose(qnode1(weights), qnode2(weights), atol=tol)
 
-    def test_random_layers_deterministic_default_seed(self, n_layers, tol):
-        """Test that RandomLayers() acts deterministically when using default seed."""
-        n_rots = 1
-        n_wires = 2
-        dev = qml.device("default.qubit", wires=n_wires)
-        weights = np.random.randn(n_layers, n_rots)
+    def test_different_circuits_for_different_seeds(self, tol):
+        """Test that RandomLayers() does not necessarily have the same output for two different seeds."""
+        dev = qml.device("default.qubit", wires=2)
+        weights = [[0.1, 0.2, 0.3]]
 
         def circuit1(weights):
-            RandomLayers(weights=weights, wires=range(n_wires))
+            RandomLayers(weights=weights, wires=range(2), seed=1)
             return qml.expval(qml.PauliZ(0))
 
         def circuit2(weights):
-            RandomLayers(weights=weights, wires=range(n_wires))
+            RandomLayers(weights=weights, wires=range(2), seed=2)
             return qml.expval(qml.PauliZ(0))
 
         qnode1 = qml.QNode(circuit1, dev)
         qnode2 = qml.QNode(circuit2, dev)
 
-        assert np.allclose(qnode1(weights), qnode2(weights), atol=tol)
+        assert not np.allclose(qnode1(weights), qnode2(weights), atol=tol)
 
-    def test_random_layers_two_seeds_different(self, n_layers, tol):
-        """Test that RandomLayers() does not have the same output for two different seeds."""
-        n_rots = 10
-        n_wires = 2
-        dev = qml.device("default.qubit", wires=n_wires)
-        weights = np.random.randn(n_layers, n_rots)
+    @pytest.mark.parametrize("mutable", [True, False])
+    def test_same_circuit_in_each_qnode_call(self, mutable, tol):
+        """Test that RandomLayers() creates the same circuit in two calls of a qnode."""
+        dev = qml.device("default.qubit", wires=2)
+        weights = [[0.1, 0.2, 0.3]]
 
-        def circuit1(weights):
-            RandomLayers(weights=weights, wires=range(n_wires), seed=0)
+        @qml.qnode(dev, mutable=mutable)
+        def circuit(weights):
+            RandomLayers(weights=weights, wires=range(2))
             return qml.expval(qml.PauliZ(0))
 
-        def circuit2(weights):
-            RandomLayers(weights=weights, wires=range(n_wires), seed=1)
-            return qml.expval(qml.PauliZ(0))
+        first_call = circuit(weights)
+        second_call = circuit(weights)
+        assert np.allclose(first_call, second_call, atol=tol)
 
-        qnode1 = qml.QNode(circuit1, dev)
-        qnode2 = qml.QNode(circuit2, dev)
+    def test_no_seed(self, tol):
+        """Test that two calls to a qnode with RandomLayers() for 'seed=None' option create the
+        same circuit for immutable qnodes."""
 
-        assert not np.allclose(qnode1(weights), qnode2(weights), atol=tol)
+        dev = qml.device("default.qubit", wires=2)
+        weights = [[0.1]*100]
+
+        @qml.qnode(dev, mutable=False)
+        def circuit(weights):
+            RandomLayers(weights=weights, wires=range(2), seed=None)
+            return qml.expval(qml.PauliZ(0))
+
+        first_call = circuit(weights)
+        second_call = circuit(weights)
+        assert np.allclose(first_call, second_call, atol=tol)
 
     def test_random_layers_nlayers(self, n_layers):
         """Test that RandomLayers() picks the correct number of gates."""