Source code for SimulaQron.cqc.backend.cqcProtocol


# Copyright (c) 2017, Stephanie Wehner and Axel Dahlberg
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
#    This product includes software developed by Stephanie Wehner, QuTech.
# 4. Neither the name of the QuTech organization nor the
#    names of its contributors may be used to endorse or promote products
#    derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ''AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


import sys, os, time, logging

from twisted.spread import pb
from twisted.internet import reactor
from twisted.internet.protocol import Factory, Protocol
from twisted.internet.defer import inlineCallbacks, returnValue, DeferredList, Deferred

from SimulaQron.virtNode.basics import *
from SimulaQron.virtNode.quantum import *
from SimulaQron.general.hostConfig import *
from SimulaQron.virtNode.crudeSimulator import *

from SimulaQron.local.setup import *

from SimulaQron.cqc.backend.cqcHeader import *
from SimulaQron.cqc.backend.entInfoHeader import *

#####################################################################################################
#
# CQC Factory
#
# Twisted factory for the CQC protocol
#

[docs]class CQCFactory(Factory): def __init__(self, host, name, cqcNet): """ Initialize CQC Factory. lhost details of the local host (class host) """ self.host = host self.name = name self.cqcNet = cqcNet self.virtRoot = None self.qReg = None # Dictionary that keeps qubit dictorionaries for each application self.qubitList = { }; # Lock governing access to the qubitList self._lock = DeferredLock()
[docs] def buildProtocol(self, addr): """ Return an instance of CQCProtocol when a connection is made. """ return CQCProtocol(self)
[docs] def set_virtual_node(self, virtRoot): """ Set the virtual root allowing connections to the SimulaQron backend. """ self.virtRoot = virtRoot
[docs] def set_virtual_reg(self, qReg): """ Set the default register to use on the SimulaQron backend. """ self.qReg = qReg
[docs] def lookup(self, ip, port): """ Lookup name of remote host used within SimulaQron given ip and portnumber. """ for entry in self.cqcNet.hostDict: node = self.cqcNet.hostDict[entry] if (node.ip == ip) and (node.port == port): return node.name logging.debug("CQC %s: No such node", self.name) return None
##################################################################################################### # # CQC Protocol # # Execute the CQC Protocol giving access to the SimulaQron backend via the universal interface. #
[docs]class CQCProtocol(Protocol): def __init__(self, factory): # CQC Factory, including our connection to the SimulaQron backend self.factory = factory; # Default application ID, typically one connection per application but we will # deliberately NOT check for that since this is the task of higher layers or an OS self.app_id = 0; # Functions to invoke when receiving a CQC Header of a certain type self.messageHandlers = { CQC_TP_HELLO : self.handle_hello, CQC_TP_COMMAND : self.handle_command, CQC_TP_FACTORY : self.handle_factory, CQC_TP_GET_TIME : self.handle_time } # Functions to invoke when receiving a certain command self.commandHandlers = { CQC_CMD_I : self.cmd_i, CQC_CMD_X : self.cmd_x, CQC_CMD_Y : self.cmd_y, CQC_CMD_Z : self.cmd_z, CQC_CMD_T : self.cmd_t, CQC_CMD_H : self.cmd_h, CQC_CMD_K : self.cmd_k, CQC_CMD_ROT_X : self.cmd_rotx, CQC_CMD_ROT_Y : self.cmd_roty, CQC_CMD_ROT_Z : self.cmd_rotz, CQC_CMD_CNOT : self.cmd_cnot, CQC_CMD_CPHASE : self.cmd_cphase, CQC_CMD_MEASURE : self.cmd_measure, CQC_CMD_MEASURE_INPLACE : self.cmd_measure_inplace, CQC_CMD_RESET : self.cmd_reset, CQC_CMD_SEND : self.cmd_send, CQC_CMD_RECV : self.cmd_recv, CQC_CMD_EPR : self.cmd_epr, CQC_CMD_EPR_RECV : self.cmd_epr_recv, CQC_CMD_NEW : self.cmd_new } # Flag to determine whether we already received _all_ of the CQC header self.gotCQCHeader = False; # Header for which we are currently processing a packet self.currHeader = None # Buffer received data (which may arrive in chunks) self.buf = None # Convenience self.name = self.factory.name; # Dictionary storing the next unique qubit id for each used app_id self.next_q_id={} # Dictionary storing the next unique entanglement id for each used (host_app_id,remote_node,remote_app_id) self.next_ent_id={} logging.debug("CQC %s: Initialized Protocol",self.name)
[docs] def connectionMade(self): pass
[docs] def connectionLost(self, reason): pass
[docs] def dataReceived(self, data): """ Receive data. We will always wait to receive enough data for the header, and then the entire packet first before commencing processing. """ # Read whatever we received into a buffer if self.buf: self.buf = self.buf + data else: self.buf = data # If we don't have the CQC header yet, try and read it in full. if not self.gotCQCHeader: if len(self.buf) < CQC_HDR_LENGTH: # Not enough data for CQC header, return and wait for the rest return # Got enough data for the CQC Header so read it in self.gotCQCHeader = True; rawHeader = self.buf[0:CQC_HDR_LENGTH] self.currHeader = CQCHeader(rawHeader); # Remove the header from the buffer self.buf = self.buf[CQC_HDR_LENGTH:len(self.buf)] logging.debug("CQC %s: Read CQC Header: %s", self.name, self.currHeader.printable()) # Check whether we already received all the data if len(self.buf) < self.currHeader.length: # Still waiting for data logging.debug("CQC %s: Incomplete data. Waiting.", self.name) return # We got the header and all the data for this packet. Start processing. # Update our app ID self.app_id = self.currHeader.app_id; # Invoke the relevant message handler, processing the possibly remaining data if self.currHeader.tp in self.messageHandlers: self.messageHandlers[self.currHeader.tp](self.currHeader, self.buf[0:self.currHeader.length]) else: self._send_back_cqc(header, CQC_ERR_UNSUPP) # Reset and await the next packet self.gotCQCHeader = False # Check if we received data already for the next packet, if so save it if self.currHeader.length < len(self.buf): self.buf = self.buf[self.currHeader.length:len(self.buf)] self.dataReceived(b'') else: self.buf = None
[docs] def get_virt_qubit(self, header, qubit_id): """ Get reference to the virtual qubit reference in SimulaQron given app and qubit id, if it exists. If not found, send back no qubit error. Caution: Twisted PB does not allow references to objects to be passed back between connections. If you need to pass a qubit reference back to the Twisted PB on a _different_ connection, then use get_virt_qubit_indep below. """ if not (header.app_id, qubit_id) in self.factory.qubitList: logging.debug("CQC %s: Qubit not found",self.name) self._send_back_cqc(header, CQC_ERR_NOQUBIT) return None qubit = self.factory.qubitList[(header.app_id, qubit_id)] return qubit.virt
@inlineCallbacks
[docs] def get_virt_qubit_indep(self, header, qubit_id): """ Get NUMBER (not reference!) to virtual qubit in SimulaQron specific to this connection. If not found, send back no qubit error. """ # First let's get the general virtual qubit reference, if any general_ref = self.get_virt_qubit(header, qubit_id) if not general_ref: return(-1) num = yield general_ref.callRemote("get_virt_num") # logging.debug("GOT NUMBER %d XXX",num) return num
def _send_back_cqc(self,header, msgType,length=0): """ Return a simple CQC header with the specified type. header CQC header of the packet we respond to msgType Message type to return length Length of additional message """ hdr = CQCHeader(); hdr.setVals(CQC_VERSION, msgType, header.app_id,length); msg = hdr.pack(); self.transport.write(msg)
[docs] def handle_hello(self, header, data): """ Hello just requires us to return hello - for testing availablility. """ hdr = CQCHeader(); hdr.setVals(CQC_VERSION, CQC_TP_HELLO, header.app_id,0); msg = hdr.pack(); self.transport.write(msg)
@inlineCallbacks
[docs] def handle_command(self, header, data): """ Handle incoming command requests. """ logging.debug("CQC %s: Command received", self.name) # Run the entire command list, incl. actions after completion which here we will do instantly (succ,shouldNotify) = yield self._process_command(header, header.length, data); if succ: if shouldNotify: # Send a notification that we are done if successful self._send_back_cqc(header, CQC_TP_DONE); logging.debug("CQC %s: Command successful, sent done.", self.name)
@inlineCallbacks def _process_command(self, cqc_header, length, data): """ Process the commands - called recursively to also process additional command lists. """ cmdData = data # Read in all the commands sent l = 0; while l < length: cmd = CQCCmdHeader(cmdData[l:l+CQC_CMD_HDR_LENGTH]); newl = l + CQC_CMD_HDR_LENGTH; # Should we notify shouldNotify=cmd.notify # Check if this command includes an additional header if self.hasXtra(cmd): if len(cmdData) < (newl + CQC_CMD_XTRA_LENGTH): logging.debug("CQC %s: Missing XTRA Header", self.name) else: # logging.debug("XXX") xtra = CQCXtraHeader(cmdData[newl:newl+CQC_CMD_XTRA_LENGTH]); newl = newl + CQC_CMD_XTRA_LENGTH; logging.debug("CQC %s: Read XTRA Header: %s", self.name, xtra.printable()) else: xtra = None; # Run this command logging.debug("CQC %s: Executing command: %s", self.name, cmd.printable()) if not cmd.instr in self.commandHandlers: self._send_back_cqc(header, CQC_ERR_UNSUPP) return (False,0) succ = yield self.commandHandlers[cmd.instr](cqc_header, cmd, xtra); if not succ: return (False,0) # Check if there are additional commands to execute afterwards if cmd.action: (succ,retNotify) = self._process_command(cqc_header, xtra.cmdLength, data[newl:newl+xtra.cmdLength]) shouldNotify=(shouldNotify or retNotify) if not succ: return (False,0) newl = newl + xtra.cmdLength; l = newl; return (True,shouldNotify)
[docs] def hasXtra(self, cmd): """ Check whether this command includes an extra header with additional information. """ if cmd.instr == CQC_CMD_SEND: return(True) if cmd.instr == CQC_CMD_EPR: return(True) if cmd.instr == CQC_CMD_CNOT: return(True) if cmd.instr == CQC_CMD_CPHASE: return(True) if cmd.instr == CQC_CMD_ROT_X: return(True) if cmd.instr == CQC_CMD_ROT_Y: return(True) if cmd.instr == CQC_CMD_ROT_Z: return(True) if cmd.action: return(True) return(False)
@inlineCallbacks
[docs] def handle_factory(self, header, data): cmd_l=CQC_CMD_HDR_LENGTH xtra_l=CQC_CMD_XTRA_LENGTH #Get command header if len(data)<cmd_l: logging.debug("CQC %s: Missing CMD Header", self.name) self._send_back_cqc(header,CQC_ERR_UNSUPP) cmdHeader=CQCCmdHeader(data[:cmd_l]) #Get xtra header if len(data)<(cmd_l+xtra_l): logging.debug("CQC %s: Missing XTRA Header", self.name) self._send_back_cqc(header,CQC_ERR_UNSUPP) xtraHeader=CQCXtraHeader(data[cmd_l:cmd_l+xtra_l]) command=cmdHeader.instr num_iter=xtraHeader.step # Perform operation multiple times all_succ=True for _ in range(num_iter): if self.hasXtra(cmdHeader): (succ,shouldNotify)=yield self._process_command(header,header.length,data) else: data=data[:cmd_l]+data[cmd_l+xtra_l:] (succ,shouldNotify)=yield self._process_command(header,header.length-xtra_l,data) all_succ=(all_succ and succ) if all_succ: if shouldNotify: # Send a notification that we are done if successful self._send_back_cqc(header, CQC_TP_DONE); logging.debug("CQC %s: Command successful, sent done.", self.name)
[docs] def handle_time(self, header, data): # Read the command header to learn the qubit ID rawCmdHeader = data[:CQC_CMD_HDR_LENGTH]; cmd_hdr = CQCCmdHeader(rawCmdHeader); # Get the qubit list qList = self.factory.qubitList # Lookup the desired qubit if (header.app_id,cmd_hdr.qubit_id) in qList: q=qList[(header.app_id,cmd_hdr.qubit_id)] else: # Specified qubit is unknown self._send_back_cqc(header, CQC_ERR_NOQUBIT); return; # Craft reply # First send an appropriate CQC Header self._send_back_cqc(header, CQC_TP_INF_TIME,length=CQC_NOTIFY_LENGTH); # Then we send a notify header with the timing details notify = CQCNotifyHeader(); notify.setVals(cmd_hdr.qubit_id, 0, 0, 0, 0, q.timestamp); msg = notify.pack(); self.transport.write(msg)
[docs] def cmd_i(self, cqc_header, cmd, xtra): """ Do nothing. In reality we would wait a timestep but in SimulaQron we just do nothing. """ logging.debug("CQC %s: Doing Nothing to App ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) return True
@inlineCallbacks
[docs] def cmd_x(self, cqc_header, cmd, xtra): """ Apply X Gate """ logging.debug("CQC %s: Applying X to App ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) virt_qubit = self.get_virt_qubit(cqc_header, cmd.qubit_id) if not virt_qubit: logging.debug("CQC %s: No such qubit",self.name) return False yield virt_qubit.callRemote("apply_X") return True
@inlineCallbacks
[docs] def cmd_z(self, cqc_header, cmd, xtra): """ Apply Z Gate """ logging.debug("CQC %s: Applying Z to App ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) virt_qubit = self.get_virt_qubit(cqc_header, cmd.qubit_id) if not virt_qubit: logging.debug("CQC %s: No such qubit",self.name) return False yield virt_qubit.callRemote("apply_Z") return True
@inlineCallbacks
[docs] def cmd_y(self, cqc_header, cmd, xtra): """ Apply Y Gate """ logging.debug("CQC %s: Applying Y to App ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) virt_qubit = self.get_virt_qubit(cqc_header, cmd.qubit_id) if not virt_qubit: logging.debug("CQC %s: No such qubit",self.name) return False yield virt_qubit.callRemote("apply_Y") return True
@inlineCallbacks
[docs] def cmd_t(self, cqc_header, cmd, xtra): """ Apply T Gate """ logging.debug("CQC %s: Applying T to App ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) virt_qubit = self.get_virt_qubit(cqc_header, cmd.qubit_id) if not virt_qubit: logging.debug("CQC %s: No such qubit",self.name) return False yield virt_qubit.callRemote("apply_T") return True
@inlineCallbacks
[docs] def cmd_h(self, cqc_header, cmd, xtra): """ Apply H Gate """ logging.debug("CQC %s: Applying H to App ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) virt_qubit = self.get_virt_qubit(cqc_header, cmd.qubit_id) if not virt_qubit: logging.debug("CQC %s: No such qubit",self.name) return False yield virt_qubit.callRemote("apply_H") return True
@inlineCallbacks
[docs] def cmd_k(self, cqc_header, cmd, xtra): """ Apply K Gate """ logging.debug("CQC %s: Applying K to App ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) virt_qubit = self.get_virt_qubit(cqc_header, cmd.qubit_id) if not virt_qubit: logging.debug("CQC %s: No such qubit",self.name) return False yield virt_qubit.callRemote("apply_K") return True
@inlineCallbacks
[docs] def cmd_rotx(self, cqc_header, cmd, xtra): """ Rotate around x axis """ logging.debug("CQC %s: Applying ROTX to App ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) virt_qubit = self.get_virt_qubit(cqc_header, cmd.qubit_id) if not virt_qubit: logging.debug("CQC %s: No such qubit",self.name) return False yield virt_qubit.callRemote("apply_rotation",[1,0,0],2 * np.pi/256 * xtra.step) return True
@inlineCallbacks
[docs] def cmd_roty(self, cqc_header, cmd, xtra): """ Rotate around y axis """ logging.debug("CQC %s: Applying ROTY to App ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) virt_qubit = self.get_virt_qubit(cqc_header, cmd.qubit_id) if not virt_qubit: logging.debug("CQC %s: No such qubit",self.name) return False yield virt_qubit.callRemote("apply_rotation",[0,1,0],2 * np.pi/256 * xtra.step) return True
@inlineCallbacks
[docs] def cmd_rotz(self, cqc_header, cmd, xtra): """ Rotate around z axis """ logging.debug("CQC %s: Applying ROTZ to App ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) virt_qubit = self.get_virt_qubit(cqc_header, cmd.qubit_id) if not virt_qubit: logging.debug("CQC %s: No such qubit",self.name) return False yield virt_qubit.callRemote("apply_rotation",[0,0,1],2 * np.pi/256 * xtra.step) return True
@inlineCallbacks
[docs] def cmd_cnot(self, cqc_header, cmd, xtra): """ Apply CNOT Gate """ if not xtra: logging.debug("CQC %s: Missing XTRA Header", self.name) return False logging.debug("CQC %s: Applying CNOT to App ID %d qubit id %d target %d",self.name,cqc_header.app_id,cmd.qubit_id, xtra.qubit_id) control = self.get_virt_qubit(cqc_header, cmd.qubit_id) target = self.get_virt_qubit(cqc_header, xtra.qubit_id) if not(control) or not(target): return False yield control.callRemote("cnot_onto",target) return True
@inlineCallbacks
[docs] def cmd_cphase(self, cqc_header, cmd, xtra): """ Apply CPHASE Gate """ if not xtra: logging.debug("CQC %s: Missing XTRA Header", self.name) return False logging.debug("CQC %s: Applying CPHASE to App ID %d qubit id %d target %d",self.name,cqc_header.app_id,cmd.qubit_id, xtra.qubit_id) control = self.get_virt_qubit(cqc_header, cmd.qubit_id) target = self.get_virt_qubit(cqc_header, xtra.qubit_id) if not(control) or not(target): return False yield control.callRemote("cphase_onto",target) return True
@inlineCallbacks
[docs] def cmd_reset(self, cqc_header, cmd, xtra): """ Reset Qubit to |0> """ logging.debug("CQC %s: Reset App ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) virt_qubit = self.get_virt_qubit(cqc_header, cmd.qubit_id) if not virt_qubit: logging.debug("CQC %s: No such qubit",self.name) return False outcome = yield virt_qubit.callRemote("measure",inplace=True) # If state is |1> do correction if outcome: yield virt_qubit.callRemote("apply_X") return True
@inlineCallbacks
[docs] def cmd_measure(self, cqc_header, cmd, xtra, inplace=False): """ Measure """ logging.debug("CQC %s: Measuring App ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) virt_qubit = self.get_virt_qubit(cqc_header, cmd.qubit_id) if not virt_qubit: logging.debug("CQC %s: No such qubit",self.name) return False outcome = yield virt_qubit.callRemote("measure",inplace) if outcome == None: logging.debug("CQC %s: Measurement failed", self.name) self._send_back_cqc(cqc_header, CQC_ERR_GENERAL) return False logging.debug("CQC %s: Measured outcome %d",self.name,outcome) # Send the outcome back as MEASOUT self._send_back_cqc(cqc_header, CQC_TP_MEASOUT,length=CQC_NOTIFY_LENGTH) # Send notify header with outcome hdr = CQCNotifyHeader(); hdr.setVals(cmd.qubit_id, outcome, 0, 0, 0, 0); msg = hdr.pack() self.transport.write(msg) logging.debug("CQC %s: Notify %s",self.name, hdr.printable()) if not inplace: # Remove from active mapped qubits del self.factory.qubitList[(cqc_header.app_id, cmd.qubit_id)] return True
@inlineCallbacks
[docs] def cmd_measure_inplace(self, cqc_header, cmd, xtra): # Call measure with inplace=True succ = yield self.cmd_measure(cqc_header,cmd,xtra,inplace=True) return succ
@inlineCallbacks
[docs] def cmd_send(self, cqc_header, cmd, xtra): """ Send qubit to another node. """ # Lookup the virtual qubit form identifier virt_num = yield self.get_virt_qubit_indep(cqc_header, cmd.qubit_id) if virt_num < 0: logging.debug("CQC %s: No such qubit",self.name) return False # Lookup the name of the remote node used within SimulaQron targetName = self.factory.lookup(xtra.remote_node, xtra.remote_port) if targetName == None: logging.debug("CQC %s: Remote node not found %s",self.name,xtra.printable()) return False # Send instruction to transfer the qubit yield self.factory.virtRoot.callRemote("cqc_send_qubit", virt_num, targetName, cqc_header.app_id, xtra.remote_app_id) logging.debug("CQC %s: Sent App ID %d qubit id %d to %s",self.name,cqc_header.app_id,cmd.qubit_id, targetName) # Remove from active mapped qubits del self.factory.qubitList[(cqc_header.app_id, cmd.qubit_id)] return True
@inlineCallbacks
[docs] def cmd_recv(self, cqc_header, cmd, xtra): """ Receive qubit from another node. Block until qubit is received. """ logging.debug("CQC %s: Asking to receive for App ID %d",self.name,cqc_header.app_id) # First get the app_id and q_id app_id = cqc_header.app_id q_id = self.new_qubit_id(app_id) # This will block until a qubit is received. noQubit = True while(noQubit): virt_qubit = yield self.factory.virtRoot.callRemote("cqc_get_recv", cqc_header.app_id) if virt_qubit: noQubit = False else: time.sleep(0.1) logging.debug("CQC %s: Qubit received for app_id %d",self.name, cqc_header.app_id) # Once we have the qubit, add it to the local list and send a reply we received it. Note that we will # recheck whether it exists: it could have been added by another connection in the mean time try: self.factory._lock.acquire() if (app_id,q_id) in self.factory.qubitList: logging.debug("CQC %s: Qubit already in use (%d,%d)", self.name, app_id, q_id) self._send_back_cqc(cqc_header, CQC_ERR_INUSE) return False q = CQCQubit(q_id, int(time.time()), virt_qubit) self.factory.qubitList[(app_id,q_id)] = q finally: self.factory._lock.release() # Send message we received a qubit back # logging.debug("GOO") self._send_back_cqc(cqc_header, CQC_TP_RECV,length=CQC_NOTIFY_LENGTH) # Send notify header with qubit ID # logging.debug("GOO") hdr = CQCNotifyHeader(); hdr.setVals(q_id, 0, 0,0,0, 0); msg = hdr.pack() self.transport.write(msg) logging.debug("CQC %s: Notify %s",self.name, hdr.printable()) return True
@inlineCallbacks
[docs] def cmd_epr(self, cqc_header, cmd, xtra): """ Create EPR pair with another node. Depending on the ips and ports this will either create an EPR-pair and send one part, or just receive. """ #Get ip and port of this host host_node=self.factory.host.ip host_port=self.factory.host.port host_app_id = cqc_header.app_id #Get ip and port of remote host remote_node=xtra.remote_node remote_port=xtra.remote_port remote_app_id=xtra.remote_app_id # Create the first qubit (succ,q_id1) = yield self.cmd_new(cqc_header,cmd,xtra,return_q_id=True) if not succ: return False # Create the second qubit (succ,q_id2) = yield self.cmd_new(cqc_header,cmd,xtra,return_q_id=True) if not succ: return False # Create headers for qubits cmd1=CQCCmdHeader() cmd1.setVals(q_id1,0,0,0,0) cmd2=CQCCmdHeader() cmd2.setVals(q_id2,0,0,0,0) xtra_cnot=CQCXtraHeader() xtra_cnot.setVals(q_id2,0,0,0,0,0) # Produce EPR-pair succ = yield self.cmd_h(cqc_header,cmd1,None) if not succ: return False succ = yield self.cmd_cnot(cqc_header,cmd1,xtra_cnot) if not succ: return False # Get entanglement id ent_id=self.new_ent_id(host_app_id,remote_node,remote_app_id) # Prepare ent_info header with entanglement information entInfo=EntInfoHeader() entInfo.setVals(host_node,host_port,host_app_id,remote_node,remote_port,remote_app_id,ent_id,int(time.time()),int(time.time()),0,1) # Send second qubit succ = yield self.send_epr_half(cqc_header,cmd2,xtra,entInfo) if not succ: return False # Send message we created EPR pair self._send_back_cqc(cqc_header, CQC_TP_EPR_OK,length=CQC_NOTIFY_LENGTH+ENT_INFO_LENGTH) # Send notify header with qubit ID hdr = CQCNotifyHeader(); hdr.setVals(q_id1, 0, 0,0,0, 0); msg = hdr.pack() self.transport.write(msg) logging.debug("CQC %s: Notify %s",self.name, hdr.printable()) # Send entanglement info msg=entInfo.pack() self.transport.write(msg) logging.debug("CQC %s: Entanglement information %s",self.name, entInfo.printable()) logging.debug("CQC %s: EPR Pair ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) return True
@inlineCallbacks
[docs] def send_epr_half(self, cqc_header, cmd, xtra, entInfo): """ Send qubit to another node. """ # Lookup the virtual qubit from identifier virt_num = yield self.get_virt_qubit_indep(cqc_header, cmd.qubit_id) if virt_num < 0: logging.debug("CQC %s: No such qubit",self.name) return False # Lookup the name of the remote node used within SimulaQron targetName = self.factory.lookup(xtra.remote_node, xtra.remote_port) if targetName == None: logging.debug("CQC %s: Remote node not found %s",self.name,xtra.printable()) return False # Prepare update raw entanglement information header updatedEntInfo=EntInfoHeader(entInfo.pack()) updatedEntInfo.switch_nodes() rawUpdatedEntInfo=updatedEntInfo.pack() # Send instruction to transfer the qubit yield self.factory.virtRoot.callRemote("cqc_send_epr_half", virt_num, targetName, cqc_header.app_id, xtra.remote_app_id, rawUpdatedEntInfo) logging.debug("CQC %s: Sent App ID %d half a EPR pair as qubit id %d to %s",self.name,cqc_header.app_id,cmd.qubit_id, targetName) # Remove from active mapped qubits del self.factory.qubitList[(cqc_header.app_id, cmd.qubit_id)] return True
@inlineCallbacks
[docs] def cmd_epr_recv(self, cqc_header, cmd, xtra): """ Receive half of epr from another node. Block until qubit is received. """ logging.debug("CQC %s: Asking to receive for App ID %d",self.name,cqc_header.app_id) # First get the app_id and q_id app_id = cqc_header.app_id q_id = self.new_qubit_id(app_id) # This will block until a qubit is received. noQubit = True while(noQubit): data = yield self.factory.virtRoot.callRemote("cqc_get_epr_recv", cqc_header.app_id) if data: noQubit = False (virt_qubit,rawEntInfo)=data entInfo=EntInfoHeader(rawEntInfo) else: time.sleep(0.1) logging.debug("CQC %s: Qubit received for app_id %d",self.name, cqc_header.app_id) # Once we have the qubit, add it to the local list and send a reply we received it. Note that we will # recheck whether it exists: it could have been added by another connection in the mean time try: self.factory._lock.acquire() if (app_id,q_id) in self.factory.qubitList: logging.debug("CQC %s: Qubit already in use (%d,%d)", self.name, app_id, q_id) self._send_back_cqc(cqc_header, CQC_ERR_INUSE) return False q = CQCQubit(q_id, int(time.time()), virt_qubit) self.factory.qubitList[(app_id,q_id)] = q finally: self.factory._lock.release() # Send message we received a qubit back self._send_back_cqc(cqc_header, CQC_TP_EPR_OK,length=CQC_NOTIFY_LENGTH+ENT_INFO_LENGTH) # Send notify header with qubit ID hdr = CQCNotifyHeader(); hdr.setVals(q_id, 0, 0,0,0, 0); msg = hdr.pack() self.transport.write(msg) logging.debug("CQC %s: Notify %s",self.name, hdr.printable()) # Send entanglement info msg=entInfo.pack() self.transport.write(msg) logging.debug("CQC %s: Entanglement information %s",self.name, entInfo.printable()) logging.debug("CQC %s: EPR Pair ID %d qubit id %d",self.name,cqc_header.app_id,cmd.qubit_id) return True
@inlineCallbacks
[docs] def cmd_new(self, cqc_header, cmd, xtra, return_q_id=False): """ Request a new qubit. Since we don't need it, this python CQC just provides very crude timing information. (return_q_id is used internally) """ app_id = cqc_header.app_id try: self.factory._lock.acquire() virt = yield self.factory.virtRoot.callRemote("new_qubit_inreg",self.factory.qReg) if not virt: # if no more qubits raise quantumError("No more qubits available") q_id=self.new_qubit_id(app_id) q = CQCQubit(q_id, int(time.time()), virt) self.factory.qubitList[(app_id,q_id)] = q logging.debug("CQC %s: Requested new qubit (%d,%d)",self.name,app_id, q_id) if not return_q_id: # Send message we created a qubit back # logging.debug("GOO") self._send_back_cqc(cqc_header, CQC_TP_NEW_OK,length=CQC_NOTIFY_LENGTH) # Send notify header with qubit ID hdr = CQCNotifyHeader(); hdr.setVals(q_id, 0, 0, 0, 0, 0); msg = hdr.pack() self.transport.write(msg) logging.debug("CQC %s: Notify %s",self.name, hdr.printable()) except quantumError: # if no more qubits logging.error("CQC %s: Maximum number of qubits reached.", self.name) self._send_back_cqc(cqc_header, CQC_ERR_NOQUBIT) self.factory._lock.release() if return_q_id: return (False,None) else: return False self.factory._lock.release() if return_q_id: return (True,q_id) else: return True
[docs] def new_qubit_id(self,app_id): """ Returns a new unique qubit id for the specified app_id. Used by cmd_new and cmd_recv """ if app_id in self.next_q_id: q_id=self.next_q_id[app_id] self.next_q_id[app_id]+=1 return q_id else: self.next_q_id[app_id]=1 return 0
[docs] def new_ent_id(self,host_app_id, remote_node, remote_app_id): """ Returns a new unique entanglement id for the specified host_app_id, remote_node and remote_app_id. Used by cmd_epr. """ pair_id=(host_app_id,remote_node,remote_app_id) if pair_id in self.next_ent_id: ent_id=self.next_ent_id[pair_id] self.next_ent_id[pair_id]+=1 return ent_id else: self.next_ent_id[pair_id]=1 return 0
####################################################################################################### # # CQC Internal qubit object to translate to the native mode of SimulaQron #
[docs]class CQCQubit: def __init__(self, qubit_id = 0, timestamp = 0, virt = 0): self.qubit_id = qubit_id; self.timestamp = timestamp; self.virt = virt;