39
39
CQC_CMD_ROT_X ,
40
40
CQC_CMD_ROT_Y ,
41
41
CQC_CMD_ROT_Z ,
42
- CQC_TP_HELLO ,
43
- CQC_TP_COMMAND ,
44
- CQC_TP_FACTORY ,
45
- CQC_TP_GET_TIME ,
46
42
CQC_CMD_I ,
47
43
CQC_CMD_X ,
48
44
CQC_CMD_Y ,
62
58
CQCXtraQubitHeader ,
63
59
CQCRotationHeader ,
64
60
CQCXtraHeader ,
65
- CQC_CMD_XTRA_LENGTH ,
66
61
CQC_VERSION ,
67
62
CQCHeader ,
68
63
CQC_TP_DONE ,
69
64
CQC_ERR_UNSUPP ,
70
65
CQC_ERR_UNKNOWN ,
71
66
CQC_ERR_GENERAL ,
72
- CQCSequenceHeader ,
73
67
CQCFactoryHeader ,
74
- CQC_CMD_HDR_LENGTH ,
68
+ CQCType ,
69
+ CQCTypeHeader ,
70
+ CQCAssignHeader ,
71
+ CQCIfHeader ,
72
+ CQCLogicalOperator
75
73
)
76
74
from twisted .internet .defer import DeferredLock , inlineCallbacks
77
75
@@ -105,6 +103,31 @@ def has_extra(cmd):
105
103
return False
106
104
107
105
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
+
108
131
def print_error (error ):
109
132
logging .error ("Uncaught twisted error found: {}" .format (error ))
110
133
@@ -119,10 +142,12 @@ class CQCMessageHandler(ABC):
119
142
def __init__ (self , factory ):
120
143
# Functions to invoke when receiving a CQC Header of a certain type
121
144
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
126
151
}
127
152
128
153
# Functions to invoke when receiving a certain command
@@ -155,6 +180,10 @@ def __init__(self, factory):
155
180
self .name = factory .name
156
181
self .return_messages = defaultdict (list ) # Dictionary of all cqc messages to return per app_id
157
182
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
+
158
187
@inlineCallbacks
159
188
def handle_cqc_message (self , header , message , transport = None ):
160
189
"""
@@ -164,6 +193,7 @@ def handle_cqc_message(self, header, message, transport=None):
164
193
if header .tp in self .messageHandlers :
165
194
try :
166
195
should_notify = yield self .messageHandlers [header .tp ](header , message )
196
+
167
197
if should_notify :
168
198
# Send a notification that we are done if successful
169
199
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):
213
243
"""
214
244
if cqc_version < 1 :
215
245
if has_extra (cmd ):
216
- cmd_length = CQC_CMD_XTRA_LENGTH
246
+ cmd_length = CQCXtraHeader . HDR_LENGTH
217
247
hdr = CQCXtraHeader (cmd_data [:cmd_length ])
218
248
return hdr
219
249
else :
@@ -229,6 +259,9 @@ def create_extra_header(cmd, cmd_data, cqc_version=CQC_VERSION):
229
259
elif instruction == CQC_CMD_ROT_X or instruction == CQC_CMD_ROT_Y or instruction == CQC_CMD_ROT_Z :
230
260
cmd_length = CQCRotationHeader .HDR_LENGTH
231
261
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 ])
232
265
else :
233
266
return None
234
267
return hdr
@@ -257,7 +290,7 @@ def _process_command(self, cqc_header, length, data, is_locked=False):
257
290
cur_length = 0
258
291
should_notify = None
259
292
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 ])
261
294
logging .debug ("CQC %s got command header %s" , self .name , cmd .printable ())
262
295
263
296
newl = cur_length + cmd .HDR_LENGTH
@@ -298,41 +331,10 @@ def _process_command(self, cqc_header, length, data, is_locked=False):
298
331
msg = self .create_return_message (cqc_header .app_id , CQC_ERR_GENERAL , cqc_version = cqc_header .version )
299
332
self .return_messages [cqc_header .app_id ].append (msg )
300
333
return False , 0
334
+
301
335
if succ is False : # only if it explicitly is false, if succ is None then we assume it went fine
302
336
return False , 0
303
337
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
-
336
338
cur_length = newl
337
339
return True , should_notify
338
340
@@ -375,6 +377,91 @@ def handle_factory(self, header, data):
375
377
376
378
return succ and should_notify
377
379
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
+
378
465
@abstractmethod
379
466
def handle_hello (self , header , data ):
380
467
pass
0 commit comments