From 70ee43cfca26d8533c6f8d36c93efb3609c3fed2 Mon Sep 17 00:00:00 2001 From: David Wierichs Date: Thu, 20 Jun 2024 22:57:03 +0200 Subject: [PATCH] Add quickstart page on mid-circuit measurements (#5870) **Context:** The PennyLane documentation has a quickstart page on measurements, which includes a large section about mid-circuit measurements. With the focus on dynamic circuits and mid-circuit measurements more generally, we would like to make the content on MCMs more visible. **Description of the Change:** Extracts the MCM part of the measurements quickstart and adds it as its own quickstart page. Adds some details on simulation techniques and polishes the content. **Benefits:** Visibility, documentation extent for MCMs **Possible Drawbacks:** **Related GitHub Issues:** [sc-65318] --------- Co-authored-by: Vincent Michaud-Rioux --- doc/development/guide/architecture.rst | 2 +- doc/index.rst | 1 + doc/introduction/dynamic_quantum_circuits.rst | 461 ++++++++++++++++++ doc/introduction/interfaces.rst | 11 +- doc/introduction/measurements.rst | 422 ---------------- doc/releases/changelog-dev.md | 4 + pennylane/ops/op_math/condition.py | 22 +- pennylane/workflow/qnode.py | 4 +- 8 files changed, 496 insertions(+), 431 deletions(-) create mode 100644 doc/introduction/dynamic_quantum_circuits.rst diff --git a/doc/development/guide/architecture.rst b/doc/development/guide/architecture.rst index adced2325cb..53fbe1beb07 100644 --- a/doc/development/guide/architecture.rst +++ b/doc/development/guide/architecture.rst @@ -8,7 +8,7 @@ PennyLane's core feature is the ability to compute gradients of variational quantum circuits in a way that is compatible with classical techniques such as backpropagation. PennyLane thus extends the automatic differentiation algorithms common in optimization and machine learning to include quantum and -:doc:`hybrid computations `. +`hybrid computations `_. A plugin system makes the framework compatible with many quantum simulators or hardware devices, remote or local. diff --git a/doc/index.rst b/doc/index.rst index a95d156da0c..04b52d62405 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -159,6 +159,7 @@ PennyLane is **free** and **open source**, released under the Apache License, Ve introduction/interfaces introduction/operations introduction/measurements + introduction/dynamic_quantum_circuits introduction/templates introduction/inspecting_circuits introduction/compiling_circuits diff --git a/doc/introduction/dynamic_quantum_circuits.rst b/doc/introduction/dynamic_quantum_circuits.rst new file mode 100644 index 00000000000..a1b4b07655c --- /dev/null +++ b/doc/introduction/dynamic_quantum_circuits.rst @@ -0,0 +1,461 @@ +.. role:: html(raw) + :format: html + +.. _mid_circuit_measurements: + +Dynamic quantum circuits +======================== + +.. currentmodule:: pennylane.measure + +PennyLane allows using measurements in the middle of a quantum circuit. +Such measurements are called mid-circuit measurements and can be used to +shape the structure of the circuit dynamically, and to gather information +about the quantum state during the circuit execution. + +Available features +------------------ + +Mid-circuit measurements +************************ + +The function to perform a mid-circuit measurement in PennyLane is +:func:`~.pennylane.measure`, and can be used as follows: + +.. code-block:: python + + dev = qml.device("default.qubit") + + @qml.qnode(dev) + def my_qnode(x, y): + qml.RY(x, wires=0) + qml.CNOT(wires=[0, 1]) + m_0 = qml.measure(1, reset=False, postselect=None) + + qml.cond(m_0, qml.RY)(y, wires=0) + return qml.probs(wires=[0]), qml.expval(m_0) + +See the following sections for details on +:func:`~.pennylane.measure`, :func:`~.pennylane.cond`, and statistics +of mid-circuit measurements, as well as information about simulation +strategies and how to configure them :ref:`further below `. +Additional information can be found in the documentation of the individual +methods. Also consider our +:doc:`Introduction to mid-circuit measurements ` +:doc:`how-to on collecting statistics of mid-circuit measurements `, +and :doc:`how-to on creating dynamic circuits with mid-circuit measurements `. + +Resetting qubits +**************** + +Wires can be reused after making mid-circuit measurements. Moreover, a measured wire can be +reset to the :math:`|0 \rangle` state by setting ``reset=True`` in :func:`~.pennylane.measure`: + +.. code-block:: python3 + + dev = qml.device("default.qubit", wires=3) + + @qml.qnode(dev) + def func(): + qml.PauliX(1) + m_0 = qml.measure(1, reset=True) + qml.PauliX(1) + return qml.probs(wires=[1]) + +Executing this QNode: + +.. code-block:: pycon + + >>> func() + tensor([0., 1.], requires_grad=True) + +Postselecting mid-circuit measurements +************************************** + +PennyLane also supports postselecting on mid-circuit measurement outcomes by specifying the +``postselect`` keyword argument of :func:`~.pennylane.measure`. By default, postselection +discards outcomes that do not match the ``postselect`` argument. +For example, specifying ``postselect=1`` is equivalent to projecting the state vector onto +the :math:`|1\rangle` state, i.e., disregarding all outcomes where :math:`|0\rangle` is measured: + +.. code-block:: python3 + + dev = qml.device("default.qubit") + + @qml.qnode(dev) + def func(x): + qml.RX(x, wires=0) + m_0 = qml.measure(0, postselect=1) + return qml.sample(wires=0) + +By postselecting on ``1``, we only consider results that measured the outcome ``1``. +Executing this QNode with 10 shots yields + +.. code-block:: pycon + + >>> func(np.pi / 2, shots=10) + array([1, 1, 1, 1, 1, 1, 1]) + +Note that only 7 samples are returned. This is because samples that do not meet the postselection criteria are +discarded. This behaviour can be customized, see the section +:ref:`"Configuring mid-circuit measurements" `. + +Conditional operators +********************* + +Users can create conditional operators controlled on mid-circuit measurements using +:func:`~.pennylane.cond`. The condition for a conditional operator may simply be +the measured value returned by a :func:`~.pennylane.measure` call, or we may construct a boolean +condition based on such values and pass it to :func:`~.pennylane.cond`: + +.. code-block:: python + + @qml.qnode(dev) + def qnode_conditional_op_on_zero(x, y): + qml.RY(x, wires=0) + qml.CNOT(wires=[0, 1]) + m_0 = qml.measure(1) + + qml.cond(m_0 == 0, qml.RY)(y, wires=0) + return qml.probs(wires=[0]) + + pars = np.array([0.643, 0.246], requires_grad=True) + +.. code-block:: pycon + + >>> qnode_conditional_op_on_zero(*pars) + tensor([0.88660045, 0.11339955], requires_grad=True) + +For more examples, refer to the :func:`~.pennylane.cond` documentation +and the :doc:`how-to on creating dynamic circuits with mid-circuit measurements +`. + +.. _mid_circuit_measurements_statistics: + +Mid-circuit measurement statistics +********************************** + +Statistics of mid-circuit measurements can be collected along with terminal measurement statistics. +Currently, :func:`~.counts`, :func:`~.expval`, :func:`~.probs`, :func:`~.sample`, and :func:`~.var` +are supported. + +.. code-block:: python3 + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def func(x, y): + qml.RX(x, wires=0) + m_0 = qml.measure(0) + qml.cond(m_0, qml.RY)(y, wires=1) + return qml.probs(wires=1), qml.probs(op=m_0) + +Executing this ``QNode``: + +.. code-block:: pycon + + >>> func(np.pi / 2, np.pi / 4) + (tensor([0.9267767, 0.0732233], requires_grad=True), + tensor([0.5, 0.5], requires_grad=True)) + +Users can also collect statistics on mid-circuit measurements manipulated using arithmetic/boolean operators. +This works for both unary and binary operators. To see a full list of supported operators, refer to the +:func:`~.pennylane.measure` documentation. An example for collecting such statistics is shown below: + +.. code-block:: python3 + + import pennylane as qml + + dev = qml.device("default.qubit") + + @qml.qnode(dev) + def circuit(phi, theta): + qml.RX(phi, wires=0) + m_0 = qml.measure(wires=0) + qml.RY(theta, wires=1) + m_1 = qml.measure(wires=1) + return qml.sample(~m_0 - 2 * m_1) + +Executing this ``QNode``: + +.. code-block:: pycon + + >>> circuit(1.23, 4.56, shots=5) + array([-1, -2, 1, -1, 1]) + +Collecting statistics for mid-circuit measurements manipulated using arithmetic/boolean operators is supported +with :func:`~.counts`, :func:`~.expval`, :func:`~.sample`, and :func:`~.var`. + +Moreover, statistics for multiple mid-circuit measurements can be collected by passing lists of mid-circuit +measurement values to the measurement process: + +.. code-block:: python3 + + import pennylane as qml + + dev = qml.device("default.qubit") + + @qml.qnode(dev) + def circuit(phi, theta): + qml.RX(phi, wires=0) + m_0 = qml.measure(wires=0) + qml.RY(theta, wires=1) + m_1 = qml.measure(wires=1) + return qml.sample([m_0, m_1]) + +Executing this ``QNode``: + +.. code-block:: pycon + + >>> circuit(1.23, 4.56, shots=5) + array([[0, 1], + [1, 1], + [0, 1], + [0, 0], + [1, 1]]) + +Collecting statistics for sequences of mid-circuit measurements is supported with +:func:`~.counts`, :func:`~.probs`, and :func:`~.sample`. + +.. warning:: + + When collecting statistics for a sequence of mid-circuit measurements, the + sequence must not contain arithmetic expressions. + +.. _simulation_techniques: + +Simulation techniques +--------------------- + +PennyLane currently offers three methods to simulate mid-circuit measurements +on classical computers: the deferred measurements principle, dynamic one-shot +sampling, and a tree-traversal approach. These methods differ in their memory requirements +and computational cost, as well as their compatibility with other features such as +shots and differentiation methods. +While the requirements depend on details of the simulation, the expected +scalings with respect to the number of mid-circuit measurements (and shots) are + +.. role:: gr +.. role:: or +.. role:: rd + +.. raw:: html + + + + + +.. rst-class:: tb + ++--------------------------+-------------------------------------------+-----------------------------------------------------------+-------------------------------------------+--------------------+ +| **Simulation technique** | **Memory** | **Time** | **Differentiation** | **shots/analytic** | ++==========================+===========================================+===========================================================+===========================================+====================+ +| Deferred measurements | :rd:`\ ` :math:`\mathcal{O}(2^{n_{MCM}})` | :rd:`\ ` :math:`\mathcal{O}(2^{n_{MCM}})` | :gr:`\ ` yes \ :math:`{}^1` | :gr:`\ ` yes / yes | ++--------------------------+-------------------------------------------+-----------------------------------------------------------+-------------------------------------------+--------------------+ +| Dynamic one-shot | :gr:`\ ` :math:`\mathcal{O}(1)` | :rd:`\ ` :math:`\mathcal{O}(n_{shots})` | :or:`\ ` finite differences\ :math:`{}^2` | :or:`\ ` yes / no | ++--------------------------+-------------------------------------------+-----------------------------------------------------------+-------------------------------------------+--------------------+ +| Tree-traversal | :or:`\ ` :math:`\mathcal{O}(n_{MCM}+1)` | :or:`\ ` :math:`\mathcal{O}(min(n_{shots}, 2^{n_{MCM}}))` | :or:`\ ` finite differences\ :math:`{}^2` | :or:`\ ` yes / no | ++--------------------------+-------------------------------------------+-----------------------------------------------------------+-------------------------------------------+--------------------+ + +:math:`{}^1` Backpropagation and finite differences are fully supported. The adjoint method +and the parameter-shift rule are supported if no postselection is used. + +:math:`{}^2` In principle, parameter-shift differentiation is supported as long as no +postselection is used. Parameters within conditionally applied operations will +fall back to finite differences, so a proper value for ``h`` should be provided (see +:func:`~.pennylane.gradients.finite_diff`). + +The strengths and weaknesses of the simulation techniques differ strongly and the best +technique will depend on details of the simulation workflow. As a rule of thumb: + +- dynamic one-shot sampling excels in the many-measurements-few-shots regime, + +- the tree-traversal technique can handle large-scale simulations with many shots + and measurements, and + +- deferred measurements are the generalist solution that enables mid-circuit measurement + support under (almost) all circumstances, but at large memory cost. It is the only method + supporting analytic simulations. + +By default, ``QNode``\ s use deferred measurements and dynamic one-shot sampling (if supported) +when executed without and with shots, respectively. The method can be configured with +the keyword argument ``mcm_method`` at ``QNode`` creation +(see :ref:`"Configuring mid-circuit measurements" `). + +.. _deferred_measurements: + +Deferred measurements +********************* + +A quantum function with mid-circuit measurements can be executed via the +`deferred measurement principle `__. +In PennyLane, this technique is available via ``mcm_method="deferred"`` or as the +transform :func:`~.pennylane.defer_measurements`. + +The deferred measurement principle provides a powerful method to simulate +mid-circuit measurements, conditional operations and measurement statistics +in a differentiable and device-independent way. It adds an auxiliary qubit +to the circuit for each mid-circuit measurement, leading to overheads of both +memory and simulation time that scale exponentially with the number of measurements. + +.. code-block:: pycon + + >>> deferred_qnode = qml.defer_measurements(my_qnode) + >>> pars = np.array([0.643, 0.246]) + >>> deferred_qnode(*pars) + (tensor([0.90165331, 0.09834669], requires_grad=True), + tensor(0.09984972, requires_grad=True)) + +The effect of deferring measurements becomes clear if we draw the ``QNode`` +before and after applying the transform: + +.. code-block:: pycon + + >>> qml.draw(my_qnode)(*pars) + 0: ──RY(0.64)─╭●───────RY(0.25)─┤ Probs + 1: ───────────╰X──┤↗├──║────────┤ + ╚═══╩════════╡ + >>> qml.draw(deferred_qnode)(*pars) + 0: ──RY(0.64)─╭●────╭RY(0.25)─┤ Probs + 1: ───────────╰X─╭●─│─────────┤ + 2: ──────────────╰X─╰●────────┤ + +Mid-circuit measurements are deferred to the end of the circuit, and conditionally applied +operations become (quantumly) controlled operations. + +.. note:: + + This method requires an additional qubit for each mid-circuit measurement, which limits + the number of measurements that can be used both on classical simulators and quantum hardware. + + Postselection with deferred measurements is only supported on + :class:`~.pennylane.devices.DefaultQubit`. + + +.. _one_shot_transform: + +Dynamic one-shot sampling +************************* + +Devices that natively support mid-circuit measurements can evaluate dynamic circuits +by executing them one shot at a time, sampling a dynamic execution path for each shot. + +In PennyLane, this technique is available via the QNode argument ``mcm_method="one-shot"`` +or as the transform :func:`~.pennylane.dynamic_one_shot`. +As the name suggests, this transform only works for a :class:`~.pennylane.QNode` executing +with finite shots and it requires the device to support mid-circuit measurements natively. + +The :func:`~.pennylane.dynamic_one_shot` transform is usually advantageous compared +with the :func:`~.pennylane.defer_measurements` transform in the +many-mid-circuit-measurements and few-shots limit. This is because, unlike the +deferred measurement principle, the method does not need an additional wire for every +mid-circuit measurement in the circuit. + +.. warning:: + + Dynamic circuits executed with shots should be differentiated with the finite difference method. + +.. _tree_traversal: + +Tree-traversal algorithm +************************ + +Dynamic circuit execution is akin to traversing a binary tree where each mid-circuit measurement +corresponds to a node and gates between them correspond to edges. The tree-traversal algorithm +explores this tree depth-first. It improves upon the dynamic one-shot approach above, which +simulates a randomly chosen branch from beginning to end for each shot, by collecting all +samples at a node or leaf at once. + +In PennyLane, this technique is available via the QNode argument ``mcm_method="tree-traversal"``; +it is not a transform. + +The tree-traversal algorithm combines the exponential savings of memory of the one-shot +approach with sampling efficiency of deferred measurements. +Neglecting overheads, simulating all branches requires the same +amount of computations as :func:`~.pennylane.defer_measurements`, but without the +:math:`O(2^{n_{MCM}})` memory cost. To save time, a copy of the state vector +is made at every mid-circuit measurement, requiring :math:`n_{MCM}+1` state +vectors, an exponential improvement over :func:`~.pennylane.defer_measurements`. +Since the counts of many nodes come out to be zero for shot-based simulations, +it is often possible to ignore entire sub-trees, thereby reducing the computational +cost. + +.. warning:: + + The tree-traversal algorithm is only supported by the + :class:`~.pennylane.devices.DefaultQubit` device, and currently does + not support just-in-time (JIT) compilation. + +.. _mcm_config: + +Configuring mid-circuit measurements +************************************ + +As described above, there are multiple simulation techniques for circuits with +mid-circuit measurements in PennyLane. They can be configured when initializing a +:class:`~pennylane.QNode`, using the following keywords: + +* ``mcm_method``: Sets the method used for applying mid-circuit measurements. The options are + ``"deferred"``, ``"one-shot"``, and ``"tree-traversal"`` for the three techniques described above. + The default is ``mcm_method="one-shot"`` when executing with shots, and ``"deferred"`` otherwise. + When using :func:`~pennylane.qjit`, there is the additional (default) option + ``mcm_method="single-branch-statistics"``, which explores a single branch of the execution + tree at random. + + .. warning:: + + If the ``mcm_method`` argument is provided, the transforms for deferred measurements + or dynamic one-shot sampling must not be applied manually to the :class:`~pennylane.QNode`. + +* ``postselect_mode``: Configures how invalid shots are handled when postselecting + mid-circuit measurements with finite-shot circuits. Use ``"hw-like"`` to discard invalid samples. + In this case, fewer than the total number of shots may be used to process results. Use + ``"fill-shots"`` to sample the postselected value unconditionally, creating valid samples + only. This is equivalent to sampling until the number of valid + samples matches the total number of shots. The default is ``"hw-like"``. + + .. code-block:: python3 + + dev = qml.device("default.qubit", wires=3, shots=10) + + def circ(): + qml.Hadamard(0) + m_0 = qml.measure(0, postselect=1) + return qml.sample(qml.PauliZ(0)) + + fill_shots = qml.QNode(circ, dev, mcm_method="one-shot", postselect_mode="fill-shots") + hw_like = qml.QNode(circ, dev, mcm_method="one-shot", postselect_mode="hw-like") + + .. code-block:: pycon + + >>> fill_shots() + array([-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.]) + >>> hw_like() + array([-1., -1., -1., -1., -1., -1., -1.]) + + .. note:: + + When using the ``jax`` interface, the postselection mode ``"hw-like"`` will change + behaviour with the simulation technique. + + * For dynamic one-shot, invalid shots will not be discarded, but will be replaced + by ``np.iinfo(np.int32).min``. They will not be used for processing final results (like + expectation values), but they will appear in the output of ``QNode``\ s that return + samples directly. + + * When using ``jax.jit``, the combination ``"deferred"`` and ``"hw-like"`` is not supported, + due to limitations of the :func:`~pennylane.defer_measurements` transform. This behaviour + will change in the future. + diff --git a/doc/introduction/interfaces.rst b/doc/introduction/interfaces.rst index 5a3bd1e6fc2..7eb3d391600 100644 --- a/doc/introduction/interfaces.rst +++ b/doc/introduction/interfaces.rst @@ -7,7 +7,8 @@ Gradients and training ====================== PennyLane offers seamless integration between classical and quantum computations. Code up quantum -circuits in PennyLane, compute :doc:`gradients of quantum circuits `, and +circuits in PennyLane, compute `gradients of quantum circuits +`_, and connect them easily to the top scientific computing and machine learning libraries. Training and interfaces @@ -182,8 +183,8 @@ The interface between PennyLane and automatic differentiation libraries relies o to compute or estimate gradients of quantum circuits. There are different strategies to do so, and they may depend on the device used. -When creating a QNode, you can specify the :doc:`differentiation method -` like this: +When creating a QNode, you can specify the `differentiation method +`_ like this: .. code-block:: python @@ -230,8 +231,8 @@ However, when using a simulator, you may notice that the number of circuit execu compute the gradients with these methods :doc:`scales linearly ` with the number of trainable circuit parameters. -* ``"parameter-shift"``: Use the analytic :doc:`parameter-shift rule - ` for all supported quantum operation arguments, with +* ``"parameter-shift"``: Use the analytic `parameter-shift rule + `_ for all supported quantum operation arguments, with finite-difference as a fallback. * ``"finite-diff"``: Use numerical finite-differences for all quantum operation arguments. diff --git a/doc/introduction/measurements.rst b/doc/introduction/measurements.rst index eab6c62e60a..2f6e6c693fe 100644 --- a/doc/introduction/measurements.rst +++ b/doc/introduction/measurements.rst @@ -244,428 +244,6 @@ state :math:`|00\rangle`, and a :math:`0.25\%` probability of measuring state :math:`|01\rangle`. -.. _mid_circuit_measurements: - -Mid-circuit measurements and conditional operations ---------------------------------------------------- - -PennyLane allows specifying measurements in the middle of the circuit. -Quantum functions such as operations can then be conditioned on the measurement -outcome of such mid-circuit measurements: - -.. code-block:: python - - def my_quantum_function(x, y): - qml.RY(x, wires=0) - qml.CNOT(wires=[0, 1]) - m_0 = qml.measure(1) - - qml.cond(m_0, qml.RY)(y, wires=0) - return qml.probs(wires=[0]) - -.. _deferred_measurements: - -Deferred measurements -********************* - -A quantum function with mid-circuit measurements (defined using -:func:`~.pennylane.measure`) and conditional operations (defined using -:func:`~.pennylane.cond`) can be executed by applying the `deferred measurement -principle `__. In -the example above, we apply the :class:`~.RY` rotation if the mid-circuit -measurement on qubit 1 yielded ``1`` as an outcome, otherwise doing nothing -for the ``0`` measurement outcome. - -PennyLane implements the deferred measurement principle to transform -conditional operations with the :func:`~.pennylane.defer_measurements` quantum -function transform. The deferred measurement principle provides a natural method -to simulate the application of mid-circuit measurements and conditional operations -in a differentiable and device-independent way. Performing true mid-circuit -measurements and conditional operations is dependent on the quantum hardware and -PennyLane device capabilities. - -.. code-block:: python - - transformed_qfunc = qml.transforms.defer_measurements(my_quantum_function) - transformed_qnode = qml.QNode(transformed_qfunc, dev) - pars = np.array([0.643, 0.246], requires_grad=True) - ->>> transformed_qnode(*pars) -tensor([0.90165331, 0.09834669], requires_grad=True) - -``qml.defer_measurements`` can be applied as decorator equally well: - -.. code-block:: python - - @qml.qnode(dev) - @qml.defer_measurements - def qnode(x, y): - (...) - - @qml.defer_measurements - @qml.qnode(dev) - def qnode(x, y): - (...) - -.. note:: - - The deferred measurements principle requires an additional wire, or qubit, for each mid-circuit - measurement, limiting the number of measurements that can be used both on classical simulators - and quantum hardware. The one-shot transform below does not have this limitation, but has - computational cost that scales with the number of shots used. - -.. _one_shot_transform: - -The one-shot transform -********************** - -Devices supporting mid-circuit measurements (defined using -:func:`~.pennylane.measure`) and conditional operations (defined using -:func:`~.pennylane.cond`) natively can estimate dynamic circuits by executing -them one shot at a time. This is the default behaviour of a :class:`~.pennylane.QNode` that has a -device supporting mid-circuit measurements, as well as any :class:`~.pennylane.QNode` with the -:func:`~.pennylane.dynamic_one_shot` quantum function transform. -As the name suggests, this transform only works for a :class:`~.pennylane.QNode` executing with finite shots -and it will raise an error if the device does not support mid-circuit measurements -natively. -The :func:`~.pennylane.defer_measurements` transform therefore remains the default for -analytic calculations. - -The :func:`~.pennylane.dynamic_one_shot` transform is usually advantageous compared -with the :func:`~.pennylane.defer_measurements` transform in the -large-number-of-mid-circuit-measurements and small-number-of-shots limit. This is because, unlike the -deferred measurement principle, the method does not need an additional wire for every -mid-circuit measurement present in the circuit. Otherwise, one generally gets -equivalent results, so you may try both in an attempt to improve performance without -worrying further about accuracy. - -The transform can be applied to a QNode as follows: - -.. code-block:: python - - @qml.dynamic_one_shot - @qml.qnode(dev) - def my_quantum_function(x, y): - (...) - -.. warning:: - - Dynamic circuits executed with shots should be differentiated with the finite-difference method. - If the ``defer_measurements`` transform is used in analytic mode, ``backprop`` is also a viable - option. - -.. _tree_traversal: - -The tree-traversal algorithm -**************************** - -Dynamic circuit execution is akin to traversing a binary tree where each MCM -corresponds to a node and groups of gates between the MCMs correspond to edges. -The :func:`~.pennylane.dynamic_one_shot` approach picks a branch of the tree randomly -and simulates it from beginning to end. -This is wasteful in many cases; the same branch is simulated many times -when there are more shots than branches for example. -The tree-traversal algorithm does away with such redundancy while retaining the -exponential gains in memory of the one-shot approach compared with the deferred -measurement principle, among other advantages. - -Briefly, it proceeds cutting an :math:`n_{MCM}` circuit into :math:`n_{MCM}+1` -circuit segments. Each segment can be executed on either the 0- or 1-branch, -which gives rise to a binary tree with :math:`2^{n_{MCM}}` leaves. Terminal -measurements are obtained at the leaves, and propagated and combined back up at each -node up the tree. The tree is visited using a depth-first pattern. The tree-traversal -method improves on :func:`~.pennylane.dynamic_one_shot` by taking all samples at a -node or leaf at once. Neglecting overheads, simulating all branches requires the same -amount of computations as :func:`~. pennylane.defer_measurements`, but without the -:math:`O(2^{n_{MCM}})` memory requirement. To save time, a copy of the state vector -is made at every branching point, or MCM, requiring at most :math:`n_{MCM}+1` state -vectors at any instant, an exponential improvement compared with :func:`~. pennylane. -defer_measurements`. Since the counts of many nodes come out to be zero in practice, -it is often possible to ignore entire sub-trees, thereby reducing the computational -burden. - -To summarize, this algorithm gives us the best of both worlds. In the limit of few -shots and/or many mid-circuit measurements, it is as fast as the naive shot-by-shot implementation -because few sub-trees are explored. In the limit of many shots and/or few mid-circuit measurements, it is -equal to or faster than the deferred measurement algorithm (albeit with more -overheads in practice) because each tree edge is visited at most once, all while -reducing the memory requirements exponentially. - -The tree-traversal algorithm is not a transform. Its usage is therefore specified -by passing an ``mcm_method`` option to a QNode (see section -:ref:`"Configuring mid-circuit measurements" `). For example, - -.. code-block:: python - - @qml.qnode(dev, mcm_method="tree-traversal") - def my_quantum_function(x, y): - (...) - -.. warning:: - - The tree-traversal algorithm is only implemented in the :class:`~.pennylane.devices.DefaultQubit` device. - -Resetting wires -*************** - -Wires can be reused as normal after making mid-circuit measurements. Moreover, a measured wire can also be -reset to the :math:`|0 \rangle` state by setting the ``reset`` keyword argument of :func:`~.pennylane.measure` -to ``True``. - -.. code-block:: python3 - - dev = qml.device("default.qubit", wires=3) - - @qml.qnode(dev) - def func(): - qml.PauliX(1) - m_0 = qml.measure(1, reset=True) - qml.PauliX(1) - return qml.probs(wires=[1]) - -Executing this QNode: - ->>> func() -tensor([0., 1.], requires_grad=True) - -Conditional operators -********************* - -Users can create conditional operators controlled on mid-circuit measurements using -:func:`~.pennylane.cond`. The condition for a conditional operator may simply be -the measured value returned by a ``measure()`` call, or we may construct a boolean -condition based on such values and pass it to ``cond()``: - -.. code-block:: python - - @qml.qnode(dev) - @qml.defer_measurements - def qnode_conditional_op_on_zero(x, y): - qml.RY(x, wires=0) - qml.CNOT(wires=[0, 1]) - m_0 = qml.measure(1) - - qml.cond(m_0 == 0, qml.RY)(y, wires=0) - return qml.probs(wires=[0]) - - pars = np.array([0.643, 0.246], requires_grad=True) - ->>> qnode_conditional_op_on_zero(*pars) -tensor([0.88660045, 0.11339955], requires_grad=True) - -For more examples on applying quantum functions conditionally, refer to the -:func:`~.pennylane.cond` documentation. - -Postselecting mid-circuit measurements -************************************** - -PennyLane also supports postselecting on mid-circuit measurement outcomes by specifying the ``postselect`` -keyword argument of :func:`~.pennylane.measure`. Postselection discards outcomes that do not meet the -criteria provided by the ``postselect`` argument. For example, specifying ``postselect=1`` on wire 0 would -be equivalent to projecting the state vector onto the :math:`|1\rangle` state on wire 0, i.e., disregarding -all outcomes where :math:`|0\rangle` is measured on wire 0: - -.. code-block:: python3 - - dev = qml.device("default.qubit") - - @qml.qnode(dev) - def func(x): - qml.RX(x, wires=0) - m0 = qml.measure(0, postselect=1) - qml.cond(m0, qml.PauliX)(wires=1) - return qml.sample(wires=1) - -By postselecting on ``1``, we only consider the ``1`` measurement outcome on wire 0. So, the probability of -measuring ``1`` on wire 1 after postselection should also be 1. Executing this QNode with 10 shots: - ->>> func(np.pi / 2, shots=10) -array([1, 1, 1, 1, 1, 1, 1]) - -Note that only 7 samples are returned. This is because samples that do not meet the postselection criteria are -discarded. To learn more about postselecting mid-circuit measurements, refer to the :func:`~.pennylane.measure` -documentation. - -.. note:: - - Currently, postselection support is only available on :class:`~.pennylane.devices.DefaultQubit`. Using - postselection on other devices will raise an error. - -.. _mid_circuit_measurements_statistics: - -Mid-circuit measurement statistics -********************************** - -Statistics can be collected on mid-circuit measurements along with terminal measurement statistics. -Currently, ``qml.probs``, ``qml.sample``, ``qml.expval``, ``qml.var``, and ``qml.counts`` are supported, -and can be requested along with other measurements. The devices that currently support collecting such -statistics are :class:`~.pennylane.devices.DefaultQubit`, :class:`~.pennylane.devices.DefaultMixed`, and -:class:`~.pennylane.devices.DefaultQubitLegacy`. - -.. code-block:: python3 - - dev = qml.device("default.qubit", wires=2) - - @qml.qnode(dev) - def func(x, y): - qml.RX(x, wires=0) - m0 = qml.measure(0) - qml.cond(m0, qml.RY)(y, wires=1) - return qml.probs(wires=1), qml.probs(op=m0) - -Executing this ``QNode``: - ->>> func(np.pi / 2, np.pi / 4) -(tensor([0.9267767, 0.0732233], requires_grad=True), - tensor([0.5, 0.5], requires_grad=True)) - -Users can also collect statistics on mid-circuit measurements manipulated using arithmetic/boolean operators. -This works for both unary and binary operators. To see a full list of supported operators, refer to the -:func:`~.pennylane.measure` documentation. An example for collecting such statistics is shown below: - -.. code-block:: python3 - - import pennylane as qml - - dev = qml.device("default.qubit") - - @qml.qnode(dev) - def circuit(phi, theta): - qml.RX(phi, wires=0) - m0 = qml.measure(wires=0) - qml.RY(theta, wires=1) - m1 = qml.measure(wires=1) - return qml.sample(~m0 - 2 * m1) - -Executing this ``QNode``: - ->>> circuit(1.23, 4.56, shots=5) -array([-1, -2, 1, -1, 1]) - -Collecting statistics for mid-circuit measurements manipulated using arithmetic/boolean operators is supported -with ``qml.expval``, ``qml.var``, ``qml.sample``, and ``qml.counts``. - -Moreover, statistics for multiple mid-circuit measurements can be collected by passing lists of mid-circuit -measurement values to the measurement process: - -.. code-block:: python3 - - import pennylane as qml - - dev = qml.device("default.qubit") - - @qml.qnode(dev) - def circuit(phi, theta): - qml.RX(phi, wires=0) - m0 = qml.measure(wires=0) - qml.RY(theta, wires=1) - m1 = qml.measure(wires=1) - return qml.sample([m0, m1]) - -Executing this ``QNode``: - ->>> circuit(1.23, 4.56, shots=5) -array([[0, 1], - [1, 1], - [0, 1], - [0, 0], - [1, 1]]) - -Collecting statistics for sequences of mid-circuit measurements is supported with ``qml.sample``, -``qml.probs``, and ``qml.counts``. - -.. warning:: - - When collecting statistics for a list of mid-circuit measurements, values manipulated using - arithmetic operators should not be used as this behaviour is not supported. - -.. _mcm_config: - -Configuring mid-circuit measurements -************************************ - -As seen above, there are multiple ways in which circuits with mid-circuit measurements can be executed with -PennyLane. For ease of use, we provide the following configuration options to users when initializing a -:class:`~pennylane.QNode`: - -* ``mcm_method``: To set the method used for applying mid-circuit measurements. Use ``mcm_method="deferred"`` - to apply the :ref:`deferred measurements principle `, ``mcm_method="one-shot"`` to apply - the :ref:`one-shot transform ` or ``mcm_method="tree-traversal"`` to execute the - :ref:`tree-traversal algorithm `. - When executing with finite shots, ``mcm_method="one-shot"`` - will be the default, and ``mcm_method="deferred"`` otherwise. Additionally, if using :func:`~pennylane.qjit`, - ``mcm_method="single-branch-statistics"`` can also be used and will be the default. Using this method, a single - branch of the execution tree will be randomly explored. - - .. warning:: - - If the ``mcm_method`` argument is provided, the :func:`~pennylane.defer_measurements` or - :func:`~pennylane.dynamic_one_shot` transforms must not be applied directly to the :class:`~pennylane.QNode` - as it can lead to incorrect behaviour. - -* ``postselect_mode``: To configure how invalid shots are handled when postselecting mid-circuit measurements - with finite-shot circuits. Use ``postselect_mode="hw-like"`` to discard invalid samples. In this case, the number - of samples that are used for processing results can be less than the total number of shots. If - ``postselect_mode="fill-shots"`` is used, then the postselected value will be sampled unconditionally, and all - samples will be valid. This is equivalent to sampling until the number of valid samples matches the total number - of shots. The default behaviour is ``postselect_mode="hw-like"``. - - .. code-block:: python3 - - import pennylane as qml - import numpy as np - - dev = qml.device("default.qubit", wires=3, shots=10) - - def circuit(x): - qml.RX(x, 0) - m0 = qml.measure(0, postselect=1) - qml.CNOT([0, 1]) - return qml.sample(qml.PauliZ(0)) - - fill_shots_qnode = qml.QNode(circuit, dev, mcm_method="one-shot", postselect_mode="fill-shots") - hw_like_qnode = qml.QNode(circuit, dev, mcm_method="one-shot", postselect_mode="hw-like") - - >>> fill_shots_qnode(np.pi / 2) - array([-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.]) - >>> hw_like_qnode(np.pi / 2) - array([-1., -1., -1., -1., -1., -1., -1.]) - - .. note:: - - When using the ``jax`` interface, ``postselect_mode="hw-like"`` will have different behaviour based on the - chosen ``mcm_method``. - - * If ``mcm_method="one-shot"``, invalid shots will not be discarded. Instead, invalid samples will be replaced - by ``np.iinfo(np.int32).min``. These invalid samples will not be used for processing final results (like - expectation values), but will appear in the ``QNode`` output if samples are requested directly. Consider - the circuit below: - - .. code-block:: python3 - - import pennylane as qml - import jax - import jax.numpy as jnp - - dev = qml.device("default.qubit", wires=3, shots=10, seed=jax.random.PRNGKey(123)) - - @qml.qnode(dev, postselect_mode="hw-like", mcm_method="one-shot") - def circuit(x): - qml.RX(x, 0) - qml.measure(0, postselect=1) - return qml.sample(qml.PauliZ(0)) - - >>> x = jnp.array(1.8) - >>> f(x) - Array([-2.1474836e+09, -1.0000000e+00, -2.1474836e+09, -2.1474836e+09, - -1.0000000e+00, -2.1474836e+09, -1.0000000e+00, -2.1474836e+09, - -1.0000000e+00, -1.0000000e+00], dtype=float32, weak_type=True) - - * When using ``jax.jit``, using ``mcm_method="deferred"`` is not supported with ``postselect_mode="hw-like"`` and - an error will be raised if this configuration is requested. This is due to limitations of the - :func:`~pennylane.defer_measurements` transform, and this behaviour will change in the future to be more - consistent with ``mcm_method="one-shot"``. - Changing the number of shots ---------------------------- diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index eb52d55607d..80597b8fcb0 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -407,6 +407,10 @@

Documentation 📝

+* Move information about mid-circuit measurements from the measurements quickstart page to its own + [mid-circuit measurements quickstart page](https://docs.pennylane.ai/en/stable/introduction/mid_circuit_measurements.html) + [(#5870)](https://github.com/PennyLaneAI/pennylane/pull/5870) + * The documentation for the `default.tensor` device has been added. [(#5719)](https://github.com/PennyLaneAI/pennylane/pull/5719) diff --git a/pennylane/ops/op_math/condition.py b/pennylane/ops/op_math/condition.py index 1764b554ed3..d3da2814719 100644 --- a/pennylane/ops/op_math/condition.py +++ b/pennylane/ops/op_math/condition.py @@ -237,7 +237,7 @@ def false_fn(): .. code-block:: python3 - dev = qml.device("default.qubit", wires=2) + dev = qml.device("default.qubit") def qfunc(par, wires): qml.Hadamard(wires[0]) @@ -256,6 +256,26 @@ def qnode(x): >>> qnode(par) tensor(0.3522399, requires_grad=True) + **Postprocessing multiple measurements into a condition** + + The Boolean condition for ``cond`` may consist of arithmetic expressions + of one or multiple mid-circuit measurements: + + .. code-block:: python3 + + def cond_fn(mcms): + first_term = np.prod(mcms) + second_term = (2 ** np.arange(len(mcms))) @ mcms + return (1 - first_term) * (second_term > 3) + + @qml.qnode(dev) + def qnode(x): + ... + mcms = [qml.measure(w) for w in range(4)] + qml.cond(cond_fn(mcms), qml.RX)(x, wires=4) + ... + return qml.expval(qml.Z(1)) + **Passing two quantum functions** In the qubit model, single-qubit measurements may result in one of two diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index 308015faaa9..3ae456d03f8 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -108,8 +108,8 @@ def _to_qfunc_output_type( class QNode: """Represents a quantum node in the hybrid computational graph. - A *quantum node* contains a :ref:`quantum function ` - (corresponding to a :ref:`variational circuit `) + A *quantum node* contains a :ref:`quantum function ` (corresponding to + a `variational circuit `) and the computational device it is executed on. The QNode calls the quantum function to construct a :class:`~.QuantumTape` instance representing