State of every qubit in a circuit

Say I have a Bell state (for simplicity), B++. Is it possible to get the state of each qubit, i.e qubit 1 and 2 separately, or more specifically at each layer of the circuit? In this particular case, using partial trace, I can see that
qubit_1_state = qubit_2_state = \frac{1}{2} \mathbb{I}_{2 \times 2} . However, if I want to understand how PennyLane applies noise model to individual wire and understand the state evolution of an each qubit, is it possible to get the state of each qubit individually in such scenario?

For example: Say I have a circuit

dev = qml.device('default.mixed',wires = 2)
@qml.qnode(dev)
def circuit(p):
    qml.Hadamard(wires = 0)
    qml.CNOT(wires=[0,1])
    qml.DepolarizingChannel(0.05, wires = 0)
    qml.DepolarizingChannel(0.05, wires = 1)
    return qml.state()

I want to see what is the state of first qubit and second qubit separately at the end of the circuit. I tried to compute
\rho_1' = \mathcal{E}(\rho_1) = (1 - p) \rho_1 + \frac{p}{3} (X \rho_1 X + Y \rho_1 Y + Z \rho_1 Z)
where \rho_1 is the partial trace of first qubit. But the state does not change for any value of 0\leq p \leq 1. However, PennyLane can simulate the noisy only in a single qubit.

I would really appreciate if you could provide some info on this matter?
Thank you.

Hey @raven_of_asgard, welcome to the forum! :partying_face:

I think qml.Snapshot might be your ticket: qml.Snapshot — PennyLane 0.34.0 documentation

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

@qml.qnode(dev)
def circuit():
    qml.Snapshot(measurement=qml.density_matrix(wires=[0]))
    qml.Snapshot(measurement=qml.density_matrix(wires=[1]))
    qml.Hadamard(wires=0)
    qml.Snapshot(measurement=qml.density_matrix(wires=[0]))
    qml.Snapshot(measurement=qml.density_matrix(wires=[1]))
    qml.CNOT(wires=[0, 1])
    qml.Snapshot(measurement=qml.density_matrix(wires=[0]))
    qml.Snapshot(measurement=qml.density_matrix(wires=[1]))

    return qml.expval(qml.PauliX(0))

qml.snapshots(circuit)()
{0: array([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]]),
 1: array([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]]),
 2: array([[0.5+0.j, 0. +0.j, 0.5+0.j, 0. +0.j],
        [0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j],
        [0.5+0.j, 0. +0.j, 0.5+0.j, 0. +0.j],
        [0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j]]),
 3: array([[0.5+0.j, 0. +0.j, 0.5+0.j, 0. +0.j],
        [0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j],
        [0.5+0.j, 0. +0.j, 0.5+0.j, 0. +0.j],
        [0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j]]),
 4: array([[0.5+0.j, 0. +0.j, 0. +0.j, 0.5+0.j],
        [0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j],
        [0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j],
        [0.5+0.j, 0. +0.j, 0. +0.j, 0.5+0.j]]),
 5: array([[0.5+0.j, 0. +0.j, 0. +0.j, 0.5+0.j],
        [0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j],
        [0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j],
        [0.5+0.j, 0. +0.j, 0. +0.j, 0.5+0.j]]),
 'execution_results': tensor(0., requires_grad=True)}

Let me know if that helps!

Thank you @isaacdevlugt , this is exactly what I was looking for.

1 Like

Yay! Glad this helps :slight_smile: