Repository: PennyLaneAI/pennylane
Pull request #2248 (2022-02-28 17:21:22)

Add `qml.eigvals(op)` function
**Context:**

Currently, to get the eigvals of an operator, you must do `op.get_eigvals()`.

However, there are many downsides to this:

- `op.get_eigvals()` is temporary, and we want to limit users relying on operator methods!

- There is no built-in way to get the eigenvalues of multiple operators.

It would be nice if there was a _single function_ that covered this singular functionality, while insulating the `op.get_eigvals()` method from changes.

**Description of the Change:**

- Adds `qml.eigvals()`

- This function works on operations, qfuncs, tapes, and QNodes.

**Benefits:** As above.

**Possible Drawbacks:** It would be nice to support providing a wire order, but this is very non-trivial.

**Related GitHub Issues:** n/a



github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.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/2248?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#2248](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (b0161f9) into [matrix-function](https://codecov.io/gh/PennyLaneAI/pennylane/commit/80124c4e05d2b34d42f222e55adb100436e7c22d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (80124c4) will **decrease** coverage by `2.69%`.
> The diff coverage is `100.00%`.

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

```diff
@@                 Coverage Diff                 @@
##           matrix-function    #2248      +/-   ##
===================================================
- Coverage            99.27%   96.57%   -2.70%     
===================================================
  Files                  234      235       +1     
  Lines                18466    18489      +23     
===================================================
- Hits                 18332    17856     -476     
- Misses                 134      633     +499     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/operation.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wZXJhdGlvbi5weQ==) | `95.95% <100.00%> (ø)` | |
| [pennylane/ops/functions/\_\_init\_\_.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9mdW5jdGlvbnMvX19pbml0X18ucHk=) | `100.00% <100.00%> (ø)` | |
| [pennylane/ops/functions/eigvals.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9mdW5jdGlvbnMvZWlndmFscy5weQ==) | `100.00% <100.00%> (ø)` | |
| [pennylane/interfaces/batch/jax.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2ludGVyZmFjZXMvYmF0Y2gvamF4LnB5) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pennylane/interfaces/batch/jax\_jit.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2ludGVyZmFjZXMvYmF0Y2gvamF4X2ppdC5weQ==) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pennylane/interfaces/jax.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2ludGVyZmFjZXMvamF4LnB5) | `0.00% <0.00%> (-97.44%)` | :arrow_down: |
| [pennylane/devices/default\_qubit\_jax.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvZGVmYXVsdF9xdWJpdF9qYXgucHk=) | `0.00% <0.00%> (-96.63%)` | :arrow_down: |
| [pennylane/qnn/cost.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3Fubi9jb3N0LnB5) | `42.85% <0.00%> (-57.15%)` | :arrow_down: |
| [pennylane/transforms/classical\_jacobian.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RyYW5zZm9ybXMvY2xhc3NpY2FsX2phY29iaWFuLnB5) | `77.08% <0.00%> (-22.92%)` | :arrow_down: |
| [pennylane/math/utils.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL21hdGgvdXRpbHMucHk=) | `84.41% <0.00%> (-15.59%)` | :arrow_down: |
| ... and [16 more](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248/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/2248?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/2248?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Last update [80124c4...b0161f9](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2248?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: Thanks @mariaschuld have incorporated all changes!
mariaschuld: Just re-request once codecov is fixed or ignorable :)
josh146: Locally everything looks fine to me re: codecov 🤔 I think Codecov just never updated after the `jax` test completed
josh146: Can confirm:

```
----------- coverage: platform linux, python 3.8.5-final-0 -----------
Name                                  Stmts   Miss  Cover   Missing
-------------------------------------------------------------------
pennylane/ops/functions/eigvals.py       22      0   100%
-------------------------------------------------------------------
TOTAL                                    22      1   100%
```

Patch:
diff --git a/doc/introduction/operations.rst b/doc/introduction/operations.rst
index be02e84b7..41d8e7dbc 100644
--- a/doc/introduction/operations.rst
+++ b/doc/introduction/operations.rst
@@ -30,12 +30,56 @@ This quantum function uses the :class:`RZ <pennylane.RZ>`,
 :ref:`noisy channel <intro_ref_ops_channels>` as well as the
 :class:`PauliZ <pennylane.PauliZ>` :ref:`observable <intro_ref_ops_qobs>`.
 
-Note that PennyLane supports inverting quantum operations via the
-:meth:`Op(param, wires).inv() <.Operation.inv>` method. Additionally, PennyLane
-provides a function :func:`qml.inv <.pennylane.inv>` that can be used to invert sequences
-of operations and :doc:`templates`.
+Below is a list of all quantum operations and operation functions supported by PennyLane.
+
+Operator functions
+------------------
+
+Various functions and transforms are available for manipulating operators,
+and extracting information.
+
+.. autosummary::
+
+    ~pennylane.adjoint
+    ~pennylane.ctrl
+    ~pennylane.matrix
+    ~pennylane.eigvals
+
+
+All operator functions can be used on instantiated operators,
+
+>>> op = qml.RX(0.54, wires=0)
+>>> qml.matrix(op)
+[[0.9637709+0.j         0.       -0.26673144j]
+[0.       -0.26673144j 0.9637709+0.j        ]]
+
+Operator functions can also be used in a functional form:
+
+>>> x = torch.tensor(0.6, requires_grad=True)
+>>> matrix_fn = qml.matrix(qml.RX)
+>>> matrix_fn(x)
+tensor([[0.9553+0.0000j, 0.0000-0.2955j],
+      [0.0000-0.2955j, 0.9553+0.0000j]], grad_fn=<AddBackward0>)
+
+In its functional form, most are fully differentiable with respect to gate arguments:
+
+>>> loss = torch.real(torch.trace(matrix_fn(x, wires=0)))
+>>> loss.backward()
+>>> x.grad
+tensor(-0.5910)
+
+Some operator transform can also act on multiple operations, by passing
+quantum functions, qnodes or tapes:
+
+>>> def circuit(theta):
+...     qml.RX(theta, wires=1)
+...     qml.PauliZ(wires=0)
+>>> qml.matrix(circuit)(np.pi / 4)
+array([[ 0.92387953+0.j,  0.+0.j ,  0.-0.38268343j,  0.+0.j],
+[ 0.+0.j,  -0.92387953+0.j,  0.+0.j,  0. +0.38268343j],
+[ 0. -0.38268343j,  0.+0.j,  0.92387953+0.j,  0.+0.j],
+[ 0.+0.j,  0.+0.38268343j,  0.+0.j,  -0.92387953+0.j]])
 
-Below is a list of all quantum operations supported by PennyLane.
 
 .. _intro_ref_ops_qubit:
 
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 5fa0c300f..1d540fff0 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -4,6 +4,56 @@
 
 <h3>New features since last release</h3>
 
+* New functions and transforms of operators have been added. These include:
+
+  - `qml.matrix()` for computing the matrix representation of one or more unitary operators.
+    [(#2241)](https://github.com/PennyLaneAI/pennylane/pull/2241)
+    
+  - `qml.eigvals()` for computing the eigenvalues of one or more operators.
+    [(#2248)](https://github.com/PennyLaneAI/pennylane/pull/2248)
+
+  All operator transforms can be used on instantiated operators,
+
+  ```pycon
+  >>> op = qml.RX(0.54, wires=0)
+  >>> qml.matrix(op)
+  [[0.9637709+0.j         0.       -0.26673144j]
+  [0.       -0.26673144j 0.9637709+0.j        ]]
+  ```
+
+  Operator transforms can also be used in a functional form:
+
+  ```pycon
+  >>> x = torch.tensor(0.6, requires_grad=True)
+  >>> matrix_fn = qml.matrix(qml.RX)
+  >>> matrix_fn(x)
+  tensor([[0.9553+0.0000j, 0.0000-0.2955j],
+          [0.0000-0.2955j, 0.9553+0.0000j]], grad_fn=<AddBackward0>)
+  ```
+
+  In its functional form, it is fully differentiable with respect to gate arguments:
+
+  ```pycon
+  >>> loss = torch.real(torch.trace(matrix_fn(x, wires=0)))
+  >>> loss.backward()
+  >>> x.grad
+  tensor(-0.5910)
+  ```
+
+  Some operator transform can also act on multiple operations, by passing
+  quantum functions or tapes:
+
+  ```pycon
+  >>> def circuit(theta):
+  ...     qml.RX(theta, wires=1)
+  ...     qml.PauliZ(wires=0)
+  >>> qml.matrix(circuit)(np.pi / 4)
+  array([[ 0.92387953+0.j,  0.+0.j ,  0.-0.38268343j,  0.+0.j],
+  [ 0.+0.j,  -0.92387953+0.j,  0.+0.j,  0. +0.38268343j],
+  [ 0. -0.38268343j,  0.+0.j,  0.92387953+0.j,  0.+0.j],
+  [ 0.+0.j,  0.+0.38268343j,  0.+0.j,  -0.92387953+0.j]])
+  ```
+
 * Added the user-interface for mid-circuit measurements.
   [(#2236)](https://github.com/PennyLaneAI/pennylane/pull/2236)
 
@@ -194,6 +244,11 @@
   (that is, gradients of tapes or QNode).
   [#2212](https://github.com/PennyLaneAI/pennylane/pull/2212)
 
+* `qml.transforms.get_unitary_matrix()` has been deprecated and will be removed
+  in a future release. For extracting matrices of operations and quantum functions,
+  please use `qml.matrix()`.
+  [(#2248)](https://github.com/PennyLaneAI/pennylane/pull/2248)
+
 <h3>Bug fixes</h3>
 
 * The `qml.QubitUnitary` operation now supports jitting. 
diff --git a/pennylane/measurements.py b/pennylane/measurements.py
index 138812d86..421b092b1 100644
--- a/pennylane/measurements.py
+++ b/pennylane/measurements.py
@@ -128,7 +128,7 @@ class MeasurementProcess:
 
         .. warning::
             The ``eigvals`` property is deprecated and will be removed in
-            an upcoming release.
+            an upcoming release. Please use :class:`qml.eigvals <.pennylane.eigvals>` instead.
 
         If the measurement process has an associated observable,
         the eigenvalues will correspond to this observable. Otherwise,
@@ -148,7 +148,8 @@ class MeasurementProcess:
             array: eigvals representation
         """
         warnings.warn(
-            "The 'eigvals' property is deprecated and will be removed in an upcoming release.",
+            "The 'eigvals' property is deprecated and will be removed in an upcoming release. "
+            "Please use 'qml.eigvals' instead.",
             UserWarning,
         )
         return self.get_eigvals()
diff --git a/pennylane/operation.py b/pennylane/operation.py
index dea58d9be..13ad8c910 100644
--- a/pennylane/operation.py
+++ b/pennylane/operation.py
@@ -696,7 +696,7 @@ class Operator(abc.ABC):
         .. warning::
 
             The ``eigvals`` property is deprecated and will be removed in
-            an upcoming release.
+            an upcoming release. Please use :class:`qml.eigvals <.pennylane.eigvals>` instead.
 
         **Example:**
 
@@ -708,7 +708,8 @@ class Operator(abc.ABC):
             array: eigvals representation
         """
         warnings.warn(
-            "The 'eigvals' property is deprecated and will be removed in an upcoming release.",
+            "The 'eigvals' property is deprecated and will be removed in an upcoming release. "
+            "Please use 'qml.eigvals' instead.",
             UserWarning,
         )
         return self.get_eigvals()
@@ -744,7 +745,7 @@ class Operator(abc.ABC):
             # By default, compute the eigenvalues from the matrix representation.
             # This will raise a NotImplementedError if the matrix is undefined.
             try:
-                return np.linalg.eigvals(self.get_matrix())
+                return qml.math.linalg.eigvals(self.get_matrix())
             except MatrixUndefinedError as e:
                 raise EigvalsUndefinedError from e
 
diff --git a/pennylane/ops/functions/__init__.py b/pennylane/ops/functions/__init__.py
index 2f6c281c2..2d33b3f48 100644
--- a/pennylane/ops/functions/__init__.py
+++ b/pennylane/ops/functions/__init__.py
@@ -14,4 +14,5 @@
 """
 This module contains functions that act on operators and tapes.
 """
+from .eigvals import eigvals
 from .matrix import matrix
diff --git a/pennylane/ops/functions/eigvals.py b/pennylane/ops/functions/eigvals.py
new file mode 100644
index 000000000..96fd7b3ca
--- /dev/null
+++ b/pennylane/ops/functions/eigvals.py
@@ -0,0 +1,123 @@
+# Copyright 2018-2022 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+"""
+This module contains the qml.eigvals function.
+"""
+# pylint: disable=protected-access
+from functools import reduce
+import warnings
+
+import pennylane as qml
+
+
+@qml.op_transform
+def eigvals(op):
+    r"""The eigenvalues of one or more operations.
+
+    Args:
+        op (.Operator, pennylane.QNode, .QuantumTape, or Callable): An operator, quantum node, tape,
+            or function that applies quantum operations.
+
+    Returns:
+        tensor_like or function: If an operator is provided as input, the eigenvalues are returned directly.
+        If a QNode or quantum function is provided as input, a function which accepts the
+        same arguments as the QNode or quantum function is returned. When called, this function will
+        return the unitary matrix in the appropriate autodiff framework (Autograd, TensorFlow, PyTorch, JAX)
+        given its parameters.
+
+    **Example**
+
+    Given an operation, ``qml.eigvals`` returns the eigenvalues:
+
+    >>> op = qml.PauliZ(0) @ qml.PauliX(1) - 0.5 * qml.PauliY(1)
+    >>> qml.eigvals(op)
+    array([-1.11803399, -1.11803399,  1.11803399,  1.11803399])
+
+    It can also be used in a functional form:
+
+    >>> x = torch.tensor(0.6, requires_grad=True)
+    >>> eigval_fn = qml.eigvals(qml.RX)
+    >>> eigval_fn(x, wires=0)
+    tensor([0.9553+0.2955j, 0.9553-0.2955j], grad_fn=<LinalgEigBackward>)
+
+    In its functional form, it is fully differentiable with respect to gate arguments:
+
+    >>> loss = torch.real(torch.sum(eigval_fn(x, wires=0)))
+    >>> loss.backward()
+    >>> x.grad
+    tensor(-0.2955)
+
+    This operator transform can also be applied to QNodes, tapes, and quantum functions
+    that contain multiple operations; see Usage Details below for more details.
+
+    .. UsageDetails::
+
+        ``qml.eigvals`` can also be used with QNodes, tapes, or quantum functions that
+        contain multiple operations. However, in this situation, **eigenvalues may
+        be computed numerically**. This can lead to a large computational overhead
+        for a large number of wires.
+
+        Consider the following quantum function:
+
+        .. code-block:: python3
+
+            def circuit(theta):
+                qml.RX(theta, wires=1)
+                qml.PauliZ(wires=0)
+
+        We can use ``qml.eigvals`` to generate a new function that returns the eigenvalues
+        corresponding to the function ``circuit``:
+
+        >>> eigvals_fn = qml.eigvals(circuit)
+        >>> theta = np.pi / 4
+        >>> eigvals_fn(theta)
+        array([ 0.92387953+0.38268343j,  0.92387953-0.38268343j,
+               -0.92387953+0.38268343j, -0.92387953-0.38268343j])
+    """
+    if isinstance(op, qml.Hamiltonian):
+        warnings.warn(
+            "For Hamiltonians, the eigenvalues will be computed numerically. "
+            "This may be computationally intensive for a large number of wires.",
+            UserWarning,
+        )
+        return qml.math.linalg.eigvalsh(qml.matrix(op))
+
+    # TODO: make `get_eigvals` take a `wire_order` argument to mimic `get_matrix`
+    return op.get_eigvals()
+
+
+@eigvals.tape_transform
+def _eigvals(tape):
+    op_wires = [op.wires for op in tape.operations]
+    all_wires = qml.wires.Wires.all_wires(op_wires).tolist()
+    unique_wires = qml.wires.Wires.unique_wires(op_wires).tolist()
+
+    if len(all_wires) != len(unique_wires):
+        warnings.warn(
+            "For multiple operations, the eigenvalues will be computed numerically. "
+            "This may be computationally intensive for a large number of wires.",
+            UserWarning,
+        )
+        return qml.math.linalg.eigvals(qml.matrix(tape))
+
+    # TODO: take into account wire ordering, by reordering eigenvalues
+    # as per operator wires/wire ordering, and by inserting implicit identity
+    # matrices (eigenvalues [1, 1]) at missing locations.
+
+    ev = [eigvals(op) for op in tape.operations]
+
+    if len(ev) == 1:
+        return ev[0]
+
+    return reduce(qml.math.kron, ev)
diff --git a/pennylane/transforms/get_unitary_matrix.py b/pennylane/transforms/get_unitary_matrix.py
index 9cfb1bd39..5b6d72af4 100644
--- a/pennylane/transforms/get_unitary_matrix.py
+++ b/pennylane/transforms/get_unitary_matrix.py
@@ -15,6 +15,8 @@
 A transform to obtain the matrix representation of a quantum circuit.
 """
 from functools import wraps
+import warnings
+
 from pennylane.wires import Wires
 import pennylane as qml
 
@@ -22,6 +24,12 @@ import pennylane as qml
 def get_unitary_matrix(circuit, wire_order=None):
     r"""Construct the matrix representation of a quantum circuit.
 
+    .. warning::
+
+        ``get_unitary_matrix`` is deprecated, and will be removed in an
+        upcoming release. For extracting matrices of operations and quantum functions,
+        please use :func:`qml.matrix() <.pennylane.matrix>`.
+
     Args:
         circuit (pennylane.QNode, .QuantumTape, or Callable): A quantum node, tape,
             or function that applies quantum operations.
@@ -92,6 +100,12 @@ def get_unitary_matrix(circuit, wire_order=None):
     >>> qml.grad(cost)(theta)
     -0.14943813247359922
     """
+    warnings.warn(
+        "get_unitary_matrix is deprecated, and will be removed in an "
+        "upcoming release. For extracting matrices of operations and quantum functions, "
+        "please use qml.matrix().",
+        UserWarning,
+    )
 
     wires = wire_order
 


Test patch:
diff --git a/tests/ops/functions/test_eigvals.py b/tests/ops/functions/test_eigvals.py
new file mode 100644
index 000000000..77189efe6
--- /dev/null
+++ b/tests/ops/functions/test_eigvals.py
@@ -0,0 +1,412 @@
+# Copyright 2018-2022 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+"""
+Unit tests for the eigvals transform
+"""
+from functools import reduce
+
+import pytest
+
+import pennylane as qml
+from pennylane import numpy as np
+from pennylane.transforms.op_transforms import OperationTransformError
+
+from gate_data import I, X, Y, Z, H, S, CNOT, Roty as RY
+
+one_qubit_no_parameter = [
+    qml.PauliX,
+    qml.PauliY,
+    qml.PauliZ,
+    qml.Hadamard,
+    qml.S,
+    qml.T,
+    qml.SX,
+]
+
+one_qubit_one_parameter = [qml.RX, qml.RY, qml.RZ, qml.PhaseShift]
+
+
+class TestSingleOperation:
+    @pytest.mark.parametrize("op_class", one_qubit_no_parameter)
+    def test_non_parametric_instantiated(self, op_class):
+        """Verify that the eigenvalues of non-parametric one-qubit gates are correct
+        when provided as an instantiated operation"""
+        op = op_class(wires=0)
+        res = qml.eigvals(op)
+        expected = op.get_eigvals()
+        assert np.allclose(res, expected)
+
+    @pytest.mark.parametrize("op_class", one_qubit_no_parameter)
+    def test_non_parametric_gates_qfunc(self, op_class):
+        """Verify that the eigenvalues of non-parametric one qubit gates is correct
+        when provided as a qfunc"""
+        res = qml.eigvals(op_class)(wires=0)
+        expected = op_class(wires=0).get_eigvals()
+        assert np.allclose(res, expected)
+
+    @pytest.mark.parametrize("op_class", one_qubit_no_parameter)
+    def test_non_parametric_gates_qnode(self, op_class):
+        """Verify that the eigenvalues of non-parametric one qubit gates is correct
+        when provided as a QNode"""
+        dev = qml.device("default.qubit", wires=1)
+        qnode = qml.QNode(lambda: op_class(wires=0) and qml.probs(wires=0), dev)
+        res = qml.eigvals(qnode)()
+        expected = op_class(wires=0).get_eigvals()
+        assert np.allclose(res, expected)
+
+    @pytest.mark.parametrize("op_class", one_qubit_one_parameter)
+    def test_parametric_gates_instantiated(self, op_class):
+        """Verify that the eigenvalues of non-parametric one qubit gates is correct
+        when provided as an instantiated operation"""
+        op = op_class(0.54, wires=0)
+        res = qml.eigvals(op)
+        expected = op.get_eigvals()
+        assert np.allclose(res, expected)
+
+    @pytest.mark.parametrize("op_class", one_qubit_one_parameter)
+    def test_parametric_gates_qfunc(self, op_class):
+        """Verify that the eigenvalues of non-parametric one qubit gates is correct
+        when provided as a qfunc"""
+        res = qml.eigvals(op_class)(0.54, wires=0)
+        expected = op_class(0.54, wires=0).get_eigvals()
+        assert np.allclose(res, expected)
+
+    @pytest.mark.parametrize("op_class", one_qubit_one_parameter)
+    def test_parametric_gates_qnode(self, op_class):
+        """Verify that the eigenvalues of non-parametric one qubit gates is correct
+        when provided as a QNode"""
+        dev = qml.device("default.qubit", wires=1)
+        qnode = qml.QNode(lambda x: op_class(x, wires=0) and qml.probs(wires=0), dev)
+        res = qml.eigvals(qnode)(0.54)
+        expected = op_class(0.54, wires=0).get_eigvals()
+        assert np.allclose(res, expected)
+
+    @pytest.mark.parametrize("op_class", one_qubit_one_parameter)
+    def test_adjoint(self, op_class):
+        """Test that the adjoint is correctly taken into account"""
+        res = qml.eigvals(qml.adjoint(op_class))(0.54, wires=0)
+        expected = op_class(-0.54, wires=0).get_eigvals()
+        assert np.allclose(res, expected)
+
+    def test_ctrl(self):
+        """Test that the ctrl is correctly taken into account"""
+        res = qml.eigvals(qml.ctrl(qml.PauliX, 0))(wires=1)
+        expected = np.linalg.eigvals(qml.matrix(qml.CNOT(wires=[0, 1])))
+        assert np.allclose(res, expected)
+
+    def test_tensor_product(self):
+        """Test a tensor product"""
+        res = qml.eigvals(qml.PauliX(0) @ qml.Identity(1) @ qml.PauliZ(1))
+        expected = reduce(np.kron, [[1, -1], [1, 1], [1, -1]])
+        assert np.allclose(res, expected)
+
+    def test_hamiltonian(self):
+        """Test that the matrix of a Hamiltonian is correctly returned"""
+        H = qml.PauliZ(0) @ qml.PauliY(1) - 0.5 * qml.PauliX(1)
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            res = qml.eigvals(H)
+
+        expected = np.linalg.eigvalsh(reduce(np.kron, [Z, Y]) - 0.5 * reduce(np.kron, [I, X]))
+        assert np.allclose(res, expected)
+
+    @pytest.mark.xfail(
+        reason="This test will fail because Hamiltonians are not queued to tapes yet!"
+    )
+    def test_hamiltonian_qfunc(self):
+        """Test that the matrix of a Hamiltonian is correctly returned"""
+
+        def ansatz(x):
+            return qml.PauliZ(0) @ qml.PauliY(1) - x * qml.PauliX(1)
+
+        x = 0.5
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            res = qml.eigvals(ansatz)(x)
+
+        expected = np.linalg.eigvalsh(reduce(np.kron, [Z, Y]) - 0.5 * reduce(np.kron, [I, X]))
+        assert np.allclose(res, expected)
+
+
+class TestMultipleOperations:
+    def test_multiple_operations_tape_no_overlaps(self):
+        """Check the eigenvalues for a tape containing multiple gates
+        assuming no overlap of wires"""
+
+        with qml.tape.QuantumTape() as tape:
+            qml.PauliX(wires="a")
+            qml.S(wires="b")
+            qml.Hadamard(wires="c")
+
+        res = qml.eigvals(tape)
+        expected = np.linalg.eigvals(np.kron(X, np.kron(S, H)))
+
+        assert np.allclose(np.sort(res.real), np.sort(expected.real))
+        assert np.allclose(np.sort(res.imag), np.sort(expected.imag))
+
+    def test_multiple_operations_tape(self):
+        """Check the eigenvalues for a tape containing multiple gates"""
+
+        with qml.tape.QuantumTape() as tape:
+            qml.PauliX(wires="a")
+            qml.S(wires="b")
+            qml.Hadamard(wires="c")
+            qml.CNOT(wires=["b", "c"])
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            res = qml.eigvals(tape)
+
+        expected = np.linalg.eigvals(np.kron(I, CNOT) @ np.kron(X, np.kron(S, H)))
+        assert np.allclose(res, expected)
+
+    def test_multiple_operations_qfunc(self):
+        """Check the eigenvalues for a qfunc containing multiple gates"""
+
+        def testcircuit():
+            qml.PauliX(wires="a")
+            qml.S(wires="b")
+            qml.Hadamard(wires="c")
+            qml.CNOT(wires=["b", "c"])
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            res = qml.eigvals(testcircuit)()
+
+        expected = np.linalg.eigvals(np.kron(I, CNOT) @ np.kron(X, np.kron(S, H)))
+        assert np.allclose(res, expected)
+
+    def test_multiple_operations_qnode(self):
+        """Check the eigenvalues for a QNode containing multiple gates"""
+        dev = qml.device("default.qubit", wires=["a", "b", "c"])
+
+        @qml.qnode(dev)
+        def testcircuit():
+            qml.PauliX(wires="a")
+            qml.adjoint(qml.S)(wires="b")
+            qml.Hadamard(wires="c")
+            qml.CNOT(wires=["b", "c"])
+            return qml.expval(qml.PauliZ("a"))
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            res = qml.eigvals(testcircuit)()
+
+        expected = np.linalg.eigvals(np.kron(I, CNOT) @ np.kron(X, np.kron(np.linalg.inv(S), H)))
+        assert np.allclose(res, expected)
+
+
+class TestTemplates:
+    """These tests are useful as they test operators that might not have
+    matrix forms defined, requiring decomposition."""
+
+    def test_instantiated(self):
+        """Test an instantiated template"""
+        weights = np.array([[[0.1, 0.2, 0.3], [0.1, 0.2, 0.3]]])
+        op = qml.StronglyEntanglingLayers(weights, wires=[0, 1])
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            res = qml.eigvals(op)
+
+        with qml.tape.QuantumTape() as tape:
+            op.decomposition()
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            expected = qml.eigvals(tape)
+
+        assert np.allclose(res, expected)
+
+    def test_qfunc(self):
+        """Test a template used within a qfunc"""
+
+        def circuit(weights, x):
+            qml.StronglyEntanglingLayers(weights, wires=[0, 1])
+            qml.RX(x, wires=0)
+
+        weights = np.array([[[0.1, 0.2, 0.3], [0.1, 0.2, 0.3]]])
+        x = 0.54
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            res = qml.eigvals(circuit)(weights, x)
+
+        op = qml.StronglyEntanglingLayers(weights, wires=[0, 1])
+
+        with qml.tape.QuantumTape() as tape:
+            op.decomposition()
+            qml.RX(x, wires=0)
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            expected = qml.eigvals(tape)
+
+        assert np.allclose(res, expected)
+
+    def test_nested_instantiated(self):
+        """Test an operation that must be decomposed twice"""
+
+        class CustomOp(qml.operation.Operation):
+            num_params = 1
+            num_wires = 2
+
+            @staticmethod
+            def compute_decomposition(weights, wires):
+                return [qml.StronglyEntanglingLayers(weights, wires=wires)]
+
+        weights = np.array([[[0.1, 0.2, 0.3], [0.1, 0.2, 0.3]]])
+        op = CustomOp(weights, wires=[0, 1])
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            res = qml.eigvals(op)
+
+        op = qml.StronglyEntanglingLayers(weights, wires=[0, 1])
+        with qml.tape.QuantumTape() as tape:
+            op.decomposition()
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            expected = qml.eigvals(tape)
+
+        assert np.allclose(res, expected)
+
+    def test_nested_qfunc(self):
+        """Test an operation that must be decomposed twice"""
+
+        class CustomOp(qml.operation.Operation):
+            num_params = 1
+            num_wires = 2
+
+            @staticmethod
+            def compute_decomposition(weights, wires):
+                return [qml.StronglyEntanglingLayers(weights, wires=wires)]
+
+        def circuit(weights, x):
+            CustomOp(weights, wires=[0, 1])
+            qml.RX(x, wires=0)
+
+        weights = np.array([[[0.1, 0.2, 0.3], [0.1, 0.2, 0.3]]])
+        x = 0.54
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            res = qml.eigvals(circuit)(weights, x)
+
+        op = qml.StronglyEntanglingLayers(weights, wires=[0, 1])
+
+        with qml.tape.QuantumTape() as tape:
+            op.decomposition()
+            qml.RX(x, wires=0)
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            expected = qml.eigvals(tape)
+
+        assert np.allclose(res, expected)
+
+
+class TestDifferentiation:
+    """Differentiation tests"""
+
+    @pytest.mark.parametrize("v", np.linspace(0.2, 1.6, 8))
+    def test_jax(self, v):
+        """Test that differentiation works correctly when using JAX"""
+
+        jax = pytest.importorskip("jax")
+
+        def circuit(theta):
+            qml.RX(theta, wires=0)
+            qml.PauliZ(wires=0)
+            qml.CNOT(wires=[0, 1])
+
+        def loss(theta):
+            U = qml.eigvals(circuit)(theta)
+            return qml.math.sum(qml.math.real(U))
+
+        x = jax.numpy.array(v)
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            l = loss(x)
+            dl = jax.grad(loss)(x)
+
+        assert isinstance(l, jax.numpy.ndarray)
+        assert np.allclose(l, 2 * np.cos(v / 2))
+        assert np.allclose(dl, -np.sin(v / 2))
+
+    @pytest.mark.parametrize("v", np.linspace(0.2, 1.6, 8))
+    def test_torch(self, v):
+        """Test that differentiation works correctly when using Torch"""
+
+        torch = pytest.importorskip("torch")
+
+        def circuit(theta):
+            qml.RX(theta, wires=0)
+            qml.PauliZ(wires=0)
+            qml.CNOT(wires=[0, 1])
+
+        def loss(theta):
+            U = qml.eigvals(circuit)(theta)
+            return qml.math.sum(qml.math.real(U))
+
+        x = torch.tensor(v, requires_grad=True)
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            l = loss(x)
+            l.backward()
+
+        dl = x.grad
+
+        assert isinstance(l, torch.Tensor)
+        assert np.allclose(l.detach(), 2 * np.cos(v / 2))
+        assert np.allclose(dl.detach(), -np.sin(v / 2))
+
+    @pytest.mark.parametrize("v", np.linspace(0.2, 1.6, 8))
+    def test_tensorflow(self, v):
+        """Test that differentiation works correctly when using TF"""
+
+        tf = pytest.importorskip("tensorflow")
+
+        def circuit(theta):
+            qml.RX(theta, wires=0)
+            qml.PauliZ(wires=0)
+            qml.CNOT(wires=[0, 1])
+
+        def loss(theta):
+            U = qml.eigvals(circuit)(theta)
+            return qml.math.sum(qml.math.real(U))
+
+        x = tf.Variable(v)
+        with tf.GradientTape() as tape:
+            with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+                l = loss(x)
+        dl = tape.gradient(l, x)
+
+        assert isinstance(l, tf.Tensor)
+        assert np.allclose(l, 2 * np.cos(v / 2))
+        assert np.allclose(dl, -np.sin(v / 2))
+
+    @pytest.mark.xfail(reason="np.linalg.eigvals not differentiable using Autograd")
+    @pytest.mark.parametrize("v", np.linspace(0.2, 1.6, 8))
+    def test_autograd(self, v):
+        """Test that differentiation works correctly when using Autograd"""
+
+        def circuit(theta):
+            qml.RX(theta, wires=0)
+            qml.PauliZ(wires=0)
+            qml.CNOT(wires=[0, 1])
+
+        def loss(theta):
+            U = qml.eigvals(circuit)(theta)
+            return qml.math.sum(qml.math.real(U))
+
+        x = np.array(v, requires_grad=True)
+
+        with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"):
+            l = loss(x)
+            dl = qml.grad(loss)(x)
+
+        assert isinstance(l, qml.numpy.tensor)
+        assert np.allclose(l, 2 * np.cos(v / 2))
+        assert np.allclose(dl, -np.sin(v / 2))