Distributing a graph state

Here we consider a more complicated example, where we have four parties; Alice, Bob, Charlie and David. They will distribute a graph state and transform this with local operations and classical communication to make a GHZ-like state. Finally they measure their qubits in the correct bases to achieved correlated outcomes. (Note that this is not a efficient way to distribute a GHZ-state but an example illustrating how to use SimulaQron)

An overview

We will first give the main idea of the protocol by describing the evolution of the graph describing the graph state shared by the parties. The actual order of the operations performed will be different in the actual implementation and is described below. For more information on definition of graph states and their transformation under local operations see https://arxiv.org/abs/quant-ph/0602096.

A graph state described by a star graph is a GHZ-state up to Hadamard operations on the qubits which are the leaves of the graph. The parties will therefore transform the shared state to make a star graph. Alice, Bob, Charlie and David generate a graph state \(|P_4\rangle\), i.e. a graph state described by a path graph on four vertices, where Alice and David are the ends of the path. The edge-set of this graph is

\[\{(A,B),(B,C),(C,D)\}\]

where \(A\), \(B\), \(C\) and \(D\) are the vertices corresponding to the parties Alice, Bob, Charlie and David, respectively. Local Clifford operations are performed at \(B\), \(C\) and \(D\) which induces a graph operation called a local complementation at \(C\) and therefore adds the edge \((B,D)\) to the graph. The graph is now a star graph with an additional edge \((C,D)\). This edge will be removed by the use of an additional qubit \(E\) generated by David. David will entangle his qubit \(D\) with this new qubit and therefore adding the edge \((D,E)\) to the graph. Qubit \(E\) is then sent to Charlie which also entangles this with his qubit. The edge-set is then

\[\{(A,B),(B,C),(C,D),(B,D),(D,E),(C,E)(\}\]

Local Cliffords are performed at \(C\), \(D\) and \(E\) which induces a local complementation at \(E\) and therefore removes the edge \((C,D)\). Finally qubit \(E\) is measured in the standard basis to disconnect is from the rest of the graph state. Depending on the measurement outcome, corrections are performed at \(C\) and \(D\).

The protocol

We now describe the operations performed in the protocol, which effectively induces the graph operations described above. Although the order described here is slightly different the end result is still the same, since local operations commute.

  • Alice performs the following operations.

    1. Alice prepares two qubits in the state \(|+\rangle_A |+\rangle_B\).

    2. Alice performs a CPHASE operation between \(A\) and \(B\).

    3. Alice sends \(B\) to Bob.

    4. Alice measures qubit \(A\) in the \(X\)-basis.

  • Bob performs the following operations.

    1. Bob receives qubit \(B\) from Alice.

    2. Bob prepares a qubit in the state \(|+\rangle_C\).

    3. Bob performs a CPHASE operation between \(B\) and \(C\).

    4. Bob performs the operation \(\exp(\frac{\mathrm{i}\pi}{4}Z)\) on \(B\) (one of the operations to induced a local complementation at \(C\)).

    5. Bob sends \(C\) to Charlie.

    6. Bob measures qubit \(B\) in the \(Z\)-basis.

  • Charlie performs the following operations

    1. Charlie receives qubit \(C\) from Bob

    2. Charlie prepares a qubit in the state \(|+\rangle_D\)

    3. Charlie performs a CPHASE operation between \(C\) and \(D\).

    4. Charlie performs the operations \(\exp(-\frac{\mathrm{i}\pi}{4}X)\) and \(\exp(\frac{\mathrm{i}\pi}{4}Z)\) on \(C\) and \(D\), respectively (two of the operations to induced a local complementation at \(C\)).

    5. Charlie sends \(D\) to David.

    6. Charlie receives qubit \(E\) from David

    7. Charlie performs a CPHASE operation between \(E\) and \(C\).

    8. Charlie performs the operations \(\exp(-\frac{\mathrm{i}\pi}{4}X)\) and \(\exp(\frac{\mathrm{i}\pi}{4}Z)\) on \(E\) and \(C\), respectively (two of the operations to induced a local complementation at \(E\)).

    9. Charlie measures qubit \(E\) in the \(Z\)-basis.

    10. Charlie performs a \(Z\)-operation on \(C\) if the measurement outcome is \(1\) and does nothing if it is \(0\).

    11. Charlie sends the measurement outcome to David.

    12. Charlie measures qubit \(C\) in the \(X\)-basis.

  • David performs the following operations

    1. David receives qubit \(D\) from Charlie

    2. David prepares a qubit in the state \(|+\rangle_E\)

    3. David performs a CPHASE operation between \(D\) and \(E\) and sends \(E\) to Bob.

    4. David performs the operation \(\exp(\frac{\mathrm{i}\pi}{4}Z)\) on \(D\) (one of the operations to induced a local complementation at \(E\)).

    5. David receives the measurement outcome from Charlie and performs a \(Z\)-operation on \(D\) if this is \(1\) and nothing if it is \(0\).

    6. David measures qubit \(D\) in the \(X\)-basis.

Setting up

We will run everything locally (localhost) using the standard virtualNodes.cfg file found in config that define the virtual quantum nodes run in the background to simulate the quantum hardware:

# Network configuration file
#
# For each host its informal name, as well as its location in the network must
# be listed.
#
# [name], [hostname], [port number]

Alice, localhost, 8801
Bob, localhost, 8802
Charlie, localhost, 8803
David, localhost, 8804

As we can see from the proocol above, Alice is the one that initializes the protocol and the others listen. We will therefore run a client at Alice and servers at Bob, Charlie and David. Since we run everything locally, we may thus use for the configuration file classicalNet.cfg:

# Network configuration file
#
# For each host its informal name, as well as its location in the network must
# be listed.
#
# [name], [hostname], [port number]
#

Bob, localhost, 8812
Charlie, localhost, 8813
David, localhost, 8814

Let us now provide the actual program code for all the parties.

Programming Alice

Since Alice acts as a client, we will only need to fill in runClientNode. This gives:

#####################################################################################################
#
# runClientNode
#
# This will be run on the local node if all communication links are set up (to the virtual node
# quantum backend, as well as the nodes in the classical communication network), and the local classical
# communication server is running (if applicable).
#
@inlineCallbacks
def runClientNode(qReg, virtRoot, myName, classicalNet):
        """
        Code to execute for the local client node. Called if all connections are established.

        Arguments
        qReg            quantum register (twisted object supporting remote method calls)
        virtRoot        virtual quantum ndoe (twisted object supporting remote method calls)
        myName          name of this node (string)
        classicalNet    servers in the classical communication network (dictionary of hosts)
        """

        logging.debug("LOCAL %s: Runing client side program.",myName)

        #Create 2 qubits
        qA = yield virtRoot.callRemote("new_qubit_inreg",qReg)
        qB = yield virtRoot.callRemote("new_qubit_inreg",qReg)

        #Make 2-qubit graph state
        yield qA.callRemote("apply_H")
        yield qB.callRemote("apply_H")
        yield qA.callRemote("cphase_onto",qB)

        #send qubit B to Bob
        #instruct virtual node to transfer qubit
        remoteNum = yield virtRoot.callRemote("send_qubit",qB,"Bob")
        logging.debug("LOCAL %s: Remote qubit is %d.",myName,remoteNum)

        #Tell number of virtual qubit to Bob and receive measurement outcome parity
        bob=classicalNet.hostDict["Bob"]
        yield bob.root.callRemote("receive_qubit",remoteNum)

        #Measure qubit (X-basis)
        yield qA.callRemote("apply_H")
        outcome=yield qA.callRemote("measure")
        print("Alice outcome was:", outcome)

        reactor.stop()

Programming Bob

Let us now program the code for Bob. Since he only acts as a server on the classical network, it is enough to edit the localNode portion of the template. Alice calls receive_qubit to convey the identifier of the virtual qubit.:

#####################################################################################################
#
# localNode
#
# This will be run if the local node acts as a server on the classical communication network,
# accepting remote method calls from the other nodes.

class localNode(pb.Root):

        def __init__(self, node, classicalNet):

                self.node = node
                self.classicalNet = classicalNet

                self.virtRoot = None
                self.qReg = None

        def set_virtual_node(self, virtRoot):
                self.virtRoot = virtRoot

        def set_virtual_reg(self, qReg):
                self.qReg = qReg

        def remote_test(self):
                return "Tested!"

        # This can be called by Alice (or other clients on the classical network) to inform Bob
        # of an event.
        @inlineCallbacks
        def remote_receive_qubit(self, virtualNum):

                logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum)

                # Get ref of qubit
                qB=yield self.virtRoot.callRemote("get_virtual_ref",virtualNum)

                #Create new qubit
                qC=yield self.virtRoot.callRemote("new_qubit_inreg",self.qReg)

                #Expand graph state
                yield qC.callRemote("apply_H")
                yield qB.callRemote("cphase_onto",qC)

                #Perform part of tau at C
                yield qB.callRemote("apply_rotation",[0,0,1],-np.pi/2)


                #send qubit C to Charlie
                #instruct virtual node to transfer qubit
                remoteNum = yield self.virtRoot.callRemote("send_qubit",qC,"Charlie")
                logging.debug("LOCAL %s: Remote qubit is %d.","Bob",remoteNum)

                #Tell number of virtual qubit to Charlie and receive measurement outcome parity
                charlie=self.classicalNet.hostDict["Charlie"]
                yield charlie.root.callRemote("receive_qubit",remoteNum,"Bob")

                #Measure qubit (Z-basis)
                outcome=yield qB.callRemote("measure")
                print("Bob outcome was:", outcome)

Programming Charlie

Let us now program the code for Charlie. Since he only acts as a server on the classical network, it is enough to edit the localNode portion of the template. Both Bob and David calls receive_qubit to convey the identifier of the virtual qubit and depending on the sender Charlie does different things.:

#####################################################################################################
#
# localNode
#
# This will be run if the local node acts as a server on the classical communication network,
# accepting remote method calls from the other nodes.

class localNode(pb.Root):

        def __init__(self, node, classicalNet):

                self.node = node
                self.classicalNet = classicalNet

                self.virtRoot = None
                self.qReg = None
                self.qC = None #Maybe not the indented way

        def set_virtual_node(self, virtRoot):
                self.virtRoot = virtRoot

        def set_virtual_reg(self, qReg):
                self.qReg = qReg

        def remote_test(self):
                return "Tested!"

        # This can be called by Alice (or other clients on the classical network) to inform Bob
        # of an event.
        @inlineCallbacks
        def remote_receive_qubit(self, virtualNum,sender):

                if sender=="Bob":

                        logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum)

                        # Get ref of qubit
                        self.qC=yield self.virtRoot.callRemote("get_virtual_ref",virtualNum)
                        qC=self.qC

                        #Create new qubit
                        qD=yield self.virtRoot.callRemote("new_qubit_inreg",self.qReg)

                        #Expand graph state
                        yield qD.callRemote("apply_H")
                        yield qC.callRemote("cphase_onto",qD)

                        #Perform part of tau at C
                        yield qC.callRemote("apply_rotation",[1,0,0],np.pi/2)
                        yield qD.callRemote("apply_rotation",[0,0,1],-np.pi/2)

                        # tmp=yield self.virtRoot.callRemote("get_register",qC)
                        # np.save("data_R",tmp[0])
                        # np.save("data_I",tmp[1])

                        #send qubit D to David
                        #instruct virtual node to transfer qubit
                        remoteNum = yield self.virtRoot.callRemote("send_qubit",qD,"David")
                        logging.debug("LOCAL %s: Remote qubit is %d.","Charlie",remoteNum)

                        #Tell number of virtual qubit to Charlie and receive measurement outcome parity
                        david=self.classicalNet.hostDict["David"]
                        yield david.root.callRemote("receive_qubit",remoteNum)

                        #Measure qubit (X-basis)
                        yield qC.callRemote("apply_H")
                        outcome=yield qC.callRemote("measure")
                        print("Charlie outcome was:", outcome)

                elif sender=="David":

                        logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum)

                        # Get ref of qubit
                        qE=yield self.virtRoot.callRemote("get_virtual_ref",virtualNum)
                        qC=self.qC

                        # Expand graph state
                        yield qE.callRemote("cphase_onto",qC)

                        #Do local part of tau
                        yield qE.callRemote("apply_rotation",[1,0,0],np.pi/2)
                        yield qC.callRemote("apply_rotation",[0,0,1],-np.pi/2)

                        #Measure extra qubit (Z-basis)
                        m=yield qE.callRemote("measure")
                        if m==1:
                                yield qC.callRemote("apply_Z")
                        return m

Programming David

Let us now program the code for David. Since he only acts as a server on the classical network, it is enough to edit the localNode portion of the template. Charlie calls receive_qubit to convey the identifier of the virtual qubit.:

#####################################################################################################
#
# localNode
#
# This will be run if the local node acts as a server on the classical communication network,
# accepting remote method calls from the other nodes.

class localNode(pb.Root):

        def __init__(self, node, classicalNet):

                self.node = node
                self.classicalNet = classicalNet

                self.virtRoot = None
                self.qReg = None

        def set_virtual_node(self, virtRoot):
                self.virtRoot = virtRoot

        def set_virtual_reg(self, qReg):
                self.qReg = qReg

        def remote_test(self):
                return "Tested!"

        # This can be called by Alice (or other clients on the classical network) to inform Bob
        # of an event.
        @inlineCallbacks
        def remote_receive_qubit(self, virtualNum):

                logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum)

                # Get ref of qubit
                qD=yield self.virtRoot.callRemote("get_virtual_ref",virtualNum)

                #Create new qubit
                qE=yield self.virtRoot.callRemote("new_qubit_inreg",self.qReg)

                #Expand graph state
                yield qE.callRemote("apply_H")
                yield qD.callRemote("cphase_onto",qE)

                #send qubit E to Charlie
                #instruct virtual node to transfer qubit
                remoteNum = yield self.virtRoot.callRemote("send_qubit",qE,"Charlie")
                logging.debug("LOCAL %s: Remote qubit is %d.","David",remoteNum)

                #Tell number of virtual qubit to Charlie and receive meas outcome
                charlie=self.classicalNet.hostDict["Charlie"]
                m=yield charlie.root.callRemote("receive_qubit",remoteNum,"David")

                logging.debug("LOCAL %s: Got outcome %d.","David",m)
                yield qD.callRemote("apply_rotation",[0,0,1],-np.pi/2)
                if m==1:
                        yield qD.callRemote("apply_Z")

                #Measure qubit (X-basis)
                # tmp=yield self.virtRoot.callRemote("get_register",qD)
                # np.save("data_R",tmp[0])
                # np.save("data_I",tmp[1])
                yield qD.callRemote("apply_H")
                outcome=yield qD.callRemote("measure")
                print("Davids outcome was:", outcome)

Starting

We first start the virtual quantum node backend, by executing:

python3 simulaqron/run/startNode.py Alice &
python3 simulaqron/run/startNode.py Bob &
python3 simulaqron/run/startNode.py David &
python3 simulaqron/run/startNode.py Charlie &

We then start up the programs for the parties themselves. These will connect to the virtual quantum nodes, and execute the quantum commands and classical communication outlined above, in the same directory as we placed classicalNet.cfg:

python3 bobTest.py &
python3 charlieTest.py &
python3 davidTest.py &
python3 aliceTest.py