Repository: PennyLaneAI/pennylane
Pull request #1243 (2021-04-21 07:12:24)

Fix inversion of templates
**Context:**

In v.0.15 of PennyLane, two conflicting changes were made:

* The templates were re-written as operations with `expand` methods
* The `qml.adjoint()` transform was introduced, which recursively inverts the `decomposition` method

As a result, `qml.adjoint()` no longer works with templates.

In addition, the template `expand()` methods did not take into account the `self.inverse` flag, which is used by the deprecated `qml.inv()` function. As a result, the `qml.inv()` function also stopped working with templates.

**Description of the Change:**

The two main changes are:

- `qml.adjoint()` now uses the `Operation.expand` method, rather than `Operation.decomposition`, for better compatibility and allowing `qml.adjoint()` to again remain working with templates.

- `qml.inv()` was modified to use `qml.adjoint()` internally, allowing it to continue to work.

Two additional bugs were fixed at the same time

- `tape.adjoint()` has been added, mirroring `Operation.adjoint()`, so that `qml.adjoint()` will now work on nested tape structures.

- `tape.inv()` now uses the `op.adjoint()` method to invert operations, rather than the deprecated `Operation.inverse` flag.

**Benefits:**

- `qml.inv()` and `qml.adjoint()` now work again in all cases.

- Tests have been added to test `qml.inv()` and `qml.adjoint()` on the templates, to ensure we catch this in future.

**Possible Drawbacks:**

Not a drawback per se, but the following functionality is due to be removed in v0.16:

- `qml.inv()`
- The `tape.inv()` method
- The `Operation.inverse` flag

This will help simplify the code base, all uses of these logical blocks can be deleted.

**Related GitHub Issues:** n/a


github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [.github/CHANGELOG.md](/PennyLaneAI/pennylane/blob/master/.github/CHANGELOG.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#1243](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (fb4bb48) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/cb4d2ccba474c05788f7b10c6ae46957ecafa4e9?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (cb4d2cc) will **decrease** coverage by `0.27%`.
> The diff coverage is `42.85%`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243/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/1243?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

```diff
@@            Coverage Diff             @@
##           master    #1243      +/-   ##
==========================================
- Coverage   98.13%   97.85%   -0.28%     
==========================================
  Files         145      145              
  Lines       10984    11065      +81     
==========================================
+ Hits        10779    10828      +49     
- Misses        205      237      +32     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243?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/1243/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RhcGUvdGFwZS5weQ==) | `95.68% <9.09%> (-2.83%)` | :arrow_down: |
| [pennylane/templates/embeddings/angle.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RlbXBsYXRlcy9lbWJlZGRpbmdzL2FuZ2xlLnB5) | `96.55% <50.00%> (-3.45%)` | :arrow_down: |
| [pennylane/templates/embeddings/displacement.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RlbXBsYXRlcy9lbWJlZGRpbmdzL2Rpc3BsYWNlbWVudC5weQ==) | `96.66% <50.00%> (-3.34%)` | :arrow_down: |
| [pennylane/templates/embeddings/iqp.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RlbXBsYXRlcy9lbWJlZGRpbmdzL2lxcC5weQ==) | `96.96% <50.00%> (-3.04%)` | :arrow_down: |
| [pennylane/templates/embeddings/qaoa.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RlbXBsYXRlcy9lbWJlZGRpbmdzL3Fhb2EucHk=) | `98.59% <50.00%> (-1.41%)` | :arrow_down: |
| [pennylane/templates/embeddings/squeezing.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RlbXBsYXRlcy9lbWJlZGRpbmdzL3NxdWVlemluZy5weQ==) | `96.66% <50.00%> (-3.34%)` | :arrow_down: |
| [pennylane/templates/layers/basic\_entangler.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RlbXBsYXRlcy9sYXllcnMvYmFzaWNfZW50YW5nbGVyLnB5) | `97.05% <50.00%> (-2.95%)` | :arrow_down: |
| [pennylane/templates/layers/cv\_neural\_net.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RlbXBsYXRlcy9sYXllcnMvY3ZfbmV1cmFsX25ldC5weQ==) | `97.50% <50.00%> (-2.50%)` | :arrow_down: |
| [...nnylane/templates/layers/particle\_conserving\_u1.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RlbXBsYXRlcy9sYXllcnMvcGFydGljbGVfY29uc2VydmluZ191MS5weQ==) | `98.03% <50.00%> (-1.97%)` | :arrow_down: |
| [...nnylane/templates/layers/particle\_conserving\_u2.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RlbXBsYXRlcy9sYXllcnMvcGFydGljbGVfY29uc2VydmluZ191Mi5weQ==) | `97.50% <50.00%> (-2.50%)` | :arrow_down: |
| ... and [17 more](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243?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/1243?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Last update [cb4d2cc...fb4bb48](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1243?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).

josh146: > Is there any way to check if a tape is the inverse of another?

Currently there is no way of doing this I believe 🤔 

It might also be safer just to manually test every template? Even though it is a lot of extra work... But yeah I don't really have a good solution for parametrizing it, especially since a lot of the templates have different signatures.
Thenerdstation: > > Is there any way to check if a tape is the inverse of another?
> 
> Currently there is no way of doing this I believe 🤔
> 
> It might also be safer just to manually test every template? Even though it is a lot of extra work... But yeah I don't really have a good solution for parametrizing it, especially since a lot of the templates have different signatures.

You can add template tests to [this parametrized test](https://github.com/PennyLaneAI/pennylane/blob/35ab137975927421e9db9e31304566bd2476576d/tests/ops/test_qubit_ops.py#L405). The lambda takes into account the different signatures.
josh146: > You can add template tests to this parametrized test. The lambda takes into account the different signatures.

Woah! Thanks for pointing this out @Thenerdstation 
josh146: @Thenerdstation @mariaschuld: I had an epiphany today! If we re-write `qml.inv()` to simply use `qml.adjoint` internally, then:

- it reduces the complexity of the code
- it avoids maintainability issues; `qml.inv()` will likely continue to work as long as `qml.adjoint()` is working
- It ensures that they are both using the same logical pathway. That is, both will now simply end up using the `Operation.adjoint()` method, and all the extra `tape.inv()`'s that I placed in the templates to get `qml.inv()` working can be removed.
josh146: > Looks good to me. In the long run I wonder if there is a better option than testing every template? I guess when all the Operations follow the same design, adjoint() could be tested with only a few examples?

Yep, I think long-term (once templates are sorted out) this is something to be moved to the template tests. I.e., every template should have `class TestMyTemplateAdjoint`.

Patch:
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index f5d2031b2..b78d3e837 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -48,6 +48,10 @@
 
 <h3>Bug fixes</h3>
 
+* A bug which resulted in `qml.adjoint()` and `qml.inv()` failing to work with
+  templates has been fixed.
+  [(#1243)](https://github.com/PennyLaneAI/pennylane/pull/1243)
+
 <h3>Documentation</h3>
 
 * Updated the order of the parameters to the `GaussianState` operation to match
@@ -58,7 +62,7 @@
 
 This release contains contributions from (in alphabetical order):
 
-Thomas Bromley, Diego Guala, Anthony Hayes, Antal Száva
+Thomas Bromley, Diego Guala, Anthony Hayes, Josh Izaac, Antal Száva
 
 # Release 0.15.0 (current release)
 
diff --git a/pennylane/tape/tape.py b/pennylane/tape/tape.py
index 3c1585c00..a557370c5 100644
--- a/pennylane/tape/tape.py
+++ b/pennylane/tape/tape.py
@@ -648,11 +648,40 @@ class QuantumTape(AnnotatedQueue):
         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 op in self._ops:
-            op.inverse = not op.inverse
+        for idx, op in enumerate(self._ops):
+            try:
+                self._ops[idx] = op.adjoint()
+            except NotImplementedError:
+                op.inverse = not op.inverse
 
         self._ops = list(reversed(self._ops))
 
+    def adjoint(self):
+        """Create a tape that is the adjoint of this one.
+
+        Adjointed tapes are the conjugated and transposed version of the
+        original tapes. Adjointed ops are equivalent to the inverted operation for unitary
+        gates.
+
+        Returns:
+            ~.QuantumTape: the adjointed tape
+        """
+        new_tape = self.copy(copy_operations=True)
+        qml.transforms.invisible(new_tape.inv)()
+
+        # the current implementation of the adjoint
+        # transform requires that the returned inverted object
+        # is automatically queued.
+        QuantumTape._lock.acquire()
+        try:
+            QueuingContext.append(new_tape)
+        except Exception as _:
+            QuantumTape._lock.release()
+            raise
+        QuantumTape._lock.release()
+
+        return new_tape
+
     # ========================================================
     # Parameter handling
     # ========================================================
diff --git a/pennylane/templates/embeddings/amplitude.py b/pennylane/templates/embeddings/amplitude.py
index f85c18dae..4c750c8de 100644
--- a/pennylane/templates/embeddings/amplitude.py
+++ b/pennylane/templates/embeddings/amplitude.py
@@ -143,6 +143,11 @@ class AmplitudeEmbedding(Operation):
         features = self._preprocess(features, wires, pad_with, normalize)
         super().__init__(features, wires=wires, do_queue=do_queue)
 
+    def adjoint(self):  # pylint: disable=arguments-differ
+        return qml.adjoint(qml.templates.MottonenStatePreparation)(
+            self.parameters[0], wires=self.wires
+        )
+
     def expand(self):
 
         with qml.tape.QuantumTape() as tape:
diff --git a/pennylane/templates/layers/particle_conserving_u1.py b/pennylane/templates/layers/particle_conserving_u1.py
index 03008d6be..097f1bfd5 100644
--- a/pennylane/templates/layers/particle_conserving_u1.py
+++ b/pennylane/templates/layers/particle_conserving_u1.py
@@ -266,13 +266,14 @@ class ParticleConservingU1(Operation):
 
         with qml.tape.QuantumTape() as tape:
 
-            qml.BasisState(self.init_state, wires=self.wires)
+            qml.templates.BasisEmbedding(self.init_state, wires=self.wires)
 
             for l in range(self.n_layers):
                 for i, wires_ in enumerate(nm_wires):
                     u1_ex_gate(
                         self.parameters[0][l, i, 0], self.parameters[0][l, i, 1], wires=wires_
                     )
+
         return tape
 
     @staticmethod
diff --git a/pennylane/templates/layers/particle_conserving_u2.py b/pennylane/templates/layers/particle_conserving_u2.py
index 940e0c320..e0ac00a8a 100644
--- a/pennylane/templates/layers/particle_conserving_u2.py
+++ b/pennylane/templates/layers/particle_conserving_u2.py
@@ -183,7 +183,7 @@ class ParticleConservingU2(Operation):
 
         with qml.tape.QuantumTape() as tape:
 
-            qml.BasisState(self.init_state, wires=self.wires)
+            qml.templates.BasisEmbedding(self.init_state, wires=self.wires)
 
             for l in range(self.n_layers):
 
diff --git a/pennylane/transforms/adjoint.py b/pennylane/transforms/adjoint.py
index ea8908c2b..fbdb6e578 100644
--- a/pennylane/transforms/adjoint.py
+++ b/pennylane/transforms/adjoint.py
@@ -18,34 +18,51 @@ from pennylane.tape import QuantumTape, get_active_tape
 
 
 def adjoint(fn):
-    """Create a function that applies the adjoint of the provided operation or template.
+    """Create a function that applies the adjoint (inverse) of the provided operation or template.
 
     This transform can be used to apply the adjoint of an arbitrary sequence of operations.
 
     Args:
-        fn (function): Any python function that applies pennylane operations.
+        fn (function): A quantum function that applies quantum operations.
 
     Returns:
         function: A new function that will apply the same operations but adjointed and in reverse order.
 
     **Example**
 
+    The adjoint transforms can be used within a QNode to apply the adjoint of
+    any quantum function. Consider the following quantum function, that applies two
+    operations:
+
     .. code-block:: python3
 
-        def my_ops():
-            qml.RX(0.123, wires=0)
-            qml.RY(0.456, wires=0)
+        def my_ops(a, b, wire):
+            qml.RX(a, wires=wire)
+            qml.RY(b, wires=wire)
+
+    We can create a QNode that applies this quantum function,
+    followed by the adjoint of this function:
+
+    .. code-block:: python3
+
+        dev = qml.device('default.qubit', wires=1)
+
+        @qml.qnode(dev)
+        def circuit(a, b):
+            my_ops(a, b, wire=0)
+            qml.adjoint(my_ops)(a, b, wire=0)
+            return qml.expval(qml.PauliZ(0))
+
+    Printing this out, we can see that the inverse quantum
+    function has indeed been applied:
 
-        with qml.tape.QuantumTape() as tape:
-            my_ops()
+    >>> print(qml.draw(circuit)(0.2, 0.5))
+     0: ──RX(0.2)──RY(0.5)──RY(-0.5)──RX(-0.2)──┤ ⟨Z⟩
 
-        with qml.tape.QuantumTape() as tape_adj:
-            qml.adjoint(my_ops)()
+    The adjoint function can also be applied directly to templates and operations:
 
-    >>> print(tape.operations)
-    [RX(0.123, wires=[0]), RY(0.456, wires=[0])]
-    >>> print(tape_adj.operatioins)
-    [RY(-0.456, wires=[0]), RX(-0.123, wires=[0])]
+    >>> qml.adjoint(qml.RX)(0.123, wires=0)
+    >>> qml.adjoint(qml.templates.StronglyEntanglingLayers)(weights, wires=[0, 1])
 
     .. UsageDetails::
 
@@ -102,9 +119,10 @@ def adjoint(fn):
             try:
                 op.adjoint()
             except NotImplementedError:
-                # Decompose the operation and adjoint the result.
+                # Expand the operation and adjoint the result.
                 # We do not do anything with the output since
-                # decomposition will automatically queue the new operations.
-                adjoint(op.decomposition)(wires=op.wires)
+                # calling adjoint on the expansion will automatically
+                # queue the new operations.
+                adjoint(op.expand)()
 
     return wrapper
diff --git a/pennylane/utils.py b/pennylane/utils.py
index a2b15e094..258b7f6bf 100644
--- a/pennylane/utils.py
+++ b/pennylane/utils.py
@@ -305,8 +305,9 @@ def inv(operation_list):
             "Please use inv on the function including its arguments, as in inv(template(args))."
         )
     elif isinstance(operation_list, qml.tape.QuantumTape):
-        operation_list.inv()
-        return operation_list
+        new_tape = operation_list.adjoint()
+        return new_tape
+
     elif not isinstance(operation_list, Iterable):
         raise ValueError("The provided operation_list is not iterable.")
 
@@ -334,13 +335,13 @@ def inv(operation_list):
             # exist on the queuing context
             pass
 
-    with qml.tape.QuantumTape() as tape:
+    def qfunc():
         for o in operation_list:
             o.queue()
-            if o.inverse:
-                o.inv()
 
-    tape.inv()
+    with qml.tape.QuantumTape() as tape:
+        qml.adjoint(qfunc)()
+
     return tape
 
 


Test patch:
diff --git a/tests/devices/test_default_qubit.py b/tests/devices/test_default_qubit.py
index 2f83dd7df..6db6fd818 100644
--- a/tests/devices/test_default_qubit.py
+++ b/tests/devices/test_default_qubit.py
@@ -2054,7 +2054,7 @@ class TestInverseDecomposition:
         test_s_inverse()
         operations = test_s_inverse.qtape.operations
         assert "S.inv" not in [i.name for i in operations]
-        assert "PhaseShift.inv" in [i.name for i in operations]
+        assert "PhaseShift" in [i.name for i in operations]
 
         expected = np.array([1.0, -1.0j]) / np.sqrt(2)
         assert np.allclose(dev.state, expected, atol=tol, rtol=0)
diff --git a/tests/tape/test_tape.py b/tests/tape/test_tape.py
index d39f4b87f..5b78ea4da 100644
--- a/tests/tape/test_tape.py
+++ b/tests/tape/test_tape.py
@@ -604,12 +604,10 @@ class TestInverse:
         tape.inv()
 
         # check that operation order is reversed
-        assert tape.operations == [prep] + ops[::-1]
+        assert [o.name for o in tape.operations] == ["BasisState", "CNOT", "Rot", "RX"]
 
         # check that operations are inverted
-        assert ops[0].inverse
-        assert not ops[1].inverse
-        assert ops[2].inverse
+        assert np.allclose(tape.operations[2].parameters, -np.array(p[-1:0:-1]))
 
         # check that parameter order has reversed
         assert tape.get_parameters() == [init_state, p[1], p[2], p[3], p[0]]
@@ -636,7 +634,7 @@ class TestInverse:
         tape.inv()
         assert tape.trainable_params == {1, 2}
         assert tape.get_parameters() == [p[0], p[1]]
-        assert tape._ops == ops
+        assert [o.name for o in tape._ops] == ["RX", "Rot", "CNOT"]
 
 
 class TestExpand:
diff --git a/tests/templates/test_layers/test_particle_conserving_u1.py b/tests/templates/test_layers/test_particle_conserving_u1.py
index a7d64976d..d65936b18 100644
--- a/tests/templates/test_layers/test_particle_conserving_u1.py
+++ b/tests/templates/test_layers/test_particle_conserving_u1.py
@@ -84,7 +84,7 @@ class TestDecomposition:
         assert gate_count == len(queue)
 
         # check initialization of the qubit register
-        assert isinstance(queue[0], qml.BasisState)
+        assert isinstance(queue[0], qml.templates.BasisEmbedding)
 
         # check all quantum operations
         idx_CRot = 8
diff --git a/tests/templates/test_layers/test_particle_conserving_u2.py b/tests/templates/test_layers/test_particle_conserving_u2.py
index f741956a5..3a05f8e2d 100644
--- a/tests/templates/test_layers/test_particle_conserving_u2.py
+++ b/tests/templates/test_layers/test_particle_conserving_u2.py
@@ -50,7 +50,7 @@ class TestDecomposition:
         assert len(queue) == n_gates
 
         # initialization
-        assert isinstance(queue[0], qml.BasisState)
+        assert isinstance(queue[0], qml.templates.BasisEmbedding)
 
         # order of gates
         for op1, op2 in zip(queue[1:], exp_gates):
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 4e48a4601..ab886069f 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -463,8 +463,8 @@ def inverted_dummy_template_operations(wires):
     ops = []
 
     for wire in reversed(wires):
-        ops.append(qml.RY(-1, wires=[wire]).inv())
-        ops.append(qml.RX(1, wires=[wire]).inv())
+        ops.append(qml.RY(1, wires=[wire]))
+        ops.append(qml.RX(-1, wires=[wire]))
 
     return ops
 
@@ -475,7 +475,7 @@ class TestInv:
     def test_inversion_without_context(self):
         """Test that a sequence of operations is properly inverted."""
         op_queue = [qml.PauliX(0), qml.PauliY(0).inv(), qml.PauliZ(0)]
-        inv_queue = [qml.PauliZ(0).inv(), qml.PauliY(0), qml.PauliX(0).inv()]
+        inv_queue = [qml.PauliZ(0), qml.PauliY(0), qml.PauliX(0)]
 
         inv_ops = pu.inv(op_queue).operations
 
@@ -527,9 +527,9 @@ class TestInv:
         inv_queue = [
             qml.Hadamard(wires=[0]),
             qml.CNOT(wires=[0, 1]),
-            qml.RZ(3, wires=[0]).inv(),
-            qml.RY(2, wires=[0]).inv(),
-            qml.RX(1, wires=[0]).inv(),
+            qml.RZ(-3, wires=[0]),
+            qml.RY(-2, wires=[0]),
+            qml.RX(-1, wires=[0]),
             qml.CNOT(wires=[0, 1]),
             qml.Hadamard(wires=[0]),
         ]
@@ -554,9 +554,9 @@ class TestInv:
         inv_queue = [
             qml.Hadamard(wires=[0]),
             qml.CNOT(wires=[0, 1]),
-            qml.RZ(3, wires=[0]).inv(),
-            qml.RY(2, wires=[0]).inv(),
-            qml.RX(1, wires=[0]).inv(),
+            qml.RZ(-3, wires=[0]),
+            qml.RY(-2, wires=[0]),
+            qml.RX(-1, wires=[0]),
             qml.CNOT(wires=[0, 1]),
             qml.Hadamard(wires=[0]),
         ]
@@ -582,10 +582,10 @@ class TestInv:
         inv_queue = [
             qml.Hadamard(wires=[0]),
             qml.CNOT(wires=[0, 1]),
-            qml.RY(2, wires=[0]).inv(),
-            qml.PauliZ(0).inv(),
-            qml.RX(1, wires=[0]).inv(),
-            qml.PauliX(0).inv(),
+            qml.RY(-2, wires=[0]),
+            qml.PauliZ(0),
+            qml.RX(-1, wires=[0]),
+            qml.PauliX(0),
             qml.CNOT(wires=[0, 1]),
             qml.Hadamard(wires=[0]),
         ]
@@ -612,10 +612,10 @@ class TestInv:
         inv_queue = [
             qml.Hadamard(wires=[0]),
             qml.CNOT(wires=[0, 1]),
-            qml.RY(2, wires=[0]).inv(),
-            qml.PauliZ(0).inv(),
-            qml.RX(1, wires=[0]).inv(),
-            qml.PauliX(0).inv(),
+            qml.RY(-2, wires=[0]),
+            qml.PauliZ(0),
+            qml.RX(-1, wires=[0]),
+            qml.PauliX(0),
             qml.CNOT(wires=[0, 1]),
             qml.Hadamard(wires=[0]),
         ]
@@ -669,9 +669,9 @@ class TestInv:
         inv_queue = [
             qml.Hadamard(wires=[0]),
             qml.CNOT(wires=[0, 1]),
-            qml.RZ(3, wires=[0]).inv(),
-            qml.RY(2, wires=[0]).inv(),
-            qml.RX(1, wires=[0]).inv(),
+            qml.RZ(-3, wires=[0]),
+            qml.RY(-2, wires=[0]),
+            qml.RX(-1, wires=[0]),
             qml.CNOT(wires=[0, 1]),
             qml.Hadamard(wires=[0]),
         ]
@@ -703,9 +703,9 @@ class TestInv:
         inv_queue = [
             qml.Hadamard(wires=[0]),
             qml.CNOT(wires=[0, 1]),
-            qml.RZ(3, wires=[0]).inv(),
-            qml.RY(2, wires=[0]).inv(),
-            qml.RX(1, wires=[0]).inv(),
+            qml.RZ(-3, wires=[0]),
+            qml.RY(-2, wires=[0]),
+            qml.RX(-1, wires=[0]),
             qml.CNOT(wires=[0, 1]),
             qml.Hadamard(wires=[0]),
         ]
@@ -737,10 +737,10 @@ class TestInv:
         inv_queue = [
             qml.Hadamard(wires=[0]),
             qml.CNOT(wires=[0, 1]),
-            qml.RY(2, wires=[0]).inv(),
-            qml.PauliZ(0).inv(),
-            qml.RX(1, wires=[0]).inv(),
-            qml.PauliX(0).inv(),
+            qml.RY(-2, wires=[0]),
+            qml.PauliZ(0),
+            qml.RX(-1, wires=[0]),
+            qml.PauliX(0),
             qml.CNOT(wires=[0, 1]),
             qml.Hadamard(wires=[0]),
         ]
@@ -783,8 +783,8 @@ class TestInv:
 
     def test_argument_wrapping(self):
         """Test that a single operation can be given to inv and is properly inverted."""
-        op = qml.PauliX(0)
-        exp_op = qml.PauliX(0).inv()
+        op = qml.RX(0.1, wires=0)
+        exp_op = qml.RX(-0.1, wires=0)
 
         inv_ops = pu.inv(op).operations
 
diff --git a/tests/transforms/test_adjoint.py b/tests/transforms/test_adjoint.py
index 00156dc83..a111df0ba 100644
--- a/tests/transforms/test_adjoint.py
+++ b/tests/transforms/test_adjoint.py
@@ -11,7 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+import pytest
 import numpy as np
 
 import pennylane as qml
@@ -85,3 +85,292 @@ def test_nested_adjoint_on_function():
     np.testing.assert_allclose(
         my_circuit(), np.array([-0.995707, 0.068644 + 6.209710e-02j]), atol=1e-6, rtol=1e-6
     )
+
+
+test_functions = [
+    lambda fn, *args, **kwargs: adjoint(fn)(*args, **kwargs),
+    lambda fn, *args, **kwargs: qml.inv(fn(*args, **kwargs)),
+]
+
+
+@pytest.mark.parametrize("fn", test_functions)
+class TestTemplateIntegration:
+    """Test that templates work correctly with the adjoint transform"""
+
+    def test_angle_embedding(self, fn):
+        """Test that the adjoint correctly inverts angle embedding"""
+        dev = qml.device("default.qubit", wires=3)
+        template = qml.templates.AngleEmbedding
+
+        @qml.qnode(dev)
+        def circuit(weights):
+            template(features=weights, wires=[0, 1, 2])
+            fn(template, features=weights, wires=[0, 1, 2])
+            return qml.state()
+
+        weights = np.array([0.2, 0.5, 0.8])
+        res = circuit(weights)
+        assert len(np.nonzero(res)) == 1
+
+    def test_amplitude_embedding(self, fn):
+        """Test that the adjoint correctly inverts amplitude embedding"""
+        dev = qml.device("default.qubit", wires=3)
+        template = qml.templates.AmplitudeEmbedding
+
+        @qml.qnode(dev)
+        def circuit(weights):
+            template(features=weights, wires=[0, 1, 2])
+            fn(template, features=weights, wires=[0, 1, 2])
+            return qml.state()
+
+        weights = np.array([0.2, 0.5, 0.8, 0.6, 0.1, 0.6, 0.1, 0.5]) / np.sqrt(1.92)
+        res = circuit(weights)
+        assert len(np.nonzero(res)) == 1
+
+    def test_basis_embedding(self, fn):
+        """Test that the adjoint correctly inverts basis embedding"""
+        dev = qml.device("default.qubit", wires=3)
+        template = qml.templates.BasisEmbedding
+
+        @qml.qnode(dev)
+        def circuit(weights):
+            template(features=weights, wires=[0, 1, 2])
+            fn(template, features=weights, wires=[0, 1, 2])
+            return qml.state()
+
+        weights = np.array([1, 0, 1])
+        res = circuit(weights)
+        expected = np.zeros([2 ** 3])
+        expected[0] = 1.0
+        assert np.allclose(res, expected)
+
+    def test_displacement_embedding(self, fn):
+        """Test that the adjoint correctly inverts displacement embedding"""
+        dev = qml.device("default.gaussian", wires=3)
+        template = qml.templates.DisplacementEmbedding
+
+        @qml.qnode(dev)
+        def circuit(weights):
+            template(features=weights, wires=[0, 1, 2])
+            fn(template, features=weights, wires=[0, 1, 2])
+            return qml.expval(qml.NumberOperator(0))
+
+        weights = np.array([0.6, 0.2, 0.1])
+        res = circuit(weights)
+        assert np.allclose(res, 0.0)
+
+    def test_squeezing_embedding(self, fn):
+        """Test that the adjoint correctly inverts squeezing embedding"""
+        dev = qml.device("default.gaussian", wires=3)
+        template = qml.templates.SqueezingEmbedding
+
+        @qml.qnode(dev)
+        def circuit(weights):
+            template(features=weights, wires=[0, 1, 2])
+            fn(template, features=weights, wires=[0, 1, 2])
+            return qml.expval(qml.NumberOperator(0))
+
+        weights = np.array([0.6, 0.2, 0.1])
+        res = circuit(weights)
+        assert np.allclose(res, 0.0)
+
+    def test_qaoa_embedding(self, fn):
+        """Test that the adjoint correctly inverts qaoa embedding"""
+        dev = qml.device("default.qubit", wires=3)
+        template = qml.templates.QAOAEmbedding
+
+        @qml.qnode(dev)
+        def circuit(features, weights):
+            template(features=features, weights=weights, wires=[0, 1, 2])
+            fn(template, features=features, weights=weights, wires=[0, 1, 2])
+            return qml.state()
+
+        features = np.array([1.0, 2.0, 3.0])
+        weights = np.random.random(template.shape(2, 3))
+
+        res = circuit(features, weights)
+        expected = np.zeros([2 ** 3])
+        expected[0] = 1.0
+
+        assert np.allclose(res, expected)
+
+    def test_iqp_embedding(self, fn):
+        """Test that the adjoint correctly inverts iqp embedding"""
+        dev = qml.device("default.qubit", wires=3)
+        template = qml.templates.IQPEmbedding
+
+        @qml.qnode(dev)
+        def circuit(features):
+            template(features=features, wires=[0, 1, 2])
+            fn(template, features=features, wires=[0, 1, 2])
+            return qml.state()
+
+        features = np.array([1.0, 2.0, 3.0])
+        res = circuit(features)
+        expected = np.zeros([2 ** 3])
+        expected[0] = 1.0
+
+        assert np.allclose(res, expected)
+
+    @pytest.mark.parametrize(
+        "template",
+        [
+            qml.templates.BasicEntanglerLayers,
+            qml.templates.StronglyEntanglingLayers,
+            qml.templates.RandomLayers,
+        ],
+    )
+    def test_layers(self, fn, template):
+        """Test that the adjoint correctly inverts layers"""
+        dev = qml.device("default.qubit", wires=3)
+
+        @qml.qnode(dev)
+        def circuit(weights):
+            template(weights=weights, wires=[0, 1, 2])
+            fn(template, weights=weights, wires=[0, 1, 2])
+            return qml.state()
+
+        weights = np.random.random(template.shape(2, 3))
+        res = circuit(weights)
+        expected = np.zeros([2 ** 3])
+        expected[0] = 1.0
+
+        assert np.allclose(res, expected)
+
+    @pytest.mark.parametrize(
+        "template",
+        [
+            qml.templates.ParticleConservingU1,
+            qml.templates.ParticleConservingU2,
+        ],
+    )
+    def test_particle_conserving(self, fn, template):
+        """Test that the adjoint correctly inverts particle conserving layers"""
+        dev = qml.device("default.qubit", wires=3)
+        init_state = np.array([0, 1, 1])
+
+        @qml.qnode(dev)
+        def circuit(weights):
+            template(weights=weights, init_state=init_state, wires=[0, 1, 2])
+            fn(template, weights=weights, init_state=init_state, wires=[0, 1, 2])
+            return qml.state()
+
+        weights = np.random.random(template.shape(2, 3))
+        res = circuit(weights)
+        expected = np.zeros([2 ** 3])
+        expected[0] = 1.0
+
+        assert np.allclose(res, expected)
+
+    def test_simplified_two_design(self, fn):
+        """Test that the adjoint correctly inverts the simplified two design"""
+        dev = qml.device("default.qubit", wires=3)
+        template = qml.templates.SimplifiedTwoDesign
+
+        @qml.qnode(dev)
+        def circuit(data, weights):
+            template(initial_layer_weights=data, weights=weights, wires=[0, 1, 2])
+            fn(template, initial_layer_weights=data, weights=weights, wires=[0, 1, 2])
+            return qml.state()
+
+        weights = [np.random.random(s) for s in template.shape(2, 3)]
+        res = circuit(weights[0], *weights[1:])
+        expected = np.zeros([2 ** 3])
+        expected[0] = 1.0
+
+        assert np.allclose(res, expected)
+
+    def test_approx_time_evolution(self, fn):
+        """Test that the adjoint correctly inverts the approx time evolution"""
+        dev = qml.device("default.qubit", wires=3)
+        template = qml.templates.ApproxTimeEvolution
+
+        coeffs = [1, 1]
+        obs = [qml.PauliX(0), qml.PauliX(1)]
+        H = qml.Hamiltonian(coeffs, obs)
+
+        @qml.qnode(dev)
+        def circuit(t):
+            template(H, t, 1)
+            fn(template, H, t, 1)
+            return qml.state()
+
+        res = circuit(0.5)
+        expected = np.zeros([2 ** 3])
+        expected[0] = 1.0
+        assert np.allclose(res, expected)
+
+    def test_arbitrary_unitary(self, fn):
+        """Test that the adjoint correctly inverts the arbitrary unitary"""
+        dev = qml.device("default.qubit", wires=3)
+        template = qml.templates.ArbitraryUnitary
+
+        @qml.qnode(dev)
+        def circuit(weights):
+            template(weights=weights, wires=[0, 1, 2])
+            fn(template, weights=weights, wires=[0, 1, 2])
+            return qml.state()
+
+        weights = np.random.random(template.shape(3))
+        res = circuit(weights)
+        expected = np.zeros([2 ** 3])
+        expected[0] = 1.0
+
+        assert np.allclose(res, expected)
+
+    def test_single_excitation(self, fn):
+        """Test that the adjoint correctly inverts the single excitation unitary"""
+        dev = qml.device("default.qubit", wires=3)
+        template = qml.templates.SingleExcitationUnitary
+
+        @qml.qnode(dev)
+        def circuit(weights):
+            template(weight=weights, wires=[0, 1, 2])
+            fn(template, weight=weights, wires=[0, 1, 2])
+            return qml.state()
+
+        res = circuit(0.6)
+        expected = np.zeros([2 ** 3])
+        expected[0] = 1.0
+
+        assert np.allclose(res, expected)
+
+    def test_double_excitation(self, fn):
+        """Test that the adjoint correctly inverts the double excitation unitary"""
+        dev = qml.device("default.qubit", wires=4)
+        template = qml.templates.DoubleExcitationUnitary
+
+        @qml.qnode(dev)
+        def circuit(weights):
+            template(weight=weights, wires1=[0, 1], wires2=[2, 3])
+            fn(template, weight=weights, wires1=[0, 1], wires2=[2, 3])
+            return qml.state()
+
+        res = circuit(0.6)
+        expected = np.zeros([2 ** 4])
+        expected[0] = 1.0
+
+        assert np.allclose(res, expected)
+
+    def test_interferometer(self, fn):
+        """Test that the adjoint correctly inverts squeezing embedding"""
+        dev = qml.device("default.gaussian", wires=3)
+        template = qml.templates.Interferometer
+        r = 1.5
+
+        @qml.qnode(dev)
+        def circuit(weights):
+            qml.Squeezing(r, 0, wires=0)
+            qml.Squeezing(r, 0, wires=1)
+            qml.Squeezing(r, 0, wires=2)
+            template(*weights, wires=[0, 1, 2])
+            fn(template, *weights, wires=[0, 1, 2])
+            return qml.expval(qml.NumberOperator(0))
+
+        weights = [
+            np.random.random([3 * (3 - 1) // 2]),
+            np.random.random([3 * (3 - 1) // 2]),
+            np.random.random([3]),
+        ]
+        res = circuit(weights)
+        assert np.allclose(res, np.sinh(r) ** 2)