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 :math:|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 .. math:: \{(A,B),(B,C),(C,D)\} where :math:A, :math:B, :math:C and :math:D are the vertices corresponding to the parties Alice, Bob, Charlie and David, respectively. Local Clifford operations are performed at :math:B, :math:C and :math:D which induces a graph operation called a local complementation at :math:C and therefore adds the edge :math:(B,D) to the graph. The graph is now a star graph with an additional edge :math:(C,D). This edge will be removed by the use of an additional qubit :math:E generated by David. David will entangle his qubit :math:D with this new qubit and therefore adding the edge :math:(D,E) to the graph. Qubit :math:E is then sent to Charlie which also entangles this with his qubit. The edge-set is then .. math:: \{(A,B),(B,C),(C,D),(B,D),(D,E),(C,E)(\} Local Cliffords are performed at :math:C, :math:D and :math:E which induces a local complementation at :math:E and therefore removes the edge :math:(C,D). Finally qubit :math: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 :math:C and :math: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. #. Alice prepares two qubits in the state :math:|+\rangle_A |+\rangle_B. #. Alice performs a CPHASE operation between :math:A and :math:B. #. Alice sends :math:B to Bob. #. Alice measures qubit :math:A in the :math:X-basis. * Bob performs the following operations. #. Bob receives qubit :math:B from Alice. #. Bob prepares a qubit in the state :math:|+\rangle_C. #. Bob performs a CPHASE operation between :math:B and :math:C. #. Bob performs the operation :math:\exp(\frac{\mathrm{i}\pi}{4}Z) on :math:B (one of the operations to induced a local complementation at :math:C). #. Bob sends :math:C to Charlie. #. Bob measures qubit :math:B in the :math:Z-basis. * Charlie performs the following operations #. Charlie receives qubit :math:C from Bob #. Charlie prepares a qubit in the state :math:|+\rangle_D #. Charlie performs a CPHASE operation between :math:C and :math:D. #. Charlie performs the operations :math:\exp(-\frac{\mathrm{i}\pi}{4}X) and :math:\exp(\frac{\mathrm{i}\pi}{4}Z) on :math:C and :math:D, respectively (two of the operations to induced a local complementation at :math:C). #. Charlie sends :math:D to David. #. Charlie receives qubit :math:E from David #. Charlie performs a CPHASE operation between :math:E and :math:C. #. Charlie performs the operations :math:\exp(-\frac{\mathrm{i}\pi}{4}X) and :math:\exp(\frac{\mathrm{i}\pi}{4}Z) on :math:E and :math:C, respectively (two of the operations to induced a local complementation at :math:E). #. Charlie measures qubit :math:E in the :math:Z-basis. #. Charlie performs a :math:Z-operation on :math:C if the measurement outcome is :math:1 and does nothing if it is :math:0. #. Charlie sends the measurement outcome to David. #. Charlie measures qubit :math:C in the :math:X-basis. * David performs the following operations #. David receives qubit :math:D from Charlie #. David prepares a qubit in the state :math:|+\rangle_E #. David performs a CPHASE operation between :math:D and :math:E and sends :math:E to Bob. #. David performs the operation :math:\exp(\frac{\mathrm{i}\pi}{4}Z) on :math:D (one of the operations to induced a local complementation at :math:E). #. David receives the measurement outcome from Charlie and performs a :math:Z-operation on :math:D if this is :math:1 and nothing if it is :math:0. #. David measures qubit :math:D in the :math: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