From a778a8550db9e3ae621220a6f7917a7edd9dd1d9 Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 8 Aug 2019 17:22:12 +0200 Subject: [PATCH 01/16] Implement IF header, Assign header, and Type header. Also initiate enums --- cqc/MessageHandler.py | 6 +- cqc/Protocol.py | 8 +- cqc/cqcHeader.py | 312 ++++++++++++++++++++++++++++++++++-------- cqc/pythonLib.py | 37 +++-- 4 files changed, 289 insertions(+), 74 deletions(-) diff --git a/cqc/MessageHandler.py b/cqc/MessageHandler.py index 7777046..f016fe0 100644 --- a/cqc/MessageHandler.py +++ b/cqc/MessageHandler.py @@ -61,7 +61,6 @@ CQCXtraQubitHeader, CQCRotationHeader, CQCXtraHeader, - CQC_CMD_XTRA_LENGTH, CQC_VERSION, CQCHeader, CQC_TP_DONE, @@ -70,7 +69,6 @@ CQC_ERR_GENERAL, CQCSequenceHeader, CQCFactoryHeader, - CQC_CMD_HDR_LENGTH, ) from twisted.internet.defer import DeferredLock, inlineCallbacks @@ -211,7 +209,7 @@ def create_extra_header(cmd, cmd_data, cqc_version=CQC_VERSION): """ if cqc_version < 1: if has_extra(cmd): - cmd_length = CQC_CMD_XTRA_LENGTH + cmd_length = CQCXtraHeader.HDR_LENGTH hdr = CQCXtraHeader(cmd_data[:cmd_length]) return hdr else: @@ -255,7 +253,7 @@ def _process_command(self, cqc_header, length, data, is_locked=False): cur_length = 0 should_notify = None while cur_length < length: - cmd = CQCCmdHeader(cmd_data[cur_length: cur_length + CQC_CMD_HDR_LENGTH]) + cmd = CQCCmdHeader(cmd_data[cur_length: cur_length + CQCCmdHeader.HDR_LENGTH]) logging.debug("CQC %s got command header %s", self.name, cmd.printable()) newl = cur_length + cmd.HDR_LENGTH diff --git a/cqc/Protocol.py b/cqc/Protocol.py index f7baa1a..210b0a6 100644 --- a/cqc/Protocol.py +++ b/cqc/Protocol.py @@ -31,7 +31,7 @@ from twisted.internet.defer import inlineCallbacks from twisted.internet.protocol import Protocol, connectionDone -from cqc.cqcHeader import CQC_HDR_LENGTH, CQC_VERSION, CQCHeader +from cqc.cqcHeader import CQC_VERSION, CQCHeader ############################################################################### # @@ -96,17 +96,17 @@ def dataReceived(self, 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: + if len(self.buf) < CQCHeader.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 - raw_header = self.buf[0:CQC_HDR_LENGTH] + raw_header = self.buf[0:CQCHeader.HDR_LENGTH] self.currHeader = CQCHeader(raw_header) # Remove the header from the buffer - self.buf = self.buf[CQC_HDR_LENGTH: len(self.buf)] + self.buf = self.buf[CQCHeader.HDR_LENGTH: len(self.buf)] logging.debug("CQC %s: Read CQC Header: %s", self.name, self.currHeader.printable()) diff --git a/cqc/cqcHeader.py b/cqc/cqcHeader.py index e4c3b7d..c139f7c 100644 --- a/cqc/cqcHeader.py +++ b/cqc/cqcHeader.py @@ -33,9 +33,16 @@ import bitstring import abc +from enum import IntEnum + # Constant defining CQC version CQC_VERSION = 2 +# !!!!!!!!! +# Please do not use these header length constants. +# Use the class static constants instead. For instance, use CQCCmdHeader.HDR_LENGTH instead of CQC_CMD_HDR_LENGTH +# !!!!!!!!! + # Lengths of the headers in bytes CQC_HDR_LENGTH = 8 # Length of the CQC Header CQC_CMD_HDR_LENGTH = 4 # Length of a command header @@ -50,6 +57,12 @@ CQC_TIMEINFO_HDR_LENGTH = 8 # Length of the time info header CQC_EPR_REQ_LENGTH = 16 # Length of EPR request header + +# !!!!!!!!! +# Please do not use these type constants. +# Use the CQCType enum instead. For instance, use CQCType.HELLO instead of CQC_TP_HELLO +# !!!!!!!!! + # Constants defining the messages types CQC_TP_HELLO = 0 # Alive check CQC_TP_COMMAND = 1 # Execute a command list @@ -103,14 +116,41 @@ CQC_OPT_BLOCK = 0x04 # Block until command is done +class CQCType(IntEnum): + HELLO = 0 # Alive check + COMMAND = 1 # Execute a command list + FACTORY = 2 # Start executing command list repeatedly + EXPIRE = 3 # Qubit has expired + DONE = 4 # Done with command + RECV = 5 # Received qubit + EPR_OK = 6 # Created EPR pair + MEASOUT = 7 # Measurement outcome + GET_TIME = 8 # Get creation time of qubit + INF_TIME = 9 # Return timinig information + NEW_OK = 10 # Created a new qubit + PROGRAM = 11 # Indicate that the program will contain multiple header types + IF = 12 # Announce a CQC IF header + + ERR_GENERAL = 20 # General purpose error (no details + ERR_NOQUBIT = 21 # No more qubits available + ERR_UNSUPP = 22 # No sequence not supported + ERR_TIMEOUT = 23 # Timeout + ERR_INUSE = 24 # Qubit already in use + ERR_UNKNOWN = 25 # Unknown qubit ID + + +class CQCLogicalOperator(IntEnum): + EQ = 0 # Equal + NEQ = 1 # Not equal + class Header(metaclass=abc.ABCMeta): """ Abstact class for headers. Should be subclassed """ - - HDR_LENGTH = 0 - packaging_format = "!" + + PACKAGING_FORMAT = "!" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) def __init__(self, headerBytes=None): """ @@ -231,8 +271,8 @@ class CQCHeader(Header): Definition of the general CQC header. """ - HDR_LENGTH = CQC_HDR_LENGTH - packaging_format = "!BBHL" + PACKAGING_FORMAT = "!BBHL" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) def _setVals(self, version=0, tp=0, app_id=0, length=0): """ @@ -248,14 +288,14 @@ def _pack(self): """ Pack data into packet format. For defnitions see cLib/cgc.h """ - cqcH = struct.pack(self.packaging_format, self.version, self.tp, self.app_id, self.length) + cqcH = struct.pack(self.PACKAGING_FORMAT, self.version, self.tp, self.app_id, self.length) return cqcH def _unpack(self, headerBytes): """ Unpack packet data. For definitions see cLib/cqc.h """ - cqcH = struct.unpack(self.packaging_format, headerBytes) + cqcH = struct.unpack(self.PACKAGING_FORMAT, headerBytes) self.version = cqcH[0] self.tp = cqcH[1] self.app_id = cqcH[2] @@ -272,13 +312,132 @@ def _printable(self): return toPrint +class CQCTypeHeader(Header): + """ + Definition of the CQC Type header. This header announces the type of the headers that will follow. + """ + + PACKAGING_FORMAT = "!BI" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) + + + def _setVals(self, tp: CQCType=0, length: int=0) -> None: + """ + Set using given values. + """ + self.type = tp + self.length = length + + + def _pack(self) -> bytes: + """ + Pack data into packet format. For defnitions see cLib/cgc.h + """ + return struct.pack(self.PACKAGING_FORMAT, self.type, self.length) + + + def _unpack(self, headerBytes) -> None: + """ + Unpack packet data. + """ + unpacked = struct.unpack(self.PACKAGING_FORMAT, headerBytes) + self.type = unpacked[0] + self.length = unpacked[1] + + + + def _printable(self) -> str: + """ + Produce a printable string for information purposes. + """ + return "CQC Type header. Type=" + str(self.type) + " | Length=" + str(self.length) + + + +class CQCIFHeader(Header): + """ + Definition of the CQC IF header. + """ + + PACKAGING_FORMAT = "!IBBII" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) + + TYPE_VALUE = 0 + TYPE_REF_ID = 1 + + def _setVals(self, + first_operand: int=0, + operator: CQCLogicalOperator=0, + type_of_second_operand: int=0, + second_operand: int=0, + length: int=0) -> None: + """ + Set the fields of this header. + first_operand must be a reference id. + second_operand will be interpreted as eiter a reference id or a value, dependent on type_of_second_operand. + type_of_second_operand must either be CQCIFHEADER.TYPE_VALUE or CQCIFHEADER.TYPE_REF_ID + """ + + self.first_operand = first_operand + self.operator = operator + self.type_of_second_operand = type_of_second_operand + self.second_operand = second_operand + self.length = length + + + def _pack(self) -> bytes: + """ + Pack data into packet format. For defnitions see cLib/cgc.h + """ + + return struct.pack( + self.PACKAGING_FORMAT, + self.first_operand, + self.operator, + self.type_of_second_operand, + self.second_operand, + self.length + ) + + + def _unpack(self, headerBytes) -> None: + """ + Unpack packet data. For definitions see cLib/cqc.h + """ + unpacked = struct.unpack(self.PACKAGING_FORMAT, headerBytes) + + self.first_operand = unpacked[0] + self.operator = unpacked[1] + self.type_of_second_operand = unpacked[2] + self.second_operand = unpacked[3] + self.length = unpacked[4] + + + def _printable(self) -> str: + """ + Produce a printable string for information purposes. + """ + + if self.type_of_second_operand == self.TYPE_REF_ID: + operand_type = "RefID" + else: + operand_type = "Value" + + # parenthesis to concatenate the string over multiple lines + return ("CQC IF header. RefID=" + str(self.first_operand) + + " | Operator=" + str(self.operator) + + " | " + operand_type + "=" + str(self.second_operand) + + " | Second_operand_type=" + operand_type) + + class CQCCmdHeader(Header): """ Header for a command instruction packet. """ - HDR_LENGTH = CQC_CMD_HDR_LENGTH - packaging_format = "!HBB" + PACKAGING_FORMAT = "!HBB" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) + def _setVals(self, qubit_id=0, instr=0, notify=False, block=False, action=False): """ @@ -303,14 +462,14 @@ def _pack(self): if self.action: opt = opt | CQC_OPT_ACTION - cmdH = struct.pack(self.packaging_format, self.qubit_id, self.instr, opt) + cmdH = struct.pack(self.PACKAGING_FORMAT, self.qubit_id, self.instr, opt) return cmdH def _unpack(self, headerBytes): """ Unpack packet data. For definitions see cLib/cqc.h """ - cmdH = struct.unpack(self.packaging_format, headerBytes) + cmdH = struct.unpack(self.PACKAGING_FORMAT, headerBytes) self.qubit_id = cmdH[0] self.instr = cmdH[1] @@ -340,13 +499,58 @@ def _printable(self): return toPrint +class CQCAssignHeader(Header): + """ + Sub header that follows a CMD Measure header. It contains the reference id which can be used + to refer to the measurement outcome + """ + + PACKAGING_FORMAT = "!I" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) + + + def _setVals(self, ref_id: int=0) -> None: + """ + Set using given values. + """ + self.ref_id = ref_id + + + def _pack(self) -> bytes: + """ + Pack data into packet format. For defnitions see cLib/cgc.h + """ + + return struct.pack(self.PACKAGING_FORMAT, self.ref_id) + + + def _unpack(self, headerBytes) -> None: + """ + Unpack packet data. For definitions see cLib/cqc.h + """ + unpacked = struct.unpack(self.PACKAGING_FORMAT, headerBytes) + + self.ref_id = unpacked[0] + + + def _printable(self) -> str: + """ + Produce a printable string for information purposes. + """ + + return "CQC Assign sub header. RefID=" + str(self.ref_id) + + + + + class CQCXtraHeader(Header): """ Optional addtional cmd header information. Only relevant for certain commands. """ - HDR_LENGTH = CQC_CMD_XTRA_LENGTH - packaging_format = "!HHLLHBB" + PACKAGING_FORMAT = "!HHLLHBB" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) # Deprecated, split into multiple headers def __init__(self, headerBytes=None): @@ -372,7 +576,7 @@ def _pack(self): Pack data into packet form. For definitions see cLib/cqc.h """ xtraH = struct.pack( - self.packaging_format, + self.PACKAGING_FORMAT, self.qubit_id, self.remote_app_id, self.remote_node, @@ -387,7 +591,7 @@ def _unpack(self, headerBytes): """ Unpack packet data. For defnitions see cLib/cqc.h """ - xtraH = struct.unpack(self.packaging_format, headerBytes) + xtraH = struct.unpack(self.PACKAGING_FORMAT, headerBytes) self.qubit_id = xtraH[0] self.remote_app_id = xtraH[1] @@ -418,8 +622,8 @@ class CQCSequenceHeader(Header): Seperate classes used clearity and for possible future adaptability. (Increase length for example) """ - packaging_format = "!B" - HDR_LENGTH = CQC_SEQ_HDR_LENGTH + PACKAGING_FORMAT = "!B" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) def _setVals(self, cmd_length=0): """ @@ -433,7 +637,7 @@ def _pack(self): Pack data into packet form. For definitions see cLib/cqc.h :returns the packed header """ - header = struct.pack(self.packaging_format, self.cmd_length) + header = struct.pack(self.PACKAGING_FORMAT, self.cmd_length) return header def _unpack(self, headerBytes): @@ -441,7 +645,7 @@ def _unpack(self, headerBytes): Unpack packet data. For defnitions see cLib/cqc.h :param headerBytes: The unpacked headers. """ - header = struct.unpack(self.packaging_format, headerBytes) + header = struct.unpack(self.PACKAGING_FORMAT, headerBytes) self.cmd_length = header[0] def _printable(self): @@ -459,8 +663,8 @@ class CQCRotationHeader(Header): Header used to define the rotation angle of a gate """ - packaging_format = "!B" - HDR_LENGTH = CQC_ROT_HDR_LENGTH + PACKAGING_FORMAT = "!B" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) def _setVals(self, step=0): """ @@ -474,7 +678,7 @@ def _pack(self): Pack data into packet form. For definitions see cLib/cqc.h :returns the packed header """ - header = struct.pack(self.packaging_format, self.step) + header = struct.pack(self.PACKAGING_FORMAT, self.step) return header def _unpack(self, headerBytes): @@ -482,7 +686,7 @@ def _unpack(self, headerBytes): Unpack packet data. For defnitions see cLib/cqc.h :param headerBytes: The unpacked headers. """ - header = struct.unpack(self.packaging_format, headerBytes) + header = struct.unpack(self.PACKAGING_FORMAT, headerBytes) self.step = header[0] def _printable(self): @@ -500,8 +704,8 @@ class CQCXtraQubitHeader(Header): Header used to send qubit of a secondary qubit for two qubit gates """ - packaging_format = "!H" - HDR_LENGTH = CQC_XTRA_QUBIT_HDR_LENGTH + PACKAGING_FORMAT = "!H" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) def _setVals(self, qubit_id=0): """ @@ -515,7 +719,7 @@ def _pack(self): Pack data into packet form. For definitions see cLib/cqc.h :returns the packed header """ - header = struct.pack(self.packaging_format, self.qubit_id) + header = struct.pack(self.PACKAGING_FORMAT, self.qubit_id) return header def _unpack(self, headerBytes): @@ -523,7 +727,7 @@ def _unpack(self, headerBytes): Unpack packet data. For definitions see cLib/cqc.h :param headerBytes: The unpacked headers. """ - header = struct.unpack(self.packaging_format, headerBytes) + header = struct.unpack(self.PACKAGING_FORMAT, headerBytes) self.qubit_id = header[0] def _printable(self): @@ -543,9 +747,9 @@ class CQCCommunicationHeader(Header): This header has a size of 8 """ - packaging_format = "!HHL" - packaging_format_v1 = "!HLH" - HDR_LENGTH = CQC_COM_HDR_LENGTH + PACKAGING_FORMAT = "!HHL" + PACKAGING_FORMAT_V1 = "!HLH" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) # Both versions have the same size def __init__(self, headerBytes=None, cqc_version=CQC_VERSION): """ @@ -573,9 +777,9 @@ def _pack(self): :returns the packed header """ if self._cqc_version < 2: - header = struct.pack(self.packaging_format_v1, self.remote_app_id, self.remote_node, self.remote_port) + header = struct.pack(self.PACKAGING_FORMAT_V1, self.remote_app_id, self.remote_node, self.remote_port) else: - header = struct.pack(self.packaging_format, self.remote_app_id, self.remote_port, self.remote_node) + header = struct.pack(self.PACKAGING_FORMAT, self.remote_app_id, self.remote_port, self.remote_node) return header def _unpack(self, headerBytes): @@ -585,12 +789,12 @@ def _unpack(self, headerBytes): :param cqc_version: The CQC version to be used """ if self._cqc_version < 2: - header = struct.unpack(self.packaging_format_v1, headerBytes) + header = struct.unpack(self.PACKAGING_FORMAT_V1, headerBytes) self.remote_app_id = header[0] self.remote_node = header[1] self.remote_port = header[2] else: - header = struct.unpack(self.packaging_format, headerBytes) + header = struct.unpack(self.PACKAGING_FORMAT, headerBytes) self.remote_app_id = header[0] self.remote_port = header[1] self.remote_node = header[2] @@ -614,8 +818,8 @@ class CQCFactoryHeader(Header): # could maybe include the notify flag in num_iter? # That halfs the amount of possible num_iter from 256 to 128 - package_format = "!BB" - HDR_LENGTH = CQC_FACTORY_HDR_LENGTH + PACKAGING_FORMAT = "!BB" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) def _setVals(self, num_iter=0, notify=0, block=0): """ @@ -638,14 +842,14 @@ def _pack(self): if self.block: opt = opt | CQC_OPT_BLOCK - factH = struct.pack(self.package_format, self.num_iter, opt) + factH = struct.pack(self.PACKAGING_FORMAT, self.num_iter, opt) return factH def _unpack(self, headerBytes): """ Unpack packet data. For defnitions see cLib/cqc.h """ - fact_hdr = struct.unpack(self.package_format, headerBytes) + fact_hdr = struct.unpack(self.PACKAGING_FORMAT, headerBytes) self.notify = fact_hdr[1] & CQC_OPT_NOTIFY self.block = fact_hdr[1] & CQC_OPT_BLOCK @@ -666,8 +870,8 @@ class CQCNotifyHeader(Header): Header used to specify notification details. """ - HDR_LENGTH = CQC_NOTIFY_LENGTH - packaging_format = "!HHLQHBB" + PACKAGING_FORMAT = "!HHLQHBB" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) def __init__(self, headerBytes=None): """ @@ -693,7 +897,7 @@ def _pack(self): Pack data into packet form. For definitions see cLib/cqc.h """ xtraH = struct.pack( - self.packaging_format, + self.PACKAGING_FORMAT, self.qubit_id, self.remote_app_id, self.remote_node, @@ -708,7 +912,7 @@ def _unpack(self, headerBytes): """ Unpack packet data. For defnitions see cLib/cqc.h """ - xtraH = struct.unpack(self.packaging_format, headerBytes) + xtraH = struct.unpack(self.PACKAGING_FORMAT, headerBytes) self.qubit_id = xtraH[0] self.remote_app_id = xtraH[1] @@ -735,8 +939,8 @@ class CQCMeasOutHeader(Header): Header used to send a measurement outcome. """ - packaging_format = "!B" - HDR_LENGTH = CQC_MEAS_OUT_HDR_LENGTH + PACKAGING_FORMAT = "!B" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) def _setVals(self, outcome=0): """ @@ -750,7 +954,7 @@ def _pack(self): Pack data into packet form. For definitions see cLib/cqc.h :returns the packed header """ - header = struct.pack(self.packaging_format, self.outcome) + header = struct.pack(self.PACKAGING_FORMAT, self.outcome) return header def _unpack(self, headerBytes): @@ -758,7 +962,7 @@ def _unpack(self, headerBytes): Unpack packet data. For definitions see cLib/cqc.h :param headerBytes: The unpacked headers. """ - header = struct.unpack(self.packaging_format, headerBytes) + header = struct.unpack(self.PACKAGING_FORMAT, headerBytes) self.outcome = header[0] def _printable(self): @@ -776,8 +980,8 @@ class CQCTimeinfoHeader(Header): Header used to send timing information """ - packaging_format = "!Q" - HDR_LENGTH = CQC_TIMEINFO_HDR_LENGTH + PACKAGING_FORMAT = "!Q" + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) def _setVals(self, datetime=0): """ @@ -791,7 +995,7 @@ def _pack(self): Pack data into packet form. For definitions see cLib/cqc.h :returns the packed header """ - header = struct.pack(self.packaging_format, self.datetime) + header = struct.pack(self.PACKAGING_FORMAT, self.datetime) return header def _unpack(self, headerBytes): @@ -799,7 +1003,7 @@ def _unpack(self, headerBytes): Unpack packet data. For definitions see cLib/cqc.h :param headerBytes: The unpacked headers. """ - header = struct.unpack(self.packaging_format, headerBytes) + header = struct.unpack(self.PACKAGING_FORMAT, headerBytes) self.datetime = header[0] def _printable(self): @@ -813,8 +1017,8 @@ def _printable(self): class CQCEPRRequestHeader(Header): - HDR_LENGTH = CQC_EPR_REQ_LENGTH - package_format = ( + HDR_LENGTH = 16 + PACKAGING_FORMAT = ( "uint:32=remote_ip, " "float:32=min_fidelity, " "float:32=max_time, " @@ -876,7 +1080,7 @@ def _pack(self): "atomic": self.atomic, "measure_directly": self.measure_directly, } - request_Bitstring = bitstring.pack(self.package_format, **to_pack) + request_Bitstring = bitstring.pack(self.PACKAGING_FORMAT, **to_pack) requestH = request_Bitstring.tobytes() return requestH @@ -888,7 +1092,7 @@ def _unpack(self, headerBytes): :return: """ request_Bitstring = bitstring.BitString(headerBytes) - request_fields = request_Bitstring.unpack(self.package_format) + request_fields = request_Bitstring.unpack(self.PACKAGING_FORMAT) self.remote_ip = request_fields[0] self.min_fidelity = request_fields[1] self.max_time = request_fields[2] diff --git a/cqc/pythonLib.py b/cqc/pythonLib.py index 8c4aa6a..2bb5aba 100644 --- a/cqc/pythonLib.py +++ b/cqc/pythonLib.py @@ -68,7 +68,6 @@ CQCXtraQubitHeader, CQCRotationHeader, CQC_VERSION, - CQC_HDR_LENGTH, CQCHeader, CQC_TP_DONE, CQC_ERR_UNSUPP, @@ -76,7 +75,6 @@ CQC_ERR_GENERAL, CQCSequenceHeader, CQCFactoryHeader, - CQC_CMD_HDR_LENGTH, CQC_TP_INF_TIME, CQC_ERR_NOQUBIT, CQCMeasOutHeader, @@ -176,6 +174,21 @@ def createXtraHeader(command, values): return header + +class CQCVariable: + _next_ref_id = 0 + + def __init__(self): + self._ref_id = CQCVariable._next_ref_id + CQCVariable._next_ref_id += 1 + + # make ref_id a read-only variable + @property + def ref_id(self): + return self._ref_id + + + class CQCConnection: _appIDs = {} @@ -509,7 +522,7 @@ def sendCommand(self, qID, command, notify=1, block=1, action=0): """ # Send Header hdr = CQCHeader() - hdr.setVals(CQC_VERSION, CQC_TP_COMMAND, self._appID, CQC_CMD_HDR_LENGTH) + hdr.setVals(CQC_VERSION, CQC_TP_COMMAND, self._appID, CQCCmdHeader.HDR_LENGTH) msg = hdr.pack() self._s.send(msg) @@ -562,11 +575,11 @@ def sendCmdXtra( xtra_hdr.setVals(step) if xtra_hdr is None: - header_length = CQC_CMD_HDR_LENGTH + header_length = CQCCmdHeader.HDR_LENGTH xtra_msg = b"" else: xtra_msg = xtra_hdr.pack() - header_length = CQC_CMD_HDR_LENGTH + xtra_hdr.HDR_LENGTH + header_length = CQCCmdHeader.HDR_LENGTH + xtra_hdr.HDR_LENGTH # Send Header hdr = CQCHeader() @@ -595,7 +608,7 @@ def sendGetTime(self, qID, notify=1, block=1, action=0): """ # Send Header hdr = CQCHeader() - hdr.setVals(CQC_VERSION, CQC_TP_GET_TIME, self._appID, CQC_CMD_HDR_LENGTH) + hdr.setVals(CQC_VERSION, CQC_TP_GET_TIME, self._appID, CQCCmdHeader.HDR_LENGTH) msg = hdr.pack() self._s.send(msg) @@ -616,7 +629,7 @@ def allocate_qubits(self, num_qubits, notify=True, block=True): # CQC header hdr = CQCHeader() - hdr.setVals(CQC_VERSION, CQC_TP_COMMAND, self._appID, CQC_CMD_HDR_LENGTH) + hdr.setVals(CQC_VERSION, CQC_TP_COMMAND, self._appID, CQCCmdHeader.HDR_LENGTH) cqc_msg = hdr.pack() # Command header @@ -757,10 +770,10 @@ def sendFactory( xtra_hdr = CQCRotationHeader() xtra_hdr.setVals(step_size) xtra_msg = xtra_hdr.pack() - hdr_length = CQC_CMD_HDR_LENGTH + CQCFactoryHeader.HDR_LENGTH + xtra_hdr.HDR_LENGTH + hdr_length = CQCCmdHeader.HDR_LENGTH + CQCFactoryHeader.HDR_LENGTH + xtra_hdr.HDR_LENGTH else: xtra_msg = b"" - hdr_length = CQC_CMD_HDR_LENGTH + CQCFactoryHeader.HDR_LENGTH + hdr_length = CQCCmdHeader.HDR_LENGTH + CQCFactoryHeader.HDR_LENGTH # Send Header hdr = CQCHeader() @@ -835,18 +848,18 @@ def readMessage(self, maxsize=192): # WHAT IS GOOD SIZE? # If we don't have the CQC header yet, try and read it in full. if not gotCQCHeader: - if len(self.buf) < CQC_HDR_LENGTH: + if len(self.buf) < CQCHeader.HDR_LENGTH: # Not enough data for CQC header, return and wait for the rest checkedBuf = True continue # Got enough data for the CQC Header so read it in gotCQCHeader = True - rawHeader = self.buf[0:CQC_HDR_LENGTH] + rawHeader = self.buf[0:CQCHeader.HDR_LENGTH] currHeader = CQCHeader(rawHeader) # Remove the header from the buffer - self.buf = self.buf[CQC_HDR_LENGTH : len(self.buf)] + self.buf = self.buf[CQCHeader.HDR_LENGTH : len(self.buf)] # Check for error self.check_error(currHeader) From 4af78180c8b658e2661f58e13deb5a763a804b4f Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 9 Aug 2019 15:53:21 +0200 Subject: [PATCH 02/16] Restructure how headers are pended --- cqc/pythonLib.py | 377 ++++++++++++++++++++++++++++------------------- 1 file changed, 227 insertions(+), 150 deletions(-) diff --git a/cqc/pythonLib.py b/cqc/pythonLib.py index 2bb5aba..62f8ba1 100644 --- a/cqc/pythonLib.py +++ b/cqc/pythonLib.py @@ -85,6 +85,7 @@ CQC_TP_EPR_OK, CQC_TP_NEW_OK, CQC_TP_EXPIRE, + CQCLogicalOperator ) from cqc.entInfoHeader import EntInfoHeader from cqc.hostConfig import cqc_node_id_from_addrinfo @@ -155,7 +156,7 @@ def get_remote_from_directory_or_address(cqcNet, name, remote_socket): remote_port = addr[4][1] return remote_ip, remote_port - +# !! Deprecated. Do not use this method def createXtraHeader(command, values): if command == CQC_CMD_SEND or command == CQC_CMD_EPR: header = CQCCommunicationHeader() @@ -187,6 +188,14 @@ def __init__(self): def ref_id(self): return self._ref_id + # override the == operator + def __eq__(self, other): + return () + + # override the != operator + def __ne__(self, other): + pass + class CQCConnection: @@ -321,13 +330,18 @@ def __init__(self, name, socket_address=None, appID=None, pend_messages=False, self._s.close() raise err - # List of pending messages waiting to be send to the back-end - self.pend_messages = pend_messages - self.pending_messages = [] - # All qubits active for this connection self.active_qubits = [] + # List of pended header objects waiting to be sent to the backend + self._pending_headers = [] # ONLY cqc.cqcHeader.Header objects should be in this list + + # Bool that indicates whether we are in a factory and thus should pend commands + self.pend_messages = pend_messages + + def _pend_header(self, header: Header) -> None: + self._pending_headers.append(header) + def __enter__(self): # This flag is used to check if CQCConnection is opened using a 'with' statement. # Otherwise an deprecation warning is printed when instantiating qubits. @@ -719,6 +733,8 @@ def release_all_qubits(self): """ return self.release_qubits(self.active_qubits[:]) + + # sendFactory is depecrated. Do not use it. # def sendFactory( self, qID, @@ -1018,15 +1034,25 @@ def sendQubit(self, q, name, remote_appID=0, remote_socket=None, notify=True, bl remote_ip, remote_port = get_remote_from_directory_or_address(self._cqcNet, name, remote_socket) if self.pend_messages: + + # Build command header and communication sub header + command_header = CQCCmdHeader() + command_header.setVals(q._qID, CQC_CMD_SEND, notify, block) + + comm_sub_header = CQCCommunicationHeader() + comm_sub_header.setVals(remote_appID, remote_ip, remote_port) + + # Pend header + self._pend_header(command_header) + self._pend_header(comm_sub_header) + + # print info logging.debug( "App {} pends message: 'Send qubit with ID {} to {} and appID {}'".format( self.name, q._qID, name, remote_appID ) ) - self.pending_messages.append( - [q, CQC_CMD_SEND, int(notify), int(block), [remote_appID, remote_ip, remote_port]] - ) else: # print info logging.debug( @@ -1069,7 +1095,14 @@ def recvQubit(self, notify=True, block=True): if self.pend_messages: # print info logging.debug("App {} pends message: 'Receive qubit'".format(self.name)) - self.pending_messages.append([q, CQC_CMD_RECV, int(notify), int(block)]) + + # Build header + header = CQCCmdHeader() + header.setVals(q._qID, CQC_CMD_RECV, notify, block) + + # Pend header + self._pend_header(header) + return q else: # print info @@ -1113,14 +1146,22 @@ def createEPR(self, name, remote_appID=0, remote_socket=None, notify=True, block q = qubit(self, createNew=False) if self.pend_messages: + + # Build command header and communication sub header + command_header = CQCCmdHeader() + command_header.setVals(q._qID, CQC_CMD_EPR, notify, block) + + comm_sub_header = CQCCommunicationHeader() + comm_sub_header.setVals(remote_appID, remote_ip, remote_port) + + # Pend header + self._pend_header(command_header) + self._pend_header(comm_sub_header) + # print info logging.debug( "App {} pends message: 'Create EPR-pair with {} and appID {}'".format(self.name, name, remote_appID) ) - - self.pending_messages.append( - [q, CQC_CMD_EPR, int(notify), int(block), [remote_appID, remote_ip, remote_port]] - ) return q else: # print info @@ -1168,9 +1209,16 @@ def recvEPR(self, notify=True, block=True): # initialize the qubit q = qubit(self, createNew=False) if self.pend_messages: + + # Build header + header = CQCCmdHeader() + header.setVals(q._qID, CQC_CMD_EPR_RECV, notify, block) + + # Pend header + self._pend_header(header) + # print info logging.debug("App {} pends message: 'Receive half of EPR'".format(self.name)) - self.pending_messages.append([q, CQC_CMD_EPR_RECV, int(notify), int(block)]) return q else: # print info @@ -1200,13 +1248,13 @@ def recvEPR(self, notify=True, block=True): def set_pending(self, pend_messages): """ Set the pend_messages flag. - If true, flush() has to be called to send all pending_messages in sequence to the backend + If true, flush() has to be called to send all self._pending_headers in sequence to the backend If false, all commands are directly send to the back_end :param pend_messages: Boolean to indicate if messages should pend or not """ # Check if the list is not empty, give a warning if it isn't - if self.pending_messages: - logging.warning("List of pending messages is not empty, flushing them") + if self._pending_headers: + logging.warning("List of pending headers is not empty, flushing them") self.flush() self.pend_messages = pend_messages @@ -1218,125 +1266,80 @@ def flush(self, do_sequence=True): """ return self.flush_factory(1, do_sequence) - def flush_factory(self, num_iter, do_sequence=True, block_factory=False): + def flush_factory(self, num_iter, do_sequence=False, block_factory=False): """ Flushes the current pending sequence in a factory. It is performed multiple times :param num_iter: The amount of times the current pending sequence is performed :return: A list of outcomes/qubits that are produced by the commands """ - # Because of new/recv we might have to send headers multiple times - # Loop over the pending_messages until there are no more - # It should only enter the while loop once if num_iter == 1 - # Otherwise it loops for every non active qubit it encounters + + # Initialize should_notify to False + should_notify = False + + # store the total message length + message_length = 0 + + # Remember the headers about which the backend will send a message in return + expect_return = [] + + # Loop over the pending_headers to determine the total length and set should_notify + for header in self._pending_headers: + + message_length += header.HDR_LENGTH + + # Check if the current header is a Command header. It can also be a sub header + if isinstance(header, CQCCmdHeader): + # set should_notify to True if at least one of all command headers has notify to True + should_notify = should_notify or header.notify + + # Remember this header if we expect a return messge + if shouldReturn(header.instr): + expect_return.append(header) + + + + # Determine the CQC Header type + if num_iter == 1: + cqc_type = CQC_TP_COMMAND + else: + cqc_type = CQC_TP_FACTORY + factory_header = CQCFactoryHeader() + factory_header.setVals(num_iter, should_notify, block_factory) + message_length += factory_header.HDR_LENGTH + # Insert the factory header at the front + self._pending_headers.insert(0, factory_header) + + # Build the CQC Header + cqc_header = CQCHeader() + cqc_header.setVals(CQC_VERSION, cqc_type, self._appID, message_length) + # Insert CQC Header at the front + self._pending_headers.insert(0, cqc_header) + + print('========================================================') + # send the headers + for header in self._pending_headers: + self._s.send(header.pack()) + print(header.printable()) + logging.debug("App {} sends CQC: {}".format(self.name, header.printable())) + + print('========================================================') + # Reset _pending_headers to an empty list after all headers are sent + self._pending_headers = [] + + # Read out any returned messages from the backend res = [] - while self.pending_messages: - logging.debug("App {} starts flushing pending messages".format(self.name)) - pending_headers = [] - should_notify = False - header_length = 0 - ready_messages = [] - # Loop over the messages until we encounter an inactive qubit (or end of list) - for message in self.pending_messages[:]: - q = message[0] - cqc_command = message[1] - - qubits_not_active = not q._active and cqc_command not in { - CQC_CMD_EPR_RECV, - CQC_CMD_RECV, - CQC_CMD_NEW, - CQC_CMD_EPR, - } - - if len(message) > 4: - values = message[4] - else: - values = [] - try: - xtra_header = createXtraHeader(cqc_command, values) - except QubitNotActiveError: - qubits_not_active = True - - # Check if the q is active, if it is not, send the current pending_headers - # Then check again, if it still not active, throw an error - if qubits_not_active: - if num_iter != 1: - raise CQCUnsuppError("Some qubits are non active in the factory, this is not supported (yet?)") - if not pending_headers: # If all messages already have been send, the qubit is inactive - raise CQCNoQubitError("Qubit is not active") - logging.debug( - "App {} encountered a non active qubit, sending current pending messages".format(self.name) - ) - break # break out the for loop - - # set qubit to inactive, since we send it away or measured it - if cqc_command == CQC_CMD_SEND or cqc_command == CQC_CMD_MEASURE: - q._set_active(False) - - q_id = q._qID if q._qID is not None else 0 - - self.pending_messages.remove(message) - ready_messages.append(message) - - notify = message[2] - should_notify = should_notify or notify - block = message[3] - - cmd_header = CQCCmdHeader() - cmd_header.setVals(q_id, cqc_command, notify, block, int(do_sequence)) - header_length += cmd_header.HDR_LENGTH - pending_headers.append(cmd_header) - - if xtra_header is not None: - header_length += xtra_header.HDR_LENGTH - pending_headers.append(xtra_header) - - if do_sequence: - sequence_header = CQCSequenceHeader() - header_length += sequence_header.HDR_LENGTH - pending_headers.append(sequence_header) - - # create the header and sequence headers if needed - # We need to find the header length for sequence, - # so loop over the pending_headers in reverse - if do_sequence: - sequence_length = 0 - for header in reversed(pending_headers): - if isinstance(header, CQCSequenceHeader): - header.setVals(sequence_length) - sequence_length += header.HDR_LENGTH - - if num_iter != 1: - factory_header = CQCFactoryHeader() - factory_header.setVals(num_iter, should_notify, block_factory) - header_length += factory_header.HDR_LENGTH - pending_headers.insert(0, factory_header) - cqc_type = CQC_TP_FACTORY - else: - cqc_type = CQC_TP_COMMAND - - cqc_header = CQCHeader() - cqc_header.setVals(CQC_VERSION, cqc_type, self._appID, header_length) - pending_headers.insert(0, cqc_header) - - # send the headers - for header in pending_headers: - logging.debug("App {} sends CQC: {}".format(self.name, header.printable())) - self._s.send(header.pack()) - - # Read out any returned messages from the backend - for i in range(num_iter): - for data in ready_messages: - q = data[0] # qubit object that might be adjusted - cmd = data[1] - if shouldReturn(cmd): - message = self.readMessage() - self.check_error(message[0]) - res.append(self.parse_CQC_msg(message, q, num_iter != 1)) - self.print_CQC_msg(message) - - if should_notify: + for _ in range(num_iter): + for header in expect_return: message = self.readMessage() self.check_error(message[0]) + res.append(self.parse_CQC_msg(message)) + self.print_CQC_msg(message) + + if should_notify: + message = self.readMessage() + self.check_error(message[0]) + + # Return information that the backend returned return res def tomography(self, preparation, iterations, progress=True): @@ -1420,6 +1423,31 @@ def test_preparation(self, preparation, exp_values, conf=2, iterations=100, prog return True + + def cqc_if(self): + + if not self.pend_messages: + raise CQCGeneralError('Conditionals can only be used if messages are pended. Run CQCConnection.set_pending(True) before any conditionals.') + + return CQCConditional(self) + + def cqc_else(self): + pass + + +class CQCConditional: + + def __init__(self, cqc_connection: CQCConnection): + pass + + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + + class ProgressBar: def __init__(self, maxitr): self.maxitr = maxitr @@ -1516,13 +1544,19 @@ def __init__(self, cqc, notify=True, block=True, createNew=True, q_id=None, entI if createNew: if cqc.pend_messages: - # print info - logging.debug("App {} pends message:'Create qubit'".format(self._cqc.name)) - - cqc.pending_messages.append([self, CQC_CMD_NEW, int(notify), int(block)]) # Set q id, None by default self._qID = q_id self._set_active(False) + + # Build header + header = CQCCmdHeader() + header.setVals(0, CQC_CMD_NEW, notify, block) + + # Pend header + self._cqc._pend_header(header) + + # print info + logging.debug("App {} pends message:'Create qubit'".format(self._cqc.name)) else: # print info logging.debug("App {} tells CQC: 'Create qubit'".format(self._cqc.name)) @@ -1547,7 +1581,7 @@ def __init__(self, cqc, notify=True, block=True, createNew=True, q_id=None, entI self._qID = q_id self._set_active(False) # Why? - # Entanglement information + # Entanglement information self._entInfo = entInfo # Lookup remote entangled node @@ -1606,10 +1640,15 @@ def check_active(self): """ Checks if the qubit is active """ - if self._cqc.pend_messages: - return # will be handled in the flush, not here if not self._active: - raise QubitNotActiveError("Qubit is not active, has either been sent, measured, released or not received") + raise QubitNotActiveError(""" + Qubit is not active. Possible causes: + - Qubit is sent to another node + - Qubit is measured (with inplace=False) + - Qubit is realeased released + - Qubit is not not received. + - Qubits is used and created in the same factory. + """) def _set_active(self, be_active): # Check if not already new state @@ -1634,14 +1673,16 @@ def _single_qubit_gate(self, command, notify, block): self.check_active() if self._cqc.pend_messages: + + # Build the header + header = CQCCmdHeader() + header.setVals(qubit_id=self._qID, instr=command, notify=notify, block=block) + # Pend the header + self._cqc._pend_header(header) + # print info - logging.debug( - "App {} pends message: 'Send command {} for qubit with ID {}'".format( - self._cqc.name, command, self._qID - ) - ) + logging.debug("App {} pends header: {}".format(self._cqc.name, header.printable())) - self._cqc.pending_messages.append([self, command, int(notify), int(block)]) else: # print info logging.debug( @@ -1750,13 +1791,24 @@ def _single_gate_rotation(self, command, step, notify, block): self.check_active() if self._cqc.pend_messages: + + # Build command header and rotation sub header + command_header = CQCCmdHeader() + command_header.setVals(self._qID, command, notify, block) + + rot_sub_header = CQCRotationHeader() + rot_sub_header.setVals(step) + + # Pend headers + self._cqc._pend_header(command_header) + self._cqc._pend_header(rot_sub_header) + # print info logging.debug( "App {} pends message: 'Perform rotation command {} (angle {}*2pi/256) to qubit with ID {}'".format( self._cqc.name, command, step, self._qID ) ) - self._cqc.pending_messages.append([self, command, int(notify), int(block), step]) else: # print info logging.debug( @@ -1827,13 +1879,24 @@ def _two_qubit_gate(self, command, target, notify, block): raise CQCUnsuppError("Cannot perform multi qubit operation where control and target are the same") if self._cqc.pend_messages: + + # Build command header and extra qubit sub header + command_header = CQCCmdHeader() + command_header.setVals(self._qID, command, notify, block) + + extra_qubit_sub_header = CQCXtraQubitHeader() + extra_qubit_sub_header.setVals(target._qID) + + # Pend headers + self._cqc._pend_header(command_header) + self._cqc._pend_header(extra_qubit_sub_header) + # print info logging.debug( "App {} pends message: 'Perform CNOT to qubits with IDs {}(control) {}(target)'".format( self._cqc.name, self._qID, target._qID ) - ) - self._cqc.pending_messages.append([self, command, int(notify), int(block), target]) + ) else: # print info logging.debug( @@ -1893,9 +1956,18 @@ def measure(self, inplace=False, block=True): command = CQC_CMD_MEASURE_INPLACE else: command = CQC_CMD_MEASURE + # Set qubit to non active so the user can receive helpful errors during compile time if this qubit is used after this measurement + self._set_active(False) if self._cqc.pend_messages: - self._cqc.pending_messages.append([self, command, 0, int(block)]) + + # Build header + header = CQCCmdHeader() + header.setVals(self._qID, command, block=block) + + # Pend header + self._cqc._pend_header(header) + # print info logging.debug("App {} pends message: 'Measure qubit with ID {}'".format(self._cqc.name, self._qID)) @@ -1907,8 +1979,7 @@ def measure(self, inplace=False, block=True): # Return measurement outcome message = self._cqc.readMessage() - if not inplace: - self._set_active(False) + try: otherHdr = message[1] return otherHdr.outcome @@ -1929,10 +2000,16 @@ def reset(self, notify=True, block=True): self.check_active() if self._cqc.pend_messages: + + # Build header + header = CQCCmdHeader() + header.setVals(self._qID, CQC_CMD_RESET, notify, block) + + # Pend header + self._cqc._pend_header(header) + # print info logging.debug("App {} pends message: 'Reset qubit with ID {}'".format(self._cqc.name, self._qID)) - - self._cqc.pending_messages.append([self, CQC_CMD_RESET, int(notify), int(block)]) else: # print info logging.debug("App {} tells CQC: 'Reset qubit with ID {}'".format(self._cqc.name, self._qID)) From 2fe43f74dc4784301e51eaebedfaca77367b9a64 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 10 Aug 2019 20:54:25 +0200 Subject: [PATCH 03/16] Implement the new CQC headers (IF, ASSIGN, TYPE) into the frontend --- cqc/cqcHeader.py | 12 +- cqc/pythonLib.py | 355 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 300 insertions(+), 67 deletions(-) diff --git a/cqc/cqcHeader.py b/cqc/cqcHeader.py index c139f7c..b74458c 100644 --- a/cqc/cqcHeader.py +++ b/cqc/cqcHeader.py @@ -143,6 +143,14 @@ class CQCLogicalOperator(IntEnum): EQ = 0 # Equal NEQ = 1 # Not equal + @staticmethod + def opposite_of(operator: 'CQCLogicalOperator'): # String literal type hint because it is a forward reference + opposites = { + CQCLogicalOperator.EQ: CQCLogicalOperator.NEQ, + CQCLogicalOperator.NEQ: CQCLogicalOperator.EQ + } + return opposites[operator] + class Header(metaclass=abc.ABCMeta): """ Abstact class for headers. @@ -427,7 +435,9 @@ def _printable(self) -> str: return ("CQC IF header. RefID=" + str(self.first_operand) + " | Operator=" + str(self.operator) + " | " + operand_type + "=" + str(self.second_operand) - + " | Second_operand_type=" + operand_type) + + " | Second_operand_type=" + operand_type + + " | Body_length=" + str(self.length) + ) class CQCCmdHeader(Header): diff --git a/cqc/pythonLib.py b/cqc/pythonLib.py index 62f8ba1..5d7026d 100644 --- a/cqc/pythonLib.py +++ b/cqc/pythonLib.py @@ -27,6 +27,12 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This import allows to use type hints for classes that have not been defined yet. +# See https://stackoverflow.com/questions/33533148/how-do-i-specify-that-the-return-type-of-a-method-is-the-same-as-the-class-itsel +# This import must be the very first import in this file, otherwise an error is raised +from __future__ import annotations + import math import os import sys @@ -34,6 +40,7 @@ import logging import socket import warnings +from typing import Union, Any, List from cqc.cqcHeader import ( Header, @@ -85,7 +92,11 @@ CQC_TP_EPR_OK, CQC_TP_NEW_OK, CQC_TP_EXPIRE, - CQCLogicalOperator + CQCLogicalOperator, + CQCIFHeader, + CQCTypeHeader, + CQCType, + CQCAssignHeader ) from cqc.entInfoHeader import EntInfoHeader from cqc.hostConfig import cqc_node_id_from_addrinfo @@ -175,29 +186,6 @@ def createXtraHeader(command, values): return header - -class CQCVariable: - _next_ref_id = 0 - - def __init__(self): - self._ref_id = CQCVariable._next_ref_id - CQCVariable._next_ref_id += 1 - - # make ref_id a read-only variable - @property - def ref_id(self): - return self._ref_id - - # override the == operator - def __eq__(self, other): - return () - - # override the != operator - def __ne__(self, other): - pass - - - class CQCConnection: _appIDs = {} @@ -339,6 +327,11 @@ def __init__(self, name, socket_address=None, appID=None, pend_messages=False, # Bool that indicates whether we are in a factory and thus should pend commands self.pend_messages = pend_messages + # Bool that indicates wheter we are in a CQCType.PROGRAM + self._inside_cqc_program = False + + + def _pend_header(self, header: Header) -> None: self._pending_headers.append(header) @@ -1258,11 +1251,11 @@ def set_pending(self, pend_messages): self.flush() self.pend_messages = pend_messages - def flush(self, do_sequence=True): + def flush(self, do_sequence=False): """ Flush all pending messages to the backend. :param do_sequence: boolean to indicate if you want to send the pending messages as a sequence - :return: A list of things that are send back from the server. Can be qubits, or outcomes + :return: A list of things that are sent back from the server. Can be qubits, or outcomes """ return self.flush_factory(1, do_sequence) @@ -1276,17 +1269,12 @@ def flush_factory(self, num_iter, do_sequence=False, block_factory=False): # Initialize should_notify to False should_notify = False - # store the total message length - message_length = 0 - - # Remember the headers about which the backend will send a message in return - expect_return = [] + # Store how many of the headers we send will get a response message from the backend + response_amount = 0 # Loop over the pending_headers to determine the total length and set should_notify for header in self._pending_headers: - message_length += header.HDR_LENGTH - # Check if the current header is a Command header. It can also be a sub header if isinstance(header, CQCCmdHeader): # set should_notify to True if at least one of all command headers has notify to True @@ -1294,42 +1282,30 @@ def flush_factory(self, num_iter, do_sequence=False, block_factory=False): # Remember this header if we expect a return messge if shouldReturn(header.instr): - expect_return.append(header) + response_amount += 1 - # Determine the CQC Header type if num_iter == 1: cqc_type = CQC_TP_COMMAND else: + # Build and insert the Factory header cqc_type = CQC_TP_FACTORY factory_header = CQCFactoryHeader() factory_header.setVals(num_iter, should_notify, block_factory) - message_length += factory_header.HDR_LENGTH # Insert the factory header at the front self._pending_headers.insert(0, factory_header) - # Build the CQC Header - cqc_header = CQCHeader() - cqc_header.setVals(CQC_VERSION, cqc_type, self._appID, message_length) - # Insert CQC Header at the front - self._pending_headers.insert(0, cqc_header) - - print('========================================================') - # send the headers - for header in self._pending_headers: - self._s.send(header.pack()) - print(header.printable()) - logging.debug("App {} sends CQC: {}".format(self.name, header.printable())) - - print('========================================================') - # Reset _pending_headers to an empty list after all headers are sent - self._pending_headers = [] + # Insert the cqc header + self.insert_cqc_header(cqc_type) + + # Send all pending headers + self.send_pending_headers() # Read out any returned messages from the backend res = [] for _ in range(num_iter): - for header in expect_return: + for _ in range(response_amount): message = self.readMessage() self.check_error(message[0]) res.append(self.parse_CQC_msg(message)) @@ -1342,6 +1318,58 @@ def flush_factory(self, num_iter, do_sequence=False, block_factory=False): # Return information that the backend returned return res + + + def send_pending_headers(self) -> List[Any]: + """ + Sends all pending headers. + After sending, self._pending_headers is emptied. + """ + + print('========================================================') + # Send all pending headers + for header in self._pending_headers: + #self._s.send(header.pack()) + print('--------------------------------') + print(header.printable()) + logging.debug("App {} sends CQC: {}".format(self.name, header.printable())) + + + print('========================================================') + + # Reset _pending_headers to an empty list after all headers are sent + self._pending_headers = [] + + + + def insert_cqc_header(self, cqc_type: CQCType, version=CQC_VERSION) -> None: + """ + Inserts a CQC Header at index 0 of self._pending_headers. + Invoke this method *after* all other headers are pended, so that the correct message length is calculated. + """ + + # Count the total message length + message_length = 0 + for header in self._pending_headers: + message_length += header.HDR_LENGTH + + # Build the CQC Header + cqc_header = CQCHeader() + cqc_header.setVals(CQC_VERSION, cqc_type, self._appID, message_length) + + # Insert CQC Header at the front + self._pending_headers.insert(0, cqc_header) + + + def _pend_type_header(self, cqc_type: CQCType, length: int) -> None: + """ + Creates a CQCTypeHeader and pends it. + """ + header = CQCTypeHeader() + header.setVals(cqc_type, length) + self._pend_header(header) + + def tomography(self, preparation, iterations, progress=True): """ Does a tomography on the output from the preparation specified. @@ -1423,28 +1451,200 @@ def test_preparation(self, preparation, exp_values, conf=2, iterations=100, prog return True + +class CQCVariable: + _next_ref_id = 0 + + def __init__(self): + self._ref_id = CQCVariable._next_ref_id + CQCVariable._next_ref_id += 1 + + # make ref_id a read-only variable + @property + def ref_id(self): + return self._ref_id + + # override the == operator + # other can be a CQCVariable or int + def __eq__(self, other: Union['CQCVariable', int]): + return LogicalFunction(self, CQCLogicalOperator.EQ, other) - def cqc_if(self): + # override the != operator + def __ne__(self, other: Union['CQCVariable', int]): + return LogicalFunction(self, CQCLogicalOperator.NEQ, other) - if not self.pend_messages: - raise CQCGeneralError('Conditionals can only be used if messages are pended. Run CQCConnection.set_pending(True) before any conditionals.') - return CQCConditional(self) +class LogicalFunction: + + def __init__(self, + operand_one: CQCVariable, + operator: CQCLogicalOperator, + operand_two: Union[CQCVariable, int] + ): + + self.operand_one = operand_one + self.operator = operator + self.operand_two = operand_two + + def get_negation(self) -> LogicalFunction: + return LogicalFunction(self.operand_one, CQCLogicalOperator.opposite_of(self.operator), self.operand_two) + + def get_CQCIFHeader(self) -> CQCIFHeader: + + if isinstance(self.operand_two, int): + type_of_operand_two = CQCIFHeader.TYPE_VALUE + operand_two = self.operand_two + else: + type_of_operand_two = CQCIFHeader.TYPE_REF_ID + operand_two = self.operand_two._ref_id + + header = CQCIFHeader() + header.setVals( + self.operand_one.ref_id, + self.operator, + type_of_operand_two, + operand_two, + length=0 + ) + return header + + + +class CQCProgram: + def __init__(self, cqc_connection: CQCConnection): + self._conn = cqc_connection + + def __enter__(self): + # Set the _inside_cqc_program bool to True on the connection + self._conn._inside_cqc_program = True + + self._conn.pend_messages = True + + # Return self so that this instance is bound to the variable after "as", i.e.: "with CQCProgram() as pgrm" + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + # Build and insert the CQC Header + self._conn.insert_cqc_header(CQCType.PROGRAM) + + # Send this program to the backend + self._conn.send_pending_headers() + + # We are no longer in a TP_PROGRAM + self._conn._inside_cqc_program = False + + self._conn.pend_messages = False + + def cqc_if(self, logical_function: LogicalFunction): + return CQCConditional(self._conn, False, logical_function) def cqc_else(self): - pass + # Find out to which if this else belongs + return CQCConditional(self._conn, True) + + def repeat(self, repetition_amount: int): + return CQCFactory(self._conn, repetition_amount) + +class CQCFactory: + + def __init__(self, cqc_connection: CQCConnection, repetition_amount: int): + self._conn = cqc_connection + self._repetition_amount = repetition_amount + + def __enter__(self): + + # Inside a TP_FACTORY, we don't want CQCType headers before every instruction. + # Therefore, we set this bool to False + self._conn._inside_cqc_program = False + + # Create the CQC Type header, and store it so that we can modify its length at __exit__ + self.type_header = CQCTypeHeader() + self.type_header.setVals(CQCType.FACTORY, length=0) + + # Build the Factory header + factory_header = CQCFactoryHeader() + factory_header.setVals(self._repetition_amount) + + # Pend the headers + self._conn._pend_header(self.type_header) + self._conn._pend_header(factory_header) + + def __exit__(self, exc_type, exc_val, exc_tb): + + # Outside a TP_FACTORY, we want CQCType headers before every instruction. + # Therefore, we set this bool to True + self._conn._inside_cqc_program = True + + # Calculate the length of the body of the factory + # Loop in reverse through all pending_headers to calculate the length of all headers + index = len(self._conn._pending_headers) - 1 + body_length = 0 + while self._conn._pending_headers[index] is not self.type_header: + body_length += self._conn._pending_headers[index].HDR_LENGTH + index -= 1 + + # Set the correct length + self.type_header.length = body_length + class CQCConditional: - def __init__(self, cqc_connection: CQCConnection): - pass + # This private class variable holds the last CQCConditional that + # functioned as an IF (as opposed to an ELSE) on which __exit__ is invoked. + # In other words, it is the last closed IF statement. + # This is important so that ELSE statements can find out to which IF statement they belong. + # If this variable is None, then there either has not been aan IF statement yet, or the last + # CQCConditional was an ELSE. + _last_closed_conditional = None + + def __init__(self, cqc_connection: CQCConnection, is_else: bool, logical_function: LogicalFunction=None): + self._conn = cqc_connection + self.is_else = is_else + + if is_else: + # If _last_closed_conditional is None, then there either has not been aan IF statement yet, or the last + # CQCConditional was an ELSE. + if CQCConditional._last_closed_conditional is None: + raise CQCGeneralError('Cannot use an ELSE if there is no IF directly before it.') + else: + # Get the negation of the logical function of the IF, + # which will be the logical function for this ELSE statement + logical_function = CQCConditional._last_closed_conditional._logical_function.get_negation() + + self._logical_function = logical_function def __enter__(self): - pass + # Pend CQC Type header + self._conn._pend_type_header(CQCType.IF, CQCIFHeader.HDR_LENGTH) + + # Build the IF header, and store it so we can modify its length at __exit__ + self.header = self._logical_function.get_CQCIFHeader() + + # Pend the IF header + self._conn._pend_header(self.header) def __exit__(self, exc_type, exc_val, exc_tb): - pass + + # Set _last_closed_conditional to the correct value + if (self.is_else): + CQCConditional._last_closed_conditional = None + else: + CQCConditional._last_closed_conditional = self + + + # Calculate the length of the body of the conditional + # Loop in reverse through all pending_headers to calculate the lenght of all headers + index = len(self._conn._pending_headers) - 1 + body_length = 0 + while self._conn._pending_headers[index] is not self.header: + body_length += self._conn._pending_headers[index].HDR_LENGTH + index -= 1 + + # Set the correct length + self.header.length = body_length + + @@ -1645,9 +1845,9 @@ def check_active(self): Qubit is not active. Possible causes: - Qubit is sent to another node - Qubit is measured (with inplace=False) - - Qubit is realeased released + - Qubit is released - Qubit is not not received. - - Qubits is used and created in the same factory. + - Qubit is used and created in the same factory. """) def _set_active(self, be_active): @@ -1673,7 +1873,11 @@ def _single_qubit_gate(self, command, notify, block): self.check_active() if self._cqc.pend_messages: - + + # If we are inside a TP_PROGRAM, then insert the CQC Type header before the command header + if self._cqc._inside_cqc_program: + self._cqc._pend_type_header(CQCType.COMMAND, CQCCmdHeader.HDR_LENGTH) + # Build the header header = CQCCmdHeader() header.setVals(qubit_id=self._qID, instr=command, notify=notify, block=block) @@ -1880,6 +2084,10 @@ def _two_qubit_gate(self, command, target, notify, block): if self._cqc.pend_messages: + # If we are inside a TP_PROGRAM, then insert the CQC Type header before the command header + if self._cqc._inside_cqc_program: + self._cqc._pend_type_header(CQCType.COMMAND, CQCCmdHeader.HDR_LENGTH + CQCXtraQubitHeader.HDR_LENGTH) + # Build command header and extra qubit sub header command_header = CQCCmdHeader() command_header.setVals(self._qID, command, notify, block) @@ -1961,16 +2169,31 @@ def measure(self, inplace=False, block=True): if self._cqc.pend_messages: + # If we are inside a TP_PROGRAM, then insert the CQC Type header before the command header + if self._cqc._inside_cqc_program: + self._cqc._pend_type_header(CQCType.COMMAND, CQCCmdHeader.HDR_LENGTH + CQCAssignHeader.HDR_LENGTH) + + # Create a CQC Variable that holds the reference id for the measurement outcome + cqc_variable = CQCVariable() + # Build header header = CQCCmdHeader() header.setVals(self._qID, command, block=block) - # Pend header + # Bild Assign sub header + assign_sub_header = CQCAssignHeader() + assign_sub_header.setVals(cqc_variable.ref_id) + + # Pend headers self._cqc._pend_header(header) + self._cqc._pend_header(assign_sub_header) + # print info logging.debug("App {} pends message: 'Measure qubit with ID {}'".format(self._cqc.name, self._qID)) + return cqc_variable + else: # print info logging.debug("App {} tells CQC: 'Measure qubit with ID {}'".format(self._cqc.name, self._qID)) From f8f60da458d47abf1f4b78a207211a0f5b364336 Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 13 Aug 2019 19:29:43 +0200 Subject: [PATCH 04/16] Implement TP_PROGRAM in backend and build scoping system in frontend --- cqc/MessageHandler.py | 48 ++++++++++++++++++++++--- cqc/cqcHeader.py | 9 +++++ cqc/pythonLib.py | 83 ++++++++++++++++++++++++++++++++----------- 3 files changed, 116 insertions(+), 24 deletions(-) diff --git a/cqc/MessageHandler.py b/cqc/MessageHandler.py index f016fe0..e34dc68 100644 --- a/cqc/MessageHandler.py +++ b/cqc/MessageHandler.py @@ -69,6 +69,8 @@ CQC_ERR_GENERAL, CQCSequenceHeader, CQCFactoryHeader, + CQCType, + CQCTypeHeader ) from twisted.internet.defer import DeferredLock, inlineCallbacks @@ -116,10 +118,12 @@ class CQCMessageHandler(ABC): def __init__(self, factory): # 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, + CQCType.HELLO: self.handle_hello, + CQCType.COMMAND: self.handle_command, + CQCType.FACTORY: self.handle_factory, + CQCType.GET_TIME: self.handle_time, + CQCType.PROGRAM: self.handle_program, + CQCType.IF: self.handle_conditional } # Functions to invoke when receiving a certain command @@ -272,6 +276,7 @@ def _process_command(self, cqc_header, length, data, is_locked=False): logging.debug("CQC %s: Read XTRA Header: %s", self.name, xtra.printable()) # Run this command + print(cmd.printable()) logging.debug("CQC %s: Executing command: %s", self.name, cmd.printable()) if cmd.instr not in self.commandHandlers: logging.debug("CQC {}: Unknown command {}".format(self.name, cmd.instr)) @@ -371,6 +376,41 @@ def handle_factory(self, header, data): return succ and should_notify + + @inlineCallbacks + def handle_program(self, header: CQCHeader, data: bytes): + """ + Handler for messages of TP_PROGRAM. Notice that header is the CQC Header, and data is the complete bocy, excluding the CQC Header. + """ + # Strategy for handling TP_PROGRAM: + # The first bit of data will be a CQC Type header. We extract this header. + # We extract from this first CQC Type header the type of the following instructions, and we invoke the + # corresponding handler from self.messageHandlers. This handler expects as parameter header a CQC Header. + # Therefore, we construct the CQC Header that corresponds to the TP_HEADER, and input that constructed + # CQC Header as header parameter. + # After this handler returns, we repeat until the end of the program. + + current_position = 0 + + while current_position < header.length: + + # Extract CQCTypeHeader + type_header = CQCTypeHeader(data[current_position : current_position + CQCTypeHeader.HDR_LENGTH]) + + current_position += CQCTypeHeader.HDR_LENGTH + + # Create equivalent CQCHeader + equiv_cqc_header = type_header.make_equivalent_CQCHeader(header.version, header.app_id) + + yield self.messageHandlers[type_header.type](equiv_cqc_header, data[current_position:]) + current_position += type_header.length + + + + def handle_conditional(self, header: CQCHeader, data: bytes): + pass + + @abstractmethod def handle_hello(self, header, data): pass diff --git a/cqc/cqcHeader.py b/cqc/cqcHeader.py index b74458c..b621110 100644 --- a/cqc/cqcHeader.py +++ b/cqc/cqcHeader.py @@ -359,6 +359,15 @@ def _printable(self) -> str: Produce a printable string for information purposes. """ return "CQC Type header. Type=" + str(self.type) + " | Length=" + str(self.length) + + + def make_equivalent_CQCHeader(self, version: int, app_id: int) -> CQCHeader: + """ + Produce a CQC Header that is equivalent to this CQCTypeHeader. This method does not make any modifications to self. + """ + cqc_header = CQCHeader() + cqc_header.setVals(version, self.type, app_id, self.length) + return cqc_header diff --git a/cqc/pythonLib.py b/cqc/pythonLib.py index 5d7026d..2f69fd6 100644 --- a/cqc/pythonLib.py +++ b/cqc/pythonLib.py @@ -41,6 +41,7 @@ import socket import warnings from typing import Union, Any, List +from anytree import NodeMixin, RenderTree from cqc.cqcHeader import ( Header, @@ -330,6 +331,8 @@ def __init__(self, name, socket_address=None, appID=None, pend_messages=False, # Bool that indicates wheter we are in a CQCType.PROGRAM self._inside_cqc_program = False + #!!! + self.current_scope = None def _pend_header(self, header: Header) -> None: @@ -716,7 +719,7 @@ def release_qubits(self, qubits, notify=True, block=False, action=False): self.check_error(msg[0]) if msg[0].tp != CQC_TP_DONE: raise CQCUnsuppError( - "Unexpected message send back from the server. Message: {}".format(msg[0].printable()) + "Unexpected message sent back from the server. Message: {}".format(msg[0].printable()) ) self.print_CQC_msg(msg) @@ -1329,9 +1332,9 @@ def send_pending_headers(self) -> List[Any]: print('========================================================') # Send all pending headers for header in self._pending_headers: - #self._s.send(header.pack()) + self._s.send(header.pack()) print('--------------------------------') - print(header.printable()) + print("SENT: " + header.printable()) logging.debug("App {} sends CQC: {}".format(self.name, header.printable())) @@ -1510,10 +1513,13 @@ def get_CQCIFHeader(self) -> CQCIFHeader: -class CQCProgram: +class CQCProgram(NodeMixin): def __init__(self, cqc_connection: CQCConnection): self._conn = cqc_connection + #!!! + self._conn.current_scope = self + def __enter__(self): # Set the _inside_cqc_program bool to True on the connection self._conn._inside_cqc_program = True @@ -1524,16 +1530,26 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): - # Build and insert the CQC Header - self._conn.insert_cqc_header(CQCType.PROGRAM) + + # Only do these things if there was no exception. + if exc_type is None: + # Build and insert the CQC Header + self._conn.insert_cqc_header(CQCType.PROGRAM) - # Send this program to the backend - self._conn.send_pending_headers() + # Send this program to the backend + self._conn.send_pending_headers() + + # We are no longer in a TP_PROGRAM + self._conn._inside_cqc_program = False + + self._conn.pend_messages = False - # We are no longer in a TP_PROGRAM - self._conn._inside_cqc_program = False - self._conn.pend_messages = False + #!!! + self._conn.current_scope = None + + print(RenderTree(self)) + def cqc_if(self, logical_function: LogicalFunction): return CQCConditional(self._conn, False, logical_function) @@ -1542,10 +1558,10 @@ def cqc_else(self): # Find out to which if this else belongs return CQCConditional(self._conn, True) - def repeat(self, repetition_amount: int): - return CQCFactory(self._conn, repetition_amount) + def loop(self, times: int): + return CQCFactory(self._conn, times) -class CQCFactory: +class CQCFactory(): def __init__(self, cqc_connection: CQCConnection, repetition_amount: int): self._conn = cqc_connection @@ -1588,7 +1604,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): -class CQCConditional: +class CQCConditional(NodeMixin): # This private class variable holds the last CQCConditional that # functioned as an IF (as opposed to an ELSE) on which __exit__ is invoked. @@ -1624,6 +1640,11 @@ def __enter__(self): # Pend the IF header self._conn._pend_header(self.header) + + #!!! + self.parent = self._conn.current_scope + self._conn.current_scope = self + def __exit__(self, exc_type, exc_val, exc_tb): # Set _last_closed_conditional to the correct value @@ -1644,6 +1665,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): # Set the correct length self.header.length = body_length + #!!! + self._conn.current_scope = self.parent @@ -1739,9 +1762,13 @@ def __init__(self, cqc, notify=True, block=True, createNew=True, q_id=None, entI "For more information, see https://softwarequtech.github.io/SimulaQron/html/PythonLib.html" ) - # Whether the qubit is active. Will be set in the first run + # Whether the qubit is active. Will be set in the first run self._active = None + # !!! + self.scope_of_deactivation = None + + if createNew: if cqc.pend_messages: # Set q id, None by default @@ -1841,19 +1868,35 @@ def check_active(self): Checks if the qubit is active """ if not self._active: - raise QubitNotActiveError(""" + + #!!! + if ( + not self._cqc._inside_cqc_program + or self.scope_of_deactivation == self._cqc.current_scope + or self.scope_of_deactivation in self._cqc.current_scope.ancestors + or self.scope_of_deactivation in self._cqc.current_scope.descendants + ): + + raise QubitNotActiveError(""" Qubit is not active. Possible causes: - Qubit is sent to another node - Qubit is measured (with inplace=False) - Qubit is released - - Qubit is not not received. - - Qubit is used and created in the same factory. + - Qubit is not received + - Qubit is used and created in the same factory + - Qubit is measured (with inplace=False) inside a cqc_if block earlier in the code """) def _set_active(self, be_active): + + #!!! + if not be_active and self._cqc._inside_cqc_program: + self.scope_of_deactivation = self._cqc.current_scope + # Check if not already new state if self._active == be_active: - return + return + if be_active: self._cqc.active_qubits.append(self) else: From 5e8d55742996afa2f5cc007186ac185938df46a8 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 14 Aug 2019 12:20:02 +0200 Subject: [PATCH 05/16] Return message after TP_PROGRAM has finished executing --- cqc/MessageHandler.py | 47 +++++++++++++++++++++++++++++++++++++------ cqc/pythonLib.py | 10 +++++++-- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/cqc/MessageHandler.py b/cqc/MessageHandler.py index e34dc68..16959fe 100644 --- a/cqc/MessageHandler.py +++ b/cqc/MessageHandler.py @@ -103,6 +103,32 @@ def has_extra(cmd): return False +def is_error_message(message: bytes): + + # Only CQCHeaders can be error messages, so if the length does not correspond it is not an error message + try: + header = CQCHeader(message) + # A ValueError is raised by Header.__init__ if the message cannot be read as a CQCHeader. + # Since only CQCHeaders can contain errors, this means the message is not an error + except ValueError: + return False + + error_types = { + CQCType.ERR_GENERAL, + CQCType.ERR_INUSE, + CQCType.ERR_NOQUBIT, + CQCType.ERR_TIMEOUT, + CQCType.ERR_UNKNOWN, + CQCType.ERR_UNSUPP + } + + if header.tp in error_types: + return True + else: + return False + + + def print_error(error): logging.error("Uncaught twisted error found: {}".format(error)) @@ -380,14 +406,14 @@ def handle_factory(self, header, data): @inlineCallbacks def handle_program(self, header: CQCHeader, data: bytes): """ - Handler for messages of TP_PROGRAM. Notice that header is the CQC Header, and data is the complete bocy, excluding the CQC Header. + Handler for messages of TP_PROGRAM. Notice that header is the CQC Header, and data is the complete body, excluding the CQC Header. """ # Strategy for handling TP_PROGRAM: - # The first bit of data will be a CQC Type header. We extract this header. - # We extract from this first CQC Type header the type of the following instructions, and we invoke the - # corresponding handler from self.messageHandlers. This handler expects as parameter header a CQC Header. - # Therefore, we construct the CQC Header that corresponds to the TP_HEADER, and input that constructed - # CQC Header as header parameter. + # The first bit of data will be a CQCType header. We extract this header. + # We extract from this first CQCType header the type of the following instructions, and we invoke the + # corresponding handler from self.messageHandlers. This handler expects as parameter "header" a CQCHeader. + # Therefore, we construct the CQCHeader that corresponds to the CQCType header (remember that the CQCType header is just a reduced CQCHeader), + # and input that constructed CQCHeader as "header" parameter. # After this handler returns, we repeat until the end of the program. current_position = 0 @@ -406,6 +432,15 @@ def handle_program(self, header: CQCHeader, data: bytes): current_position += type_header.length + # A TP_PROGRAM should return the first error if there is an error message present, and otherwise return one TP_DONE + # We use the next function to retrieve the first error message from the list. + # Notice the [:] syntax. This ensures the underlying list is updated, and not just the variable. + # See https://stackoverflow.com/questions/2361426/get-the-first-item-from-an-iterable-that-matches-a-condition + # and https://stackoverflow.com/questions/1207406/how-to-remove-items-from-a-list-while-iterating + self.return_messages[:] = [next( + (message for message in self.return_messages if is_error_message(message)), + self.create_return_message(header.app_id, CQCType.DONE, cqc_version=header.version) + )] def handle_conditional(self, header: CQCHeader, data: bytes): pass diff --git a/cqc/pythonLib.py b/cqc/pythonLib.py index 2f69fd6..c050934 100644 --- a/cqc/pythonLib.py +++ b/cqc/pythonLib.py @@ -1539,16 +1539,22 @@ def __exit__(self, exc_type, exc_val, exc_tb): # Send this program to the backend self._conn.send_pending_headers() + # We expect one message back, which can be an error or TP_DONE + # This also blocks the program until we have received a message from the backend, + # which is important because it avoids that we send more messages before the backend is finished. + message = self._conn.readMessage() + + # Check if it is an error and assume it is a TP_DONE if it is not an error + self._conn.check_error(message[0]) + # We are no longer in a TP_PROGRAM self._conn._inside_cqc_program = False self._conn.pend_messages = False - #!!! self._conn.current_scope = None - print(RenderTree(self)) def cqc_if(self, logical_function: LogicalFunction): From c6e023c9caa2dbae22c990c1933815e3cfcf8dab Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 14 Aug 2019 15:56:04 +0200 Subject: [PATCH 06/16] Implement CQCAssign and CQCIF in the backend --- cqc/MessageHandler.py | 92 ++++++++++++++++++++++++++----------------- cqc/cqcHeader.py | 14 +++++++ 2 files changed, 69 insertions(+), 37 deletions(-) diff --git a/cqc/MessageHandler.py b/cqc/MessageHandler.py index 16959fe..3551f4c 100644 --- a/cqc/MessageHandler.py +++ b/cqc/MessageHandler.py @@ -70,7 +70,10 @@ CQCSequenceHeader, CQCFactoryHeader, CQCType, - CQCTypeHeader + CQCTypeHeader, + CQCAssignHeader, + CQCIFHeader, + CQCLogicalOperator ) from twisted.internet.defer import DeferredLock, inlineCallbacks @@ -180,7 +183,13 @@ def __init__(self, factory): # Convenience self.name = factory.name - self.return_messages = [] # List of all cqc messages to return + + # List of all cqc messages to return + self.return_messages = [] + + # Dictionary that stores all reference ids and their values. + self.references = {} + @inlineCallbacks def handle_cqc_message(self, header, message, transport=None): @@ -191,6 +200,7 @@ def handle_cqc_message(self, header, message, transport=None): if header.tp in self.messageHandlers: try: should_notify = yield self.messageHandlers[header.tp](header, message) + if should_notify: # Send a notification that we are done if successful logging.debug("CQC %s: Command successful, sent done.", self.name) @@ -255,6 +265,9 @@ def create_extra_header(cmd, cmd_data, cqc_version=CQC_VERSION): elif instruction == CQC_CMD_ROT_X or instruction == CQC_CMD_ROT_Y or instruction == CQC_CMD_ROT_Z: cmd_length = CQCRotationHeader.HDR_LENGTH hdr = CQCRotationHeader(cmd_data[:cmd_length]) + elif instruction == CQC_CMD_MEASURE or instruction == CQC_CMD_MEASURE_INPLACE: + cmd_length = CQCAssignHeader.HDR_LENGTH + hdr = CQCAssignHeader(cmd_data[:cmd_length]) else: return None return hdr @@ -325,41 +338,10 @@ def _process_command(self, cqc_header, length, data, is_locked=False): msg = self.create_return_message(cqc_header.app_id, CQC_ERR_GENERAL, cqc_version=cqc_header.version) self.return_messages.append(msg) return False, 0 + if succ is False: # only if it explicitly is false, if succ is None then we assume it went fine return False, 0 - # Check if there are additional commands to execute afterwards - if cmd.action: - # lock the sequence - if not is_locked: - self._sequence_lock.acquire() - sequence_header = CQCSequenceHeader(data[newl: newl + CQCSequenceHeader.HDR_LENGTH]) - newl += sequence_header.HDR_LENGTH - logging.debug("CQC %s: Reading extra action commands", self.name) - try: - (succ, retNotify) = yield self._process_command( - cqc_header, - sequence_header.cmd_length, - data[newl: newl + sequence_header.cmd_length], - is_locked=True, - ) - except Exception as err: - logging.error( - "CQC {}: Got the following unexpected error when process commands: {}".format(self.name, err) - ) - msg = self.create_return_message(cqc_header.app_id, CQC_ERR_GENERAL, cqc_version=cqc_header.version) - self.return_messages.append(msg) - return False, 0 - - should_notify = should_notify or retNotify - if not succ: - return False, 0 - newl = newl + sequence_header.cmd_length - if not is_locked: - logging.debug("CQC %s: Releasing lock", self.name) - # unlock - self._sequence_lock.release() - cur_length = newl return True, should_notify @@ -428,9 +410,13 @@ def handle_program(self, header: CQCHeader, data: bytes): # Create equivalent CQCHeader equiv_cqc_header = type_header.make_equivalent_CQCHeader(header.version, header.app_id) - yield self.messageHandlers[type_header.type](equiv_cqc_header, data[current_position:]) + result = yield self.messageHandlers[type_header.type](equiv_cqc_header, data[current_position:]) + current_position += type_header.length - + + if type_header.type == CQCType.IF: + current_position += result + # A TP_PROGRAM should return the first error if there is an error message present, and otherwise return one TP_DONE # We use the next function to retrieve the first error message from the list. @@ -442,8 +428,40 @@ def handle_program(self, header: CQCHeader, data: bytes): self.create_return_message(header.app_id, CQCType.DONE, cqc_version=header.version) )] + # The other handlers from self.message_handlers return a bool that indicates whether + # self.handle_cqc_message should append a TP_DONE message. This handle_program method does that itself + # if necessary so we just return nothing (None). + def handle_conditional(self, header: CQCHeader, data: bytes): - pass + """ + Handler for messages of TP_IF. + """ + # Strategy for handling TP_IF: + # We extract the CQCIFHeader from the data. We then extract all necessary variables from the header. + # We then evaluate the conditional. If the conditional evaluates to FALSE, then we return the bodylength of + # the IF. The program handler will than skip this bodylength. If the conditional evaluates to True, then we return 0. + + if_header = CQCIFHeader(data[:CQCIFHeader.HDR_LENGTH]) + + try: + first_operand_value = self.references[if_header.first_operand] + except KeyError: + self.return_messages.append( + self.create_return_message(header.app_id, CQC_ERR_GENERAL, cqc_version=header.version)) + + if if_header.type_of_second_operand is CQCIFHeader.TYPE_VALUE: + second_operand_value = if_header.second_operand + else: + try: + second_operand_value = self.references[if_header.second_operand] + except KeyError: + self.return_messages.append( + self.create_return_message(header.app_id, CQC_ERR_GENERAL, cqc_version=header.version)) + + if CQCLogicalOperator.is_true(first_operand_value, if_header.operator, second_operand_value): + return 0 + else: + return if_header.length @abstractmethod diff --git a/cqc/cqcHeader.py b/cqc/cqcHeader.py index b621110..9e7a915 100644 --- a/cqc/cqcHeader.py +++ b/cqc/cqcHeader.py @@ -27,6 +27,12 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This import allows to use type hints for classes that have not been defined yet. +# See https://stackoverflow.com/questions/33533148/how-do-i-specify-that-the-return-type-of-a-method-is-the-same-as-the-class-itsel +# This import must be the very first import in this file, otherwise an error is raised +from __future__ import annotations + import warnings import struct @@ -151,6 +157,14 @@ def opposite_of(operator: 'CQCLogicalOperator'): # String literal type hint beca } return opposites[operator] + @staticmethod + def is_true(first_operand: int, operator: CQCLogicalOperator, second_operand: int): + comparison_method = { + CQCLogicalOperator.EQ: first_operand.__eq__, + CQCLogicalOperator.NEQ: first_operand.__ne__ + } + return comparison_method[operator](second_operand) + class Header(metaclass=abc.ABCMeta): """ Abstact class for headers. From b58846ef243bbce6bc82c47fadd830f39c80d357 Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 1 Oct 2019 23:30:03 +0200 Subject: [PATCH 07/16] Writing comments --- cqc/pythonLib.py | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/cqc/pythonLib.py b/cqc/pythonLib.py index c050934..7f1a230 100644 --- a/cqc/pythonLib.py +++ b/cqc/pythonLib.py @@ -331,7 +331,8 @@ def __init__(self, name, socket_address=None, appID=None, pend_messages=False, # Bool that indicates wheter we are in a CQCType.PROGRAM self._inside_cqc_program = False - #!!! + # Variable of type NodeMixin. This variable is used in CQCMix types to create a + # scoping mechanism. self.current_scope = None @@ -554,6 +555,7 @@ def sendCmdXtra( remote_appID=0, remote_node=0, remote_port=0, + ref_id=0 ): """ Sends a simple message, command message and xtra message to the cqc server. @@ -583,6 +585,9 @@ def sendCmdXtra( elif command == CQC_CMD_ROT_X or command == CQC_CMD_ROT_Y or command == CQC_CMD_ROT_Z: xtra_hdr = CQCRotationHeader() xtra_hdr.setVals(step) + elif command == CQC_CMD_MEASURE: + xtra_hdr = CQCAssignHeader() + xtra_hdr.setVals(ref_id) if xtra_hdr is None: header_length = CQCCmdHeader.HDR_LENGTH @@ -1517,7 +1522,7 @@ class CQCProgram(NodeMixin): def __init__(self, cqc_connection: CQCConnection): self._conn = cqc_connection - #!!! + # Set the current scope to self self._conn.current_scope = self def __enter__(self): @@ -1552,7 +1557,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): self._conn.pend_messages = False - #!!! + # Set the current scope to None, since we exit the CQCMix context + # current_scope is only used inside CQCMix contexts self._conn.current_scope = None @@ -1647,7 +1653,7 @@ def __enter__(self): self._conn._pend_header(self.header) - #!!! + # Register the parent scope, and set the current scope to self self.parent = self._conn.current_scope self._conn.current_scope = self @@ -1671,7 +1677,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): # Set the correct length self.header.length = body_length - #!!! + # Set the scope to the parent scope self._conn.current_scope = self.parent @@ -1771,7 +1777,8 @@ def __init__(self, cqc, notify=True, block=True, createNew=True, q_id=None, entI # Whether the qubit is active. Will be set in the first run self._active = None - # !!! + # This stores the scope (type NodeMixin) in which this qubit was deactivated + # If the qubit has not yet been deactivated, this is set to None self.scope_of_deactivation = None @@ -1875,7 +1882,17 @@ def check_active(self): """ if not self._active: - #!!! + # This conditional checks whether it is certain that the qubit is inactive at this + # point in the code. If such is the case, an error is raised. + # At this point, it is certain that self_active is False. However, this does not necessarily + # mean that the qubit is inactive due to the possibility to write cqc_if blocks. + # There are four options: + # 1) Control is currently not inside a CQCMix. In that case, the qubit is inactive. + # 2) The qubit was deactivated in the current scope. The qubit therefore is inactive. + # 3) The qubit was deactivated in an ancestor scope. The qubit therefore is inactive. + # 4) The qubit was deactivated in a descendent scope. The qubit is therefore inactive. + # The only possible way self_active can be False but the qubit is in fact active, is + # if the qubit was deactivated in a sibling scope, such as the sibling if-block of an else-block. if ( not self._cqc._inside_cqc_program or self.scope_of_deactivation == self._cqc.current_scope @@ -1895,7 +1912,7 @@ def check_active(self): def _set_active(self, be_active): - #!!! + # Set the scope of deactivation to the current scope, if inside a CQCMix. if not be_active and self._cqc._inside_cqc_program: self.scope_of_deactivation = self._cqc.current_scope @@ -2247,7 +2264,8 @@ def measure(self, inplace=False, block=True): # print info logging.debug("App {} tells CQC: 'Measure qubit with ID {}'".format(self._cqc.name, self._qID)) - self._cqc.sendCommand(self._qID, command, notify=0, block=int(block)) + # Ref id is unimportant in this case because we are not inside a CQCMix + self._cqc.sendCmdXtra(self._qID, command, notify=0, block=int(block), ref_id=0) # Return measurement outcome message = self._cqc.readMessage() From c2c128a465ace3aa73613ac5c0db8968a938476f Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 1 Oct 2019 23:54:13 +0200 Subject: [PATCH 08/16] Make reference IDs application private --- cqc/MessageHandler.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cqc/MessageHandler.py b/cqc/MessageHandler.py index 3551f4c..a4936e3 100644 --- a/cqc/MessageHandler.py +++ b/cqc/MessageHandler.py @@ -187,7 +187,8 @@ def __init__(self, factory): # List of all cqc messages to return self.return_messages = [] - # Dictionary that stores all reference ids and their values. + # Dictionary that stores all reference ids and their values privately for each app_id. + # Query/assign like this: self.references[app_id][ref_id] self.references = {} @@ -197,6 +198,11 @@ def handle_cqc_message(self, header, message, transport=None): This calls the correct method to handle the cqcmessage, based on the type specified in the header """ self.return_messages = [] + + # References are app_id private. If this app doesn't yet have a references dictionary, create one. + if header.app_id not in self.references: + self.references[header.app_id] = {} + if header.tp in self.messageHandlers: try: should_notify = yield self.messageHandlers[header.tp](header, message) @@ -444,7 +450,7 @@ def handle_conditional(self, header: CQCHeader, data: bytes): if_header = CQCIFHeader(data[:CQCIFHeader.HDR_LENGTH]) try: - first_operand_value = self.references[if_header.first_operand] + first_operand_value = self.references[header.app_id][if_header.first_operand] except KeyError: self.return_messages.append( self.create_return_message(header.app_id, CQC_ERR_GENERAL, cqc_version=header.version)) @@ -453,7 +459,7 @@ def handle_conditional(self, header: CQCHeader, data: bytes): second_operand_value = if_header.second_operand else: try: - second_operand_value = self.references[if_header.second_operand] + second_operand_value = self.references[header.app_id][if_header.second_operand] except KeyError: self.return_messages.append( self.create_return_message(header.app_id, CQC_ERR_GENERAL, cqc_version=header.version)) From 82d94c0a92cf35a87f58e1359aa817a8c3a4ad98 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 2 Oct 2019 00:38:36 +0200 Subject: [PATCH 09/16] Change name of CQC type "Program" to "Mix" --- cqc/MessageHandler.py | 14 +++++++------- cqc/cqcHeader.py | 2 +- cqc/pythonLib.py | 38 +++++++++++++++++++------------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/cqc/MessageHandler.py b/cqc/MessageHandler.py index a4936e3..cc42561 100644 --- a/cqc/MessageHandler.py +++ b/cqc/MessageHandler.py @@ -151,7 +151,7 @@ def __init__(self, factory): CQCType.COMMAND: self.handle_command, CQCType.FACTORY: self.handle_factory, CQCType.GET_TIME: self.handle_time, - CQCType.PROGRAM: self.handle_program, + CQCType.MIX: self.handle_mix, CQCType.IF: self.handle_conditional } @@ -392,11 +392,11 @@ def handle_factory(self, header, data): @inlineCallbacks - def handle_program(self, header: CQCHeader, data: bytes): + def handle_mix(self, header: CQCHeader, data: bytes): """ - Handler for messages of TP_PROGRAM. Notice that header is the CQC Header, and data is the complete body, excluding the CQC Header. + Handler for messages of TP_MIX. Notice that header is the CQC Header, and data is the complete body, excluding the CQC Header. """ - # Strategy for handling TP_PROGRAM: + # Strategy for handling TP_MIX: # The first bit of data will be a CQCType header. We extract this header. # We extract from this first CQCType header the type of the following instructions, and we invoke the # corresponding handler from self.messageHandlers. This handler expects as parameter "header" a CQCHeader. @@ -424,7 +424,7 @@ def handle_program(self, header: CQCHeader, data: bytes): current_position += result - # A TP_PROGRAM should return the first error if there is an error message present, and otherwise return one TP_DONE + # A TP_MIX should return the first error if there is an error message present, and otherwise return one TP_DONE # We use the next function to retrieve the first error message from the list. # Notice the [:] syntax. This ensures the underlying list is updated, and not just the variable. # See https://stackoverflow.com/questions/2361426/get-the-first-item-from-an-iterable-that-matches-a-condition @@ -435,7 +435,7 @@ def handle_program(self, header: CQCHeader, data: bytes): )] # The other handlers from self.message_handlers return a bool that indicates whether - # self.handle_cqc_message should append a TP_DONE message. This handle_program method does that itself + # self.handle_cqc_message should append a TP_DONE message. This handle_mix method does that itself # if necessary so we just return nothing (None). def handle_conditional(self, header: CQCHeader, data: bytes): @@ -445,7 +445,7 @@ def handle_conditional(self, header: CQCHeader, data: bytes): # Strategy for handling TP_IF: # We extract the CQCIFHeader from the data. We then extract all necessary variables from the header. # We then evaluate the conditional. If the conditional evaluates to FALSE, then we return the bodylength of - # the IF. The program handler will than skip this bodylength. If the conditional evaluates to True, then we return 0. + # the IF. The mix handler will then skip this bodylength. If the conditional evaluates to True, then we return 0. if_header = CQCIFHeader(data[:CQCIFHeader.HDR_LENGTH]) diff --git a/cqc/cqcHeader.py b/cqc/cqcHeader.py index 9e7a915..b4be60c 100644 --- a/cqc/cqcHeader.py +++ b/cqc/cqcHeader.py @@ -134,7 +134,7 @@ class CQCType(IntEnum): GET_TIME = 8 # Get creation time of qubit INF_TIME = 9 # Return timinig information NEW_OK = 10 # Created a new qubit - PROGRAM = 11 # Indicate that the program will contain multiple header types + MIX = 11 # Indicate that the CQC program will contain multiple header types IF = 12 # Announce a CQC IF header ERR_GENERAL = 20 # General purpose error (no details diff --git a/cqc/pythonLib.py b/cqc/pythonLib.py index 7f1a230..1f1c2dc 100644 --- a/cqc/pythonLib.py +++ b/cqc/pythonLib.py @@ -328,8 +328,8 @@ def __init__(self, name, socket_address=None, appID=None, pend_messages=False, # Bool that indicates whether we are in a factory and thus should pend commands self.pend_messages = pend_messages - # Bool that indicates wheter we are in a CQCType.PROGRAM - self._inside_cqc_program = False + # Bool that indicates wheter we are in a CQCType.MIX + self._inside_cqc_mix = False # Variable of type NodeMixin. This variable is used in CQCMix types to create a # scoping mechanism. @@ -1518,7 +1518,7 @@ def get_CQCIFHeader(self) -> CQCIFHeader: -class CQCProgram(NodeMixin): +class CQCMix(NodeMixin): def __init__(self, cqc_connection: CQCConnection): self._conn = cqc_connection @@ -1526,12 +1526,12 @@ def __init__(self, cqc_connection: CQCConnection): self._conn.current_scope = self def __enter__(self): - # Set the _inside_cqc_program bool to True on the connection - self._conn._inside_cqc_program = True + # Set the _inside_cqc_mix bool to True on the connection + self._conn._inside_cqc_mix = True self._conn.pend_messages = True - # Return self so that this instance is bound to the variable after "as", i.e.: "with CQCProgram() as pgrm" + # Return self so that this instance is bound to the variable after "as", i.e.: "with CQCMix() as pgrm" return self def __exit__(self, exc_type, exc_val, exc_tb): @@ -1539,7 +1539,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): # Only do these things if there was no exception. if exc_type is None: # Build and insert the CQC Header - self._conn.insert_cqc_header(CQCType.PROGRAM) + self._conn.insert_cqc_header(CQCType.MIX) # Send this program to the backend self._conn.send_pending_headers() @@ -1552,8 +1552,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): # Check if it is an error and assume it is a TP_DONE if it is not an error self._conn.check_error(message[0]) - # We are no longer in a TP_PROGRAM - self._conn._inside_cqc_program = False + # We are no longer in a TP_MIX + self._conn._inside_cqc_mix = False self._conn.pend_messages = False @@ -1583,7 +1583,7 @@ def __enter__(self): # Inside a TP_FACTORY, we don't want CQCType headers before every instruction. # Therefore, we set this bool to False - self._conn._inside_cqc_program = False + self._conn._inside_cqc_mix = False # Create the CQC Type header, and store it so that we can modify its length at __exit__ self.type_header = CQCTypeHeader() @@ -1601,7 +1601,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): # Outside a TP_FACTORY, we want CQCType headers before every instruction. # Therefore, we set this bool to True - self._conn._inside_cqc_program = True + self._conn._inside_cqc_mix = True # Calculate the length of the body of the factory # Loop in reverse through all pending_headers to calculate the length of all headers @@ -1894,7 +1894,7 @@ def check_active(self): # The only possible way self_active can be False but the qubit is in fact active, is # if the qubit was deactivated in a sibling scope, such as the sibling if-block of an else-block. if ( - not self._cqc._inside_cqc_program + not self._cqc._inside_cqc_mix or self.scope_of_deactivation == self._cqc.current_scope or self.scope_of_deactivation in self._cqc.current_scope.ancestors or self.scope_of_deactivation in self._cqc.current_scope.descendants @@ -1913,7 +1913,7 @@ def check_active(self): def _set_active(self, be_active): # Set the scope of deactivation to the current scope, if inside a CQCMix. - if not be_active and self._cqc._inside_cqc_program: + if not be_active and self._cqc._inside_cqc_mix: self.scope_of_deactivation = self._cqc.current_scope # Check if not already new state @@ -1940,8 +1940,8 @@ def _single_qubit_gate(self, command, notify, block): if self._cqc.pend_messages: - # If we are inside a TP_PROGRAM, then insert the CQC Type header before the command header - if self._cqc._inside_cqc_program: + # If we are inside a TP_MIX, then insert the CQC Type header before the command header + if self._cqc._inside_cqc_mix: self._cqc._pend_type_header(CQCType.COMMAND, CQCCmdHeader.HDR_LENGTH) # Build the header @@ -2150,8 +2150,8 @@ def _two_qubit_gate(self, command, target, notify, block): if self._cqc.pend_messages: - # If we are inside a TP_PROGRAM, then insert the CQC Type header before the command header - if self._cqc._inside_cqc_program: + # If we are inside a TP_MIX, then insert the CQC Type header before the command header + if self._cqc._inside_cqc_mix: self._cqc._pend_type_header(CQCType.COMMAND, CQCCmdHeader.HDR_LENGTH + CQCXtraQubitHeader.HDR_LENGTH) # Build command header and extra qubit sub header @@ -2235,8 +2235,8 @@ def measure(self, inplace=False, block=True): if self._cqc.pend_messages: - # If we are inside a TP_PROGRAM, then insert the CQC Type header before the command header - if self._cqc._inside_cqc_program: + # If we are inside a TP_MIX, then insert the CQC Type header before the command header + if self._cqc._inside_cqc_mix: self._cqc._pend_type_header(CQCType.COMMAND, CQCCmdHeader.HDR_LENGTH + CQCAssignHeader.HDR_LENGTH) # Create a CQC Variable that holds the reference id for the measurement outcome From f12fcca31fb09fcf9ce7818c77e19eaa50cfed75 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 2 Oct 2019 01:19:17 +0200 Subject: [PATCH 10/16] Write comments and give CQCMix support to all qubit commands --- cqc/pythonLib.py | 92 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/cqc/pythonLib.py b/cqc/pythonLib.py index 1f1c2dc..ef6d52a 100644 --- a/cqc/pythonLib.py +++ b/cqc/pythonLib.py @@ -1334,17 +1334,11 @@ def send_pending_headers(self) -> List[Any]: After sending, self._pending_headers is emptied. """ - print('========================================================') # Send all pending headers for header in self._pending_headers: self._s.send(header.pack()) - print('--------------------------------') - print("SENT: " + header.printable()) logging.debug("App {} sends CQC: {}".format(self.name, header.printable())) - - print('========================================================') - # Reset _pending_headers to an empty list after all headers are sent self._pending_headers = [] @@ -1461,15 +1455,26 @@ def test_preparation(self, preparation, exp_values, conf=2, iterations=100, prog class CQCVariable: + """ + Instances of this class are returned by measure command, if executed inside a CQCMix context. + A CQCVariable holds a reference ID with which one can refer to the outcome of the measurement. + """ _next_ref_id = 0 def __init__(self): + """ + Increments the reference ID, and assigns the new unique reference ID to this CQCVariable. + This system ensures no two CQCVariable instances have the same reference ID. + """ self._ref_id = CQCVariable._next_ref_id CQCVariable._next_ref_id += 1 # make ref_id a read-only variable @property def ref_id(self): + """ + Get the refernce ID of this CQCVariable. This is a read-only property. + """ return self._ref_id # override the == operator @@ -1483,12 +1488,27 @@ def __ne__(self, other: Union['CQCVariable', int]): class LogicalFunction: + """ + Private helper class. This class should never be used outside this pythonLib. + """ def __init__(self, operand_one: CQCVariable, operator: CQCLogicalOperator, operand_two: Union[CQCVariable, int] ): + """ + Stores all information necessary to create a logical comparison + + - **Arguments** + + :operand_one: The CQCVariable that stores the measurement outcome that must be compared + :operator: One of the CQCLogicalOperator types that CQC supports. + At present, equality and inequality are supported. + :operand_two: Either a CQCVariable or an integer. + If a CQCVariable, then the value behind this variable will be compared to operand_one. + If an integer, then the value behind operand_one will be compared to this integer. + """ self.operand_one = operand_one self.operator = operator @@ -1498,6 +1518,9 @@ def get_negation(self) -> LogicalFunction: return LogicalFunction(self.operand_one, CQCLogicalOperator.opposite_of(self.operator), self.operand_two) def get_CQCIFHeader(self) -> CQCIFHeader: + """ + Builds the If header corresponding to this logical function. + """ if isinstance(self.operand_two, int): type_of_operand_two = CQCIFHeader.TYPE_VALUE @@ -1519,7 +1542,20 @@ def get_CQCIFHeader(self) -> CQCIFHeader: class CQCMix(NodeMixin): + """ + This Python Context Manager Type can be used to create CQC programs that consist of more than a single type. + Hence the name CQC Mix. Programs of this type can consist of any number and mix of the other CQC types. + """ + def __init__(self, cqc_connection: CQCConnection): + """ + Initializes the Mix context. + + - **Arguments** + + :cqc_connection: The CQCConnection to which this CQC Program must be sent. + """ + self._conn = cqc_connection # Set the current scope to self @@ -1564,16 +1600,45 @@ def __exit__(self, exc_type, exc_val, exc_tb): def cqc_if(self, logical_function: LogicalFunction): + """ + Open a Python Context Manager Type to start an if-statement block. + + - **Arguments** + + :logical_function: A LogicalFunction instance. Never instantiate this explicitely; instead + use the following: CQCVariable == 1 OR CQCVariable == CQCVariable. + CQCVariable can be any instance that you want to test to a value, or to another + CQCVariable. The operator can be == or !=. + The value can be any integer (though only 1 and 0 make sense). + + """ return CQCConditional(self._conn, False, logical_function) def cqc_else(self): + """ + Open a Python Context Manager Type to start an else-statement block. + This will be an else-block of the last closed cqc_if-block. + """ # Find out to which if this else belongs return CQCConditional(self._conn, True) def loop(self, times: int): + """ + Open a Python Context Manager Type to start a factory (i.e. repeated sequence of commands). + + - **Arguments** + + :times: The number of times the commands inside body of this context should be repeated. + + """ return CQCFactory(self._conn, times) -class CQCFactory(): +class CQCFactory: + """ + Private class to create factories inside CQCMix contexts. Never explicitely instantiate this class outside + the source code of this library. + Instead, use CQCMix.loop(x), where x is the amount of times to repeat. + """ def __init__(self, cqc_connection: CQCConnection, repetition_amount: int): self._conn = cqc_connection @@ -1617,6 +1682,11 @@ def __exit__(self, exc_type, exc_val, exc_tb): class CQCConditional(NodeMixin): + """ + Private helper class. Never explicitely instantiate this class outside the source code of this library. + This Context Manager class is instantiated by CQCMix.cqc_if() and CQCMix.cqc_else(). Its + function is to build and pend CQC If headers. + """ # This private class variable holds the last CQCConditional that # functioned as an IF (as opposed to an ELSE) on which __exit__ is invoked. @@ -2062,6 +2132,10 @@ def _single_gate_rotation(self, command, step, notify, block): if self._cqc.pend_messages: + # If we are inside a TP_MIX, then insert the CQC Type header before the command header + if self._cqc._inside_cqc_mix: + self._cqc._pend_type_header(CQCType.COMMAND, CQCCmdHeader.HDR_LENGTH + CQCRotationHeader.HDR_LENGTH) + # Build command header and rotation sub header command_header = CQCCmdHeader() command_header.setVals(self._qID, command, notify, block) @@ -2291,6 +2365,10 @@ def reset(self, notify=True, block=True): if self._cqc.pend_messages: + # If we are inside a TP_MIX, then insert the CQC Type header before the command header + if self._cqc._inside_cqc_mix: + self._cqc._pend_type_header(CQCType.COMMAND, CQCCmdHeader.HDR_LENGTH) + # Build header header = CQCCmdHeader() header.setVals(self._qID, CQC_CMD_RESET, notify, block) From 246a7c2c395b5a7ef84684e856ab1fe85aaff1dd Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 4 Oct 2019 13:15:40 +0200 Subject: [PATCH 11/16] Fixed linting errors. --- .flake8 | 2 +- cqc/MessageHandler.py | 24 +++++-------- cqc/cqcHeader.py | 79 ++++++++++++++++++------------------------- cqc/pythonLib.py | 41 +++++++--------------- 4 files changed, 55 insertions(+), 91 deletions(-) diff --git a/.flake8 b/.flake8 index 986d993..f62aea7 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,6 @@ [flake8] count = True exclude = docs, .git, .idea, __pycache__ -ignore = E203 W291 W293 E743 E265 W605 +ignore = E203 W291 W293 E743 E265 W605 W503 max-line-length = 120 statistics = True diff --git a/cqc/MessageHandler.py b/cqc/MessageHandler.py index cc42561..09c37eb 100644 --- a/cqc/MessageHandler.py +++ b/cqc/MessageHandler.py @@ -38,10 +38,6 @@ CQC_CMD_ROT_X, CQC_CMD_ROT_Y, CQC_CMD_ROT_Z, - CQC_TP_HELLO, - CQC_TP_COMMAND, - CQC_TP_FACTORY, - CQC_TP_GET_TIME, CQC_CMD_I, CQC_CMD_X, CQC_CMD_Y, @@ -67,7 +63,6 @@ CQC_ERR_UNSUPP, CQC_ERR_UNKNOWN, CQC_ERR_GENERAL, - CQCSequenceHeader, CQCFactoryHeader, CQCType, CQCTypeHeader, @@ -106,6 +101,7 @@ def has_extra(cmd): return False + def is_error_message(message: bytes): # Only CQCHeaders can be error messages, so if the length does not correspond it is not an error message @@ -130,8 +126,6 @@ def is_error_message(message: bytes): else: return False - - def print_error(error): logging.error("Uncaught twisted error found: {}".format(error)) @@ -191,7 +185,6 @@ def __init__(self, factory): # Query/assign like this: self.references[app_id][ref_id] self.references = {} - @inlineCallbacks def handle_cqc_message(self, header, message, transport=None): """ @@ -390,17 +383,18 @@ def handle_factory(self, header, data): return succ and should_notify - @inlineCallbacks def handle_mix(self, header: CQCHeader, data: bytes): """ - Handler for messages of TP_MIX. Notice that header is the CQC Header, and data is the complete body, excluding the CQC Header. + Handler for messages of TP_MIX. Notice that header is the CQC Header, + and data is the complete body, excluding the CQC Header. """ # Strategy for handling TP_MIX: # The first bit of data will be a CQCType header. We extract this header. # We extract from this first CQCType header the type of the following instructions, and we invoke the # corresponding handler from self.messageHandlers. This handler expects as parameter "header" a CQCHeader. - # Therefore, we construct the CQCHeader that corresponds to the CQCType header (remember that the CQCType header is just a reduced CQCHeader), + # Therefore, we construct the CQCHeader that corresponds to the CQCType header + # (remember that the CQCType header is just a reduced CQCHeader), # and input that constructed CQCHeader as "header" parameter. # After this handler returns, we repeat until the end of the program. @@ -423,7 +417,6 @@ def handle_mix(self, header: CQCHeader, data: bytes): if type_header.type == CQCType.IF: current_position += result - # A TP_MIX should return the first error if there is an error message present, and otherwise return one TP_DONE # We use the next function to retrieve the first error message from the list. # Notice the [:] syntax. This ensures the underlying list is updated, and not just the variable. @@ -445,7 +438,8 @@ def handle_conditional(self, header: CQCHeader, data: bytes): # Strategy for handling TP_IF: # We extract the CQCIFHeader from the data. We then extract all necessary variables from the header. # We then evaluate the conditional. If the conditional evaluates to FALSE, then we return the bodylength of - # the IF. The mix handler will then skip this bodylength. If the conditional evaluates to True, then we return 0. + # the IF. The mix handler will then skip this bodylength. + # If the conditional evaluates to True, then we return 0. if_header = CQCIFHeader(data[:CQCIFHeader.HDR_LENGTH]) @@ -453,7 +447,8 @@ def handle_conditional(self, header: CQCHeader, data: bytes): first_operand_value = self.references[header.app_id][if_header.first_operand] except KeyError: self.return_messages.append( - self.create_return_message(header.app_id, CQC_ERR_GENERAL, cqc_version=header.version)) + self.create_return_message(header.app_id, CQC_ERR_GENERAL, cqc_version=header.version) + ) if if_header.type_of_second_operand is CQCIFHeader.TYPE_VALUE: second_operand_value = if_header.second_operand @@ -469,7 +464,6 @@ def handle_conditional(self, header: CQCHeader, data: bytes): else: return if_header.length - @abstractmethod def handle_hello(self, header, data): pass diff --git a/cqc/cqcHeader.py b/cqc/cqcHeader.py index b4be60c..50be4ee 100644 --- a/cqc/cqcHeader.py +++ b/cqc/cqcHeader.py @@ -29,7 +29,7 @@ # This import allows to use type hints for classes that have not been defined yet. -# See https://stackoverflow.com/questions/33533148/how-do-i-specify-that-the-return-type-of-a-method-is-the-same-as-the-class-itsel +# See https://stackoverflow.com/questions/33533148/ # This import must be the very first import in this file, otherwise an error is raised from __future__ import annotations @@ -134,8 +134,8 @@ class CQCType(IntEnum): GET_TIME = 8 # Get creation time of qubit INF_TIME = 9 # Return timinig information NEW_OK = 10 # Created a new qubit - MIX = 11 # Indicate that the CQC program will contain multiple header types - IF = 12 # Announce a CQC IF header + MIX = 11 # Indicate that the CQC program will contain multiple header types + IF = 12 # Announce a CQC IF header ERR_GENERAL = 20 # General purpose error (no details ERR_NOQUBIT = 21 # No more qubits available @@ -146,16 +146,16 @@ class CQCType(IntEnum): class CQCLogicalOperator(IntEnum): - EQ = 0 # Equal - NEQ = 1 # Not equal + EQ = 0 # Equal + NEQ = 1 # Not equal @staticmethod - def opposite_of(operator: 'CQCLogicalOperator'): # String literal type hint because it is a forward reference - opposites = { - CQCLogicalOperator.EQ: CQCLogicalOperator.NEQ, - CQCLogicalOperator.NEQ: CQCLogicalOperator.EQ - } - return opposites[operator] + def opposite_of(operator: CQCLogicalOperator): + opposites = { + CQCLogicalOperator.EQ: CQCLogicalOperator.NEQ, + CQCLogicalOperator.NEQ: CQCLogicalOperator.EQ + } + return opposites[operator] @staticmethod def is_true(first_operand: int, operator: CQCLogicalOperator, second_operand: int): @@ -165,6 +165,7 @@ def is_true(first_operand: int, operator: CQCLogicalOperator, second_operand: in } return comparison_method[operator](second_operand) + class Header(metaclass=abc.ABCMeta): """ Abstact class for headers. @@ -342,22 +343,19 @@ class CQCTypeHeader(Header): PACKAGING_FORMAT = "!BI" HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) - - def _setVals(self, tp: CQCType=0, length: int=0) -> None: + def _setVals(self, tp: CQCType = 0, length: int = 0) -> None: """ Set using given values. """ self.type = tp self.length = length - def _pack(self) -> bytes: """ Pack data into packet format. For defnitions see cLib/cgc.h """ return struct.pack(self.PACKAGING_FORMAT, self.type, self.length) - def _unpack(self, headerBytes) -> None: """ Unpack packet data. @@ -366,24 +364,21 @@ def _unpack(self, headerBytes) -> None: self.type = unpacked[0] self.length = unpacked[1] - - def _printable(self) -> str: """ Produce a printable string for information purposes. """ return "CQC Type header. Type=" + str(self.type) + " | Length=" + str(self.length) - def make_equivalent_CQCHeader(self, version: int, app_id: int) -> CQCHeader: """ - Produce a CQC Header that is equivalent to this CQCTypeHeader. This method does not make any modifications to self. + Produce a CQC Header that is equivalent to this CQCTypeHeader. + This method does not make any modifications to self. """ cqc_header = CQCHeader() cqc_header.setVals(version, self.type, app_id, self.length) return cqc_header - - + class CQCIFHeader(Header): """ @@ -396,12 +391,14 @@ class CQCIFHeader(Header): TYPE_VALUE = 0 TYPE_REF_ID = 1 - def _setVals(self, - first_operand: int=0, - operator: CQCLogicalOperator=0, - type_of_second_operand: int=0, - second_operand: int=0, - length: int=0) -> None: + def _setVals( + self, + first_operand: int = 0, + operator: CQCLogicalOperator = 0, + type_of_second_operand: int = 0, + second_operand: int = 0, + length: int = 0 + ) -> None: """ Set the fields of this header. first_operand must be a reference id. @@ -415,7 +412,6 @@ def _setVals(self, self.second_operand = second_operand self.length = length - def _pack(self) -> bytes: """ Pack data into packet format. For defnitions see cLib/cgc.h @@ -428,8 +424,7 @@ def _pack(self) -> bytes: self.type_of_second_operand, self.second_operand, self.length - ) - + ) def _unpack(self, headerBytes) -> None: """ @@ -443,7 +438,6 @@ def _unpack(self, headerBytes) -> None: self.second_operand = unpacked[3] self.length = unpacked[4] - def _printable(self) -> str: """ Produce a printable string for information purposes. @@ -455,11 +449,12 @@ def _printable(self) -> str: operand_type = "Value" # parenthesis to concatenate the string over multiple lines - return ("CQC IF header. RefID=" + str(self.first_operand) - + " | Operator=" + str(self.operator) - + " | " + operand_type + "=" + str(self.second_operand) - + " | Second_operand_type=" + operand_type - + " | Body_length=" + str(self.length) + return ( + "CQC IF header. RefID=" + str(self.first_operand) + + " | Operator=" + str(self.operator) + + " | " + operand_type + "=" + str(self.second_operand) + + " | Second_operand_type=" + operand_type + + " | Body_length=" + str(self.length) ) @@ -471,7 +466,6 @@ class CQCCmdHeader(Header): PACKAGING_FORMAT = "!HBB" HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) - def _setVals(self, qubit_id=0, instr=0, notify=False, block=False, action=False): """ Set using given values. @@ -540,15 +534,13 @@ class CQCAssignHeader(Header): PACKAGING_FORMAT = "!I" HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) - - def _setVals(self, ref_id: int=0) -> None: + def _setVals(self, ref_id: int = 0) -> None: """ Set using given values. """ self.ref_id = ref_id - def _pack(self) -> bytes: """ Pack data into packet format. For defnitions see cLib/cgc.h @@ -556,7 +548,6 @@ def _pack(self) -> bytes: return struct.pack(self.PACKAGING_FORMAT, self.ref_id) - def _unpack(self, headerBytes) -> None: """ Unpack packet data. For definitions see cLib/cqc.h @@ -565,7 +556,6 @@ def _unpack(self, headerBytes) -> None: self.ref_id = unpacked[0] - def _printable(self) -> str: """ Produce a printable string for information purposes. @@ -574,9 +564,6 @@ def _printable(self) -> str: return "CQC Assign sub header. RefID=" + str(self.ref_id) - - - class CQCXtraHeader(Header): """ Optional addtional cmd header information. Only relevant for certain commands. @@ -782,7 +769,7 @@ class CQCCommunicationHeader(Header): PACKAGING_FORMAT = "!HHL" PACKAGING_FORMAT_V1 = "!HLH" - HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) # Both versions have the same size + HDR_LENGTH = struct.calcsize(PACKAGING_FORMAT) # Both versions have the same size def __init__(self, headerBytes=None, cqc_version=CQC_VERSION): """ diff --git a/cqc/pythonLib.py b/cqc/pythonLib.py index ef6d52a..8abab65 100644 --- a/cqc/pythonLib.py +++ b/cqc/pythonLib.py @@ -29,7 +29,7 @@ # This import allows to use type hints for classes that have not been defined yet. -# See https://stackoverflow.com/questions/33533148/how-do-i-specify-that-the-return-type-of-a-method-is-the-same-as-the-class-itsel +# See https://stackoverflow.com/questions/33533148/ # This import must be the very first import in this file, otherwise an error is raised from __future__ import annotations @@ -41,7 +41,7 @@ import socket import warnings from typing import Union, Any, List -from anytree import NodeMixin, RenderTree +from anytree import NodeMixin from cqc.cqcHeader import ( Header, @@ -168,6 +168,7 @@ def get_remote_from_directory_or_address(cqcNet, name, remote_socket): remote_port = addr[4][1] return remote_ip, remote_port + # !! Deprecated. Do not use this method def createXtraHeader(command, values): if command == CQC_CMD_SEND or command == CQC_CMD_EPR: @@ -323,7 +324,7 @@ def __init__(self, name, socket_address=None, appID=None, pend_messages=False, self.active_qubits = [] # List of pended header objects waiting to be sent to the backend - self._pending_headers = [] # ONLY cqc.cqcHeader.Header objects should be in this list + self._pending_headers = [] # ONLY cqc.cqcHeader.Header objects should be in this list # Bool that indicates whether we are in a factory and thus should pend commands self.pend_messages = pend_messages @@ -335,7 +336,6 @@ def __init__(self, name, socket_address=None, appID=None, pend_messages=False, # scoping mechanism. self.current_scope = None - def _pend_header(self, header: Header) -> None: self._pending_headers.append(header) @@ -734,7 +734,6 @@ def release_all_qubits(self): """ return self.release_qubits(self.active_qubits[:]) - # sendFactory is depecrated. Do not use it. # def sendFactory( self, @@ -1047,7 +1046,6 @@ def sendQubit(self, q, name, remote_appID=0, remote_socket=None, notify=True, bl self._pend_header(command_header) self._pend_header(comm_sub_header) - # print info logging.debug( "App {} pends message: 'Send qubit with ID {} to {} and appID {}'".format( @@ -1292,7 +1290,6 @@ def flush_factory(self, num_iter, do_sequence=False, block_factory=False): if shouldReturn(header.instr): response_amount += 1 - # Determine the CQC Header type if num_iter == 1: cqc_type = CQC_TP_COMMAND @@ -1326,8 +1323,6 @@ def flush_factory(self, num_iter, do_sequence=False, block_factory=False): # Return information that the backend returned return res - - def send_pending_headers(self) -> List[Any]: """ Sends all pending headers. @@ -1342,8 +1337,6 @@ def send_pending_headers(self) -> List[Any]: # Reset _pending_headers to an empty list after all headers are sent self._pending_headers = [] - - def insert_cqc_header(self, cqc_type: CQCType, version=CQC_VERSION) -> None: """ Inserts a CQC Header at index 0 of self._pending_headers. @@ -1362,7 +1355,6 @@ def insert_cqc_header(self, cqc_type: CQCType, version=CQC_VERSION) -> None: # Insert CQC Header at the front self._pending_headers.insert(0, cqc_header) - def _pend_type_header(self, cqc_type: CQCType, length: int) -> None: """ Creates a CQCTypeHeader and pends it. @@ -1371,7 +1363,6 @@ def _pend_type_header(self, cqc_type: CQCType, length: int) -> None: header.setVals(cqc_type, length) self._pend_header(header) - def tomography(self, preparation, iterations, progress=True): """ Does a tomography on the output from the preparation specified. @@ -1453,7 +1444,6 @@ def test_preparation(self, preparation, exp_values, conf=2, iterations=100, prog return True - class CQCVariable: """ Instances of this class are returned by measure command, if executed inside a CQCMix context. @@ -1492,11 +1482,12 @@ class LogicalFunction: Private helper class. This class should never be used outside this pythonLib. """ - def __init__(self, + def __init__( + self, operand_one: CQCVariable, operator: CQCLogicalOperator, operand_two: Union[CQCVariable, int] - ): + ): """ Stores all information necessary to create a logical comparison @@ -1540,7 +1531,6 @@ def get_CQCIFHeader(self) -> CQCIFHeader: return header - class CQCMix(NodeMixin): """ This Python Context Manager Type can be used to create CQC programs that consist of more than a single type. @@ -1597,8 +1587,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): # current_scope is only used inside CQCMix contexts self._conn.current_scope = None - - def cqc_if(self, logical_function: LogicalFunction): """ Open a Python Context Manager Type to start an if-statement block. @@ -1633,6 +1621,7 @@ def loop(self, times: int): """ return CQCFactory(self._conn, times) + class CQCFactory: """ Private class to create factories inside CQCMix contexts. Never explicitely instantiate this class outside @@ -1680,7 +1669,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.type_header.length = body_length - class CQCConditional(NodeMixin): """ Private helper class. Never explicitely instantiate this class outside the source code of this library. @@ -1696,7 +1684,7 @@ class CQCConditional(NodeMixin): # CQCConditional was an ELSE. _last_closed_conditional = None - def __init__(self, cqc_connection: CQCConnection, is_else: bool, logical_function: LogicalFunction=None): + def __init__(self, cqc_connection: CQCConnection, is_else: bool, logical_function: LogicalFunction = None): self._conn = cqc_connection self.is_else = is_else @@ -1722,7 +1710,6 @@ def __enter__(self): # Pend the IF header self._conn._pend_header(self.header) - # Register the parent scope, and set the current scope to self self.parent = self._conn.current_scope self._conn.current_scope = self @@ -1735,7 +1722,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): else: CQCConditional._last_closed_conditional = self - # Calculate the length of the body of the conditional # Loop in reverse through all pending_headers to calculate the lenght of all headers index = len(self._conn._pending_headers) - 1 @@ -1751,8 +1737,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): self._conn.current_scope = self.parent - - class ProgressBar: def __init__(self, maxitr): self.maxitr = maxitr @@ -1851,7 +1835,6 @@ def __init__(self, cqc, notify=True, block=True, createNew=True, q_id=None, entI # If the qubit has not yet been deactivated, this is set to None self.scope_of_deactivation = None - if createNew: if cqc.pend_messages: # Set q id, None by default @@ -1988,7 +1971,7 @@ def _set_active(self, be_active): # Check if not already new state if self._active == be_active: - return + return if be_active: self._cqc.active_qubits.append(self) @@ -2304,7 +2287,8 @@ def measure(self, inplace=False, block=True): command = CQC_CMD_MEASURE_INPLACE else: command = CQC_CMD_MEASURE - # Set qubit to non active so the user can receive helpful errors during compile time if this qubit is used after this measurement + # Set qubit to non active so the user can receive helpful errors during compile time + # if this qubit is used after this measurement self._set_active(False) if self._cqc.pend_messages: @@ -2328,7 +2312,6 @@ def measure(self, inplace=False, block=True): self._cqc._pend_header(header) self._cqc._pend_header(assign_sub_header) - # print info logging.debug("App {} pends message: 'Measure qubit with ID {}'".format(self._cqc.name, self._qID)) From 5206a10950c3ed37b49dc9a23f3dfed671e1bd5f Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 4 Oct 2019 20:45:48 +0200 Subject: [PATCH 12/16] A) bugfixes, B) compatible with Python V <3.7, C) renamed CQCIfHeader --- .gitignore | 2 + cqc/MessageHandler.py | 9 ++- cqc/cqcHeader.py | 12 +--- cqc/pythonLib.py | 124 ++++++++++++++++++++---------------------- 4 files changed, 69 insertions(+), 78 deletions(-) diff --git a/.gitignore b/.gitignore index 302cee2..8eac1a2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ /cqc/settings.ini .idea/* + +.vscode/ diff --git a/cqc/MessageHandler.py b/cqc/MessageHandler.py index 09c37eb..bdaeff5 100644 --- a/cqc/MessageHandler.py +++ b/cqc/MessageHandler.py @@ -67,7 +67,7 @@ CQCType, CQCTypeHeader, CQCAssignHeader, - CQCIFHeader, + CQCIfHeader, CQCLogicalOperator ) from twisted.internet.defer import DeferredLock, inlineCallbacks @@ -314,7 +314,6 @@ def _process_command(self, cqc_header, length, data, is_locked=False): logging.debug("CQC %s: Read XTRA Header: %s", self.name, xtra.printable()) # Run this command - print(cmd.printable()) logging.debug("CQC %s: Executing command: %s", self.name, cmd.printable()) if cmd.instr not in self.commandHandlers: logging.debug("CQC {}: Unknown command {}".format(self.name, cmd.instr)) @@ -436,12 +435,12 @@ def handle_conditional(self, header: CQCHeader, data: bytes): Handler for messages of TP_IF. """ # Strategy for handling TP_IF: - # We extract the CQCIFHeader from the data. We then extract all necessary variables from the header. + # We extract the CQCIfHeader from the data. We then extract all necessary variables from the header. # We then evaluate the conditional. If the conditional evaluates to FALSE, then we return the bodylength of # the IF. The mix handler will then skip this bodylength. # If the conditional evaluates to True, then we return 0. - if_header = CQCIFHeader(data[:CQCIFHeader.HDR_LENGTH]) + if_header = CQCIfHeader(data[:CQCIfHeader.HDR_LENGTH]) try: first_operand_value = self.references[header.app_id][if_header.first_operand] @@ -450,7 +449,7 @@ def handle_conditional(self, header: CQCHeader, data: bytes): self.create_return_message(header.app_id, CQC_ERR_GENERAL, cqc_version=header.version) ) - if if_header.type_of_second_operand is CQCIFHeader.TYPE_VALUE: + if if_header.type_of_second_operand is CQCIfHeader.TYPE_VALUE: second_operand_value = if_header.second_operand else: try: diff --git a/cqc/cqcHeader.py b/cqc/cqcHeader.py index 50be4ee..7415522 100644 --- a/cqc/cqcHeader.py +++ b/cqc/cqcHeader.py @@ -27,12 +27,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# This import allows to use type hints for classes that have not been defined yet. -# See https://stackoverflow.com/questions/33533148/ -# This import must be the very first import in this file, otherwise an error is raised -from __future__ import annotations - import warnings import struct @@ -150,7 +144,7 @@ class CQCLogicalOperator(IntEnum): NEQ = 1 # Not equal @staticmethod - def opposite_of(operator: CQCLogicalOperator): + def opposite_of(operator: 'CQCLogicalOperator'): opposites = { CQCLogicalOperator.EQ: CQCLogicalOperator.NEQ, CQCLogicalOperator.NEQ: CQCLogicalOperator.EQ @@ -158,7 +152,7 @@ def opposite_of(operator: CQCLogicalOperator): return opposites[operator] @staticmethod - def is_true(first_operand: int, operator: CQCLogicalOperator, second_operand: int): + def is_true(first_operand: int, operator: 'CQCLogicalOperator', second_operand: int): comparison_method = { CQCLogicalOperator.EQ: first_operand.__eq__, CQCLogicalOperator.NEQ: first_operand.__ne__ @@ -380,7 +374,7 @@ def make_equivalent_CQCHeader(self, version: int, app_id: int) -> CQCHeader: return cqc_header -class CQCIFHeader(Header): +class CQCIfHeader(Header): """ Definition of the CQC IF header. """ diff --git a/cqc/pythonLib.py b/cqc/pythonLib.py index 8abab65..48d027a 100644 --- a/cqc/pythonLib.py +++ b/cqc/pythonLib.py @@ -27,12 +27,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# This import allows to use type hints for classes that have not been defined yet. -# See https://stackoverflow.com/questions/33533148/ -# This import must be the very first import in this file, otherwise an error is raised -from __future__ import annotations - import math import os import sys @@ -94,7 +88,7 @@ CQC_TP_NEW_OK, CQC_TP_EXPIRE, CQCLogicalOperator, - CQCIFHeader, + CQCIfHeader, CQCTypeHeader, CQCType, CQCAssignHeader @@ -169,8 +163,10 @@ def get_remote_from_directory_or_address(cqcNet, name, remote_socket): return remote_ip, remote_port -# !! Deprecated. Do not use this method +# Deprecated. Do not use this method def createXtraHeader(command, values): + warnings.warn("Method 'createXtraHeader' is deprecated.", DeprecationWarning) + if command == CQC_CMD_SEND or command == CQC_CMD_EPR: header = CQCCommunicationHeader() header.setVals(remote_app_id=values[0], remote_node=values[1], remote_port=values[2]) @@ -585,7 +581,7 @@ def sendCmdXtra( elif command == CQC_CMD_ROT_X or command == CQC_CMD_ROT_Y or command == CQC_CMD_ROT_Z: xtra_hdr = CQCRotationHeader() xtra_hdr.setVals(step) - elif command == CQC_CMD_MEASURE: + elif command == CQC_CMD_MEASURE or CQC_CMD_MEASURE_INPLACE: xtra_hdr = CQCAssignHeader() xtra_hdr.setVals(ref_id) @@ -1046,6 +1042,9 @@ def sendQubit(self, q, name, remote_appID=0, remote_socket=None, notify=True, bl self._pend_header(command_header) self._pend_header(comm_sub_header) + # Deactivate qubit + q._set_active(False) + # print info logging.debug( "App {} pends message: 'Send qubit with ID {} to {} and appID {}'".format( @@ -1071,8 +1070,8 @@ def sendQubit(self, q, name, remote_appID=0, remote_socket=None, notify=True, bl if notify: message = self.readMessage() self.print_CQC_msg(message) - - # Deactivate qubit + + # Deactivate qubit q._set_active(False) def recvQubit(self, notify=True, block=True): @@ -1088,21 +1087,17 @@ def recvQubit(self, notify=True, block=True): :block: Do we want the qubit to be blocked """ - # initialize the qubit - q = qubit(self, createNew=False) - if self.pend_messages: # print info logging.debug("App {} pends message: 'Receive qubit'".format(self.name)) # Build header header = CQCCmdHeader() - header.setVals(q._qID, CQC_CMD_RECV, notify, block) + header.setVals(0, CQC_CMD_RECV, notify, block) # Pend header self._pend_header(header) - return q else: # print info logging.debug("App {} tells CQC: 'Receive qubit'".format(self.name)) @@ -1119,7 +1114,8 @@ def recvQubit(self, notify=True, block=True): message = self.readMessage() self.print_CQC_msg(message) - # initialize the qubit + # initialize the qubit + q = qubit(self, createNew=False) q._qID = q_id # Activate and return qubit @@ -1141,14 +1137,11 @@ def createEPR(self, name, remote_appID=0, remote_socket=None, notify=True, block remote_ip, remote_port = get_remote_from_directory_or_address(self._cqcNet, name, remote_socket) - # initialize the qubit - q = qubit(self, createNew=False) - if self.pend_messages: # Build command header and communication sub header command_header = CQCCmdHeader() - command_header.setVals(q._qID, CQC_CMD_EPR, notify, block) + command_header.setVals(0, CQC_CMD_EPR, notify, block) comm_sub_header = CQCCommunicationHeader() comm_sub_header.setVals(remote_appID, remote_ip, remote_port) @@ -1161,7 +1154,7 @@ def createEPR(self, name, remote_appID=0, remote_socket=None, notify=True, block logging.debug( "App {} pends message: 'Create EPR-pair with {} and appID {}'".format(self.name, name, remote_appID) ) - return q + else: # print info logging.debug( @@ -1189,6 +1182,9 @@ def createEPR(self, name, remote_appID=0, remote_socket=None, notify=True, block message = self.readMessage() self.print_CQC_msg(message) + # initialize the qubit + q = qubit(self, createNew=False) + q.set_entInfo(entInfoHdr) q._qID = q_id # Activate and return qubit @@ -1205,20 +1201,18 @@ def recvEPR(self, notify=True, block=True): :block: Do we want the qubit to be blocked """ - # initialize the qubit - q = qubit(self, createNew=False) if self.pend_messages: # Build header header = CQCCmdHeader() - header.setVals(q._qID, CQC_CMD_EPR_RECV, notify, block) + header.setVals(0, CQC_CMD_EPR_RECV, notify, block) # Pend header self._pend_header(header) # print info logging.debug("App {} pends message: 'Receive half of EPR'".format(self.name)) - return q + else: # print info logging.debug("App {} tells CQC: 'Receive half of EPR'".format(self.name)) @@ -1236,7 +1230,9 @@ def recvEPR(self, notify=True, block=True): message = self.readMessage() self.print_CQC_msg(message) - # initialize the qubit + # initialize the qubit + q = qubit(self, createNew=False) + q.set_entInfo(entInfoHdr) q._qID = q_id @@ -1257,6 +1253,21 @@ def set_pending(self, pend_messages): self.flush() self.pend_messages = pend_messages + def create_qubits(self, nb_of_qubits: int) -> List['qubit']: + """ + Creates (i.e. allocates) multiple qubits, and returns a list with qubit objects. + :nb_of_qubits: The amount of qubits to be created. + """ + # First, flush all pending headers + self.flush() + + # Build and insert the new qubit Command header + cmd_header = CQCCmdHeader() + cmd_header.setVals(qubit_id=0, instr=CQC_CMD_NEW, notify=1, block=1) + self._pend_header(cmd_header) + + return self.flush_factory(nb_of_qubits) + def flush(self, do_sequence=False): """ Flush all pending messages to the backend. @@ -1505,22 +1516,22 @@ def __init__( self.operator = operator self.operand_two = operand_two - def get_negation(self) -> LogicalFunction: + def get_negation(self) -> 'LogicalFunction': return LogicalFunction(self.operand_one, CQCLogicalOperator.opposite_of(self.operator), self.operand_two) - def get_CQCIFHeader(self) -> CQCIFHeader: + def get_CQCIfHeader(self) -> CQCIfHeader: """ Builds the If header corresponding to this logical function. """ if isinstance(self.operand_two, int): - type_of_operand_two = CQCIFHeader.TYPE_VALUE + type_of_operand_two = CQCIfHeader.TYPE_VALUE operand_two = self.operand_two else: - type_of_operand_two = CQCIFHeader.TYPE_REF_ID + type_of_operand_two = CQCIfHeader.TYPE_REF_ID operand_two = self.operand_two._ref_id - header = CQCIFHeader() + header = CQCIfHeader() header.setVals( self.operand_one.ref_id, self.operator, @@ -1702,10 +1713,10 @@ def __init__(self, cqc_connection: CQCConnection, is_else: bool, logical_functio def __enter__(self): # Pend CQC Type header - self._conn._pend_type_header(CQCType.IF, CQCIFHeader.HDR_LENGTH) + self._conn._pend_type_header(CQCType.IF, CQCIfHeader.HDR_LENGTH) # Build the IF header, and store it so we can modify its length at __exit__ - self.header = self._logical_function.get_CQCIFHeader() + self.header = self._logical_function.get_CQCIfHeader() # Pend the IF header self._conn._pend_header(self.header) @@ -1836,40 +1847,25 @@ def __init__(self, cqc, notify=True, block=True, createNew=True, q_id=None, entI self.scope_of_deactivation = None if createNew: - if cqc.pend_messages: - # Set q id, None by default - self._qID = q_id - self._set_active(False) - - # Build header - header = CQCCmdHeader() - header.setVals(0, CQC_CMD_NEW, notify, block) - - # Pend header - self._cqc._pend_header(header) + # print info + logging.debug("App {} tells CQC: 'Create qubit'".format(self._cqc.name)) - # print info - logging.debug("App {} pends message:'Create qubit'".format(self._cqc.name)) - else: - # print info - logging.debug("App {} tells CQC: 'Create qubit'".format(self._cqc.name)) + # Create new qubit at the cqc server + self._cqc.sendCommand(0, CQC_CMD_NEW, notify=int(notify), block=int(block)) - # Create new qubit at the cqc server - self._cqc.sendCommand(0, CQC_CMD_NEW, notify=int(notify), block=int(block)) + # Get qubit id + message = self._cqc.readMessage() + try: + otherHdr = message[1] + self._qID = otherHdr.qubit_id + except AttributeError: + raise CQCGeneralError("Didn't receive the qubit id") + # Activate qubit + self._set_active(True) - # Get qubit id + if notify: message = self._cqc.readMessage() - try: - otherHdr = message[1] - self._qID = otherHdr.qubit_id - except AttributeError: - raise CQCGeneralError("Didn't receive the qubit id") - # Activate qubit - self._set_active(True) - - if notify: - message = self._cqc.readMessage() - self._cqc.print_CQC_msg(message) + self._cqc.print_CQC_msg(message) else: self._qID = q_id self._set_active(False) # Why? From c6d8adce854549c16ac18c9134adb33506e94d95 Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 21 Oct 2019 19:34:58 +0200 Subject: [PATCH 13/16] Improvements in response to pull request comments --- cqc/MessageHandler.py | 43 ++++++++++++++++--------------- cqc/cqcHeader.py | 2 +- cqc/pythonLib.py | 60 +++++++++++++++++++++---------------------- 3 files changed, 53 insertions(+), 52 deletions(-) diff --git a/cqc/MessageHandler.py b/cqc/MessageHandler.py index bdaeff5..9709537 100644 --- a/cqc/MessageHandler.py +++ b/cqc/MessageHandler.py @@ -27,6 +27,7 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from abc import ABC, abstractmethod +from collections import defaultdict import logging from cqc.cqcHeader import ( @@ -183,7 +184,7 @@ def __init__(self, factory): # Dictionary that stores all reference ids and their values privately for each app_id. # Query/assign like this: self.references[app_id][ref_id] - self.references = {} + self.references = defaultdict(dict) @inlineCallbacks def handle_cqc_message(self, header, message, transport=None): @@ -192,10 +193,6 @@ def handle_cqc_message(self, header, message, transport=None): """ self.return_messages = [] - # References are app_id private. If this app doesn't yet have a references dictionary, create one. - if header.app_id not in self.references: - self.references[header.app_id] = {} - if header.tp in self.messageHandlers: try: should_notify = yield self.messageHandlers[header.tp](header, message) @@ -417,14 +414,18 @@ def handle_mix(self, header: CQCHeader, data: bytes): current_position += result # A TP_MIX should return the first error if there is an error message present, and otherwise return one TP_DONE - # We use the next function to retrieve the first error message from the list. # Notice the [:] syntax. This ensures the underlying list is updated, and not just the variable. - # See https://stackoverflow.com/questions/2361426/get-the-first-item-from-an-iterable-that-matches-a-condition - # and https://stackoverflow.com/questions/1207406/how-to-remove-items-from-a-list-while-iterating - self.return_messages[:] = [next( - (message for message in self.return_messages if is_error_message(message)), - self.create_return_message(header.app_id, CQCType.DONE, cqc_version=header.version) - )] + + return_message = None + for message in self.return_messages: + if is_error_message(message): + return_message = message + break + + if return_message is None: + return_message = self.create_return_message(header.app_id, CQCType.DONE, cqc_version=header.version) + + self.return_messages[:] = [return_message] # The other handlers from self.message_handlers return a bool that indicates whether # self.handle_cqc_message should append a TP_DONE message. This handle_mix method does that itself @@ -444,19 +445,19 @@ def handle_conditional(self, header: CQCHeader, data: bytes): try: first_operand_value = self.references[header.app_id][if_header.first_operand] + + if if_header.type_of_second_operand is CQCIfHeader.TYPE_VALUE: + second_operand_value = if_header.second_operand + else: + second_operand_value = self.references[header.app_id][if_header.second_operand] + # If one of the above lookups in self.references fails because the queried reference IDs haven't + # been assigned earlier, a KeyError will be raised except KeyError: self.return_messages.append( self.create_return_message(header.app_id, CQC_ERR_GENERAL, cqc_version=header.version) ) - - if if_header.type_of_second_operand is CQCIfHeader.TYPE_VALUE: - second_operand_value = if_header.second_operand - else: - try: - second_operand_value = self.references[header.app_id][if_header.second_operand] - except KeyError: - self.return_messages.append( - self.create_return_message(header.app_id, CQC_ERR_GENERAL, cqc_version=header.version)) + # Since the referenced IDs don't exist, we consider this IF-statement to evaluate to False. + return if_header.length if CQCLogicalOperator.is_true(first_operand_value, if_header.operator, second_operand_value): return 0 diff --git a/cqc/cqcHeader.py b/cqc/cqcHeader.py index 7415522..40f2214 100644 --- a/cqc/cqcHeader.py +++ b/cqc/cqcHeader.py @@ -397,7 +397,7 @@ def _setVals( Set the fields of this header. first_operand must be a reference id. second_operand will be interpreted as eiter a reference id or a value, dependent on type_of_second_operand. - type_of_second_operand must either be CQCIFHEADER.TYPE_VALUE or CQCIFHEADER.TYPE_REF_ID + type_of_second_operand must either be CQCIfHeader.TYPE_VALUE or CQCIfHeader.TYPE_REF_ID """ self.first_operand = first_operand diff --git a/cqc/pythonLib.py b/cqc/pythonLib.py index 48d027a..aa06eaf 100644 --- a/cqc/pythonLib.py +++ b/cqc/pythonLib.py @@ -730,7 +730,7 @@ def release_all_qubits(self): """ return self.release_qubits(self.active_qubits[:]) - # sendFactory is depecrated. Do not use it. # + # sendFactory is depecrated. Use flush_factory() instead. # def sendFactory( self, qID, @@ -1481,14 +1481,14 @@ def ref_id(self): # override the == operator # other can be a CQCVariable or int def __eq__(self, other: Union['CQCVariable', int]): - return LogicalFunction(self, CQCLogicalOperator.EQ, other) + return _LogicalFunction(self, CQCLogicalOperator.EQ, other) # override the != operator def __ne__(self, other: Union['CQCVariable', int]): - return LogicalFunction(self, CQCLogicalOperator.NEQ, other) + return _LogicalFunction(self, CQCLogicalOperator.NEQ, other) -class LogicalFunction: +class _LogicalFunction: """ Private helper class. This class should never be used outside this pythonLib. """ @@ -1516,8 +1516,8 @@ def __init__( self.operator = operator self.operand_two = operand_two - def get_negation(self) -> 'LogicalFunction': - return LogicalFunction(self.operand_one, CQCLogicalOperator.opposite_of(self.operator), self.operand_two) + def get_negation(self) -> '_LogicalFunction': + return _LogicalFunction(self.operand_one, CQCLogicalOperator.opposite_of(self.operator), self.operand_two) def get_CQCIfHeader(self) -> CQCIfHeader: """ @@ -1598,20 +1598,20 @@ def __exit__(self, exc_type, exc_val, exc_tb): # current_scope is only used inside CQCMix contexts self._conn.current_scope = None - def cqc_if(self, logical_function: LogicalFunction): + def cqc_if(self, logical_function: _LogicalFunction): """ Open a Python Context Manager Type to start an if-statement block. - **Arguments** - :logical_function: A LogicalFunction instance. Never instantiate this explicitely; instead + :logical_function: A _LogicalFunction instance. Never instantiate this explicitely; instead use the following: CQCVariable == 1 OR CQCVariable == CQCVariable. CQCVariable can be any instance that you want to test to a value, or to another CQCVariable. The operator can be == or !=. The value can be any integer (though only 1 and 0 make sense). """ - return CQCConditional(self._conn, False, logical_function) + return _CQCConditional(self._conn, False, logical_function) def cqc_else(self): """ @@ -1619,7 +1619,7 @@ def cqc_else(self): This will be an else-block of the last closed cqc_if-block. """ # Find out to which if this else belongs - return CQCConditional(self._conn, True) + return _CQCConditional(self._conn, True) def loop(self, times: int): """ @@ -1630,10 +1630,10 @@ def loop(self, times: int): :times: The number of times the commands inside body of this context should be repeated. """ - return CQCFactory(self._conn, times) + return _CQCFactory(self._conn, times) -class CQCFactory: +class _CQCFactory: """ Private class to create factories inside CQCMix contexts. Never explicitely instantiate this class outside the source code of this library. @@ -1680,34 +1680,34 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.type_header.length = body_length -class CQCConditional(NodeMixin): +class _CQCConditional(NodeMixin): """ Private helper class. Never explicitely instantiate this class outside the source code of this library. This Context Manager class is instantiated by CQCMix.cqc_if() and CQCMix.cqc_else(). Its function is to build and pend CQC If headers. """ - # This private class variable holds the last CQCConditional that + # This private class variable holds the last _CQCConditional that # functioned as an IF (as opposed to an ELSE) on which __exit__ is invoked. # In other words, it is the last closed IF statement. # This is important so that ELSE statements can find out to which IF statement they belong. # If this variable is None, then there either has not been aan IF statement yet, or the last - # CQCConditional was an ELSE. + # _CQCConditional was an ELSE. _last_closed_conditional = None - def __init__(self, cqc_connection: CQCConnection, is_else: bool, logical_function: LogicalFunction = None): + def __init__(self, cqc_connection: CQCConnection, is_else: bool, logical_function: _LogicalFunction = None): self._conn = cqc_connection self.is_else = is_else if is_else: # If _last_closed_conditional is None, then there either has not been aan IF statement yet, or the last - # CQCConditional was an ELSE. - if CQCConditional._last_closed_conditional is None: + # _CQCConditional was an ELSE. + if _CQCConditional._last_closed_conditional is None: raise CQCGeneralError('Cannot use an ELSE if there is no IF directly before it.') else: # Get the negation of the logical function of the IF, # which will be the logical function for this ELSE statement - logical_function = CQCConditional._last_closed_conditional._logical_function.get_negation() + logical_function = _CQCConditional._last_closed_conditional._logical_function.get_negation() self._logical_function = logical_function @@ -1729,9 +1729,9 @@ def __exit__(self, exc_type, exc_val, exc_tb): # Set _last_closed_conditional to the correct value if (self.is_else): - CQCConditional._last_closed_conditional = None + _CQCConditional._last_closed_conditional = None else: - CQCConditional._last_closed_conditional = self + _CQCConditional._last_closed_conditional = self # Calculate the length of the body of the conditional # Loop in reverse through all pending_headers to calculate the lenght of all headers @@ -1949,15 +1949,15 @@ def check_active(self): or self.scope_of_deactivation in self._cqc.current_scope.descendants ): - raise QubitNotActiveError(""" - Qubit is not active. Possible causes: - - Qubit is sent to another node - - Qubit is measured (with inplace=False) - - Qubit is released - - Qubit is not received - - Qubit is used and created in the same factory - - Qubit is measured (with inplace=False) inside a cqc_if block earlier in the code - """) + raise QubitNotActiveError( + "Qubit is not active. Possible causes:\n" + "- Qubit is sent to another node\n" + "- Qubit is measured (with inplace=False)\n" + "- Qubit is released\n" + "- Qubit is not received\n" + "- Qubit is used and created in the same factory\n" + "- Qubit is measured (with inplace=False) inside a cqc_if block earlier in the code\n" + ) def _set_active(self, be_active): From 30518a07ab723b61f76948e4d09ade86fbeb9bb0 Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 21 Oct 2019 20:02:21 +0200 Subject: [PATCH 14/16] Make return_messages application private --- cqc/MessageHandler.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/cqc/MessageHandler.py b/cqc/MessageHandler.py index 090ea77..2d1496e 100644 --- a/cqc/MessageHandler.py +++ b/cqc/MessageHandler.py @@ -180,9 +180,6 @@ def __init__(self, factory): self.name = factory.name self.return_messages = defaultdict(list) # Dictionary of all cqc messages to return per app_id - # List of all cqc messages to return - self.return_messages = [] - # Dictionary that stores all reference ids and their values privately for each app_id. # Query/assign like this: self.references[app_id][ref_id] self.references = defaultdict(dict) @@ -418,7 +415,7 @@ def handle_mix(self, header: CQCHeader, data: bytes): # Notice the [:] syntax. This ensures the underlying list is updated, and not just the variable. return_message = None - for message in self.return_messages: + for message in self.return_messages[header.app_id]: if is_error_message(message): return_message = message break @@ -426,7 +423,7 @@ def handle_mix(self, header: CQCHeader, data: bytes): if return_message is None: return_message = self.create_return_message(header.app_id, CQCType.DONE, cqc_version=header.version) - self.return_messages[:] = [return_message] + self.return_messages[header.app_id][:] = [return_message] # The other handlers from self.message_handlers return a bool that indicates whether # self.handle_cqc_message should append a TP_DONE message. This handle_mix method does that itself @@ -454,7 +451,7 @@ def handle_conditional(self, header: CQCHeader, data: bytes): # If one of the above lookups in self.references fails because the queried reference IDs haven't # been assigned earlier, a KeyError will be raised except KeyError: - self.return_messages.append( + self.return_messages[header.app_id].append( self.create_return_message(header.app_id, CQC_ERR_GENERAL, cqc_version=header.version) ) # Since the referenced IDs don't exist, we consider this IF-statement to evaluate to False. From fbc0b9caadd778ce7b6827606b3635952390c89b Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 23 Oct 2019 12:55:30 +0200 Subject: [PATCH 15/16] Refactor building and pending of headers --- cqc/pythonLib.py | 95 ++++++++++++++++-------------------------------- 1 file changed, 31 insertions(+), 64 deletions(-) diff --git a/cqc/pythonLib.py b/cqc/pythonLib.py index 3b18199..98d4697 100644 --- a/cqc/pythonLib.py +++ b/cqc/pythonLib.py @@ -1996,18 +1996,14 @@ def _single_qubit_gate(self, command, notify, block): if self._cqc.pend_messages: - # If we are inside a TP_MIX, then insert the CQC Type header before the command header - if self._cqc._inside_cqc_mix: - self._cqc._pend_type_header(CQCType.COMMAND, CQCCmdHeader.HDR_LENGTH) - - # Build the header - header = CQCCmdHeader() - header.setVals(qubit_id=self._qID, instr=command, notify=notify, block=block) - # Pend the header - self._cqc._pend_header(header) + self._build_and_pend_command(command, notify, block) # print info - logging.debug("App {} pends header: {}".format(self._cqc.name, header.printable())) + logging.debug( + "App {} pends message: 'Perform command {} to qubit with ID {}'".format( + self._cqc.name, command, self._qID + ) + ) else: # print info @@ -2104,6 +2100,26 @@ def K(self, notify=True, block=True): """ self._single_qubit_gate(CQC_CMD_K, notify, block) + def _build_and_pend_command(self, command, notify=False, block=False, subheader: Header=None, *subheader_values): + + # If we are inside a TP_MIX, then insert the CQC Type header before the command header + if self._cqc._inside_cqc_mix: + self._cqc._pend_type_header( + CQCType.COMMAND, + CQCCmdHeader.HDR_LENGTH + (subheader.HDR_LENGTH if subheader is not None else 0) + ) + + # Build and pend the command header + command_header = CQCCmdHeader() + command_header.setVals(self._qID, command, notify, block) + self._cqc._pend_header(command_header) + + # Build and pend the subheader, if there is one + if subheader is not None: + subheader.setVals(*subheader_values) + self._cqc._pend_header(subheader) + + def _single_gate_rotation(self, command, step, notify, block): """ Perform a rotation on a qubit @@ -2118,21 +2134,8 @@ def _single_gate_rotation(self, command, step, notify, block): if self._cqc.pend_messages: - # If we are inside a TP_MIX, then insert the CQC Type header before the command header - if self._cqc._inside_cqc_mix: - self._cqc._pend_type_header(CQCType.COMMAND, CQCCmdHeader.HDR_LENGTH + CQCRotationHeader.HDR_LENGTH) - - # Build command header and rotation sub header - command_header = CQCCmdHeader() - command_header.setVals(self._qID, command, notify, block) - - rot_sub_header = CQCRotationHeader() - rot_sub_header.setVals(step) - - # Pend headers - self._cqc._pend_header(command_header) - self._cqc._pend_header(rot_sub_header) - + self._build_and_pend_command(command, notify, block, CQCRotationHeader(), step) + # print info logging.debug( "App {} pends message: 'Perform rotation command {} (angle {}*2pi/256) to qubit with ID {}'".format( @@ -2210,20 +2213,7 @@ def _two_qubit_gate(self, command, target, notify, block): if self._cqc.pend_messages: - # If we are inside a TP_MIX, then insert the CQC Type header before the command header - if self._cqc._inside_cqc_mix: - self._cqc._pend_type_header(CQCType.COMMAND, CQCCmdHeader.HDR_LENGTH + CQCXtraQubitHeader.HDR_LENGTH) - - # Build command header and extra qubit sub header - command_header = CQCCmdHeader() - command_header.setVals(self._qID, command, notify, block) - - extra_qubit_sub_header = CQCXtraQubitHeader() - extra_qubit_sub_header.setVals(target._qID) - - # Pend headers - self._cqc._pend_header(command_header) - self._cqc._pend_header(extra_qubit_sub_header) + self._build_and_pend_command(command, notify, block, CQCXtraQubitHeader(), target._qID) # print info logging.debug( @@ -2296,24 +2286,10 @@ def measure(self, inplace=False, block=True): if self._cqc.pend_messages: - # If we are inside a TP_MIX, then insert the CQC Type header before the command header - if self._cqc._inside_cqc_mix: - self._cqc._pend_type_header(CQCType.COMMAND, CQCCmdHeader.HDR_LENGTH + CQCAssignHeader.HDR_LENGTH) - # Create a CQC Variable that holds the reference id for the measurement outcome cqc_variable = CQCVariable() - # Build header - header = CQCCmdHeader() - header.setVals(self._qID, command, block=block) - - # Bild Assign sub header - assign_sub_header = CQCAssignHeader() - assign_sub_header.setVals(cqc_variable.ref_id) - - # Pend headers - self._cqc._pend_header(header) - self._cqc._pend_header(assign_sub_header) + self._build_and_pend_command(command, False, block, CQCAssignHeader(), cqc_variable.ref_id) # print info logging.debug("App {} pends message: 'Measure qubit with ID {}'".format(self._cqc.name, self._qID)) @@ -2351,16 +2327,7 @@ def reset(self, notify=True, block=True): if self._cqc.pend_messages: - # If we are inside a TP_MIX, then insert the CQC Type header before the command header - if self._cqc._inside_cqc_mix: - self._cqc._pend_type_header(CQCType.COMMAND, CQCCmdHeader.HDR_LENGTH) - - # Build header - header = CQCCmdHeader() - header.setVals(self._qID, CQC_CMD_RESET, notify, block) - - # Pend header - self._cqc._pend_header(header) + self._build_and_pend_command(CQC_CMD_RESET, notify, block) # print info logging.debug("App {} pends message: 'Reset qubit with ID {}'".format(self._cqc.name, self._qID)) From bf827d18122efb0a1ad1c3528b84caa8230ad394 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 23 Oct 2019 13:12:10 +0200 Subject: [PATCH 16/16] Fix linting errors --- cqc/pythonLib.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cqc/pythonLib.py b/cqc/pythonLib.py index 98d4697..a4eee3e 100644 --- a/cqc/pythonLib.py +++ b/cqc/pythonLib.py @@ -2100,7 +2100,7 @@ def K(self, notify=True, block=True): """ self._single_qubit_gate(CQC_CMD_K, notify, block) - def _build_and_pend_command(self, command, notify=False, block=False, subheader: Header=None, *subheader_values): + def _build_and_pend_command(self, command, notify=False, block=False, subheader: Header = None, *subheader_values): # If we are inside a TP_MIX, then insert the CQC Type header before the command header if self._cqc._inside_cqc_mix: @@ -2118,8 +2118,7 @@ def _build_and_pend_command(self, command, notify=False, block=False, subheader: if subheader is not None: subheader.setVals(*subheader_values) self._cqc._pend_header(subheader) - - + def _single_gate_rotation(self, command, step, notify, block): """ Perform a rotation on a qubit