Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/cqc conditionals #35

Merged
merged 17 commits into from
Oct 25, 2019
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
/cqc/settings.ini

.idea/*

.vscode/
182 changes: 136 additions & 46 deletions cqc/MessageHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -61,16 +57,18 @@
CQCXtraQubitHeader,
CQCRotationHeader,
CQCXtraHeader,
CQC_CMD_XTRA_LENGTH,
CQC_VERSION,
CQCHeader,
CQC_TP_DONE,
CQC_ERR_UNSUPP,
CQC_ERR_UNKNOWN,
CQC_ERR_GENERAL,
CQCSequenceHeader,
CQCFactoryHeader,
CQC_CMD_HDR_LENGTH,
CQCType,
CQCTypeHeader,
CQCAssignHeader,
CQCIfHeader,
CQCLogicalOperator
)
from twisted.internet.defer import DeferredLock, inlineCallbacks

Expand Down Expand Up @@ -104,6 +102,31 @@ 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))

Expand All @@ -118,10 +141,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.MIX: self.handle_mix,
CQCType.IF: self.handle_conditional
}

# Functions to invoke when receiving a certain command
Expand Down Expand Up @@ -152,17 +177,29 @@ 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 privately for each app_id.
# Query/assign like this: self.references[app_id][ref_id]
self.references = {}

@inlineCallbacks
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)

if should_notify:
# Send a notification that we are done if successful
logging.debug("CQC %s: Command successful, sent done.", self.name)
Expand Down Expand Up @@ -211,7 +248,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:
Expand All @@ -227,6 +264,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
Expand Down Expand Up @@ -255,7 +295,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
Expand Down Expand Up @@ -296,41 +336,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

Expand Down Expand Up @@ -373,6 +382,87 @@ 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.
"""
# 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),
# and input that constructed CQCHeader 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)

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_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)
)]

# 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
# if necessary so we just return nothing (None).

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 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])

try:
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)
)

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))

if CQCLogicalOperator.is_true(first_operand_value, if_header.operator, second_operand_value):
return 0
else:
return if_header.length

@abstractmethod
def handle_hello(self, header, data):
pass
Expand Down
8 changes: 4 additions & 4 deletions cqc/Protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

###############################################################################
#
Expand Down Expand Up @@ -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())

Expand Down
Loading