Repository: PennyLaneAI/pennylane
Pull request #1904 (2021-11-16 19:21:50)

Make `tape.trainable_params` a list
**Context:**
Recently, the `trainable_params` property of `QuantumTape`s, which is a `set`, caused bugs based on the fact that it is not guaranteed to maintain its ordering.
In particular for larger tapes these rare instances seem to occur, which likely is the reason why it was not caught before.
As an example, consider the "unordered" set `{3, 1}`. As integer hashes are the integers themselves, a "canonical" ordering for python is according to the hash table, so that
```pycon
>>> {3, 1}
{1, 3}
```
This makes constructing small tests that fail based on `trainable_params` being a set quite hard.

**Description of the Change:**
`QuantumTape.trainable_params` is made a `list` instead, with an intermediate conversion to `set` and subsequent sorting within the attribute setter function to exclude duplicates and assure the entries are sorted. Whenever the uniqueness and ordering of the entries is guaranteed, the private attribute `_trainable_params` may be set directly, as is done in `_update_trainable_params`.

**Benefits:**
Fixes rare but apparently increasingly important edge case bugs where the operation/parameter ordering was disturbed by the loss of ordering within `trainable_params`.

**Possible Drawbacks:**
For very large tapes with many trainable parameters, a lookup will be slower.

**Related GitHub Issues:**
--

codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1904?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#1904](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1904?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (73574fa) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/ec0b0487ffdce9c45604f0043f24498cdc6c9de0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (ec0b048) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1904/graphs/tree.svg?width=650&height=150&src=pr&token=9S3YzlUgGk&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1904?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

```diff
@@           Coverage Diff           @@
##           master    #1904   +/-   ##
=======================================
  Coverage   98.98%   98.98%           
=======================================
  Files         219      219           
  Lines       16555    16556    +1     
=======================================
+ Hits        16387    16388    +1     
  Misses        168      168           
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1904?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/tape/tape.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1904/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RhcGUvdGFwZS5weQ==) | `98.92% <100.00%> (+<0.01%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1904?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1904?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Last update [ec0b048...73574fa](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1904?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).


Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 5bd786dce..9947dd30e 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -37,6 +37,11 @@
 
 <h3>Breaking changes</h3>
 
+* `QuantumTape.trainable_params` now is a list instead of a set. This
+  means that `tape.trainable_params` will return a list unlike before,
+  but setting the `trainable_params` with a set works exactly as before.
+  [(#1xxx)](https://github.com/PennyLaneAI/pennylane/pull/1xxx)
+
 * The `num_params` attribute in the operator class is now dynamic. This makes it easier
   to define operator subclasses with a flexible number of parameters. 
   [(#1898)](https://github.com/PennyLaneAI/pennylane/pull/1898)
@@ -62,6 +67,10 @@
 
 <h3>Bug fixes</h3>
 
+* `QuantumTape.trainable_params` now is a list instead of a set, making
+  it more stable in very rare edge cases.
+  [(#1904)](https://github.com/PennyLaneAI/pennylane/pull/1904)
+
 * `ExpvalCost` now returns corrects results shape when `optimize=True` with 
   shots batch.
   [(#1897)](https://github.com/PennyLaneAI/pennylane/pull/1897)
@@ -85,4 +94,5 @@
 This release contains contributions from (in alphabetical order): 
 
 Guillermo Alonso-Linaje, Olivia Di Matteo, Jalani Kanem, Shumpei Kobayashi, Christina Lee, Alejandro Montanez,
-Romain Moyard, Maria Schuld, Jay Soni
+Romain Moyard, Maria Schuld, Jay Soni, David Wierichs
+
diff --git a/pennylane/tape/tape.py b/pennylane/tape/tape.py
index 186331773..4b5212adc 100644
--- a/pennylane/tape/tape.py
+++ b/pennylane/tape/tape.py
@@ -271,7 +271,7 @@ class QuantumTape(AnnotatedQueue):
     The trainable parameters of the tape can be explicitly set, and the values of
     the parameters modified in-place:
 
-    >>> tape.trainable_params = {0} # set only the first parameter as free
+    >>> tape.trainable_params = [0] # set only the first parameter as trainable
     >>> tape.set_parameters([0.56])
     >>> tape.get_parameters()
     [0.56]
@@ -318,7 +318,7 @@ class QuantumTape(AnnotatedQueue):
         parameter indices (in the order they appear on the tape), and values are a
         dictionary containing the corresponding operation and operation parameter index."""
 
-        self._trainable_params = set()
+        self._trainable_params = []
         self._graph = None
         self._specs = None
         self._depth = None
@@ -494,8 +494,13 @@ class QuantumTape(AnnotatedQueue):
                 param_count += 1
 
     def _update_trainable_params(self):
-        """Set the trainable parameters"""
-        self._trainable_params = set(self._par_info)
+        """Set the trainable parameters
+
+        self._par_info.keys() is assumed to be sorted
+        As its order is maintained, this assumes that self._par_info
+        is created in a sorted manner, as in _update_par_info
+        """
+        self._trainable_params = list(self._par_info)
 
     def _update(self):
         """Update all internal tape metadata regarding processed operations and observables"""
@@ -594,7 +599,7 @@ class QuantumTape(AnnotatedQueue):
 
         Here, let's set some trainable parameters:
 
-        >>> tape.trainable_params = {1, 2}
+        >>> tape.trainable_params = [1, 2]
         >>> tape.get_parameters()
         [0.432, 0.543]
 
@@ -614,7 +619,7 @@ class QuantumTape(AnnotatedQueue):
         >>> tape.get_parameters(trainable_only=True)
         [0.543, 0.432]
         >>> tape.trainable_params
-        {1, 4}
+        [1, 4]
         """
         # we must remap the old parameter
         # indices to the new ones after the operation order is reversed.
@@ -645,7 +650,7 @@ class QuantumTape(AnnotatedQueue):
         parameter_mapping = dict(zip(parameter_indices, range(len(parameter_indices))))
 
         # map the params
-        self.trainable_params = {parameter_mapping[i] for i in self.trainable_params}
+        self.trainable_params = [parameter_mapping[i] for i in self.trainable_params]
         self._par_info = {parameter_mapping[k]: v for k, v in self._par_info.items()}
 
         for idx, op in enumerate(self._ops):
@@ -685,7 +690,7 @@ class QuantumTape(AnnotatedQueue):
 
     @property
     def trainable_params(self):
-        """Store or return a set containing the indices of parameters that support
+        """Store or return a list containing the indices of parameters that support
         differentiability. The indices provided match the order of appearence in the
         quantum circuit.
 
@@ -716,13 +721,10 @@ class QuantumTape(AnnotatedQueue):
                 qml.expval(qml.PauliZ(wires=[0]))
 
         >>> tape.trainable_params
-        {0, 1, 2}
-        >>> tape.trainable_params = {0} # set only the first parameter as free
+        [0, 1, 2]
+        >>> tape.trainable_params = [0] # set only the first parameter as trainable
         >>> tape.get_parameters()
         [0.432]
-
-        Args:
-            param_indices (set[int]): parameter indices
         """
         return self._trainable_params
 
@@ -731,15 +733,15 @@ class QuantumTape(AnnotatedQueue):
         """Store the indices of parameters that support differentiability.
 
         Args:
-            param_indices (set[int]): parameter indices
+            param_indices (list[int]): parameter indices
         """
         if any(not isinstance(i, int) or i < 0 for i in param_indices):
-            raise ValueError("Argument indices must be positive integers.")
+            raise ValueError("Argument indices must be non-negative integers.")
 
         if any(i > len(self._par_info) for i in param_indices):
             raise ValueError(f"Tape has at most {self.num_params} parameters.")
 
-        self._trainable_params = param_indices
+        self._trainable_params = sorted(set(param_indices))
 
     def get_operation(self, idx):
         """Returns the trainable operation, and the corresponding operation argument
@@ -754,14 +756,17 @@ class QuantumTape(AnnotatedQueue):
             for the provided trainable parameter.
         """
         # get the index of the parameter in the tape
-        t_idx = list(self.trainable_params)[idx]
+        t_idx = self.trainable_params[idx]
+
+        # get the info for the parameter
+        info = self._par_info[t_idx]
 
         # get the corresponding operation
-        op = self._par_info[t_idx]["op"]
+        op = info["op"]
 
         # get the corresponding operation parameter index
         # (that is, index of the parameter within the operation)
-        p_idx = self._par_info[t_idx]["p_idx"]
+        p_idx = info["p_idx"]
         return op, p_idx
 
     def get_parameters(self, trainable_only=True, **kwargs):  # pylint:disable=unused-argument
@@ -792,7 +797,7 @@ class QuantumTape(AnnotatedQueue):
         Setting the trainable parameter indices will result in only the specified
         parameters being returned:
 
-        >>> tape.trainable_params = {1} # set the second parameter as free
+        >>> tape.trainable_params = [1] # set the second parameter as trainable
         >>> tape.get_parameters()
         [0.543]
 
@@ -803,7 +808,7 @@ class QuantumTape(AnnotatedQueue):
         [0.432, 0.543, 0.133]
         """
         params = []
-        iterator = sorted(self.trainable_params) if trainable_only else self._par_info
+        iterator = self.trainable_params if trainable_only else self._par_info
 
         for p_idx in iterator:
             op = self._par_info[p_idx]["op"]
@@ -841,7 +846,7 @@ class QuantumTape(AnnotatedQueue):
         parameters being modifiable. Note that this only modifies the number of
         parameters that must be passed.
 
-        >>> tape.trainable_params = {0, 2} # set the first and third parameter as free
+        >>> tape.trainable_params = [0, 2] # set the first and third parameter as trainable
         >>> tape.set_parameters([-0.1, 0.5])
         >>> tape.get_parameters(trainable_only=False)
         [-0.1, 0.2, 0.5]
@@ -854,7 +859,7 @@ class QuantumTape(AnnotatedQueue):
         [4, 1, 6]
         """
         if trainable_only:
-            iterator = zip(sorted(self.trainable_params), params)
+            iterator = zip(self.trainable_params, params)
             required_length = self.num_params
         else:
             iterator = enumerate(params)
@@ -888,7 +893,7 @@ class QuantumTape(AnnotatedQueue):
         ...     with tape.unwrap():
         ...         print("Trainable params:", tape.trainable_params)
         ...         print("Unwrapped params:", tape.get_parameters())
-        Trainable params: {0, 2}
+        Trainable params: [0, 2]
         Unwrapped params: [0.1, 0.3]
         >>> print("Original parameters:", tape.get_parameters())
         Original parameters: [<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.1>,


Test patch:
diff --git a/tests/interfaces/test_batch_autograd.py b/tests/interfaces/test_batch_autograd.py
index 4a2ae90a9..63e3818c3 100644
--- a/tests/interfaces/test_batch_autograd.py
+++ b/tests/interfaces/test_batch_autograd.py
@@ -406,7 +406,7 @@ class TestAutogradExecuteIntegration:
             qml.RY(a, wires=0)
             qml.expval(qml.PauliZ(0))
 
-        tape.trainable_params = {0}
+        tape.trainable_params = [0]
         tapes, fn = param_shift(tape)
         expected = fn(dev.batch_execute(tapes))
 
@@ -485,7 +485,7 @@ class TestAutogradExecuteIntegration:
             qml.expval(qml.PauliZ(0))
             qml.expval(qml.PauliY(1))
 
-        assert tape.trainable_params == {0, 1}
+        assert tape.trainable_params == [0, 1]
 
         def cost(a, b):
             tape.set_parameters([a, b])
diff --git a/tests/interfaces/test_batch_autograd_qnode.py b/tests/interfaces/test_batch_autograd_qnode.py
index 472401b4c..038dc8fcc 100644
--- a/tests/interfaces/test_batch_autograd_qnode.py
+++ b/tests/interfaces/test_batch_autograd_qnode.py
@@ -129,7 +129,7 @@ class TestQNode:
         assert circuit.interface == "autograd"
 
         # the tape is able to deduce trainable parameters
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         # gradients should work
         grad = qml.grad(circuit)(a)
@@ -157,7 +157,7 @@ class TestQNode:
 
         res = circuit(a, b)
 
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
         assert res.shape == (2,)
 
         expected = [np.cos(a), -np.cos(a) * np.sin(b)]
@@ -254,7 +254,7 @@ class TestQNode:
         res = grad_fn(a, b)
 
         # the tape has reported both arguments as trainable
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         expected = [-np.sin(a) + np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)]
         assert np.allclose(res, expected, atol=tol, rtol=0)
@@ -269,7 +269,7 @@ class TestQNode:
         res = grad_fn(a, b)
 
         # the tape has reported only the first argument as trainable
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         expected = [-np.sin(a) + np.sin(a) * np.sin(b)]
         assert np.allclose(res, expected, atol=tol, rtol=0)
@@ -281,7 +281,7 @@ class TestQNode:
         a = np.array(0.54, requires_grad=False)
         b = np.array(0.8, requires_grad=True)
         circuit(a, b)
-        assert circuit.qtape.trainable_params == {1}
+        assert circuit.qtape.trainable_params == [1]
 
     def test_classical_processing(self, dev_name, diff_method, mode, tol):
         """Test classical processing within the quantum tape"""
@@ -301,7 +301,7 @@ class TestQNode:
         res = qml.jacobian(circuit)(a, b, c)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {0, 2}
+            assert circuit.qtape.trainable_params == [0, 2]
             tape_params = np.array(circuit.qtape.get_parameters())
             assert np.all(tape_params == [a * c, c + c ** 2 + np.sin(a)])
 
@@ -324,7 +324,7 @@ class TestQNode:
         res = circuit(a, b)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == set()
+            assert circuit.qtape.trainable_params == []
 
         assert res.shape == (2,)
         assert isinstance(res, np.ndarray)
@@ -356,7 +356,7 @@ class TestQNode:
         res = circuit(U, a)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {1}
+            assert circuit.qtape.trainable_params == [1]
 
         res = qml.grad(circuit)(U, a)
         assert np.allclose(res, np.sin(a), atol=tol, rtol=0)
@@ -769,11 +769,11 @@ class TestQubitIntegration:
         if diff_method != "backprop":
             # Check that the gradient was computed
             # for all parameters in circuit2
-            assert circuit2.qtape.trainable_params == {0, 1, 2, 3}
+            assert circuit2.qtape.trainable_params == [0, 1, 2, 3]
 
             # Check that the parameter-shift rule was not applied
             # to the first parameter of circuit1.
-            assert circuit1.qtape.trainable_params == {1, 2}
+            assert circuit1.qtape.trainable_params == [1, 2]
 
     def test_second_derivative(self, dev_name, diff_method, mode, tol):
         """Test second derivative calculation of a scalar valued QNode"""
diff --git a/tests/interfaces/test_batch_jax.py b/tests/interfaces/test_batch_jax.py
index a5e50e24a..46d97d6b4 100644
--- a/tests/interfaces/test_batch_jax.py
+++ b/tests/interfaces/test_batch_jax.py
@@ -417,7 +417,7 @@ class TestJaxExecuteIntegration:
             qml.RY(a, wires=0)
             qml.expval(qml.PauliZ(0))
 
-        tape.trainable_params = {0}
+        tape.trainable_params = [0]
         tapes, fn = param_shift(tape)
         expected = fn(dev.batch_execute(tapes))
 
@@ -437,7 +437,7 @@ class TestJaxExecuteIntegration:
             qml.CNOT(wires=[0, 1])
             qml.expval(qml.PauliZ(0))
 
-        assert tape.trainable_params == {0, 1}
+        assert tape.trainable_params == [0, 1]
 
         def cost(a, b):
 
@@ -550,7 +550,7 @@ class TestJaxExecuteIntegration:
                 qml.QubitUnitary(U, wires=0)
                 qml.expval(qml.PauliZ(0))
 
-            tape.trainable_params = {0}
+            tape.trainable_params = [0]
             return execute([tape], device, interface="jax", **execute_kwargs)[0][0]
 
         dev = qml.device("default.qubit", wires=2)
diff --git a/tests/interfaces/test_batch_jax_qnode.py b/tests/interfaces/test_batch_jax_qnode.py
index d6a5ac229..024adc320 100644
--- a/tests/interfaces/test_batch_jax_qnode.py
+++ b/tests/interfaces/test_batch_jax_qnode.py
@@ -59,7 +59,7 @@ class TestQNode:
         assert circuit.interface == "jax"
 
         # the tape is able to deduce trainable parameters
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         # gradients should work
         grad = jax.grad(circuit)(a)
@@ -90,7 +90,7 @@ class TestQNode:
 
         res = circuit(a, b)
 
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
         assert res.shape == (2,)
 
         expected = [np.cos(a), -np.cos(a) * np.sin(b)]
@@ -187,7 +187,7 @@ class TestQNode:
         res = grad_fn(a, b)
 
         # the tape has reported both arguments as trainable
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         expected = [-np.sin(a) + np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)]
         assert np.allclose(res, expected, atol=tol, rtol=0)
@@ -200,7 +200,7 @@ class TestQNode:
         res = grad_fn(a, b)
 
         # the tape has reported only the first argument as trainable
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         expected = [-np.sin(a) + np.sin(a) * np.sin(b)]
         assert np.allclose(res, expected, atol=tol, rtol=0)
@@ -212,7 +212,7 @@ class TestQNode:
         a = np.array(0.54, requires_grad=False)
         b = np.array(0.8, requires_grad=True)
         circuit(a, b)
-        assert circuit.qtape.trainable_params == {1}
+        assert circuit.qtape.trainable_params == [1]
 
     def test_classical_processing(self, dev_name, diff_method, mode, tol):
         """Test classical processing within the quantum tape"""
@@ -232,7 +232,7 @@ class TestQNode:
         res = jax.grad(circuit, argnums=[0, 2])(a, b, c)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {0, 2}
+            assert circuit.qtape.trainable_params == [0, 2]
 
         assert len(res) == 2
 
@@ -254,7 +254,7 @@ class TestQNode:
         assert np.allclose(res, np.sin(a), atol=tol, rtol=0)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {1}
+            assert circuit.qtape.trainable_params == [1]
 
     def test_differentiable_expand(self, dev_name, diff_method, mode, tol):
         """Test that operation and nested tape expansion
diff --git a/tests/interfaces/test_batch_tensorflow.py b/tests/interfaces/test_batch_tensorflow.py
index 9b6fa692e..ea1b09586 100644
--- a/tests/interfaces/test_batch_tensorflow.py
+++ b/tests/interfaces/test_batch_tensorflow.py
@@ -337,7 +337,7 @@ class TestTensorFlowExecuteIntegration:
             qml.RY(a, wires=0)
             qml.expval(qml.PauliZ(0))
 
-        tape.trainable_params = {0}
+        tape.trainable_params = [0]
         tapes, fn = param_shift(tape)
         expected = fn(dev.batch_execute(tapes))
 
@@ -414,7 +414,7 @@ class TestTensorFlowExecuteIntegration:
                 qml.expval(qml.PauliZ(0))
                 qml.expval(qml.PauliY(1))
 
-            assert tape.trainable_params == {0, 1}
+            assert tape.trainable_params == [0, 1]
             res = execute([tape], dev, **execute_kwargs)[0]
 
         jac = t.jacobian(res, [a, b])
@@ -455,7 +455,7 @@ class TestTensorFlowExecuteIntegration:
 
         with tf.GradientTape() as t:
             tape.set_parameters([a, b])
-            assert tape.trainable_params == {0, 1}
+            assert tape.trainable_params == [0, 1]
             res = execute([tape], dev, **execute_kwargs)[0]
 
         jac = t.jacobian(res, [a, b])
@@ -493,7 +493,7 @@ class TestTensorFlowExecuteIntegration:
                 qml.expval(qml.PauliZ(0))
 
             res = execute([tape], dev, **execute_kwargs)[0]
-            assert tape.trainable_params == {0, 2}
+            assert tape.trainable_params == [0, 2]
             assert tape.get_parameters() == [a * c, c + c ** 2 + tf.sin(a)]
 
         res = t.jacobian(res, [a, b, c])
@@ -539,7 +539,7 @@ class TestTensorFlowExecuteIntegration:
                 qml.expval(qml.PauliZ(0))
 
             res = execute([tape], dev, **execute_kwargs)[0]
-            assert tape.trainable_params == {1}
+            assert tape.trainable_params == [1]
 
         assert np.allclose(res, -tf.cos(a), atol=tol, rtol=0)
 
@@ -577,7 +577,7 @@ class TestTensorFlowExecuteIntegration:
             qtape = qtape.expand()
             res = execute([qtape], dev, **execute_kwargs)[0]
 
-            assert qtape.trainable_params == {1, 2, 3, 4}
+            assert qtape.trainable_params == [1, 2, 3, 4]
             assert [i.name for i in qtape.operations] == ["RX", "Rot", "PhaseShift"]
             assert np.all(qtape.get_parameters() == [p[2], p[0], -p[2], p[1] + p[2]])
 
diff --git a/tests/interfaces/test_batch_tensorflow_qnode.py b/tests/interfaces/test_batch_tensorflow_qnode.py
index 2401ab461..0241f3281 100644
--- a/tests/interfaces/test_batch_tensorflow_qnode.py
+++ b/tests/interfaces/test_batch_tensorflow_qnode.py
@@ -53,7 +53,7 @@ class TestQNode:
 
         # if executing outside a gradient tape, the number of trainable parameters
         # cannot be determined by TensorFlow
-        assert circuit.qtape.trainable_params == set()
+        assert circuit.qtape.trainable_params == []
 
         with tf.GradientTape() as tape:
             res = circuit(a)
@@ -65,7 +65,7 @@ class TestQNode:
         assert res.shape == tuple()
 
         # the tape is able to deduce trainable parameters
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         # gradients should work
         grad = tape.gradient(res, a)
@@ -152,7 +152,7 @@ class TestQNode:
         with tf.GradientTape() as tape:
             res = circuit(a, b)
 
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         assert isinstance(res, tf.Tensor)
         assert res.shape == (2,)
@@ -192,7 +192,7 @@ class TestQNode:
             res = circuit(a, b)
 
         assert circuit.qtape.interface == "tf"
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         assert isinstance(res, tf.Tensor)
         assert res.shape == (2,)
@@ -249,7 +249,7 @@ class TestQNode:
             res = circuit(a, b)
 
         # the tape has reported both gate arguments as trainable
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         expected = [tf.cos(a), -tf.cos(a) * tf.sin(b)]
         assert np.allclose(res, expected, atol=tol, rtol=0)
@@ -274,7 +274,7 @@ class TestQNode:
             res = circuit(a, b)
 
         # the tape has reported only the first argument as trainable
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         expected = [tf.cos(a), -tf.cos(a) * tf.sin(b)]
         assert np.allclose(res, expected, atol=tol, rtol=0)
@@ -306,7 +306,7 @@ class TestQNode:
             res = circuit(a, b, c)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {0, 2}
+            assert circuit.qtape.trainable_params == [0, 2]
             assert circuit.qtape.get_parameters() == [a * c, c + c ** 2 + tf.sin(a)]
 
         res = tape.jacobian(res, [a, b, c])
@@ -333,7 +333,7 @@ class TestQNode:
             res = circuit(a, b)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == set()
+            assert circuit.qtape.trainable_params == []
 
         assert res.shape == (2,)
         assert isinstance(res, tf.Tensor)
@@ -356,7 +356,7 @@ class TestQNode:
             res = circuit(U, a)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {1}
+            assert circuit.qtape.trainable_params == [1]
 
         assert np.allclose(res, -tf.cos(a), atol=tol, rtol=0)
 
@@ -391,7 +391,7 @@ class TestQNode:
         with tf.GradientTape() as tape:
             res = circuit(a, p)
 
-        assert circuit.qtape.trainable_params == {1, 2, 3}
+        assert circuit.qtape.trainable_params == [1, 2, 3]
 
         expected = tf.cos(a) * tf.cos(p[1]) * tf.sin(p[0]) + tf.sin(a) * (
             tf.cos(p[2]) * tf.sin(p[1]) + tf.cos(p[0]) * tf.cos(p[1]) * tf.sin(p[2])
diff --git a/tests/interfaces/test_batch_torch.py b/tests/interfaces/test_batch_torch.py
index 8775d1e6e..f7c8b9459 100644
--- a/tests/interfaces/test_batch_torch.py
+++ b/tests/interfaces/test_batch_torch.py
@@ -420,7 +420,7 @@ class TestTorchExecuteIntegration:
             qml.expval(qml.PauliY(1))
 
         res = execute([tape], dev, **execute_kwargs)[0]
-        assert tape.trainable_params == {1, 2}
+        assert tape.trainable_params == [1, 2]
 
         assert isinstance(res, torch.Tensor)
         assert res.shape == (2,)
@@ -491,7 +491,7 @@ class TestTorchExecuteIntegration:
             qml.expval(qml.PauliZ(0))
             qml.expval(qml.PauliY(1))
 
-        assert tape.trainable_params == {0, 1}
+        assert tape.trainable_params == [0, 1]
 
         res = execute([tape], dev, **execute_kwargs)[0]
         loss = torch.sum(res)
@@ -542,7 +542,7 @@ class TestTorchExecuteIntegration:
 
         res = execute([tape], dev, **execute_kwargs)[0]
 
-        assert tape.trainable_params == {0, 2}
+        assert tape.trainable_params == [0, 2]
 
         tape_params = torch.tensor([i.detach() for i in tape.get_parameters()], device=torch_device)
         expected = torch.tensor(
@@ -575,7 +575,7 @@ class TestTorchExecuteIntegration:
             qml.expval(qml.PauliZ(1))
 
         res = execute([tape], dev, **execute_kwargs)[0]
-        assert tape.trainable_params == set()
+        assert tape.trainable_params == []
 
         assert res.shape == (2,)
         assert isinstance(res, torch.Tensor)
@@ -606,7 +606,7 @@ class TestTorchExecuteIntegration:
             qml.expval(qml.PauliZ(0))
 
         res = execute([tape], dev, **execute_kwargs)[0]
-        assert tape.trainable_params == {1}
+        assert tape.trainable_params == [1]
 
         expected = torch.tensor(-np.cos(a_val), dtype=res.dtype, device=torch_device)
         assert torch.allclose(res.detach(), expected, atol=tol, rtol=0)
@@ -645,7 +645,7 @@ class TestTorchExecuteIntegration:
         tape = tape.expand()
         res = execute([tape], dev, **execute_kwargs)[0]
 
-        assert tape.trainable_params == {1, 2, 3, 4}
+        assert tape.trainable_params == [1, 2, 3, 4]
         assert [i.name for i in tape.operations] == ["RX", "Rot", "PhaseShift"]
 
         tape_params = torch.tensor([i.detach() for i in tape.get_parameters()], device=torch_device)
diff --git a/tests/interfaces/test_batch_torch_qnode.py b/tests/interfaces/test_batch_torch_qnode.py
index 7b483e8af..74d141952 100644
--- a/tests/interfaces/test_batch_torch_qnode.py
+++ b/tests/interfaces/test_batch_torch_qnode.py
@@ -60,7 +60,7 @@ class TestQNode:
         assert res.shape == tuple()
 
         # the tape is able to deduce trainable parameters
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         # gradients should work
         res.backward()
@@ -149,7 +149,7 @@ class TestQNode:
 
         res = circuit(a, b)
 
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         assert isinstance(res, torch.Tensor)
         assert res.shape == (2,)
@@ -191,7 +191,7 @@ class TestQNode:
         res = circuit(a, b)
 
         assert circuit.interface == "torch"
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         assert isinstance(res, torch.Tensor)
         assert res.shape == (2,)
@@ -250,7 +250,7 @@ class TestQNode:
         res = circuit(a, b)
 
         # the tape has reported both gate arguments as trainable
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         expected = [np.cos(a_val), -np.cos(a_val) * np.sin(b_val)]
         assert np.allclose(res.detach().numpy(), expected, atol=tol, rtol=0)
@@ -279,7 +279,7 @@ class TestQNode:
         res = circuit(a, b)
 
         # the tape has reported only the first argument as trainable
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         expected = [np.cos(a_val), -np.cos(a_val) * np.sin(b_val)]
         assert np.allclose(res.detach().numpy(), expected, atol=tol, rtol=0)
@@ -311,7 +311,7 @@ class TestQNode:
         res = circuit(a, b, c)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {0, 2}
+            assert circuit.qtape.trainable_params == [0, 2]
             assert circuit.qtape.get_parameters() == [a * c, c + c ** 2 + torch.sin(a)]
 
         res.backward()
@@ -337,7 +337,7 @@ class TestQNode:
         res = circuit(a, b)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == set()
+            assert circuit.qtape.trainable_params == []
 
         assert res.shape == (2,)
         assert isinstance(res, torch.Tensor)
@@ -372,7 +372,7 @@ class TestQNode:
         res = circuit(U, a)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {1}
+            assert circuit.qtape.trainable_params == [1]
 
         assert np.allclose(res.detach(), -np.cos(a_val), atol=tol, rtol=0)
 
@@ -407,7 +407,7 @@ class TestQNode:
 
         res = circuit(a, p)
 
-        assert circuit.qtape.trainable_params == {1, 2, 3}
+        assert circuit.qtape.trainable_params == [1, 2, 3]
 
         expected = np.cos(a) * np.cos(p_val[1]) * np.sin(p_val[0]) + np.sin(a) * (
             np.cos(p_val[2]) * np.sin(p_val[1])
diff --git a/tests/interfaces/test_qnode_autograd.py b/tests/interfaces/test_qnode_autograd.py
index 685252860..3e9cff04d 100644
--- a/tests/interfaces/test_qnode_autograd.py
+++ b/tests/interfaces/test_qnode_autograd.py
@@ -108,7 +108,7 @@ class TestQNode:
 
         # without the interface, the tape is unable to deduce
         # trainable parameters
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         # gradients should cause an error
         with pytest.raises(TypeError, match="must be real number, not ArrayBox"):
@@ -133,7 +133,7 @@ class TestQNode:
         assert circuit.qtape.interface == "autograd"
 
         # the tape is able to deduce trainable parameters
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         # gradients should work
         grad = qml.grad(circuit)(a)
@@ -191,7 +191,7 @@ class TestQNode:
 
         res = circuit(a, b)
 
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
         assert res.shape == (2,)
 
         expected = [np.cos(a), -np.cos(a) * np.sin(b)]
@@ -289,7 +289,7 @@ class TestQNode:
         res = grad_fn(a, b)
 
         # the tape has reported both arguments as trainable
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         expected = [-np.sin(a) + np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)]
         assert np.allclose(res, expected, atol=tol, rtol=0)
@@ -305,7 +305,7 @@ class TestQNode:
         res = grad_fn(a, b)
 
         # the tape has reported only the first argument as trainable
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         expected = [-np.sin(a) + np.sin(a) * np.sin(b)]
         assert np.allclose(res, expected, atol=tol, rtol=0)
@@ -317,7 +317,7 @@ class TestQNode:
         a = np.array(0.54, requires_grad=False)
         b = np.array(0.8, requires_grad=True)
         circuit(a, b)
-        assert circuit.qtape.trainable_params == {1}
+        assert circuit.qtape.trainable_params == [1]
 
     def test_classical_processing(self, dev_name, diff_method, tol):
         """Test classical processing within the quantum tape"""
@@ -337,7 +337,7 @@ class TestQNode:
         res = qml.jacobian(circuit)(a, b, c)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {0, 2}
+            assert circuit.qtape.trainable_params == [0, 2]
             tape_params = np.array(circuit.qtape.get_parameters())
             assert np.all(tape_params == [a * c, c + c ** 2 + np.sin(a)])
 
@@ -360,7 +360,7 @@ class TestQNode:
         res = circuit(a, b)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == set()
+            assert circuit.qtape.trainable_params == []
 
         assert res.shape == (2,)
         assert isinstance(res, np.ndarray)
@@ -392,7 +392,7 @@ class TestQNode:
         res = circuit(U, a)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {1}
+            assert circuit.qtape.trainable_params == [1]
 
         res = qml.grad(circuit)(U, a)
         assert np.allclose(res, np.sin(a), atol=tol, rtol=0)
@@ -423,7 +423,7 @@ class TestQNode:
             return qml.expval(qml.PauliX(0))
 
         res = circuit(a, p)
-        assert circuit.qtape.trainable_params == {1, 2, 3, 4}
+        assert circuit.qtape.trainable_params == [1, 2, 3, 4]
 
         assert [i.name for i in circuit.qtape.operations] == ["RX", "Rot", "PhaseShift"]
         assert np.all(circuit.qtape.get_parameters() == [p[2], p[0], -p[2], p[1] + p[2]])
@@ -729,11 +729,11 @@ class TestQNode:
         if diff_method != "backprop":
             # Check that the gradient was computed
             # for all parameters in circuit2
-            assert circuit2.qtape.trainable_params == {0, 1, 2, 3}
+            assert circuit2.qtape.trainable_params == [0, 1, 2, 3]
 
             # Check that the parameter-shift rule was not applied
             # to the first parameter of circuit1.
-            assert circuit1.qtape.trainable_params == {1, 2}
+            assert circuit1.qtape.trainable_params == [1, 2]
 
     def test_second_derivative(self, dev_name, diff_method, mocker, tol):
         """Test second derivative calculation of a scalar valued QNode"""
diff --git a/tests/interfaces/test_qnode_tf.py b/tests/interfaces/test_qnode_tf.py
index ff728a49a..e6e62afbf 100644
--- a/tests/interfaces/test_qnode_tf.py
+++ b/tests/interfaces/test_qnode_tf.py
@@ -85,7 +85,7 @@ class TestQNode:
 
         # without the interface, the tape is unable to deduce
         # trainable parameters
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         # gradients should cause an error
         with pytest.raises(AttributeError, match="has no attribute '_id'"):
@@ -109,7 +109,7 @@ class TestQNode:
 
         # if executing outside a gradient tape, the number of trainable parameters
         # cannot be determined by TensorFlow
-        assert circuit.qtape.trainable_params == set()
+        assert circuit.qtape.trainable_params == []
 
         with tf.GradientTape() as tape:
             res = circuit(a)
@@ -121,7 +121,7 @@ class TestQNode:
         assert res.shape == tuple()
 
         # the tape is able to deduce trainable parameters
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         # gradients should work
         grad = tape.gradient(res, a)
@@ -207,7 +207,7 @@ class TestQNode:
         with tf.GradientTape() as tape:
             res = circuit(a, b)
 
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         assert isinstance(res, tf.Tensor)
         assert res.shape == (2,)
@@ -248,7 +248,7 @@ class TestQNode:
             res = circuit(a, b)
 
         assert circuit.qtape.interface == "tf"
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         assert isinstance(res, tf.Tensor)
         assert res.shape == (2,)
@@ -305,7 +305,7 @@ class TestQNode:
             res = circuit(a, b)
 
         # the tape has reported both gate arguments as trainable
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         expected = [tf.cos(a), -tf.cos(a) * tf.sin(b)]
         assert np.allclose(res, expected, atol=tol, rtol=0)
@@ -330,7 +330,7 @@ class TestQNode:
             res = circuit(a, b)
 
         # the tape has reported only the first argument as trainable
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         expected = [tf.cos(a), -tf.cos(a) * tf.sin(b)]
         assert np.allclose(res, expected, atol=tol, rtol=0)
@@ -362,7 +362,7 @@ class TestQNode:
             res = circuit(a, b, c)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {0, 2}
+            assert circuit.qtape.trainable_params == [0, 2]
             assert circuit.qtape.get_parameters() == [a * c, c + c ** 2 + tf.sin(a)]
 
         res = tape.jacobian(res, [a, b, c])
@@ -389,7 +389,7 @@ class TestQNode:
             res = circuit(a, b)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == set()
+            assert circuit.qtape.trainable_params == []
 
         assert res.shape == (2,)
         assert isinstance(res, tf.Tensor)
@@ -412,7 +412,7 @@ class TestQNode:
             res = circuit(U, a)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {1}
+            assert circuit.qtape.trainable_params == [1]
 
         assert np.allclose(res, -tf.cos(a), atol=tol, rtol=0)
 
@@ -447,7 +447,7 @@ class TestQNode:
         with tf.GradientTape() as tape:
             res = circuit(a, p)
 
-        assert circuit.qtape.trainable_params == {1, 2, 3, 4}
+        assert circuit.qtape.trainable_params == [1, 2, 3, 4]
         assert [i.name for i in circuit.qtape.operations] == ["RX", "Rot", "PhaseShift"]
         assert np.all(circuit.qtape.get_parameters() == [p[2], p[0], -p[2], p[1] + p[2]])
 
diff --git a/tests/interfaces/test_qnode_torch.py b/tests/interfaces/test_qnode_torch.py
index 55bab6a20..c4218aa16 100644
--- a/tests/interfaces/test_qnode_torch.py
+++ b/tests/interfaces/test_qnode_torch.py
@@ -84,7 +84,7 @@ class TestQNode:
 
         # without the interface, the tape is unable to deduce
         # trainable parameters
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
     def test_execution_with_interface(self, dev_name, diff_method):
         """Test execution works with the interface"""
@@ -110,7 +110,7 @@ class TestQNode:
         assert res.shape == tuple()
 
         # the tape is able to deduce trainable parameters
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         # gradients should work
         res.backward()
@@ -196,7 +196,7 @@ class TestQNode:
 
         res = circuit(a, b)
 
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         assert isinstance(res, torch.Tensor)
         assert res.shape == (2,)
@@ -242,7 +242,7 @@ class TestQNode:
         res = circuit(a, b)
 
         assert circuit.qtape.interface == "torch"
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         assert isinstance(res, torch.Tensor)
         assert res.shape == (2,)
@@ -301,7 +301,7 @@ class TestQNode:
         res = circuit(a, b)
 
         # the tape has reported both gate arguments as trainable
-        assert circuit.qtape.trainable_params == {0, 1}
+        assert circuit.qtape.trainable_params == [0, 1]
 
         expected = [np.cos(a_val), -np.cos(a_val) * np.sin(b_val)]
         assert np.allclose(res.detach().numpy(), expected, atol=tol, rtol=0)
@@ -330,7 +330,7 @@ class TestQNode:
         res = circuit(a, b)
 
         # the tape has reported only the first argument as trainable
-        assert circuit.qtape.trainable_params == {0}
+        assert circuit.qtape.trainable_params == [0]
 
         expected = [np.cos(a_val), -np.cos(a_val) * np.sin(b_val)]
         assert np.allclose(res.detach().numpy(), expected, atol=tol, rtol=0)
@@ -362,7 +362,7 @@ class TestQNode:
         res = circuit(a, b, c)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {0, 2}
+            assert circuit.qtape.trainable_params == [0, 2]
             assert circuit.qtape.get_parameters() == [a * c, c + c ** 2 + torch.sin(a)]
 
         res.backward()
@@ -388,7 +388,7 @@ class TestQNode:
         res = circuit(a, b)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == set()
+            assert circuit.qtape.trainable_params == []
 
         assert res.shape == (2,)
         assert isinstance(res, torch.Tensor)
@@ -423,7 +423,7 @@ class TestQNode:
         res = circuit(U, a)
 
         if diff_method == "finite-diff":
-            assert circuit.qtape.trainable_params == {1}
+            assert circuit.qtape.trainable_params == [1]
 
         assert np.allclose(res.detach(), -np.cos(a_val), atol=tol, rtol=0)
 
@@ -458,7 +458,7 @@ class TestQNode:
 
         res = circuit(a, p)
 
-        assert circuit.qtape.trainable_params == {1, 2, 3, 4}
+        assert circuit.qtape.trainable_params == [1, 2, 3, 4]
         assert [i.name for i in circuit.qtape.operations] == ["RX", "Rot", "PhaseShift"]
         assert np.all(circuit.qtape.get_parameters() == [p[2], p[0], -p[2], p[1] + p[2]])
 
diff --git a/tests/interfaces/test_tape_autograd.py b/tests/interfaces/test_tape_autograd.py
index 0f6a29f71..6d850e55e 100644
--- a/tests/interfaces/test_tape_autograd.py
+++ b/tests/interfaces/test_tape_autograd.py
@@ -46,7 +46,7 @@ class TestAutogradQuantumTape:
             qml.CNOT(wires=[0, 1])
             qml.expval(qml.PauliX(0))
 
-        assert tape.trainable_params == {0, 2}
+        assert tape.trainable_params == [0, 2]
         assert np.all(tape.get_parameters(trainable_only=True) == [a, c])
         assert np.all(tape.get_parameters(trainable_only=False) == [a, b, c, d])
 
@@ -60,7 +60,7 @@ class TestAutogradQuantumTape:
                 qml.RY(a, wires=0)
                 qml.RX(b, wires=0)
                 qml.expval(qml.PauliZ(0))
-            assert tape.trainable_params == {0}
+            assert tape.trainable_params == [0]
             return tape.execute(device)
 
         dev = qml.device("default.qubit", wires=1)
@@ -75,7 +75,7 @@ class TestAutogradQuantumTape:
             with AutogradInterface.apply(JacobianTape()) as tape:
                 qml.RY(a, wires=0)
                 qml.expval(qml.PauliZ(0))
-            assert tape.trainable_params == {0}
+            assert tape.trainable_params == [0]
             return tape.execute(device)
 
         dev = qml.device("default.qubit", wires=2)
@@ -87,7 +87,7 @@ class TestAutogradQuantumTape:
             qml.RY(a, wires=0)
             qml.expval(qml.PauliZ(0))
 
-        tape.trainable_params = {0}
+        tape.trainable_params = [0]
         expected = tape.jacobian(dev)
         assert expected.shape == (1, 1)
         assert np.allclose(res, np.squeeze(expected), atol=tol, rtol=0)
@@ -104,7 +104,7 @@ class TestAutogradQuantumTape:
                 qml.CNOT(wires=[0, 1])
                 qml.expval(qml.PauliZ(0))
                 qml.expval(qml.PauliY(1))
-            assert tape.trainable_params == {0, 1}
+            assert tape.trainable_params == [0, 1]
             return tape.execute(device)
 
         dev = qml.device("default.qubit", wires=2)
@@ -156,7 +156,7 @@ class TestAutogradQuantumTape:
             qml.expval(qml.PauliZ(0))
             qml.expval(qml.PauliY(1))
 
-        assert tape.trainable_params == {0, 1}
+        assert tape.trainable_params == [0, 1]
 
         def cost(a, b):
             tape.set_parameters([a, b])
@@ -192,7 +192,7 @@ class TestAutogradQuantumTape:
                 qml.RZ(b, wires=0)
                 qml.RX(c + c ** 2 + np.sin(a), wires=0)
                 qml.expval(qml.PauliZ(0))
-            assert tape.trainable_params == {0, 2}
+            assert tape.trainable_params == [0, 2]
             return tape.execute(device)
 
         dev = qml.device("default.qubit", wires=2)
@@ -211,7 +211,7 @@ class TestAutogradQuantumTape:
                 qml.CNOT(wires=[0, 1])
                 qml.expval(qml.PauliZ(0))
                 qml.expval(qml.PauliZ(1))
-            assert tape.trainable_params == set()
+            assert tape.trainable_params == []
             return tape.execute(device)
 
         dev = qml.device("default.qubit", wires=2)
@@ -240,7 +240,7 @@ class TestAutogradQuantumTape:
                 qml.QubitUnitary(U, wires=0)
                 qml.RY(a, wires=0)
                 qml.expval(qml.PauliZ(0))
-            assert tape.trainable_params == {1}
+            assert tape.trainable_params == [1]
             return tape.execute(device)
 
         dev = qml.device("default.qubit", wires=2)
@@ -276,7 +276,7 @@ class TestAutogradQuantumTape:
 
             tape = AutogradInterface.apply(tape.expand())
 
-            assert tape.trainable_params == {1, 2, 3, 4}
+            assert tape.trainable_params == [1, 2, 3, 4]
             assert [i.name for i in tape.operations] == ["RX", "Rot", "PhaseShift"]
             assert np.all(np.array(tape.get_parameters()) == [p[2], p[0], -p[2], p[1] + p[2]])
 
diff --git a/tests/interfaces/test_tape_tf.py b/tests/interfaces/test_tape_tf.py
index 12799f9c2..91bcd8279 100644
--- a/tests/interfaces/test_tape_tf.py
+++ b/tests/interfaces/test_tape_tf.py
@@ -69,7 +69,7 @@ class TestTFQuantumTape:
                 qml.CNOT(wires=[0, 1])
                 qml.expval(qml.PauliX(0))
 
-        assert qtape.trainable_params == {0, 2}
+        assert qtape.trainable_params == [0, 2]
         assert np.all(qtape.get_parameters() == [a, c])
 
     def test_execution(self):
@@ -83,7 +83,7 @@ class TestTFQuantumTape:
                 qml.RX(0.2, wires=0)
                 qml.expval(qml.PauliZ(0))
 
-            assert qtape.trainable_params == {0}
+            assert qtape.trainable_params == [0]
             res = qtape.execute(dev)
 
         assert isinstance(res, tf.Tensor)
@@ -105,7 +105,7 @@ class TestTFQuantumTape:
                 qml.expval(qml.PauliZ(0))
                 qml.expval(qml.PauliY(1))
 
-            assert qtape.trainable_params == {0, 1}
+            assert qtape.trainable_params == [0, 1]
             res = qtape.execute(dev)
 
         assert isinstance(res, tf.Tensor)
@@ -136,7 +136,7 @@ class TestTFQuantumTape:
                 qml.expval(qml.PauliZ(0))
                 qml.expval(qml.PauliY(1))
 
-            assert qtape.trainable_params == {0, 1}
+            assert qtape.trainable_params == [0, 1]
             res = qtape.execute(dev)
 
         assert isinstance(res, tf.Tensor)
@@ -186,7 +186,7 @@ class TestTFQuantumTape:
                 qml.expval(qml.PauliZ(0))
                 qml.expval(qml.PauliY(1))
 
-            assert qtape.trainable_params == {0, 1}
+            assert qtape.trainable_params == [0, 1]
 
             res = qtape.execute(dev)
 
@@ -226,7 +226,7 @@ class TestTFQuantumTape:
         with tf.GradientTape() as tape:
             qtape.set_parameters([a, b], trainable_only=False)
             qtape._update_trainable_params()
-            assert qtape.trainable_params == {0, 1}
+            assert qtape.trainable_params == [0, 1]
             res = qtape.execute(dev)
 
         jac = tape.jacobian(res, [a, b])
@@ -262,7 +262,7 @@ class TestTFQuantumTape:
                 qml.RX(c + c ** 2 + tf.sin(a), wires=0)
                 qml.expval(qml.PauliZ(0))
 
-            assert qtape.trainable_params == {0, 2}
+            assert qtape.trainable_params == [0, 2]
             assert qtape.get_parameters() == [a * c, c + c ** 2 + tf.sin(a)]
             res = qtape.execute(dev)
 
@@ -284,7 +284,7 @@ class TestTFQuantumTape:
                 qml.expval(qml.PauliZ(0))
                 qml.expval(qml.PauliZ(1))
 
-            assert qtape.trainable_params == set()
+            assert qtape.trainable_params == []
 
             res = qtape.execute(dev)
 
@@ -306,7 +306,7 @@ class TestTFQuantumTape:
                 qml.RY(a, wires=0)
                 qml.expval(qml.PauliZ(0))
 
-            assert qtape.trainable_params == {1}
+            assert qtape.trainable_params == [1]
             res = qtape.execute(dev)
 
         assert np.allclose(res, -tf.cos(a), atol=tol, rtol=0)
@@ -344,7 +344,7 @@ class TestTFQuantumTape:
 
             qtape = TFInterface.apply(qtape.expand())
 
-            assert qtape.trainable_params == {1, 2, 3, 4}
+            assert qtape.trainable_params == [1, 2, 3, 4]
             assert [i.name for i in qtape.operations] == ["RX", "Rot", "PhaseShift"]
             assert np.all(qtape.get_parameters() == [p[2], p[0], -p[2], p[1] + p[2]])
 
diff --git a/tests/interfaces/test_tape_torch.py b/tests/interfaces/test_tape_torch.py
index d418734f2..4d9309b1d 100644
--- a/tests/interfaces/test_tape_torch.py
+++ b/tests/interfaces/test_tape_torch.py
@@ -66,7 +66,7 @@ class TestTorchQuantumTape:
             qml.CNOT(wires=[0, 1])
             qml.expval(qml.PauliX(0))
 
-        assert tape.trainable_params == {0, 2}
+        assert tape.trainable_params == [0, 2]
         assert np.all(tape.get_parameters() == [a, c])
 
     def test_execution(self):
@@ -79,7 +79,7 @@ class TestTorchQuantumTape:
             qml.RX(torch.tensor(0.2), wires=0)
             qml.expval(qml.PauliZ(0))
 
-        assert tape.trainable_params == {0}
+        assert tape.trainable_params == [0]
         res = tape.execute(dev)
 
         assert isinstance(res, torch.Tensor)
@@ -105,7 +105,7 @@ class TestTorchQuantumTape:
             qml.expval(qml.PauliZ(0))
             qml.expval(qml.PauliY(1))
 
-        assert tape.trainable_params == {1, 2}
+        assert tape.trainable_params == [1, 2]
         res = tape.execute(dev)
 
         assert isinstance(res, torch.Tensor)
@@ -161,7 +161,7 @@ class TestTorchQuantumTape:
             qml.expval(qml.PauliZ(0))
             qml.expval(qml.PauliY(1))
 
-        assert tape.trainable_params == {0, 1}
+        assert tape.trainable_params == [0, 1]
         res = tape.execute(dev)
 
         assert isinstance(res, torch.Tensor)
@@ -187,7 +187,7 @@ class TestTorchQuantumTape:
             qml.expval(qml.PauliZ(0))
             qml.expval(qml.PauliY(1))
 
-        assert tape.trainable_params == {0, 1}
+        assert tape.trainable_params == [0, 1]
 
         loss = torch.sum(tape.execute(dev))
         loss.backward()
@@ -225,7 +225,7 @@ class TestTorchQuantumTape:
             qml.RX(params[1] + params[1] ** 2 + torch.sin(params[0]), wires=0)
             qml.expval(qml.PauliZ(0))
 
-        assert tape.trainable_params == {0, 2}
+        assert tape.trainable_params == [0, 2]
 
         tape_params = [i.detach().numpy() for i in tape.get_parameters()]
         assert np.allclose(
@@ -252,7 +252,7 @@ class TestTorchQuantumTape:
             qml.expval(qml.PauliZ(0))
             qml.expval(qml.PauliZ(1))
 
-        assert tape.trainable_params == set()
+        assert tape.trainable_params == []
 
         res = tape.execute(dev)
 
@@ -279,7 +279,7 @@ class TestTorchQuantumTape:
             qml.RY(a, wires=0)
             qml.expval(qml.PauliZ(0))
 
-        assert tape.trainable_params == {1}
+        assert tape.trainable_params == [1]
         res = tape.execute(dev)
 
         assert np.allclose(res.detach().numpy(), -np.cos(a_val), atol=tol, rtol=0)
@@ -316,7 +316,7 @@ class TestTorchQuantumTape:
 
         tape = TorchInterface.apply(tape.expand())
 
-        assert tape.trainable_params == {1, 2, 3, 4}
+        assert tape.trainable_params == [1, 2, 3, 4]
         assert [i.name for i in tape.operations] == ["RX", "Rot", "PhaseShift"]
 
         tape_params = [i.detach().numpy() for i in tape.get_parameters()]
diff --git a/tests/tape/test_jacobian_tape.py b/tests/tape/test_jacobian_tape.py
index 983b96de3..6b0c8c0b2 100644
--- a/tests/tape/test_jacobian_tape.py
+++ b/tests/tape/test_jacobian_tape.py
@@ -45,7 +45,7 @@ class TestConstruction:
         """Test that parameter information is correctly extracted"""
         tape, ops, obs = make_tape
         tape._update_gradient_info()
-        assert tape._trainable_params == set(range(5))
+        assert tape._trainable_params == list(range(5))
         assert tape._par_info == {
             0: {"op": ops[0], "p_idx": 0, "grad_method": "F"},
             1: {"op": ops[1], "p_idx": 0, "grad_method": "F"},
@@ -148,7 +148,7 @@ class TestJacobian:
             tape.jacobian(None)
 
         # setting trainable parameters avoids this
-        tape.trainable_params = {1, 2}
+        tape.trainable_params = [1, 2]
         dev = qml.device("default.qubit", wires=2)
         res = tape.jacobian(dev)
         assert res.shape == (4, 2)
@@ -303,7 +303,7 @@ class TestJacobian:
             qml.expval(qml.PauliZ(0))
 
         dev = qml.device("default.qubit", wires=2)
-        tape.trainable_params = {}
+        tape.trainable_params = []
 
         res = tape.jacobian(dev)
         assert res.size == 0
@@ -656,7 +656,7 @@ class TestJacobianCVIntegration:
             qml.Displacement(a, 0, wires=0)
             qml.var(qml.NumberOperator(0))
 
-        tape.trainable_params = {0, 1}
+        tape.trainable_params = [0, 1]
         res = tape.jacobian(dev)
         assert res.shape == (1, 2)
 
@@ -676,7 +676,7 @@ class TestJacobianCVIntegration:
             qml.expval(qml.NumberOperator(1))
             qml.var(qml.NumberOperator(0))
 
-        tape.trainable_params = {0, 1}
+        tape.trainable_params = [0, 1]
         res = tape.jacobian(dev)
         assert res.shape == (2, 2)
 
@@ -693,7 +693,7 @@ class TestJacobianCVIntegration:
             qml.Displacement(a, 0, wires=0)
             qml.expval(qml.QuadOperator(phi, wires=0))
 
-        tape.trainable_params = {2}
+        tape.trainable_params = [2]
         res = tape.jacobian(dev)
         expected = np.array([[-2 * a * np.sin(phi)]])
         assert np.allclose(res, expected, atol=tol, rtol=0)
diff --git a/tests/tape/test_tape.py b/tests/tape/test_tape.py
index 2bfe534fd..e51191164 100644
--- a/tests/tape/test_tape.py
+++ b/tests/tape/test_tape.py
@@ -467,13 +467,13 @@ class TestParameters:
         """Test that parameters are correctly counted and processed"""
         tape, params = make_tape
         assert tape.num_params == len(params)
-        assert tape.trainable_params == set(range(len(params)))
+        assert tape.trainable_params == list(range(len(params)))
         assert tape.get_parameters() == params
 
     def test_set_trainable_params(self, make_tape):
         """Test that setting trainable parameters works as expected"""
         tape, params = make_tape
-        trainable = {0, 2, 3}
+        trainable = [0, 2, 3]
         tape.trainable_params = trainable
         assert tape._trainable_params == trainable
         assert tape.num_params == 3
@@ -482,16 +482,23 @@ class TestParameters:
         # add additional trainable parameters
         trainable = {1, 2, 3, 4}
         tape.trainable_params = trainable
-        assert tape._trainable_params == trainable
+        assert tape._trainable_params == [1, 2, 3, 4]
         assert tape.num_params == 4
         assert tape.get_parameters() == [params[i] for i in tape.trainable_params]
 
+        # set trainable_params in wrong order
+        trainable = {3, 4, 1}
+        tape.trainable_params = trainable
+        assert tape._trainable_params == [1, 3, 4]
+        assert tape.num_params == 3
+        assert tape.get_parameters() == [params[i] for i in tape.trainable_params]
+
     def test_changing_params(self, make_tape):
         """Test that changing trainable parameters works as expected"""
         tape, params = make_tape
-        trainable = {0, 2, 3}
+        trainable = (0, 2, 3)
         tape.trainable_params = trainable
-        assert tape._trainable_params == trainable
+        assert tape._trainable_params == list(trainable)
         assert tape.num_params == 3
         assert tape.get_parameters() == [params[i] for i in tape.trainable_params]
         assert tape.get_parameters(trainable_only=False) == params
@@ -501,11 +508,11 @@ class TestParameters:
         are set as trainable"""
         tape, _ = make_tape
 
-        with pytest.raises(ValueError, match="must be positive integers"):
-            tape.trainable_params = {-1, 0}
+        with pytest.raises(ValueError, match="must be non-negative integers"):
+            tape.trainable_params = [-1, 0]
 
-        with pytest.raises(ValueError, match="must be positive integers"):
-            tape.trainable_params = {0.5}
+        with pytest.raises(ValueError, match="must be non-negative integers"):
+            tape.trainable_params = (0.5,)
 
         with pytest.raises(ValueError, match="has at most 5 parameters"):
             tape.trainable_params = {0, 7}
@@ -535,7 +542,7 @@ class TestParameters:
         tape, params = make_tape
         new_params = [-0.654, 0.3]
 
-        tape.trainable_params = {1, 3}
+        tape.trainable_params = [1, 3]
         tape.set_parameters(new_params)
 
         count = 0
@@ -560,29 +567,24 @@ class TestParameters:
         tape, params = make_tape
         new_params = [-0.654, 0.3]
 
-        with monkeypatch.context() as m:
-            m.setattr(tape, "_trainable_params", {3, 1})
-            tape.set_parameters(new_params)
-
-            assert tape.get_parameters(trainable_only=True) == [
-                new_params[0],
-                new_params[1],
-            ]
-
-            assert tape.get_parameters(trainable_only=False) == [
-                params[0],
-                new_params[0],
-                params[2],
-                new_params[1],
-                params[4],
-            ]
+        tape.trainable_params = [3, 1]
+        tape.set_parameters(new_params)
+
+        assert tape.get_parameters(trainable_only=True) == new_params
+        assert tape.get_parameters(trainable_only=False) == [
+            params[0],
+            new_params[0],
+            params[2],
+            new_params[1],
+            params[4],
+        ]
 
     def test_setting_all_parameters(self, make_tape):
         """Test that all parameters are correctly modified after construction"""
         tape, params = make_tape
         new_params = [0.6543, -0.654, 0, 0.3, 0.6]
 
-        tape.trainable_params = {1, 3}
+        tape.trainable_params = [1, 3]
         tape.set_parameters(new_params, trainable_only=False)
 
         for pinfo, pval in zip(tape._par_info.values(), new_params):
@@ -599,7 +601,7 @@ class TestParameters:
             tape.set_parameters([0.54])
 
         with pytest.raises(ValueError, match="Number of provided parameters does not match"):
-            tape.trainable_params = {2, 3}
+            tape.trainable_params = [2, 3]
             tape.set_parameters([0.54, 0.54, 0.123])
 
     def test_array_parameter(self):
@@ -678,16 +680,16 @@ class TestInverse:
             m1 = qml.probs(wires=0)
             m2 = qml.probs(wires="a")
 
-        tape.trainable_params = {1, 2}
+        tape.trainable_params = [1, 2]
         tape.inv()
 
         # check that operation order is reversed
-        assert tape.trainable_params == {1, 4}
+        assert tape.trainable_params == [1, 4]
         assert tape.get_parameters() == [p[1], p[0]]
 
         # undo the inverse
         tape.inv()
-        assert tape.trainable_params == {1, 2}
+        assert tape.trainable_params == [1, 2]
         assert tape.get_parameters() == [p[0], p[1]]
         assert [o.name for o in tape._ops] == ["RX", "Rot", "CNOT"]
 
@@ -704,7 +706,7 @@ class TestExpand:
 
         assert len(new_tape.operations) == 3
         assert new_tape.get_parameters() == [0.1, 0.2, 0.3]
-        assert new_tape.trainable_params == {0, 1, 2}
+        assert new_tape.trainable_params == [0, 1, 2]
 
         assert isinstance(new_tape.operations[0], qml.RZ)
         assert isinstance(new_tape.operations[1], qml.RY)
@@ -712,11 +714,11 @@ class TestExpand:
 
         # check that modifying the new tape does not affect the old tape
 
-        new_tape.trainable_params = {0}
+        new_tape.trainable_params = [0]
         new_tape.set_parameters([10])
 
         assert tape.get_parameters() == [0.1, 0.2, 0.3]
-        assert tape.trainable_params == {0, 1, 2}
+        assert tape.trainable_params == [0, 1, 2]
 
     def test_decomposition_removing_parameters(self):
         """Test that decompositions which reduce the number of parameters
@@ -1558,8 +1560,8 @@ class TestHashing:
             qml.CNOT(wires=[0, 1])
             qml.expval(qml.PauliZ(0) @ qml.PauliX(1))
 
-        tape1.trainable_params = {0}
-        tape2.trainable_params = {0, 1}
+        tape1.trainable_params = [0]
+        tape2.trainable_params = [0, 1]
         assert tape1.hash != tape2.hash
 
     def test_different_parameters(self):
diff --git a/tests/tape/test_unwrap.py b/tests/tape/test_unwrap.py
index 0424c3e02..37506f87f 100644
--- a/tests/tape/test_unwrap.py
+++ b/tests/tape/test_unwrap.py
@@ -39,7 +39,7 @@ def test_unwrap_tensorflow():
             params = tape.get_parameters(trainable_only=False)
             assert all(isinstance(i, (float, np.float32)) for i in params)
             assert np.allclose(params, [0.1, 0.2, 0.5, 0.3])
-            assert tape.trainable_params == {0, 3}
+            assert tape.trainable_params == [0, 3]
 
     # outside the context, the original parameters have been restored.
     assert tape.get_parameters(trainable_only=False) == p
@@ -69,7 +69,7 @@ def test_unwrap_torch():
         params = tape.get_parameters(trainable_only=False)
         assert all(isinstance(i, float) for i in params)
         assert np.allclose(params, [0.1, 0.2, 0.5, 0.3])
-        assert tape.trainable_params == {0, 3}
+        assert tape.trainable_params == [0, 3]
 
     # outside the context, the original parameters have been restored.
     assert tape.get_parameters(trainable_only=False) == p
@@ -99,7 +99,7 @@ def test_unwrap_autograd():
         params = tape.get_parameters(trainable_only=False)
         assert all(isinstance(i, float) for i in params)
         assert np.allclose(params, [0.1, 0.2, 0.5, 0.3])
-        assert tape.trainable_params == {0, 3}
+        assert tape.trainable_params == [0, 3]
 
     # outside the context, the original parameters have been restored.
     assert tape.get_parameters(trainable_only=False) == p
@@ -129,7 +129,7 @@ def test_unwrap_autograd_backward():
             params = tape.get_parameters(trainable_only=False)
             assert all(isinstance(i, float) for i in params)
             assert np.allclose(params, [0.1, 0.2, 0.5, 0.3])
-            assert tape.trainable_params == {0, 2, 3}
+            assert tape.trainable_params == [0, 2, 3]
 
         # outside the context, the original parameters have been restored.
         params = tape.get_parameters(trainable_only=False)
@@ -169,7 +169,7 @@ def test_unwrap_jax():
 
         # During the forward pass, Device arrays are treated as
         # trainable, but no other types are.
-        assert tape.trainable_params == {0, 1, 3}
+        assert tape.trainable_params == [0, 1, 3]
 
     # outside the context, the original parameters have been restored.
     assert tape.get_parameters(trainable_only=False) == p
@@ -201,7 +201,7 @@ def test_unwrap_jax_backward():
             params = tape.get_parameters(trainable_only=False)
             assert all(isinstance(i, float) for i in params)
             assert np.allclose(params, [0.1, 0.2, 0.5, 0.3])
-            assert tape.trainable_params == {0, 2, 3}
+            assert tape.trainable_params == [0, 2, 3]
 
         # outside the context, the original parameters have been restored.
         params = tape.get_parameters(trainable_only=False)
@@ -241,11 +241,11 @@ def test_multiple_unwrap():
         # will be unwrapped to NumPy arrays
         params = tape1.get_parameters(trainable_only=False)
         assert all(isinstance(i, float) for i in params)
-        assert tape1.trainable_params == {0, 3}
+        assert tape1.trainable_params == [0, 3]
 
         params = tape2.get_parameters(trainable_only=False)
         assert all(isinstance(i, float) for i in params)
-        assert tape2.trainable_params == {1, 2}
+        assert tape2.trainable_params == [1, 2]
 
     # outside the context, the original parameters have been restored.
     assert tape1.get_parameters(trainable_only=False) == p
diff --git a/tests/transforms/test_tape_expand.py b/tests/transforms/test_tape_expand.py
index 465855890..514d59a9d 100644
--- a/tests/transforms/test_tape_expand.py
+++ b/tests/transforms/test_tape_expand.py
@@ -300,7 +300,7 @@ class TestExpandInvalidTrainable:
 
         params = tape.get_parameters(trainable_only=False)
         tape.trainable_params = qml.math.get_trainable_indices(params)
-        assert tape.trainable_params == {1}
+        assert tape.trainable_params == [1]
 
         spy = mocker.spy(tape, "expand")
         new_tape = qml.transforms.expand_invalid_trainable(tape)