Source code for pennylane.optimize.momentum_qng
# Copyright 2024 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.
"""Quantum natural gradient optimizer with momentum"""
# pylint: disable=too-many-branches
# pylint: disable=too-many-arguments
from pennylane import numpy as pnp
from pennylane.utils import _flatten, unflatten
from .qng import QNGOptimizer
[docs]class MomentumQNGOptimizer(QNGOptimizer):
r"""A generalization of the Quantum Natural Gradient (QNG) optimizer by considering a discrete-time Langevin equation
with QNG force. For details of the theory and derivation of Momentum-QNG, please see:
Oleksandr Borysenko, Mykhailo Bratchenko, Ilya Lukin, Mykola Luhanko, Ihor Omelchenko,
Andrii Sotnikov and Alessandro Lomi.
"Application of Langevin Dynamics to Advance the Quantum Natural Gradient Optimization Algorithm"
`arXiv:2409.01978 <https://arxiv.org/abs/2409.01978>`__
We are grateful to David Wierichs for his generous help with the multi-argument variant of the ``MomentumQNGOptimizer`` class.
``MomentumQNGOptimizer`` is a subclass of ``QNGOptimizer`` that requires one additional
hyperparameter (the momentum coefficient) :math:`0 \leq \rho < 1`, the default value being :math:`\rho=0.9`. For :math:`\rho=0` Momentum-QNG
reduces to the basic QNG.
In this way, the parameter update rule in Momentum-QNG reads:
.. math::
x^{(t+1)} = x^{(t)} + \rho (x^{(t)} - x^{(t-1)}) - \eta g(f(x^{(t)}))^{-1} \nabla f(x^{(t)}),
where :math:`\eta` is a stepsize (learning rate) value, :math:`g(f(x^{(t)}))^{-1}` is the pseudo-inverse
of the Fubini-Study metric tensor and :math:`f(x^{(t)}) = \langle 0 | U(x^{(t)})^\dagger \hat{B} U(x^{(t)}) | 0 \rangle`
is an expectation value of some observable measured on the variational
quantum circuit :math:`U(x^{(t)})`.
**Examples:**
Consider an objective function realized as a :class:`~.QNode` that returns the
expectation value of a Hamiltonian.
>>> dev = qml.device("default.qubit", wires=(0, 1, "aux"))
>>> @qml.qnode(dev)
... def circuit(params):
... qml.RX(params[0], wires=0)
... qml.RY(params[1], wires=0)
... return qml.expval(qml.X(0))
Once constructed, the cost function can be passed directly to the
optimizer's :meth:`~.step` function. In addition to the standard learning
rate, the ``MomentumQNGOptimizer`` takes a ``momentum`` parameter:
>>> eta = 0.01
>>> rho = 0.93
>>> init_params = qml.numpy.array([0.5, 0.23], requires_grad=True)
>>> opt = qml.MomentumQNGOptimizer(stepsize=eta, momentum=rho)
>>> theta_new = opt.step(circuit, init_params)
>>> theta_new
tensor([0.50437193, 0.18562052], requires_grad=True)
An alternative function to calculate the metric tensor of the QNode can be provided to ``step``
via the ``metric_tensor_fn`` keyword argument, see :class:`~.pennylane.QNGOptimizer` for
details.
.. seealso::
For details on quantum natural gradient, see :class:`~.pennylane.QNGOptimizer`.
See :class:`~.pennylane.MomentumOptimizer` for a first-order optimizer with momentum.
Also see the examples from the reference above, benchmarking the Momentum-QNG optimizer
against the basic QNG, Momentum and Adam:
- `QAOA <https://github.com/borbysh/Momentum-QNG/blob/main/QAOA_depth4.ipynb>`__
- `VQE <https://github.com/borbysh/Momentum-QNG/blob/main/portfolio_optimization.ipynb>`__
Keyword Args:
stepsize=0.01 (float): the user-defined hyperparameter :math:`\eta`
momentum=0.9 (float): the user-defined hyperparameter :math:`\rho`
approx (str): Which approximation of the metric tensor to compute.
- If ``None``, the full metric tensor is computed
- If ``"block-diag"``, the block-diagonal approximation is computed, reducing
the number of evaluated circuits significantly.
- If ``"diag"``, only the diagonal approximation is computed, slightly
reducing the classical overhead but not the quantum resources
(compared to ``"block-diag"``).
lam=0 (float): metric tensor regularization :math:`G_{ij}+\lambda I`
to be applied at each optimization step
"""
def __init__(self, stepsize=0.01, momentum=0.9, approx="block-diag", lam=0):
super().__init__(stepsize, approx, lam)
self.momentum = momentum
self.accumulation = None
[docs] def apply_grad(self, grad, args):
r"""Update the parameter array :math:`x` for a single optimization step. Flattens and
unflattens the inputs to maintain nested iterables as the parameters of the optimization.
Args:
grad (array): The gradient of the objective
function at point :math:`x^{(t)}`: :math:`\nabla f(x^{(t)})`
args (array): the current value of the variables :math:`x^{(t)}`
Returns:
array: the new values :math:`x^{(t+1)}`
"""
args_new = list(args)
if self.accumulation is None:
self.accumulation = [pnp.zeros_like(g) for g in grad]
metric_tensor = (
self.metric_tensor if isinstance(self.metric_tensor, tuple) else (self.metric_tensor,)
)
trained_index = 0
for index, arg in enumerate(args):
if getattr(arg, "requires_grad", False):
grad_flat = pnp.array(list(_flatten(grad[trained_index])))
# self.metric_tensor has already been reshaped to 2D, matching flat gradient.
qng_update = pnp.linalg.pinv(metric_tensor[trained_index]) @ grad_flat
self.accumulation[trained_index] *= self.momentum
self.accumulation[trained_index] += self.stepsize * unflatten(
qng_update, grad[trained_index]
)
args_new[index] = arg - self.accumulation[trained_index]
trained_index += 1
return tuple(args_new)
_modules/pennylane/optimize/momentum_qng
Download Python script
Download Notebook
View on GitHub