-
Notifications
You must be signed in to change notification settings - Fork 603
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
Changes from all commits
cb1fe52
931515f
17bf5ba
b5586b2
ef42ab2
2beaa40
1c49491
397c343
3580cf9
e7c803c
955ca89
0ae781c
94b2f5a
da6278f
1018410
190a7d4
6ce4f9b
cf045e0
80124c4
4ea7fb1
a66e6e1
f3d72c0
874c3ca
502a105
8c2a303
5a88c52
b0161f9
c19fffe
8d56cdf
f85f106
249ff25
780e943
7b63422
6215b74
696504b
c6ca52f
f3e219e
2f5762d
131e752
c7653ee
379aa56
fbd3d94
1b1801a
2282b76
7109d2c
e40a691
3b79b7a
9688f82
3a54758
0376469
20c102d
9749648
0e24463
109a288
a398f03
5d7073e
1a346b0
ae410b7
06a6e2e
dcf79f4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1291,23 +1291,15 @@ def parameter_frequencies(self): | |
if self.num_params == 1: | ||
# if the operator has a single parameter, we can query the | ||
# generator, and if defined, use its eigenvalues. | ||
gen = self.generator() | ||
|
||
try: | ||
gen_eigvals = tuple(self.generator().eigvals()) | ||
return qml.gradients.eigvals_to_frequencies(gen_eigvals) | ||
|
||
except (MatrixUndefinedError, EigvalsUndefinedError): | ||
|
||
if isinstance(gen, qml.Hamiltonian): | ||
mat = qml.utils.sparse_hamiltonian(gen).toarray() | ||
eigvals = tuple(np.round(np.linalg.eigvalsh(mat), 8)) | ||
return qml.gradients.eigvals_to_frequencies(eigvals) | ||
with warnings.catch_warnings(): | ||
warnings.filterwarnings( | ||
action="ignore", message=r".+ eigenvalues will be computed numerically\." | ||
) | ||
eigvals = qml.eigvals(qml.generator(self, format="observable")) | ||
|
||
if isinstance(gen, qml.SparseHamiltonian): | ||
mat = gen.sparse_matrix().toarray() | ||
eigvals = tuple(np.round(np.linalg.eigvalsh(mat), 8)) | ||
return qml.gradients.eigvals_to_frequencies(eigvals) | ||
eigvals = tuple(np.round(eigvals, 8)) | ||
return qml.gradients.eigvals_to_frequencies(eigvals) | ||
Comment on lines
+1295
to
+1302
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dwierichs I made the change here because I realized the old version was simply duplicating There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh great, this does look very nice :) |
||
|
||
raise OperatorPropertyUndefined( | ||
f"Operation {self.name} does not have parameter frequencies." | ||
|
@@ -1398,14 +1390,13 @@ def __init__(self, *params, wires=None, do_queue=True, id=None): | |
super().__init__(*params, wires=wires, do_queue=do_queue, id=id) | ||
|
||
# check the grad_recipe validity | ||
if self.grad_method == "A": | ||
if self.grad_recipe is None: | ||
# default recipe for every parameter | ||
self.grad_recipe = [None] * self.num_params | ||
else: | ||
assert ( | ||
len(self.grad_recipe) == self.num_params | ||
), "Gradient recipe must have one entry for each parameter!" | ||
if self.grad_recipe is None: | ||
# default recipe for every parameter | ||
self.grad_recipe = [None] * self.num_params | ||
else: | ||
assert ( | ||
len(self.grad_recipe) == self.num_params | ||
), "Gradient recipe must have one entry for each parameter!" | ||
|
||
|
||
class Channel(Operation, abc.ABC): | ||
|
@@ -2394,8 +2385,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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
# 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 ```(obs, prefactor)`` 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 obs, prefactor | ||
|
||
|
||
@qml.op_transform | ||
def generator(op, format="prefactor"): | ||
r"""Returns the generator of an operation. | ||
|
||
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 ```(obs, prefactor)`` (representing | ||
:math:`G=p \hat{O}`), where | ||
|
||
- observable `\hat{O}` is one of :class:`~.Hermitian`, | ||
:class:`~.SparseHamiltonian`, or a tensor product | ||
of Pauli words. | ||
- prefactor :math:`p` is a float | ||
|
||
The prefactor will in most cases be :math:`\pm 1.0`, unless the generator is a single Pauli | ||
word, in which case the prefactor is the coefficient of the Pauli word. | ||
|
||
* ``"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` regardless of how ``op`` | ||
encodes the generator. | ||
|
||
**Example** | ||
|
||
Given an operation, ``qml.generator`` returns the generator representation: | ||
|
||
>>> op = qml.CRX(0.6, wires=[0, 1]) | ||
>>> qml.generator(op) | ||
(Projector([1], wires=[0]) @ PauliX(wires=[1]), -0.5) | ||
|
||
It can also be used in a functional form: | ||
|
||
>>> qml.generator(qml.CRX)(0.6, wires=[0, 1]) | ||
(Projector([1], wires=[0]) @ PauliX(wires=[1]), -0.5) | ||
|
||
By default, ``generator`` will return the generator in the format of ``(obs, prefactor)``, | ||
corresponding to :math:`G=p \hat{O}`, where the observable :math:`\hat{O}` will | ||
always be given 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) | ||
(Projector([1], wires=[0]), 1.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]) | ||
""" | ||
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
|
||
|
||
gen = op.generator() | ||
|
||
if not isinstance(gen, qml.operation.Observable): | ||
raise qml.QuantumFunctionError( | ||
f"Generator {gen.name} of operation {op.name} is not an observable" | ||
) | ||
|
||
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')") |
There was a problem hiding this comment.
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 usingparameter_frequencies
, grouping it into an "application" PR).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea!
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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? :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep :)