Source code for pennylane.transforms.qmc
# Copyright 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.
"""
Contains the quantum_monte_carlo transform.
"""
from copy import copy
import pennylane as qml
from pennylane import CZ, Hadamard, MultiControlledX, PauliX, adjoint
from pennylane.tape import QuantumScript, QuantumScriptBatch
from pennylane.templates import QFT
from pennylane.transforms.core import transform
from pennylane.typing import PostprocessingFn
from pennylane.wires import Wires
def _apply_controlled_z(wires, control_wire, work_wires):
r"""Provides the circuit to apply a controlled version of the :math:`Z` gate defined in
`this <https://arxiv.org/abs/1805.00109>`__ paper.
The multi-qubit gate :math:`Z = I - 2|0\rangle \langle 0|` can be performed using the
conventional multi-controlled-Z gate with an additional bit flip on each qubit before and after.
This function returns the multi-controlled-Z gate via a multi-controlled-X gate by picking an
arbitrary target wire to perform the X and adding a Hadamard on that wire either side of the
transformation.
Additional control from ``control_wire`` is then included within the multi-controlled-X gate.
Args:
wires (Wires): the wires on which the Z gate is applied
control_wire (Wires): the control wire from the register of phase estimation qubits
work_wires (Wires): the work wires used in the decomposition
"""
target_wire = wires[0]
updated_operations = []
updated_operations.append(PauliX(target_wire))
updated_operations.append(Hadamard(target_wire))
control_values = [0] * (len(wires) - 1) + [1]
control_wires = wires[1:] + control_wire
updated_operations.append(
MultiControlledX(
wires=[*control_wires, target_wire],
control_values=control_values,
work_wires=work_wires,
)
)
updated_operations.append(Hadamard(target_wire))
updated_operations.append(PauliX(target_wire))
return updated_operations
def _apply_controlled_v(target_wire, control_wire):
"""Provides the circuit to apply a controlled version of the :math:`V` gate defined in
`this <https://arxiv.org/abs/1805.00109>`__ paper.
The :math:`V` gate is simply a Pauli-Z gate applied to the ``target_wire``, i.e., the ancilla
wire in which the expectation value is encoded.
The controlled version of this gate is then a CZ gate.
Args:
target_wire (Wires): the ancilla wire in which the expectation value is encoded
control_wire (Wires): the control wire from the register of phase estimation qubits
"""
return [CZ(wires=[control_wire[0], target_wire[0]])]
[docs]@transform
def apply_controlled_Q(
tape: QuantumScript, wires, target_wire, control_wire, work_wires
) -> tuple[QuantumScriptBatch, PostprocessingFn]:
r"""Applies the transform that performs a controlled version of the :math:`\mathcal{Q}` unitary
defined in `this <https://arxiv.org/abs/1805.00109>`__ paper.
The input ``tape`` should be the quantum circuit corresponding to the :math:`\mathcal{F}` unitary
in the paper above. This function transforms this circuit into a controlled version of the
:math:`\mathcal{Q}` unitary, which forms part of the quantum Monte Carlo algorithm. The
:math:`\mathcal{Q}` unitary encodes the target expectation value as a phase in one of its
eigenvalues. This phase can be estimated using quantum phase estimation (see
:class:`~.QuantumPhaseEstimation` for more details).
Args:
tape (QNode or QuantumTape or Callable): the quantum circuit that applies quantum operations
according to the :math:`\mathcal{F}` unitary used as part of quantum Monte Carlo estimation
wires (Union[Wires or Sequence[int]]): the wires acted upon by the ``fn`` circuit
target_wire (Union[Wires, int]): The wire in which the expectation value is encoded. Must be
contained within ``wires``.
control_wire (Union[Wires, int]): the control wire from the register of phase estimation
qubits
work_wires (Union[Wires, Sequence[int], or int]): additional work wires used when
decomposing :math:`\mathcal{Q}`
Returns:
qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]:
The transformed circuit as described in :func:`qml.transform <pennylane.transform>`. Executing this circuit
will perform control on :math:`\mathcal{Q}` unitary.
Raises:
ValueError: if ``target_wire`` is not in ``wires``
"""
operations = tape.operations.copy()
updated_operations = []
with qml.queuing.QueuingManager.stop_recording():
op_inv = [adjoint(copy(op)) for op in reversed(operations)]
wires = Wires(wires)
target_wire = Wires(target_wire)
control_wire = Wires(control_wire)
work_wires = Wires(work_wires) if work_wires is not None else Wires([])
if not wires.contains_wires(target_wire):
raise ValueError("The target wire must be contained within wires")
updated_operations.extend(
_apply_controlled_v(target_wire=target_wire, control_wire=control_wire)
)
updated_operations.extend(op_inv)
updated_operations.extend(
_apply_controlled_z(wires=wires, control_wire=control_wire, work_wires=work_wires)
)
updated_operations.extend(operations)
updated_operations.extend(
_apply_controlled_v(target_wire=target_wire, control_wire=control_wire)
)
updated_operations.extend(op_inv)
updated_operations.extend(
_apply_controlled_z(wires=wires, control_wire=control_wire, work_wires=work_wires)
)
updated_operations.extend(operations)
tape = type(tape)(updated_operations, tape.measurements, shots=tape.shots)
return [tape], lambda x: x[0]
[docs]@transform
def quantum_monte_carlo(
tape: QuantumScript, wires, target_wire, estimation_wires
) -> tuple[QuantumScriptBatch, PostprocessingFn]:
r"""Applies the transform
`quantum Monte Carlo estimation <https://arxiv.org/abs/1805.00109>`__ algorithm.
The input `tape`` should be the quantum circuit corresponding to the :math:`\mathcal{F}` unitary
in the paper above. This unitary encodes the probability distribution and random variable onto
``wires`` so that measurement of the ``target_wire`` provides the expectation value to be
estimated. The quantum Monte Carlo algorithm then estimates the expectation value using quantum
phase estimation (check out :class:`~.QuantumPhaseEstimation` for more details), using the
``estimation_wires``.
.. note::
A complementary approach for quantum Monte Carlo is available with the
:class:`~.QuantumMonteCarlo` template.
The ``quantum_monte_carlo`` transform is intended for
use when you already have the circuit for performing :math:`\mathcal{F}` set up, and is
compatible with resource estimation and potential hardware implementation. The
:class:`~.QuantumMonteCarlo` template is only compatible with
simulators, but may perform faster and is suited to quick prototyping.
Args:
tape (QNode or QuantumTape or Callable): the quantum circuit that applies quantum operations according to the
:math:`\mathcal{F}` unitary used as part of quantum Monte Carlo estimation
wires (Union[Wires or Sequence[int]]): the wires acted upon by the ``fn`` circuit
target_wire (Union[Wires, int]): The wire in which the expectation value is encoded. Must be
contained within ``wires``.
estimation_wires (Union[Wires, Sequence[int], or int]): the wires used for phase estimation
Returns:
qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]:
The transformed circuit as described in :func:`qml.transform <pennylane.transform>`. Executing this circuit
will perform the quantum Monte Carlo estimation.
Raises:
ValueError: if ``wires`` and ``estimation_wires`` share a common wire
.. details::
:title: Usage Details
Consider an input quantum circuit ``fn`` that performs the unitary
.. math::
\mathcal{F} = \mathcal{R} \mathcal{A}.
.. figure:: ../../_static/ops/f.svg
:align: center
:width: 15%
:target: javascript:void(0);
Here, the unitary :math:`\mathcal{A}` prepares a probability distribution :math:`p(i)` of
dimension :math:`M = 2^{m}` over :math:`m \geq 1` qubits:
.. math::
\mathcal{A}|0\rangle^{\otimes m} = \sum_{i \in X} p(i) |i\rangle,
where :math:`X = \{0, 1, \ldots, M - 1\}` and :math:`|i\rangle` is the basis state
corresponding to :math:`i`. The :math:`\mathcal{R}` unitary imprints the
result of a function :math:`f: X \rightarrow [0, 1]` onto an ancilla qubit:
.. math::
\mathcal{R}|i\rangle |0\rangle = |i\rangle \left(\sqrt{1 - f(i)} |0\rangle + \sqrt{f(i)}|1\rangle\right).
Following `this <https://arxiv.org/abs/1805.00109>`__ paper,
the probability of measuring the state :math:`|1\rangle` in the final
qubit is
.. math::
\mu = \sum_{i \in X} p(i) f(i).
However, it is possible to measure :math:`\mu` more efficiently using quantum Monte Carlo
estimation. This function transforms an input quantum circuit ``fn`` that performs the
unitary :math:`\mathcal{F}` to a larger circuit for measuring :math:`\mu` using the quantum
Monte Carlo algorithm.
.. figure:: ../../_static/ops/qmc.svg
:align: center
:width: 60%
:target: javascript:void(0);
The algorithm proceeds as follows:
#. The probability distribution :math:`p(i)` is encoded using a unitary :math:`\mathcal{A}`
applied to the first :math:`m` qubits specified by ``wires``.
#. The function :math:`f(i)` is encoded onto the ``target_wire`` using a unitary
:math:`\mathcal{R}`.
#. The unitary :math:`\mathcal{Q}` is defined with eigenvalues
:math:`e^{\pm 2 \pi i \theta}` such that the phase :math:`\theta` encodes the expectation
value through the equation :math:`\mu = (1 + \cos (\pi \theta)) / 2`. The circuit in
steps 1 and 2 prepares an equal superposition over the two states corresponding to the
eigenvalues :math:`e^{\pm 2 \pi i \theta}`.
#. The circuit returned by this function is applied so that :math:`\pm\theta` can be
estimated by finding the probabilities of the :math:`n` estimation wires. This in turn
allows for the estimation of :math:`\mu`.
Visit `Rebentrost et al. (2018)
<https://arxiv.org/abs/1805.00109>`__ for further details.
In this algorithm, the number of applications :math:`N` of the :math:`\mathcal{Q}` unitary
scales as :math:`2^{n}`. However, due to the use of quantum phase estimation, the error
:math:`\epsilon` scales as :math:`\mathcal{O}(2^{-n})`. Hence,
.. math::
N = \mathcal{O}\left(\frac{1}{\epsilon}\right).
This scaling can be compared to standard Monte Carlo estimation, where :math:`N` samples are
generated from the probability distribution and the average over :math:`f` is taken. In that
case,
.. math::
N = \mathcal{O}\left(\frac{1}{\epsilon^{2}}\right).
Hence, the quantum Monte Carlo algorithm has a quadratically improved time complexity with
:math:`N`.
**Example**
Consider a standard normal distribution :math:`p(x)` and a function
:math:`f(x) = \sin ^{2} (x)`. The expectation value of :math:`f(x)` is
:math:`\int_{-\infty}^{\infty}f(x)p(x)dx \approx 0.432332`. This number can be approximated by
discretizing the problem and using the quantum Monte Carlo algorithm.
First, the problem is discretized:
.. code-block:: python
from scipy.stats import norm
m = 5
M = 2 ** m
xmax = np.pi # bound to region [-pi, pi]
xs = np.linspace(-xmax, xmax, M)
probs = np.array([norm().pdf(x) for x in xs])
probs /= np.sum(probs)
func = lambda i: np.sin(xs[i]) ** 2
r_rotations = np.array([2 * np.arcsin(np.sqrt(func(i))) for i in range(M)])
The ``quantum_monte_carlo`` transform can then be used:
.. code-block::
from pennylane.templates.state_preparations.mottonen import (
_apply_uniform_rotation_dagger as r_unitary,
)
n = 6
N = 2 ** n
a_wires = range(m)
wires = range(m + 1)
target_wire = m
estimation_wires = range(m + 1, n + m + 1)
dev = qml.device("default.qubit", wires=(n + m + 1))
def fn():
qml.templates.MottonenStatePreparation(np.sqrt(probs), wires=a_wires)
r_unitary(qml.RY, r_rotations, control_wires=a_wires[::-1], target_wire=target_wire)
@qml.qnode(dev)
def qmc():
qml.quantum_monte_carlo(fn, wires, target_wire, estimation_wires)()
return qml.probs(estimation_wires)
phase_estimated = np.argmax(qmc()[:int(N / 2)]) / N
The estimated value can be retrieved using the formula :math:`\mu = (1-\cos(\pi \theta))/2`
>>> (1 - np.cos(np.pi * phase_estimated)) / 2
0.42663476277231915
It is also possible to explore the resources required to perform the quantum Monte Carlo
algorithm
>>> qml.specs(qmc, level="device")()
{'resources': Resources(
num_wires=12,
num_gates=31882,
gate_types=defaultdict(<class 'int'>, {'RY': 7747, 'CNOT': 7874, 'Hadamard': 258, 'CZ': 126, 'Adjoint(CNOT)': 7812, 'Adjoint(RY)': 7686, 'PauliX': 252, 'MultiControlledX': 126, 'Adjoint(QFT)': 1}),
gate_sizes=defaultdict(<class 'int'>, {1: 15943, 2: 15812, 7: 126, 6: 1}), depth=30610, shots=Shots(total_shots=None, shot_vector=()),
),
'num_observables': 1,
'num_diagonalizing_gates': 0,
'num_trainable_params': 15433,
'num_device_wires': 12,
'device_name': 'default.qubit',
'expansion_strategy': 'gradient',
'gradient_options': {},
'interface': 'auto',
'diff_method': 'best',
'gradient_fn': 'backprop'}
"""
operations = tape.operations.copy()
wires = Wires(wires)
target_wire = Wires(target_wire)
estimation_wires = Wires(estimation_wires)
if Wires.shared_wires([wires, estimation_wires]):
raise ValueError("No wires can be shared between the wires and estimation_wires registers")
updated_operations = []
with qml.queuing.QueuingManager.stop_recording():
updated_operations.extend(operations)
for i, control_wire in enumerate(estimation_wires):
updated_operations.append(Hadamard(control_wire))
# Find wires eligible to be used as helper wires
work_wires = estimation_wires.toset() - {control_wire}
n_reps = 2 ** (len(estimation_wires) - (i + 1))
tapes_q, _ = apply_controlled_Q(
tape,
wires=wires,
target_wire=target_wire,
control_wire=control_wire,
work_wires=work_wires,
)
tape_q = tapes_q[0]
for _ in range(n_reps):
updated_operations.extend(tape_q.operations)
updated_operations.append(adjoint(QFT(wires=estimation_wires), lazy=False))
updated_tape = type(tape)(updated_operations, tape.measurements, shots=tape.shots)
return [updated_tape], lambda x: x[0]
_modules/pennylane/transforms/qmc
Download Python script
Download Notebook
View on GitHub