# Source code for pennylane.ops.functions.eigvals

# Copyright 2018-2022 Xanadu Quantum Technologies Inc.

# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# Unless required by applicable law or agreed to in writing, software
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
"""
This module contains the qml.eigvals function.
"""
from typing import Sequence, Callable
import warnings

# pylint: disable=protected-access
from functools import reduce, partial
import scipy

import pennylane as qml
from pennylane.transforms import TransformError
from pennylane import transform
from pennylane.typing import TensorLike

[docs]def eigvals(op: qml.operation.Operator, k=1, which="SA") -> TensorLike:
r"""The eigenvalues of one or more operations.

.. note::

- For a :class:~.SparseHamiltonian object, the eigenvalues are computed with the efficient
scipy.sparse.linalg.eigsh method which returns :math:k eigenvalues. The default value
of :math:k is :math:1. For an :math:N \times N sparse matrix, :math:k must be
smaller than :math:N - 1, otherwise scipy.sparse.linalg.eigsh fails. If the requested
:math:k is equal or larger than :math:N - 1, the regular qml.math.linalg.eigvalsh
is applied on the dense matrix. For more details see the scipy.sparse.linalg.eigsh
documentation <https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.linalg.eigsh.html#scipy.sparse.linalg.eigsh>_.
- A second-quantized :mod:molecular Hamiltonian <pennylane.qchem.molecular_hamiltonian> is
independent of the number of electrons and its eigenspectrum contains the energies of the
neutral and charged molecules. Therefore, the smallest eigenvalue returned by qml.eigvals
for a molecular Hamiltonian might not always correspond to the neutral molecule.

Args:
op (Operator or QNode or QuantumTape or Callable): A quantum operator or quantum circuit.
k (int): The number of eigenvalues to be returned for a :class:~.SparseHamiltonian.
which (str): Method for computing the eigenvalues of a :class:~.SparseHamiltonian. The
possible methods are 'LM' (largest in magnitude), 'SM' (smallest in magnitude),
'LA' (largest algebraic), 'SA' (smallest algebraic) and 'BE' (:math:k/2
from each end of the spectrum).

Returns:
TensorLike or qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]:

If an operator is provided as input, the eigenvalues are returned directly in the form of a tensor.
Otherwise, the transformed circuit is returned as described in :func:qml.transform <pennylane.transform>.
Executing this circuit will provide the eigenvalues as a tensor.

**Example**

Given an operation, qml.eigvals returns the eigenvalues:

>>> op = qml.Z(0) @ qml.X(1) - 0.5 * qml.Y(1)
>>> qml.eigvals(op)
array([-1.11803399, -1.11803399,  1.11803399,  1.11803399])

It can also be used in a functional form:

>>> eigval_fn = qml.eigvals(qml.RX)
>>> eigval_fn(x, wires=0)

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()
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.

.. details::
:title: Usage Details

qml.eigvals can also be used with QNodes, tapes, or quantum functions that
contain multiple operations. However, in this situation, **eigenvalues may
for a large number of wires.

Consider the following quantum function:

.. code-block:: python3

def circuit(theta):
qml.RX(theta, wires=1)
qml.Z(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 not isinstance(op, qml.operation.Operator):
if not isinstance(op, (qml.tape.QuantumScript, qml.QNode)) and not callable(op):
raise TransformError("Input is not an Operator, tape, QNode, or quantum function")
return _eigvals_tranform(op, k=k, which=which)

if isinstance(op, qml.ops.Hamiltonian):

warnings.warn(
"For Hamiltonians, the eigenvalues will be computed numerically. "
"This may be computationally intensive for a large number of wires. "
"Consider using a sparse representation of the Hamiltonian with qml.SparseHamiltonian.",
UserWarning,
)
return qml.math.linalg.eigvalsh(qml.matrix(op))

if isinstance(op, qml.SparseHamiltonian):
sparse_matrix = op.sparse_matrix()
if k < sparse_matrix.shape[0] - 1:
return scipy.sparse.linalg.eigsh(sparse_matrix, k=k, which=which)[0]
return qml.math.linalg.eigvalsh(sparse_matrix.toarray())

# TODO: make eigvals take a wire_order argument to mimic matrix
try:
return op.eigvals()
except qml.operation.EigvalsUndefinedError:
return eigvals(op.expand(), k=k, which=which)

@partial(transform, is_informative=True)
def _eigvals_tranform(
tape: qml.tape.QuantumTape, k=1, which="SA"
) -> (Sequence[qml.tape.QuantumTape], Callable):
def processing_fn(res):
[qs] = res
op_wires = [op.wires for op in qs.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,
)
matrix = qml.matrix(qs, wire_order=qs.wires)
return qml.math.linalg.eigvals(matrix)

# 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, k=k, which=which) for op in qs.operations]

if len(ev) == 1:
return ev[0]

return reduce(qml.math.kron, ev)

return [tape], processing_fn


Using PennyLane

Release news

Development

API

Internals