# Copyright (c) 2017-2018, Stephanie Wehner and Axel Dahlberg
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
# must display the following acknowledgement:
# This product includes software developed by Stephanie Wehner, QuTech.
# 4. Neither the name of the QuTech organization nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ''AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import logging
from collections import defaultdict
from abc import ABC, abstractmethod
from cqc.cqcHeader import (
CQCCmdHeader,
CQC_CMD_SEND,
CQC_CMD_EPR,
CQC_CMD_CNOT,
CQC_CMD_CPHASE,
CQC_CMD_ROT_X,
CQC_CMD_ROT_Y,
CQC_CMD_ROT_Z,
CQC_CMD_I,
CQC_CMD_X,
CQC_CMD_Y,
CQC_CMD_Z,
CQC_CMD_T,
CQC_CMD_H,
CQC_CMD_K,
CQC_CMD_NEW,
CQC_CMD_MEASURE,
CQC_CMD_MEASURE_INPLACE,
CQC_CMD_RESET,
CQC_CMD_RECV,
CQC_CMD_EPR_RECV,
CQC_CMD_ALLOCATE,
CQC_CMD_RELEASE,
CQCCommunicationHeader,
CQCXtraQubitHeader,
CQCRotationHeader,
CQCXtraHeader,
CQC_VERSION,
CQCHeader,
CQC_TP_DONE,
CQC_ERR_UNSUPP,
CQC_ERR_UNKNOWN,
CQC_ERR_GENERAL,
CQCFactoryHeader,
CQCType,
CQCTypeHeader,
CQCAssignHeader,
CQCIfHeader,
CQCLogicalOperator
)
from twisted.internet.defer import DeferredLock, inlineCallbacks
[docs]class UnknownQubitError(Exception):
def __init__(self, message):
super().__init__(message)
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))
######
# Abstract class. Classes that inherit this class define how to handle incoming cqc messages.
######
[docs]class CQCMessageHandler(ABC):
_sequence_lock = DeferredLock()
def __init__(self, factory):
# Functions to invoke when receiving a CQC Header of a certain type
self.messageHandlers = {
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
self.commandHandlers = {
CQC_CMD_I: self.cmd_i,
CQC_CMD_X: self.cmd_x,
CQC_CMD_Y: self.cmd_y,
CQC_CMD_Z: self.cmd_z,
CQC_CMD_T: self.cmd_t,
CQC_CMD_H: self.cmd_h,
CQC_CMD_K: self.cmd_k,
CQC_CMD_ROT_X: self.cmd_rotx,
CQC_CMD_ROT_Y: self.cmd_roty,
CQC_CMD_ROT_Z: self.cmd_rotz,
CQC_CMD_CNOT: self.cmd_cnot,
CQC_CMD_CPHASE: self.cmd_cphase,
CQC_CMD_MEASURE: self.cmd_measure,
CQC_CMD_MEASURE_INPLACE: self.cmd_measure_inplace,
CQC_CMD_RESET: self.cmd_reset,
CQC_CMD_SEND: self.cmd_send,
CQC_CMD_RECV: self.cmd_recv,
CQC_CMD_EPR: self.cmd_epr,
CQC_CMD_EPR_RECV: self.cmd_epr_recv,
CQC_CMD_NEW: self.cmd_new,
CQC_CMD_ALLOCATE: self.cmd_allocate,
CQC_CMD_RELEASE: self.cmd_release,
}
# Convenience
self.name = factory.name
self.return_messages = defaultdict(list) # Dictionary of all cqc messages to return per app_id
# 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)
[docs] @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[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)
self.return_messages[header.app_id].append(
self.create_return_message(header.app_id, CQC_TP_DONE, cqc_version=header.version))
except UnknownQubitError:
logging.error("CQC {}: Couldn't find qubit with given ID".format(self.name))
self.return_messages[header.app_id].append(
self.create_return_message(header.app_id, CQC_ERR_UNKNOWN, cqc_version=header.version))
except NotImplementedError:
logging.error("CQC {}: Command not implemented yet".format(self.name))
self.return_messages[header.app_id].append(
self.create_return_message(header.app_id, CQC_ERR_UNSUPP, cqc_version=header.version))
except Exception as err:
logging.error(
"CQC {}: Got the following unexpected error when handling CQC message: {}".format(self.name, err)
)
self.return_messages[header.app_id].append(
self.create_return_message(header.app_id, CQC_ERR_GENERAL, cqc_version=header.version))
else:
logging.error("CQC %s: Could not find cqc type %d in handlers.", self.name, header.yp)
self.return_messages[header.app_id].append(
self.create_return_message(header.app_id, CQC_ERR_UNSUPP, cqc_version=header.version))
[docs] def retrieve_return_messages(self, app_id):
"""Retrieve the return messages of a given app_id"""
return self.return_messages[app_id]
[docs] @staticmethod
def create_return_message(app_id, msg_type, length=0, cqc_version=CQC_VERSION):
"""
Creates a messaage that the protocol should send back
:param app_id: the app_id to which the message should be send
:param msg_type: the type of message to return
:param length: the length of additional message
:param cqc_version: The cqc version of the message
:return: a new header message to be send back
"""
hdr = CQCHeader()
hdr.setVals(cqc_version, msg_type, app_id, length)
return hdr.pack()
[docs] @staticmethod
def create_extra_header(cmd, cmd_data, cqc_version=CQC_VERSION):
"""
Create the extra header (communication header, rotation header, etc) based on the command
"""
if cqc_version < 1:
if has_extra(cmd):
cmd_length = CQCXtraHeader.HDR_LENGTH
hdr = CQCXtraHeader(cmd_data[:cmd_length])
return hdr
else:
return None
instruction = cmd.instr
if instruction == CQC_CMD_SEND or instruction == CQC_CMD_EPR:
cmd_length = CQCCommunicationHeader.HDR_LENGTH
hdr = CQCCommunicationHeader(cmd_data[:cmd_length], cqc_version=cqc_version)
elif instruction == CQC_CMD_CNOT or instruction == CQC_CMD_CPHASE:
cmd_length = CQCXtraQubitHeader.HDR_LENGTH
hdr = CQCXtraQubitHeader(cmd_data[:cmd_length])
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
[docs] @inlineCallbacks
def handle_command(self, header, data):
"""
Handle incoming command requests.
"""
logging.debug("CQC %s: Command received", self.name)
# Run the entire command list, incl. actions after completion which here we will do instantly
try:
success, should_notify = yield self._process_command(header, header.length, data)
except Exception as err:
print_error(err)
return False
return success and should_notify
@inlineCallbacks
def _process_command(self, cqc_header, length, data, is_locked=False):
"""
Process the commands - called recursively to also process additional command lists.
"""
cmd_data = data
# Read in all the commands sent
cur_length = 0
should_notify = None
while cur_length < length:
cmd = CQCCmdHeader(cmd_data[cur_length: cur_length + CQCCmdHeader.HDR_LENGTH])
logging.debug("CQC %s got command header %s", self.name, cmd)
newl = cur_length + cmd.HDR_LENGTH
# Should we notify
should_notify = should_notify or cmd.notify
# Create the extra header if it exist
try:
xtra = self.create_extra_header(cmd, cmd_data[newl:], cqc_header.version)
except IndexError:
xtra = None
logging.debug("CQC %s: Missing XTRA Header", self.name)
if xtra is not None:
newl += xtra.HDR_LENGTH
logging.debug("CQC %s: Read XTRA Header: %s", self.name, xtra)
# Run this command
logging.debug("CQC %s: Executing command: %s", self.name, cmd)
if cmd.instr not in self.commandHandlers:
logging.debug("CQC {}: Unknown command {}".format(self.name, cmd.instr))
msg = self.create_return_message(cqc_header.app_id, CQC_ERR_UNSUPP, cqc_version=cqc_header.version)
self.return_messages[cqc_header.app_id].append(msg)
return False, 0
try:
succ = yield self.commandHandlers[cmd.instr](cqc_header, cmd, xtra)
except NotImplementedError:
logging.error("CQC {}: Command not implemented yet".format(self.name))
self.return_messages[cqc_header.app_id].append(
self.create_return_message(cqc_header.app_id, CQC_ERR_UNSUPP, cqc_version=cqc_header.verstion))
return False, 0
except Exception as err:
logging.error(
"CQC {}: Got the following unexpected error when process command {}: {}".format(
self.name, cmd.instr, err
)
)
msg = self.create_return_message(cqc_header.app_id, CQC_ERR_GENERAL, cqc_version=cqc_header.version)
self.return_messages[cqc_header.app_id].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
cur_length = newl
return True, should_notify
@inlineCallbacks
def handle_factory(self, header, data):
fact_l = CQCFactoryHeader.HDR_LENGTH
# Get factory header
if len(data) < header.length:
logging.debug("CQC %s: Missing header(s) in factory", self.name)
self.return_messages[header.app_id].append(
self.create_return_message(header.app_id, CQC_ERR_UNSUPP, cqc_version=header.version))
return False
fact_header = CQCFactoryHeader(data[:fact_l])
num_iter = fact_header.num_iter
# Perform operation multiple times
succ = True
should_notify = fact_header.notify
block_factory = fact_header.block
logging.debug("CQC %s: Performing factory command with %s iterations", self.name, num_iter)
if block_factory:
logging.debug("CQC %s: Acquire lock for factory", self.name)
self._sequence_lock.acquire()
for _ in range(num_iter):
try:
succ, _ = yield self._process_command(header, header.length - fact_l, data[fact_l:], block_factory)
if succ is False:
return False
except Exception as err:
logging.error(
"CQC {}: Got the following unexpected error when processing factory: {}".format(self.name, err)
)
self.return_messages[header.app_id].append(
self.create_return_message(header.app_id, CQC_ERR_GENERAL, cqc_version=header.version))
return False
if block_factory:
logging.debug("CQC %s: Releasing lock for factory", self.name)
self._sequence_lock.release()
return succ and should_notify
[docs] @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
# Notice the [:] syntax. This ensures the underlying list is updated, and not just the variable.
return_message = None
for message in self.return_messages[header.app_id]:
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[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
# if necessary so we just return nothing (None).
[docs] 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]
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[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.
return if_header.length
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
@abstractmethod
def handle_time(self, header, data):
pass
@abstractmethod
def cmd_i(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_x(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_y(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_z(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_t(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_h(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_k(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_rotx(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_roty(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_rotz(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_cnot(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_cphase(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_measure(self, cqc_header, cmd, xtra, inplace=False):
pass
@abstractmethod
def cmd_measure_inplace(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_reset(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_send(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_recv(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_epr(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_epr_recv(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_new(self, cqc_header, cmd, xtra, return_q_id=False):
pass
@abstractmethod
def cmd_allocate(self, cqc_header, cmd, xtra):
pass
@abstractmethod
def cmd_release(self, cqc_header, cmd, xtra):
pass