How to apply multiple mid-circuit measurements on the same wire with resetting and post-selection?

Hello! I’m trying to implement a circuit with several mid-circuit measurements on the auxiliary qubit. A post-selection and a resetting is intended to be applied after the measurement. The following is a simple example, and it will run into an error:

import pennylane as qml

dev=qml.device("default.qubit", wires=2)

@qml.qnode(dev)
def circuit():
    for i in range(3):
        qml.H(0)
        qml.measure(0, reset=True, postselect=0)
    return qml.expval(qml.X(1))

circuit()

The error information is:

File "C:\Users\Timmy\AppData\Local\Programs\Python\Python312\Lib\site-packages\pennylane\devices\preprocess.py", line 144, in validate_device_wires
    raise WireError(
pennylane.exceptions.WireError: Cannot run circuit(s) on default.qubit as they contain wires not found on the device: {2, 3, 4}

It indicates that I’m trying to operate the qubit 2 and 3, but my code does not operate these qubits. If I replace the statement qml.measure(0, reset=True, postselect=0) with qml.measure(0, reset=False, postselect=0), the same type of error will also occur.

My qml.about:

Version: 0.43.1
Summary: PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemi
stry. Train a quantum computer the same way as a neural network.                                                        Home-page:
Author:
Author-email:
License-Expression: Apache-2.0
Location: C:\Users\Timmy\AppData\Local\Programs\Python\Python312\Lib\site-packages
Requires: appdirs, autograd, autoray, cachetools, diastatic-malt, networkx, numpy, packaging, pennylane-lightning, reque
sts, rustworkx, scipy, tomlkit, typing_extensions                                                                       Required-by: pennylane_lightning

Platform info:           Windows-11-10.0.26220-SP0
Python version:          3.12.7
Numpy version:           2.3.4
Scipy version:           1.16.2
JAX version:             None
Installed devices:
- default.clifford (pennylane-0.43.1)
- default.gaussian (pennylane-0.43.1)
- default.mixed (pennylane-0.43.1)
- default.qubit (pennylane-0.43.1)
- default.qutrit (pennylane-0.43.1)
- default.qutrit.mixed (pennylane-0.43.1)
- default.tensor (pennylane-0.43.1)
- null.qubit (pennylane-0.43.1)
- reference.qubit (pennylane-0.43.1)
- lightning.qubit (pennylane_lightning-0.43.0)

Hi @Tz_19 ,

This is actually due to the way mid-circuit measurements are performed. It’s a very common error! I recently explained it in Forum post #9102, so I would recommend checking it out for more details. In short, you need to add more wires to your device, so the easiest way to do it is to let the device automatically detect the number of wires it needs. You can do this by simply not setting a number of wires when you create an instance of default.qubit.
dev=qml.device("default.qubit")

Let me know if this solves your issue!

Hi @CatalinaAlbornoz, thank you for your explanation! It seems that the memory usage will grow up quickly with the number of measurements, just like I’m simulating a larger circuit with more qubits. For example, If I run the following code:

import pennylane as qml

dev=qml.device("default.qubit")

@qml.qnode(dev)
def circuit():
    for i in range(32):
        qml.H(0)
        qml.CNOT(wires=[0,1])
        qml.measure(0, reset=True, postselect=0)
    return qml.expval(qml.X(1))

print(circuit())

The following error will raise:

  File "C:\Users\Timmy\AppData\Local\Programs\Python\Python312\Lib\site-packages\pennylane\devices\qubit\initialize_state.py", line 43, in create_initial_state
    state = np.zeros((2,) * num_wires, dtype=complex)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
numpy._core._exceptions._ArrayMemoryError: Unable to allocate 256. GiB for an array with shape (2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2) and data type complex128

Is there any method to avoid the memory usage explosion currently? Meanwhile, given that there is a reset argument of qml.measure(), will it be possible to reuse the measured qubit directly without increasing the qubit number in the future?

Hi @Tz_19 ,

As you have your code right now this would require 33 qubits, which indeed needs a lot of memory. This is because by default the mid-circuit-measurements use the deferred measurements principle. If you check the simulation techniques section on the docs page for dynamic circuits, you’ll find a nice table with an explanation below, which suggests two different methods you could use, with their advantages and disadvantages. I would recommend that you try those methods (dynamic one-shot and tree-traversal) by setting them in the qnode argument as described in the following sections of that page. Here’s an example code:

import pennylane as qml

dev=qml.device("default.qubit")

@qml.qnode(dev, mcm_method="tree-traversal")
def circuit():
    for i in range(32):
        qml.H(0)
        qml.CNOT(wires=[0,1])
        qml.measure(0, reset=True, postselect=0)
    return qml.expval(qml.X(1))

print(circuit())

I hope this helps!

1 Like

I ran into this issue recently, another thing that works but may not always fit the use case is adding ‘shots’ to the device.

dev=qml.device("default.qubit", shots=100)

@qml.qnode(dev)
def circuit():
    for i in range(32):
        qml.H(0)
        qml.CNOT(wires=[0,1])
        qml.measure(0, reset=True, postselect=0)
    return qml.expval(qml.X(1))

print(circuit())

But this will get really slow possibly for that many qubits

Hi @vinayak19th ,

I see two issues here.

On one hand the way you’re using shots has been deprecated. Instead of adding them as part of the device, you should set the shots right above the qnode with: @qml.set_shots(num_shots). You can see this in the code example below.

The second problem is that you’re postselecting too many times so you’re ending up with a state with zero probability of being measured. Here’s what’s happening:

You’re postselecting 32 times on the state zero. This means that you’re preparing qubit 0 in superposition, entangling it with qubit 1, and then measuring qubit 0. If qubit 0 is measured in state 0 you continue, otherwise you discard the result. Since you have 100 shots you’re doing it 100 times. So after the first postselection you’re likely to end up with only 50 measurements, after the second postselection you’re likely to end with 25 measurements, then 12, then 6, then 3, then 1, then none. So by the 7th postselection you’re likely to have no result at all.

If you look at the docs for qml.measure you’ll see this:

If postselection is requested on a state with zero probability of being measured, the result may contain NaN or Inf values

So this is why you’ll get NaN if you do postselection too many times. I recommend in that case that you don’t postselect, as shown in the code below:

import pennylane as qml
import numpy as np

np.random.seed(1)

dev=qml.device("default.qubit")

@qml.set_shots(100)
@qml.qnode(dev)
def circuit():
    for i in range(32):
        qml.H(0)
        qml.CNOT(wires=[0,1])
        qml.measure(0, reset=True)#, postselect=0)
    return qml.expval(qml.X(1))

print(circuit())
qml.draw_mpl(circuit)()

The PennyLane Codebook module on Dynamic circuits has some really good explanations and exercises on this topic. Please try the exercises and let me know if they help! The codercises are the best way to learn and validate your understanding.

I hope this helps clarify things.