Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add qml.generator(op) function #2256

Merged
merged 60 commits into from
Mar 3, 2022
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
cb1fe52
Add operator transform functionality
josh146 Feb 28, 2022
931515f
Add matrix function:
josh146 Feb 28, 2022
17bf5ba
add tests
josh146 Feb 28, 2022
b5586b2
add tests
josh146 Feb 28, 2022
ef42ab2
add tests
josh146 Feb 28, 2022
2beaa40
add tests
josh146 Feb 28, 2022
1c49491
add tests
josh146 Feb 28, 2022
397c343
Merge branch 'op-transform' into matrix-function
josh146 Feb 28, 2022
3580cf9
add tests
josh146 Feb 28, 2022
e7c803c
add tests
josh146 Feb 28, 2022
955ca89
Merge branch 'master' into op-transform
josh146 Feb 28, 2022
0ae781c
more docs
josh146 Feb 28, 2022
94b2f5a
Merge branch 'op-transform' into matrix-function
josh146 Feb 28, 2022
da6278f
more
josh146 Feb 28, 2022
1018410
add tests
josh146 Feb 28, 2022
190a7d4
Apply suggestions from code review
josh146 Feb 28, 2022
6ce4f9b
Add qml.eigval() function
josh146 Feb 28, 2022
cf045e0
add test
josh146 Feb 28, 2022
80124c4
Merge branch 'matrix-function' of github.com:PennyLaneAI/pennylane in…
josh146 Feb 28, 2022
4ea7fb1
Merge branch 'matrix-function' into eigvals-function
josh146 Feb 28, 2022
a66e6e1
finish adding tests
josh146 Feb 28, 2022
f3d72c0
finish adding tests
josh146 Feb 28, 2022
874c3ca
Add qml.generator(op) function
josh146 Mar 1, 2022
502a105
fixes
josh146 Mar 1, 2022
8c2a303
more tests
josh146 Mar 1, 2022
5a88c52
more tests
josh146 Mar 1, 2022
b0161f9
fix
josh146 Mar 1, 2022
c19fffe
more tests
josh146 Mar 1, 2022
8d56cdf
Apply suggestions from code review
josh146 Mar 1, 2022
f85f106
Apply suggestions from code review
josh146 Mar 1, 2022
249ff25
Update tests/ops/functions/test_eigvals.py
josh146 Mar 1, 2022
780e943
Apply suggestions from code review
josh146 Mar 1, 2022
7b63422
update deprecation warning
josh146 Mar 1, 2022
6215b74
Update tests/ops/functions/test_eigvals.py
josh146 Mar 1, 2022
696504b
update deprecation warning
josh146 Mar 1, 2022
c6ca52f
Merge branch 'matrix-function' into eigvals-function
josh146 Mar 1, 2022
f3e219e
add another test
josh146 Mar 1, 2022
2f5762d
suggested changes
josh146 Mar 1, 2022
131e752
black
josh146 Mar 1, 2022
c7653ee
black
josh146 Mar 1, 2022
379aa56
Merge branch 'eigvals-function' into gen-fn
josh146 Mar 1, 2022
fbd3d94
Merge branch 'master' into matrix-function
josh146 Mar 2, 2022
1b1801a
Merge branch 'matrix-function' into eigvals-function
josh146 Mar 2, 2022
2282b76
Apply suggestions from code review
josh146 Mar 2, 2022
7109d2c
Merge branch 'eigvals-function' into gen-fn
josh146 Mar 2, 2022
e40a691
fix
josh146 Mar 2, 2022
3b79b7a
changelog
josh146 Mar 2, 2022
9688f82
more
josh146 Mar 2, 2022
3a54758
Merge branch 'master' into eigvals-function
josh146 Mar 2, 2022
0376469
Merge branch 'eigvals-function' into gen-fn
josh146 Mar 2, 2022
20c102d
bugfix
josh146 Mar 2, 2022
9749648
suggested changes
josh146 Mar 2, 2022
0e24463
change parameter-frequency logic
josh146 Mar 2, 2022
109a288
Apply suggestions from code review
josh146 Mar 2, 2022
a398f03
Merge branch 'eigvals-function' into gen-fn
josh146 Mar 2, 2022
5d7073e
Merge branch 'master' into eigvals-function
josh146 Mar 2, 2022
1a346b0
fix
josh146 Mar 2, 2022
ae410b7
Merge branch 'eigvals-function' into gen-fn
josh146 Mar 2, 2022
06a6e2e
Merge branch 'master' into gen-fn
josh146 Mar 2, 2022
dcf79f4
Merge branch 'master' into gen-fn
josh146 Mar 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pennylane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,12 @@
metric_tensor,
specs,
qfunc_transform,
op_transform,
single_tape_transform,
quantum_monte_carlo,
apply_controlled_Q,
)
from pennylane.ops.functions import *
from pennylane.optimize import *
from pennylane.vqe import ExpvalCost, VQECost

Expand Down
5 changes: 2 additions & 3 deletions pennylane/fourier/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import numpy as np


from pennylane.utils import get_generator
import pennylane as qml


def format_nvec(nvec):
Expand Down Expand Up @@ -63,8 +63,7 @@ def get_spectrum(op, decimals):
Returns:
set[float]: non-negative frequencies contributed by this input-encoding gate
"""
matrix, coeff = get_generator(op, return_matrix=True)
matrix = coeff * matrix
matrix = qml.matrix(qml.generator(op, format="observable"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could have been done in #2182 already, but now that we're touching this code, we could simply use parameter_frequencies instead of this whole utility function? Might be a separate PR, though (maybe together with Rotosolve using parameter_frequencies, grouping it into an "application" PR).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dwierichs which would you prefer, doing it here, or doing it in a separate PR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess a separate one that uses parameter_frequencies where applicable is more clean? :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep :)


# todo: use qml.math.linalg once it is tested properly
evals = np.linalg.eigvalsh(matrix)
Expand Down
8 changes: 4 additions & 4 deletions pennylane/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@ def get_eigvals(self):
# 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

Expand Down Expand Up @@ -1250,7 +1250,7 @@ def parameter_frequencies(self):
gen = self.generator()

try:
gen_eigvals = tuple(self.generator().eigvals())
gen_eigvals = tuple(self.generator().get_eigvals())
josh146 marked this conversation as resolved.
Show resolved Hide resolved
return qml.gradients.eigvals_to_frequencies(gen_eigvals)

except (MatrixUndefinedError, EigvalsUndefinedError):
Expand Down Expand Up @@ -2348,8 +2348,8 @@ def operation_derivative(operation) -> np.ndarray:
ValueError: if the operation does not have a generator or is not composed of a single
trainable parameter
"""
generator, prefactor = qml.utils.get_generator(operation, return_matrix=True)
return 1j * prefactor * generator @ operation.get_matrix()
generator = qml.matrix(qml.generator(operation, format="observable"))
return 1j * generator @ operation.get_matrix()


@qml.BooleanFn
Expand Down
19 changes: 19 additions & 0 deletions pennylane/ops/functions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2018-2021 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 functions that act on operators and tapes.
"""
from .eigvals import eigvals
from .generator import generator
from .matrix import matrix
123 changes: 123 additions & 0 deletions pennylane/ops/functions/eigvals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Copyright 2018-2021 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 matrix representation of an operation or quantum circuit.

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.matrix`` returns the matrix representation:

>>> 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: take into account for wire ordering
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 for 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)
175 changes: 175 additions & 0 deletions pennylane/ops/functions/generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Copyright 2018-2021 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.generator function.
"""
# pylint: disable=protected-access
import pennylane as qml


def _generator_observable(gen, op):
"""Return the generator as type :class:`~.Hermitian`,
:class:`~.SparseHamiltonian`, or :class:`~.Hamiltonian`,
as provided by the original gate.
"""
if isinstance(gen, (qml.Hermitian, qml.SparseHamiltonian)):
if not op.inverse:
return gen

param = gen.parameters[0]
wires = gen.wires

return gen.__class__(-param, wires=wires)

if op.inverse:
gen = -1.0 * gen

return gen


def _generator_hamiltonian(gen, op):
"""Return the generator as type :class:`~.Hamiltonian`."""
wires = op.wires

if isinstance(gen, qml.Hamiltonian):
H = gen

elif isinstance(gen, (qml.Hermitian, qml.SparseHamiltonian)):

if isinstance(gen, qml.Hermitian):
mat = gen.parameters[0]

elif isinstance(gen, qml.SparseHamiltonian):
mat = gen.parameters[0].toarray()

coeffs, obs = qml.utils.decompose_hamiltonian(mat, wire_order=wires, hide_identity=True)
H = qml.Hamiltonian(coeffs, obs)

elif isinstance(gen, qml.operation.Observable):
H = 1.0 * gen

if op.inverse:
H = -1.0 * H

return H


def _generator_prefactor(gen, op):
r"""Return the generator as ```(prefactor, obs)`` representing
:math:`G=p \hat{O}`, where

- prefactor :math:`p` is a float
- observable `\hat{O}` is one of :class:`~.Hermitian`,
:class:`~.SparseHamiltonian`, or a tensor product
of Pauli words.
"""
if isinstance(gen, (qml.Hermitian, qml.SparseHamiltonian)):
obs = gen
prefactor = 1.0

elif isinstance(gen, qml.operation.Observable):
# convert to a qml.Hamiltonian
gen = 1.0 * gen

if len(gen.ops) == 1:
# case where the Hamiltonian is a single Pauli word
obs = gen.ops[0]
prefactor = gen.coeffs[0]
else:
obs = gen
prefactor = 1.0

if op.inverse:
prefactor *= -1.0

return prefactor, obs


@qml.op_transform
def generator(op, format="prefactor"):
r"""Returns the generator of operation.
josh146 marked this conversation as resolved.
Show resolved Hide resolved

Args:
op (.Operator or Callable): A single operator, or a function that
applies a single quantum operation.
format (str): The format to return the generator in. Must be one of ``'prefactor'``,
``'observable'``, or ``'hamiltonian'``. See below for more details.

Returns:
.Observable or tuple[float, .Observable]: The returned generator, with format/type
dependent on the ``format`` argument.

* ``"prefactor"``: Return the generator as ```(prefactor, obs)`` (representing
josh146 marked this conversation as resolved.
Show resolved Hide resolved
:math:`G=p \hat{O}`), where

- prefactor :math:`p` is a float
- observable `\hat{O}` is one of :class:`~.Hermitian`,
:class:`~.SparseHamiltonian`, or a tensor product
of Pauli words.
josh146 marked this conversation as resolved.
Show resolved Hide resolved

* ``"observable"``: Return the generator as a single observable as directly defined
by ``op``. Returned generators may be any type of observable, including
:class:`~.Hermitian`, :class:`~.Tensor`,
:class:`~.SparseHamiltonian`, or :class:`~.Hamiltonian`.

* ``"hamiltonian"``: Similar to ``"observable"``, however the returned observable
will always be converted into :class:`~.Hamiltonian` irregardless of how ``op``
josh146 marked this conversation as resolved.
Show resolved Hide resolved
encodes the generator.

**Example**

Given an operation, ``qml.generator`` returns the generator representation:

>>> op = qml.CRX(0.6, wires=[0, 1])
>>> qml.generator(op)
(-0.5, Projector([1], wires=[0]) @ PauliX(wires=[1]))

It can also be used in a functional form:

>>> qml.generator(qml.CRX)(0.6, wires=[0, 1])
(-0.5, Projector([1], wires=[0]) @ PauliX(wires=[1]))

By default, ``generator`` will return the generator in the format of ``(prefactor, obs)``,
corresponding to :math:`G=p \hat{O}`, where the observable :math:`\hat{O}` will
always be given in as in tensor product form, or as a dense/sparse matrix.

By using the ``format`` argument, the returned generator representation can
be altered:

>>> op = qml.RX(0.2, wires=0)
>>> qml.generator(op, format="prefactor") # output will always be (prefactor, obs)
(1.0, Projector([1], wires=[0]))
>>> qml.generator(op, format="hamiltonian") # output will always be a Hamiltonian
<Hamiltonian: terms=1, wires=[0]>
>>> qml.generator(op, format="observable") # ouput will be a simplified obs where possible
Projector([1], wires=[0])
"""
gen = op.generator()

if op.num_params != 1:
raise ValueError(f"Operation {op.name} is not written in terms of a single parameter")
josh146 marked this conversation as resolved.
Show resolved Hide resolved

if not isinstance(gen, qml.operation.Observable):
raise qml.QuantumFunctionError(f"Generator {gen} is not an observable")
josh146 marked this conversation as resolved.
Show resolved Hide resolved

if format == "prefactor":
return _generator_prefactor(gen, op)

if format == "hamiltonian":
return _generator_hamiltonian(gen, op)

if format == "observable":
return _generator_observable(gen, op)

raise ValueError("'format' must be one of ('prefactor', 'hamiltonian', 'observable'")
josh146 marked this conversation as resolved.
Show resolved Hide resolved
Loading