Post selection error

I aim to perform post-selection on the first outcome of the third qubit. Subsequently, I wish to execute a partial trace on this third subsystem. However, I am facing a limitation: post-selection is only feasible with default.qubit, which, unfortunately, does not support the use of noisy channels. How can I effectively carry out post-selection with ‘‘default.mixed’’?’

import pennylane as qml
from pennylane import numpy as np

dev=qml.device('default.mixed',wires=3)
p=0.1

def circuit1(params):
    qml.Hadamard(wires=0)
    qml.CNOT(wires=[0,1])
    qml.U3(params[0],params[1],params[2],wires=1)
    qml.U3(params[3],params[4],params[5],wires=2)
    qml.CNOT(wires=[2,1])
    qml.RZ(params[6],wires=1)
    qml.RY(params[7],wires=2)
    qml.CNOT(wires=[1,2])
    qml.RY(params[8],wires=2)
    qml.CNOT(wires=[2,1])
    qml.U3(params[9],params[10],params[11],wires=1)
    qml.U3(params[12],params[13],params[14],wires=2)
    qml.PhaseDamping(p,wires=1)
    qml.PhaseDamping(p,wires=2)
    qml.adjoint(qml.U3(params[12],params[13],params[14],wires=2))
    qml.adjoint(qml.U3(params[9],params[10],params[11],wires=1))
    qml.adjoint(qml.CNOT(wires=[2,1]))
    qml.adjoint(qml.RY(params[8],wires=2))
    qml.adjoint(qml.CNOT(wires=[1,2]))
    qml.adjoint(qml.RY(params[7],wires=2))
    qml.adjoint(qml.RZ(params[6],wires=1))
    qml.adjoint(qml.CNOT(wires=[2,1]))
    qml.adjoint(qml.U3(params[3],params[4],params[5],wires=2))
    qml.adjoint(qml.U3(params[0],params[1],params[2],wires=1))
    
params = np.array(range(0, 15))
circuit_drawer = qml.draw_mpl(circuit1)(params)

pr = np.kron(np.eye(4), np.array([[1, 0], [0, 0]]))

@qml.qnode(dev)
def circuit2(params):
    np.dot(np.dot(pr.conj().T,circuit1(params)), pr)#Post selection 
    return qml.density_matrix(wires=[0,1])

error message

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[13], line 1
----> 1 print(circuit2(params))

File ~\anaconda3\Lib\site-packages\pennylane\qnode.py:976, in QNode.__call__(self, *args, **kwargs)
    973         kwargs["shots"] = _get_device_shots(self._original_device)
    975 # construct the tape
--> 976 self.construct(args, kwargs)
    978 cache = self.execute_kwargs.get("cache", False)
    979 using_custom_cache = (
    980     hasattr(cache, "__getitem__")
    981     and hasattr(cache, "__setitem__")
    982     and hasattr(cache, "__delitem__")
    983 )

File ~\anaconda3\Lib\site-packages\pennylane\qnode.py:862, in QNode.construct(self, args, kwargs)
    859     self.interface = qml.math.get_interface(*args, *list(kwargs.values()))
    861 with qml.queuing.AnnotatedQueue() as q:
--> 862     self._qfunc_output = self.func(*args, **kwargs)
    864 self._tape = QuantumScript.from_queue(q, shots)
    866 params = self.tape.get_parameters(trainable_only=False)

Cell In[12], line 3, in circuit2(params)
      1 @qml.qnode(dev)
      2 def circuit2(params):
----> 3     np.dot(np.dot(pr.conj().T,circuit1(params)), pr)
      4     return qml.density_matrix(wires=[0,1])

File ~\anaconda3\Lib\site-packages\pennylane\numpy\wrapper.py:117, in tensor_wrapper.<locals>._wrapped(*args, **kwargs)
    114         tensor_kwargs["requires_grad"] = _np.any([i.requires_grad for i in tensor_args])
    116 # evaluate the original object
--> 117 res = obj(*args, **kwargs)
    119 if isinstance(res, _np.ndarray):
    120     # only if the output of the object is a ndarray,
    121     # then convert to a PennyLane tensor
    122     res = tensor(res, **tensor_kwargs)

File ~\anaconda3\Lib\site-packages\autograd\tracer.py:48, in primitive.<locals>.f_wrapped(*args, **kwargs)
     46     return new_box(ans, trace, node)
     47 else:
---> 48     return f_raw(*args, **kwargs)

File <__array_function__ internals>:180, in dot(*args, **kwargs)

File ~\anaconda3\Lib\site-packages\pennylane\numpy\tensor.py:155, in tensor.__array_ufunc__(self, ufunc, method, *inputs, **kwargs)
    151 args = [i.unwrap() if hasattr(i, "unwrap") else i for i in inputs]
    153 # call the ndarray.__array_ufunc__ method to compute the result
    154 # of the vectorized ufunc
--> 155 res = super().__array_ufunc__(ufunc, method, *args, **kwargs)
    157 if isinstance(res, Operator):
    158     return res

TypeError: unsupported operand type(s) for *: 'float' and 'DensityMatrixMP'

Hey @Aaqib_Ali, welcome to the forum!

The problem is with this line:

QNodes in PennyLane can’t have mathematical operations like this. Also, calling circuit1(params) here returns nothing because it’s just a quantum function with operations in it. Generally, how circuits work in PennyLane is by defining functions that have quantum operations/instructions in them, return a measurement (like any of these: Measurements — PennyLane 0.34.0 documentation), and by decorating the function with @qml.qnode.

What you want to do is something like this:

@qml.qnode(dev)
def circuit2(params):
    circuit1(params) # makes circuit2 have all of your operations in circuit1
    qml.Projector(...) # some instance of a projector operator based on your desired output
    return qml.density_matrix(wires=[0,1])

Here, you might be able to accomplish what you want to do via qml.Projector (qml.Projector — PennyLane 0.34.0 documentation) or qml.QubitUnitary
(qml.QubitUnitary — PennyLane 0.34.0 documentation). You might also be able to accomplish post-selection more elegantly with qml.measure: qml.measure — PennyLane 0.34.0 documentation. Just use the postselect keyword argument :slight_smile:.

Let me know if this helps!