Mid-circuit reset usage

Hi there,
With the 0.32 update, there is the feature to reset wires with mid-circuit measurements, but I’m not following the usage entirely.

In the example on the ‘measurements’ page of the pennylane docs, 3 wires are used to reset the ‘1’ wire. When I try this with 2 wires, I get an error as below.

For large circuits that already have many wires, where one might like to reset an ancilla wire instead of using another, this seems that this is just adding another wire. I am wondering if there is a way to reset a wire without needing an extra wire in the device (or if this is the intended behaviour).

@qml.qnode(qml.device("default.qubit", wires=2))
def func():
    qml.PauliX(1)
    m_0 = qml.measure(1, reset=True)
    return qml.probs(wires=[1])
func()

Resulting in:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/qdrive/anaconda3/envs/penny/lib/python3.10/site-packages/pennylane/wires.py:239, in Wires.index(self, wire)
    238 try:
--> 239     return self._labels.index(wire)
    240 except ValueError as e:

ValueError: tuple.index(x): x not in tuple

The above exception was the direct cause of the following exception:

WireError                                 Traceback (most recent call last)
Cell In[14], line 7
      5     #qml.PauliX(1)
      6     return qml.probs(wires=[1])
----> 7 func()

File ~/qdrive/anaconda3/envs/penny/lib/python3.10/site-packages/pennylane/qnode.py:989, in QNode.__call__(self, *args, **kwargs)
    986     self.execute_kwargs.pop("mode")
    988 # pylint: disable=unexpected-keyword-arg
--> 989 res = qml.execute(
    990     (self._tape,),
    991     device=self.device,
    992     gradient_fn=self.gradient_fn,
    993     interface=self.interface,
    994     transform_program=self.transform_program,
    995     gradient_kwargs=self.gradient_kwargs,
    996     override_shots=override_shots,
    997     **self.execute_kwargs,
    998 )
   1000 res = res[0]
   1002 # convert result to the interface in case the qfunc has no parameters

File ~/qdrive/anaconda3/envs/penny/lib/python3.10/site-packages/pennylane/interfaces/execution.py:636, in execute(tapes, device, gradient_fn, interface, transform_program, grad_on_execution, gradient_kwargs, cache, cachesize, max_diff, override_shots, expand_fn, max_expansion, device_batch_transform)
    634 # Exiting early if we do not need to deal with an interface boundary
    635 if no_interface_boundary_required:
--> 636     results = inner_execute(tapes)
    637     results = batch_fn(results)
    638     return program_post_processing(results)

File ~/qdrive/anaconda3/envs/penny/lib/python3.10/site-packages/pennylane/interfaces/execution.py:255, in _make_inner_execute.<locals>.inner_execute(tapes, **_)
    253 if numpy_only:
    254     tapes = tuple(qml.transforms.convert_to_numpy_parameters(t) for t in tapes)
--> 255 return cached_device_execution(tapes)

File ~/qdrive/anaconda3/envs/penny/lib/python3.10/site-packages/pennylane/interfaces/execution.py:377, in cache_execute.<locals>.wrapper(tapes, **kwargs)
    372         return (res, []) if return_tuple else res
    374 else:
    375     # execute all unique tapes that do not exist in the cache
    376     # convert to list as new device interface returns a tuple
--> 377     res = list(fn(tuple(execution_tapes.values()), **kwargs))
    379 final_res = []
    381 for i, tape in enumerate(tapes):

File ~/qdrive/anaconda3/envs/penny/lib/python3.10/contextlib.py:79, in ContextDecorator.__call__.<locals>.inner(*args, **kwds)
     76 @wraps(func)
     77 def inner(*args, **kwds):
     78     with self._recreate_cm():
---> 79         return func(*args, **kwds)

File ~/qdrive/anaconda3/envs/penny/lib/python3.10/site-packages/pennylane/_qubit_device.py:629, in QubitDevice.batch_execute(self, circuits)
    624 for circuit in circuits:
    625     # we need to reset the device here, else it will
    626     # not start the next computation in the zero state
    627     self.reset()
--> 629     res = self.execute(circuit)
    630     results.append(res)
    632 if self.tracker.active:

File ~/qdrive/anaconda3/envs/penny/lib/python3.10/site-packages/pennylane/_qubit_device.py:337, in QubitDevice.execute(self, circuit, **kwargs)
    334 self.check_validity(circuit.operations, circuit.observables)
    336 # apply all circuit operations
--> 337 self.apply(circuit.operations, rotations=self._get_diagonalizing_gates(circuit), **kwargs)
    339 # generate computational basis samples
    340 if self.shots is not None or circuit.is_sampled:

File ~/qdrive/anaconda3/envs/penny/lib/python3.10/site-packages/pennylane/devices/default_qubit.py:294, in DefaultQubit.apply(self, operations, rotations, **kwargs)
    292         self._state = self._apply_parametrized_evolution(self._state, operation)
    293     else:
--> 294         self._state = self._apply_operation(self._state, operation)
    296 # store the pre-rotated state
    297 self._pre_rotated_state = self._state

File ~/qdrive/anaconda3/envs/penny/lib/python3.10/site-packages/pennylane/devices/default_qubit.py:331, in DefaultQubit._apply_operation(self, state, operation)
    329 if str(operation.name) in self._apply_ops:  # cast to string because of Tensor
    330     shift = int(self._ndim(state) > self.num_wires)
--> 331     axes = [ax + shift for ax in self.wires.indices(wires)]
    332     return self._apply_ops[operation.name](state, axes)
    334 matrix = self._asarray(self._get_unitary_matrix(operation), dtype=self.C_DTYPE)

File ~/qdrive/anaconda3/envs/penny/lib/python3.10/site-packages/pennylane/wires.py:265, in Wires.indices(self, wires)
    262 if not isinstance(wires, Iterable):
    263     return [self.index(wires)]
--> 265 return [self.index(w) for w in wires]

File ~/qdrive/anaconda3/envs/penny/lib/python3.10/site-packages/pennylane/wires.py:265, in <listcomp>(.0)
    262 if not isinstance(wires, Iterable):
    263     return [self.index(wires)]
--> 265 return [self.index(w) for w in wires]

File ~/qdrive/anaconda3/envs/penny/lib/python3.10/site-packages/pennylane/wires.py:241, in Wires.index(self, wire)
    239     return self._labels.index(wire)
    240 except ValueError as e:
--> 241     raise WireError(f"Wire with label {wire} not found in {self}.") from e

WireError: Wire with label 2 not found in <Wires = [0, 1]>.

Using Python 3.10.11 and pennylane 0.32.0

Hey @jarrett_smalley, welcome to the forum! :smiley:

Thank you for trying out the new qubit reset feature we recently introduced :grin:. Indeed, the behaviour you are seeing is anticipated. Running circuits where a qubit is being reused will result in the qml.defer_measurements transform being applied, which replaces each reused wire with an additional qubit. We plan on having this not be the case in future releases :slight_smile:.

If this answers your question and you have a moment, we have a PennyLane survey where you can let us know your thoughts about PennyLane so that we can keep bringing you amazing features :star_struck:. We appreciate your feedback!