Dynamic quantum circuits¶
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
measure()
, and can be used as follows:
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
measure()
, cond()
, and statistics
of mid-circuit measurements, as well as information about simulation
strategies and how to configure them further below.
Additional information can be found in the documentation of the individual
methods. Also consider our
Introduction to mid-circuit measurements
how-to on collecting statistics of mid-circuit measurements,
and 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 \(|0 \rangle\) state by setting reset=True
in measure()
:
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)
Postselecting mid-circuit measurements¶
PennyLane also supports postselecting on mid-circuit measurement outcomes by specifying the
postselect
keyword argument of 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 \(|1\rangle\) state, i.e., disregarding all outcomes where \(|0\rangle\) is measured:
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
>>> 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 “Configuring mid-circuit measurements”.
Conditional operators¶
Users can create conditional operators controlled on mid-circuit measurements using
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()
:
@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)
>>> qnode_conditional_op_on_zero(*pars)
tensor([0.88660045, 0.11339955], requires_grad=True)
For more examples, refer to the cond()
documentation
and the how-to on creating dynamic circuits with mid-circuit measurements.
Mid-circuit measurement statistics¶
Statistics of mid-circuit measurements can be collected along with terminal measurement statistics.
Currently, counts()
, expval()
, probs()
, sample()
, and var()
are supported.
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
:
>>> 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
measure()
documentation. An example for collecting such statistics is shown below:
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
:
>>> 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 counts()
, expval()
, sample()
, and var()
.
Moreover, statistics for multiple mid-circuit measurements can be collected by passing lists of mid-circuit measurement values to the measurement process:
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
:
>>> 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
counts()
, probs()
, and sample()
.
Warning
When collecting statistics for a sequence of mid-circuit measurements, the sequence must not contain arithmetic expressions.
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
Simulation technique |
Memory |
Time |
Differentiation |
shots |
analytic |
---|---|---|---|---|---|
Deferred measurements |
\(\mathcal{O}(2^{n_{MCM}})\) |
\(\mathcal{O}(2^{n_{MCM}})\) |
yes \({}^1\) |
yes |
yes |
Dynamic one-shot |
\(\mathcal{O}(1)\) |
\(\mathcal{O}(n_{shots})\) |
finite differences\({}^2\) |
yes |
no |
Tree-traversal |
\(\mathcal{O}(n_{MCM}+1)\) |
\(\mathcal{O}(min(n_{shots}, 2^{n_{MCM}}))\) |
finite differences\({}^2\) |
yes |
no |
\({}^1\) Backpropagation and finite differences are fully supported. The adjoint method and the parameter-shift rule are supported if no postselection is used.
\({}^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
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 “Configuring mid-circuit 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 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.
>>> 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:
>>> print(qml.draw(my_qnode)(*pars))
0: ──RY(0.64)─╭●───────RY(0.25)─┤ Probs
1: ───────────╰X──┤↗├──║────────┤
╚═══╩════════╡ <MCM>
>>> print(qml.draw(deferred_qnode)(*pars))
0: ──RY(0.64)─╭●────╭RY(0.25)─┤ Probs
1: ───────────╰X─╭●─│─────────┤
2: ──────────────╰X─╰●────────┤ <None>
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
DefaultQubit
.
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 dynamic_one_shot()
.
As the name suggests, this transform only works for a QNode
executing
with finite shots and it requires the device to support mid-circuit measurements natively.
The dynamic_one_shot()
transform is usually advantageous compared
with the 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 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 defer_measurements()
, but without the
\(O(2^{n_{MCM}})\) memory cost. To save time, a copy of the state vector
is made at every mid-circuit measurement, requiring \(n_{MCM}+1\) state
vectors, an exponential improvement over 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
DefaultQubit
device, and currently does
not support just-in-time (JIT) compilation.
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
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 ismcm_method="one-shot"
when executing with shots, and"deferred"
otherwise. When usingqjit()
, there is the additional (default) optionmcm_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 theQNode
.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"
.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")
>>> 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 ofQNode
s that return samples directly.When using
jax.jit
, the combination"deferred"
and"hw-like"
is not supported, due to limitations of thedefer_measurements()
transform. This behaviour will change in the future.