Mid Circuits Measurements with default.mixed device

Hello.

I am currently trying to create a circuit that has both mid circuit measurements and noise. Below is a simple example of a circuit that has both features.

import pennylane as qml
from pennylane import numpy as np

num_qubits = 2
dev = qml.device('default.mixed', wires = num_qubits)

@qml.qnode(dev)
def circuit():
  qml.Hadamard(wires = 0)
  qml.CNOT(wires = [0,1])
  qml.measure(0, reset = True)
  qml.AmplitudeDamping(0.5, wires = 0)
  qml.AmplitudeDamping(0.5, wires = 1)
  return qml.expval(qml.PauliZ(0)@qml.PauliZ(1))

circuit()

Running this code gives the error message which you can find below. The error seems to be related to not finding some wires.

Here is the error message

---------------------------------------------------------------------------
WireError                                 Traceback (most recent call last)
/usr/local/lib/python3.11/dist-packages/pennylane/devices/_legacy_device.py in map_wires(self, wires)
    387         try:
--> 388             mapped_wires = wires.map(self.wire_map)
    389         except WireError as e:

18 frames
/usr/local/lib/python3.11/dist-packages/pennylane/wires.py in map(self, wire_map)
    324             if w not in wire_map:
--> 325                 raise WireError(f"No mapping for wire label {w} specified in wire map {wire_map}.")
    326 

WireError: No mapping for wire label 2 specified in wire map OrderedDict([(0, 0), (1, 1)]).

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

WireError                                 Traceback (most recent call last)
<ipython-input-7-f80880ac011a> in <cell line: 0>()
     11   return qml.expval(qml.PauliZ(0)@qml.PauliZ(1))
     12 
---> 13 circuit()

/usr/local/lib/python3.11/dist-packages/pennylane/workflow/qnode.py in __call__(self, *args, **kwargs)
    903         if qml.capture.enabled():
    904             return capture_qnode(self, *args, **kwargs)
--> 905         return self._impl_call(*args, **kwargs)
    906 
    907 

/usr/local/lib/python3.11/dist-packages/pennylane/workflow/qnode.py in _impl_call(self, *args, **kwargs)
    879         self._transform_program.set_classical_component(self, args, kwargs)
    880 
--> 881         res = qml.execute(
    882             (tape,),
    883             device=self.device,

/usr/local/lib/python3.11/dist-packages/pennylane/workflow/execution.py in execute(tapes, device, diff_method, interface, transform_program, inner_transform, config, grad_on_execution, gradient_kwargs, cache, cachesize, max_diff, device_vjp, mcm_config, gradient_fn)
    230         return post_processing(tapes)
    231 
--> 232     results = run(tapes, device, config, inner_transform)
    233     return post_processing(results)

/usr/local/lib/python3.11/dist-packages/pennylane/workflow/run.py in run(tapes, device, config, inner_transform_program)
    285     )
    286     if no_interface_boundary_required:
--> 287         results = inner_execute(tapes)
    288         return results
    289 

/usr/local/lib/python3.11/dist-packages/pennylane/workflow/run.py in inner_execute(tapes)
    245 
    246         if transformed_tapes:
--> 247             results = device.execute(transformed_tapes, execution_config=execution_config)
    248         else:
    249             results = ()

/usr/local/lib/python3.11/dist-packages/pennylane/devices/modifiers/single_tape_support.py in execute(self, circuits, execution_config)
     30             is_single_circuit = True
     31             circuits = (circuits,)
---> 32         results = batch_execute(self, circuits, execution_config)
     33         return results[0] if is_single_circuit else results
     34 

/usr/local/lib/python3.11/dist-packages/pennylane/devices/legacy_facade.py in execute(self, circuits, execution_config)
    372         first_shot = circuits[0].shots
    373         if all(t.shots == first_shot for t in circuits):
--> 374             return _set_shots(dev, first_shot)(dev.batch_execute)(circuits, **kwargs)
    375         return tuple(
    376             _set_shots(dev, t.shots)(dev.batch_execute)((t,), **kwargs)[0] for t in circuits

/usr/lib/python3.11/contextlib.py in inner(*args, **kwds)
     79         def inner(*args, **kwds):
     80             with self._recreate_cm():
---> 81                 return func(*args, **kwds)
     82         return inner
     83 

/usr/local/lib/python3.11/dist-packages/pennylane/devices/_qubit_device.py in batch_execute(self, circuits, **kwargs)
    481             self.reset()
    482 
--> 483             res = self.execute(circuit, **kwargs)
    484             results.append(res)
    485 

/usr/local/lib/python3.11/dist-packages/pennylane/logging/decorators.py in wrapper_entry(*args, **kwargs)
     59                 **_debug_log_kwargs,
     60             )
---> 61         return func(*args, **kwargs)
     62 
     63     @wraps(func)

/usr/local/lib/python3.11/dist-packages/pennylane/devices/default_mixed.py in execute(self, circuit, **kwargs)
    851                 wires_list.append(m.wires)
    852             self.measured_wires = qml.wires.Wires.all_wires(wires_list)
--> 853         return super().execute(circuit, **kwargs)
    854 
    855     @debug_logger

/usr/local/lib/python3.11/dist-packages/pennylane/devices/_qubit_device.py in execute(self, circuit, **kwargs)
    269             return tuple(results)
    270         # apply all circuit operations
--> 271         self.apply(
    272             circuit.operations,
    273             rotations=self._get_diagonalizing_gates(circuit),

/usr/local/lib/python3.11/dist-packages/pennylane/logging/decorators.py in wrapper_entry(*args, **kwargs)
     59                 **_debug_log_kwargs,
     60             )
---> 61         return func(*args, **kwargs)
     62 
     63     @wraps(func)

/usr/local/lib/python3.11/dist-packages/pennylane/devices/default_mixed.py in apply(self, operations, rotations, **kwargs)
    866 
    867         for operation in operations:
--> 868             self._apply_operation(operation)
    869 
    870         # store the pre-rotated state

/usr/local/lib/python3.11/dist-packages/pennylane/devices/default_mixed.py in _apply_operation(self, operation)
    793                 self._apply_channel_tensordot(matrices, wires)
    794             else:
--> 795                 self._apply_channel(matrices, wires)
    796 
    797     # pylint: disable=arguments-differ

/usr/local/lib/python3.11/dist-packages/pennylane/devices/default_mixed.py in _apply_channel(self, kraus, wires)
    413             wires (Wires): target wires
    414         """
--> 415         channel_wires = self.map_wires(wires)
    416         rho_dim = 2 * self.num_wires
    417         num_ch_wires = len(channel_wires)

/usr/local/lib/python3.11/dist-packages/pennylane/devices/_legacy_device.py in map_wires(self, wires)
    388             mapped_wires = wires.map(self.wire_map)
    389         except WireError as e:
--> 390             raise WireError(
    391                 f"Did not find some of the wires {wires} on device with wires {self.wires}."
    392             ) from e

WireError: Did not find some of the wires Wires([0, 2]) on device with wires Wires([0, 1]).

I have found that simply adding one qubit to the device solves the error message and the circuit seems to give the correct output.

Unfortunately this is really a bad solution for the code I am developing since I am applying many mid circuits measurements and the more mid circuits measurements the more extra qubits one needs to add, making everything unpracticable. As an example here is a circuit with 2 mid circuit measurements and you’ll find that 2 qubits need to be added for the error to go away.

import pennylane as qml
from pennylane import numpy as np

num_qubits = 2
dev = qml.device('default.mixed', wires = num_qubits)

@qml.qnode(dev)
def circuit():
  qml.Hadamard(wires = 0)
  qml.CNOT(wires = [0,1])
  qml.measure(0, reset = True)
  qml.AmplitudeDamping(0.5, wires = 0)
  qml.AmplitudeDamping(0.5, wires = 1)
  qml.RY(np.pi/2, wires = 0)
  qml.measure(0, reset = True)
  return qml.expval(qml.PauliZ(0)@qml.PauliZ(1))

circuit()

Do you have any recommendations on how to solve this issue without changing the number of qubits?

Here is information on the pennylane version.

Pennylane Version:
Name: PennyLane
Version: 0.40.0
Summary: PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network.
Home-page: https://github.com/PennyLaneAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /usr/local/lib/python3.11/dist-packages
Requires: appdirs, autograd, autoray, cachetools, diastatic-malt, networkx, numpy, packaging, pennylane-lightning, requests, rustworkx, scipy, tomlkit, typing-extensions
Required-by: PennyLane_Lightning

Platform info:           Linux-6.1.85+-x86_64-with-glibc2.35
Python version:          3.11.11
Numpy version:           2.0.2
Scipy version:           1.14.1
Installed devices:
- default.clifford (PennyLane-0.40.0)
- default.gaussian (PennyLane-0.40.0)
- default.mixed (PennyLane-0.40.0)
- default.qubit (PennyLane-0.40.0)
- default.qutrit (PennyLane-0.40.0)
- default.qutrit.mixed (PennyLane-0.40.0)
- default.tensor (PennyLane-0.40.0)
- null.qubit (PennyLane-0.40.0)
- reference.qubit (PennyLane-0.40.0)
- lightning.qubit (PennyLane_Lightning-0.40.0)

Hi @Francesco_Ghisoni , welcome to the Forum!

You’re right, you need to add an extra qubit per mid-circuit measurement, which makes things slow. This is because the deferred measurements principle is use by default under the hood.

The Dynamic quantum circuits section of the PennyLane docs has some nice explanations in the Simulation techniques sub-section. Here you’ll see a comparison of three methods.

  • Deferred measurements is the only method supporting analytic simulations.
  • If you’re ok with using shots you can try dynamic one-shot sampling (mcm_method="one-shot"). I’m not 100% sure whether it will work for you, so let me know if you run into any issues.
  • The tree-traversal method doesn’t work with default.mixed, so it won’t work for you.

Let me know if this helps!