Skip to content

Commit 2c207fe

Browse files
author
Axel Dahlberg
authored
Merge pull request #35 from VictorPrins/Feature/CQC_conditionals
Feature/cqc conditionals
2 parents 8c805e2 + bf827d1 commit 2c207fe

File tree

6 files changed

+1012
-302
lines changed

6 files changed

+1012
-302
lines changed

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[flake8]
22
count = True
33
exclude = docs, .git, .idea, __pycache__
4-
ignore = E203 W291 W293 E743 E265 W605
4+
ignore = E203 W291 W293 E743 E265 W605 W503
55
max-line-length = 120
66
statistics = True

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@
88
/cqc/settings.ini
99

1010
.idea/*
11+
12+
.vscode/

cqc/MessageHandler.py

Lines changed: 132 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@
3939
CQC_CMD_ROT_X,
4040
CQC_CMD_ROT_Y,
4141
CQC_CMD_ROT_Z,
42-
CQC_TP_HELLO,
43-
CQC_TP_COMMAND,
44-
CQC_TP_FACTORY,
45-
CQC_TP_GET_TIME,
4642
CQC_CMD_I,
4743
CQC_CMD_X,
4844
CQC_CMD_Y,
@@ -62,16 +58,18 @@
6258
CQCXtraQubitHeader,
6359
CQCRotationHeader,
6460
CQCXtraHeader,
65-
CQC_CMD_XTRA_LENGTH,
6661
CQC_VERSION,
6762
CQCHeader,
6863
CQC_TP_DONE,
6964
CQC_ERR_UNSUPP,
7065
CQC_ERR_UNKNOWN,
7166
CQC_ERR_GENERAL,
72-
CQCSequenceHeader,
7367
CQCFactoryHeader,
74-
CQC_CMD_HDR_LENGTH,
68+
CQCType,
69+
CQCTypeHeader,
70+
CQCAssignHeader,
71+
CQCIfHeader,
72+
CQCLogicalOperator
7573
)
7674
from twisted.internet.defer import DeferredLock, inlineCallbacks
7775

@@ -105,6 +103,31 @@ def has_extra(cmd):
105103
return False
106104

107105

106+
def is_error_message(message: bytes):
107+
108+
# Only CQCHeaders can be error messages, so if the length does not correspond it is not an error message
109+
try:
110+
header = CQCHeader(message)
111+
# A ValueError is raised by Header.__init__ if the message cannot be read as a CQCHeader.
112+
# Since only CQCHeaders can contain errors, this means the message is not an error
113+
except ValueError:
114+
return False
115+
116+
error_types = {
117+
CQCType.ERR_GENERAL,
118+
CQCType.ERR_INUSE,
119+
CQCType.ERR_NOQUBIT,
120+
CQCType.ERR_TIMEOUT,
121+
CQCType.ERR_UNKNOWN,
122+
CQCType.ERR_UNSUPP
123+
}
124+
125+
if header.tp in error_types:
126+
return True
127+
else:
128+
return False
129+
130+
108131
def print_error(error):
109132
logging.error("Uncaught twisted error found: {}".format(error))
110133

@@ -119,10 +142,12 @@ class CQCMessageHandler(ABC):
119142
def __init__(self, factory):
120143
# Functions to invoke when receiving a CQC Header of a certain type
121144
self.messageHandlers = {
122-
CQC_TP_HELLO: self.handle_hello,
123-
CQC_TP_COMMAND: self.handle_command,
124-
CQC_TP_FACTORY: self.handle_factory,
125-
CQC_TP_GET_TIME: self.handle_time,
145+
CQCType.HELLO: self.handle_hello,
146+
CQCType.COMMAND: self.handle_command,
147+
CQCType.FACTORY: self.handle_factory,
148+
CQCType.GET_TIME: self.handle_time,
149+
CQCType.MIX: self.handle_mix,
150+
CQCType.IF: self.handle_conditional
126151
}
127152

128153
# Functions to invoke when receiving a certain command
@@ -155,6 +180,10 @@ def __init__(self, factory):
155180
self.name = factory.name
156181
self.return_messages = defaultdict(list) # Dictionary of all cqc messages to return per app_id
157182

183+
# Dictionary that stores all reference ids and their values privately for each app_id.
184+
# Query/assign like this: self.references[app_id][ref_id]
185+
self.references = defaultdict(dict)
186+
158187
@inlineCallbacks
159188
def handle_cqc_message(self, header, message, transport=None):
160189
"""
@@ -164,6 +193,7 @@ def handle_cqc_message(self, header, message, transport=None):
164193
if header.tp in self.messageHandlers:
165194
try:
166195
should_notify = yield self.messageHandlers[header.tp](header, message)
196+
167197
if should_notify:
168198
# Send a notification that we are done if successful
169199
logging.debug("CQC %s: Command successful, sent done.", self.name)
@@ -213,7 +243,7 @@ def create_extra_header(cmd, cmd_data, cqc_version=CQC_VERSION):
213243
"""
214244
if cqc_version < 1:
215245
if has_extra(cmd):
216-
cmd_length = CQC_CMD_XTRA_LENGTH
246+
cmd_length = CQCXtraHeader.HDR_LENGTH
217247
hdr = CQCXtraHeader(cmd_data[:cmd_length])
218248
return hdr
219249
else:
@@ -229,6 +259,9 @@ def create_extra_header(cmd, cmd_data, cqc_version=CQC_VERSION):
229259
elif instruction == CQC_CMD_ROT_X or instruction == CQC_CMD_ROT_Y or instruction == CQC_CMD_ROT_Z:
230260
cmd_length = CQCRotationHeader.HDR_LENGTH
231261
hdr = CQCRotationHeader(cmd_data[:cmd_length])
262+
elif instruction == CQC_CMD_MEASURE or instruction == CQC_CMD_MEASURE_INPLACE:
263+
cmd_length = CQCAssignHeader.HDR_LENGTH
264+
hdr = CQCAssignHeader(cmd_data[:cmd_length])
232265
else:
233266
return None
234267
return hdr
@@ -257,7 +290,7 @@ def _process_command(self, cqc_header, length, data, is_locked=False):
257290
cur_length = 0
258291
should_notify = None
259292
while cur_length < length:
260-
cmd = CQCCmdHeader(cmd_data[cur_length: cur_length + CQC_CMD_HDR_LENGTH])
293+
cmd = CQCCmdHeader(cmd_data[cur_length: cur_length + CQCCmdHeader.HDR_LENGTH])
261294
logging.debug("CQC %s got command header %s", self.name, cmd.printable())
262295

263296
newl = cur_length + cmd.HDR_LENGTH
@@ -298,41 +331,10 @@ def _process_command(self, cqc_header, length, data, is_locked=False):
298331
msg = self.create_return_message(cqc_header.app_id, CQC_ERR_GENERAL, cqc_version=cqc_header.version)
299332
self.return_messages[cqc_header.app_id].append(msg)
300333
return False, 0
334+
301335
if succ is False: # only if it explicitly is false, if succ is None then we assume it went fine
302336
return False, 0
303337

304-
# Check if there are additional commands to execute afterwards
305-
if cmd.action:
306-
# lock the sequence
307-
if not is_locked:
308-
self._sequence_lock.acquire()
309-
sequence_header = CQCSequenceHeader(data[newl: newl + CQCSequenceHeader.HDR_LENGTH])
310-
newl += sequence_header.HDR_LENGTH
311-
logging.debug("CQC %s: Reading extra action commands", self.name)
312-
try:
313-
(succ, retNotify) = yield self._process_command(
314-
cqc_header,
315-
sequence_header.cmd_length,
316-
data[newl: newl + sequence_header.cmd_length],
317-
is_locked=True,
318-
)
319-
except Exception as err:
320-
logging.error(
321-
"CQC {}: Got the following unexpected error when process commands: {}".format(self.name, err)
322-
)
323-
msg = self.create_return_message(cqc_header.app_id, CQC_ERR_GENERAL, cqc_version=cqc_header.version)
324-
self.return_messages[cqc_header.app_id].append(msg)
325-
return False, 0
326-
327-
should_notify = should_notify or retNotify
328-
if not succ:
329-
return False, 0
330-
newl = newl + sequence_header.cmd_length
331-
if not is_locked:
332-
logging.debug("CQC %s: Releasing lock", self.name)
333-
# unlock
334-
self._sequence_lock.release()
335-
336338
cur_length = newl
337339
return True, should_notify
338340

@@ -375,6 +377,91 @@ def handle_factory(self, header, data):
375377

376378
return succ and should_notify
377379

380+
@inlineCallbacks
381+
def handle_mix(self, header: CQCHeader, data: bytes):
382+
"""
383+
Handler for messages of TP_MIX. Notice that header is the CQC Header,
384+
and data is the complete body, excluding the CQC Header.
385+
"""
386+
# Strategy for handling TP_MIX:
387+
# The first bit of data will be a CQCType header. We extract this header.
388+
# We extract from this first CQCType header the type of the following instructions, and we invoke the
389+
# corresponding handler from self.messageHandlers. This handler expects as parameter "header" a CQCHeader.
390+
# Therefore, we construct the CQCHeader that corresponds to the CQCType header
391+
# (remember that the CQCType header is just a reduced CQCHeader),
392+
# and input that constructed CQCHeader as "header" parameter.
393+
# After this handler returns, we repeat until the end of the program.
394+
395+
current_position = 0
396+
397+
while current_position < header.length:
398+
399+
# Extract CQCTypeHeader
400+
type_header = CQCTypeHeader(data[current_position : current_position + CQCTypeHeader.HDR_LENGTH])
401+
402+
current_position += CQCTypeHeader.HDR_LENGTH
403+
404+
# Create equivalent CQCHeader
405+
equiv_cqc_header = type_header.make_equivalent_CQCHeader(header.version, header.app_id)
406+
407+
result = yield self.messageHandlers[type_header.type](equiv_cqc_header, data[current_position:])
408+
409+
current_position += type_header.length
410+
411+
if type_header.type == CQCType.IF:
412+
current_position += result
413+
414+
# A TP_MIX should return the first error if there is an error message present, and otherwise return one TP_DONE
415+
# Notice the [:] syntax. This ensures the underlying list is updated, and not just the variable.
416+
417+
return_message = None
418+
for message in self.return_messages[header.app_id]:
419+
if is_error_message(message):
420+
return_message = message
421+
break
422+
423+
if return_message is None:
424+
return_message = self.create_return_message(header.app_id, CQCType.DONE, cqc_version=header.version)
425+
426+
self.return_messages[header.app_id][:] = [return_message]
427+
428+
# The other handlers from self.message_handlers return a bool that indicates whether
429+
# self.handle_cqc_message should append a TP_DONE message. This handle_mix method does that itself
430+
# if necessary so we just return nothing (None).
431+
432+
def handle_conditional(self, header: CQCHeader, data: bytes):
433+
"""
434+
Handler for messages of TP_IF.
435+
"""
436+
# Strategy for handling TP_IF:
437+
# We extract the CQCIfHeader from the data. We then extract all necessary variables from the header.
438+
# We then evaluate the conditional. If the conditional evaluates to FALSE, then we return the bodylength of
439+
# the IF. The mix handler will then skip this bodylength.
440+
# If the conditional evaluates to True, then we return 0.
441+
442+
if_header = CQCIfHeader(data[:CQCIfHeader.HDR_LENGTH])
443+
444+
try:
445+
first_operand_value = self.references[header.app_id][if_header.first_operand]
446+
447+
if if_header.type_of_second_operand is CQCIfHeader.TYPE_VALUE:
448+
second_operand_value = if_header.second_operand
449+
else:
450+
second_operand_value = self.references[header.app_id][if_header.second_operand]
451+
# If one of the above lookups in self.references fails because the queried reference IDs haven't
452+
# been assigned earlier, a KeyError will be raised
453+
except KeyError:
454+
self.return_messages[header.app_id].append(
455+
self.create_return_message(header.app_id, CQC_ERR_GENERAL, cqc_version=header.version)
456+
)
457+
# Since the referenced IDs don't exist, we consider this IF-statement to evaluate to False.
458+
return if_header.length
459+
460+
if CQCLogicalOperator.is_true(first_operand_value, if_header.operator, second_operand_value):
461+
return 0
462+
else:
463+
return if_header.length
464+
378465
@abstractmethod
379466
def handle_hello(self, header, data):
380467
pass

cqc/Protocol.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from twisted.internet.defer import inlineCallbacks
3232
from twisted.internet.protocol import Protocol, connectionDone
3333

34-
from cqc.cqcHeader import CQC_HDR_LENGTH, CQC_VERSION, CQCHeader
34+
from cqc.cqcHeader import CQC_VERSION, CQCHeader
3535

3636
###############################################################################
3737
#
@@ -96,17 +96,17 @@ def dataReceived(self, data):
9696

9797
# If we don't have the CQC header yet, try and read it in full.
9898
if not self.gotCQCHeader:
99-
if len(self.buf) < CQC_HDR_LENGTH:
99+
if len(self.buf) < CQCHeader.HDR_LENGTH:
100100
# Not enough data for CQC header, return and wait for the rest
101101
return
102102

103103
# Got enough data for the CQC Header so read it in
104104
self.gotCQCHeader = True
105-
raw_header = self.buf[0:CQC_HDR_LENGTH]
105+
raw_header = self.buf[0:CQCHeader.HDR_LENGTH]
106106
self.currHeader = CQCHeader(raw_header)
107107

108108
# Remove the header from the buffer
109-
self.buf = self.buf[CQC_HDR_LENGTH: len(self.buf)]
109+
self.buf = self.buf[CQCHeader.HDR_LENGTH: len(self.buf)]
110110

111111
logging.debug("CQC %s: Read CQC Header: %s", self.name, self.currHeader.printable())
112112

0 commit comments

Comments
 (0)