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 ,
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)
7674from 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+
108131def 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
0 commit comments