Ejemplo n.º 1
0
    def __init__(self):
        # _host_dict stores host_id -> array with qubits of the host.
        self._host_dict = {}
        # _qubit_dict stores qubit_id -> dict Host_id -> Qubit objects with this id.
        self._qubit_dict = {}
        # _purpose_dict stores qubit_id -> dict Host_id -> Purpose belonging to
        # the Qubit with the same Host and ID.
        self._purpose_dict = {}
        self._storage_mode = QuantumStorage.STORAGE_LIMIT_INDIVIDUALLY_PER_HOST
        self._storage_limits_per_host = {}
        self._amount_qubits_stored_per_host = {}
        self._default_storage_limit_per_host = -1
        self._storage_limit = -1
        self._amount_qubit_stored = 0
        # read write lock, for threaded access
        self.lock = RWLock()

        self.logger = Logger.get_instance()

        # for tracking pending requests
        # dictionary tracks the request made by a pending request.
        self._pending_request_dict = {}
        # Determines a unique ID for a pending request.
        self._request_id = 0
        # Amount of pending requests
        self._amount_pending_requests = 0
Ejemplo n.º 2
0
    def __init__(self):
        self._host_to_msg_dict = {}
        self._host_to_read_index = {}

        # read write lock, for threaded access
        self._lock = RWLock()

        # for tracking pending requests
        # dictionary tracks the request made by a pending request.
        self._pending_request_dict = {}
        # Determines a unique ID for a pending request.
        self._request_id = 0
        # Amount of pending requests
        self._amount_pending_requests = 0
Ejemplo n.º 3
0
class QuantumStorage(object):
    """
    An object which stores qubits.
    """

    STORAGE_LIMIT_ALL = 1
    STORAGE_LIMIT_PER_HOST = 2
    STORAGE_LIMIT_INDIVIDUALLY_PER_HOST = 3

    def __init__(self):
        # _host_dict stores host_id -> array with qubits of the host.
        self._host_dict = {}
        # _qubit_dict stores qubit_id -> dict Host_id -> Qubit objects with this id.
        self._qubit_dict = {}
        # _purpose_dict stores qubit_id -> dict Host_id -> Purpose belonging to
        # the Qubit with the same Host and ID.
        self._purpose_dict = {}
        self._storage_mode = QuantumStorage.STORAGE_LIMIT_INDIVIDUALLY_PER_HOST
        self._storage_limits_per_host = {}
        self._amount_qubits_stored_per_host = {}
        self._default_storage_limit_per_host = -1
        self._storage_limit = -1
        self._amount_qubit_stored = 0
        # read write lock, for threaded access
        self.lock = RWLock()

        self.logger = Logger.get_instance()

        # for tracking pending requests
        # dictionary tracks the request made by a pending request.
        self._pending_request_dict = {}
        # Determines a unique ID for a pending request.
        self._request_id = 0
        # Amount of pending requests
        self._amount_pending_requests = 0

    def __str__(self):
        out = ""
        out += "Quantum storage with the properties:\nstorage mode: %d\nstorage limit: %d\n" % (
            self._storage_mode, self._storage_limit)
        out += "Host dictionary is:\n"
        out += "; ".join([
            str(key) + ":" + str([v.id for v in value])
            for key, value in self._host_dict.items()
        ])
        out += "\n"
        out += "Qubit dictionary is:\n"
        out += "; ".join([
            str(key) + ":" + str(value)
            for key, value in self._qubit_dict.items()
        ])
        out += "\n"
        out += "Purpose dictionary is:\n"
        out += "; ".join([
            str(key) + ":" + str(value)
            for key, value in self._purpose_dict.items()
        ])
        out += "\n"
        return out

    @property
    def storage_limit(self):
        return self._storage_limit

    @storage_limit.setter
    def storage_limit(self, new_limit):
        """
        Set a new storage limit for the storage. The implementations depends on
        the storage mode.

        Args:
            new_limit (int): The new max amount of qubit.
        """
        if self._storage_mode == QuantumStorage.STORAGE_LIMIT_ALL:
            self._storage_limit = new_limit
        elif self._storage_mode == QuantumStorage.STORAGE_LIMIT_PER_HOST:
            self._storage_limit = new_limit
        elif self._storage_mode == QuantumStorage.STORAGE_LIMIT_INDIVIDUALLY_PER_HOST:
            self._default_storage_limit_per_host = new_limit
            for id_ in list(self._storage_limits_per_host):
                self._storage_limits_per_host[id_] = new_limit
        else:
            raise ValueError(
                "Internal Value Error, this storage mode does not exist.")

    @property
    def storage_limit_mode(self):
        return self._storage_mode

    @storage_limit_mode.setter
    def storage_limit_mode(self, new_mode):
        self._storage_mode = new_mode

    @property
    def amount_qubits_stored(self):
        return self._amount_qubit_stored

    def amount_qubits_stored_with_host(self, host_id):
        return self._amount_qubits_stored_per_host[host_id]

    def set_storage_limit_with_host(self, new_limit, host_id):
        """
        Set a new storage limit for the storage. The implementations depends on
        the storage mode.

        Args:
            new_limit (int): The new max amount of qubit.
            host_id (str): optional, if given, and the storage mode is
                            STORAGE_LIMIT_INDIVIDUALLY_PER_HOST, the limit is only
                            set for this specific host.
        """
        if self._storage_mode == QuantumStorage.STORAGE_LIMIT_INDIVIDUALLY_PER_HOST:
            if host_id is None:
                raise ValueError("Host ID must be given in this storage mode")
            else:
                self._storage_limits_per_host[host_id] = new_limit
        else:
            raise ValueError(
                "Internal Value Error, this storage mode does not exist.")

    def reset_storage(self):
        """
        Reset the quantum storage.
        """
        for host in self._host_dict:
            self.reset_qubits_from_host(host)

    def release_storage(self):
        """
        Releases all qubits in this storage. The storage is not
        usable anymore after this function has been called.
        """
        self.lock.acquire_write()
        for q in self._qubit_dict.values():
            for ele in q.values():
                ele.release()
        # do not release write, storage not usable anymore

    def check_qubit_from_host_exists(self, from_host_id, purpose=None):
        """
        Check if a qubit from a host exists in this quantum storage.

        Args:
            from_host_id (str): The host id of the host from which the qubit is from.
            purpose (int): Optional, purpose of the qubit which should exist.

        Returns:
            (bool): True, if such a qubit is in the storage, false if not.
        """
        self.lock.acquire_write()
        if from_host_id not in self._host_dict:
            self.lock.release_write()
            return False
        for q in self._host_dict[from_host_id]:
            if self._check_qubit_in_system(q, from_host_id, purpose):
                self.lock.release_write()
                return True
        self.lock.release_write()
        return False

    def get_qubit_by_id(self, q_id):
        """
        Return the qubit that has the id *q_id*

        Args:
            q_id (str): The ID of the qubit
        Returns:
            (Qubit): The qubit with the id *q_id* or None if it does not exist
        """
        if q_id in self._qubit_dict:
            return list(self._qubit_dict[q_id].values())[0]
        return None

    def change_qubit_id(self, from_host_id, new_id, old_id=None):
        """
        Changes the ID of a qubit. If the ID is not given, a random
        qubit which is from a host is changed to the new id.

        Args:
            from_host_id (str): The ID of the owner
            new_id (str): The ID to change to
            old_id (str): The old ID

        Returns:
            (str): The new ID
        """
        new_id = str(new_id)
        self.lock.acquire_write()
        if old_id is not None:
            old_id = str(old_id)
            qubit, purpose = self._pop_qubit_with_id_and_host_from_qubit_dict(
                old_id, from_host_id)
            if qubit is not None:
                qubit.id = new_id
                self._add_qubit_to_qubit_dict(qubit, purpose, from_host_id)
        else:
            if from_host_id in self._host_dict and self._host_dict[
                    from_host_id]:
                qubit = self._host_dict[from_host_id][0]
                old_id = qubit.id
                _, purpose = self._pop_qubit_with_id_and_host_from_qubit_dict(
                    old_id, from_host_id)
                qubit.id = new_id
                self._add_qubit_to_qubit_dict(qubit, purpose, from_host_id)
        self.lock.release_write()
        return old_id

    def add_qubit_from_host(self, qubit, purpose, from_host_id):
        """
        Adds a qubit which has been received from a host.

        Args:
            qubit (Qubit): qubit which should be stored.
            from_host_id (str): Id of the Host from whom the qubit has
                             been received.
            purpose (str): Purpose of the Qubit, for example EPR or data.
        """

        self.lock.acquire_write()
        if self._check_qubit_in_system(qubit, from_host_id, purpose=purpose):
            self.logger.log("Qubit with id %s, purpose %s and from host %s"
                            " already in storage" %
                            (qubit.id, purpose, from_host_id))
            raise ValueError("Qubit with these parameters already in storage!")
        if from_host_id not in self._host_dict:
            self._add_new_host(from_host_id)
        if not self._increase_qubit_counter(from_host_id):
            qubit.release()
            self.lock.release_write()
            return

        self._host_dict[from_host_id].append(qubit)
        self._add_qubit_to_qubit_dict(qubit, purpose, from_host_id)

        # Check if a Qubit of one of the callbacks has arrived
        self._check_all_requests()
        self.lock.release_write()

    def get_all_qubits_from_host(self,
                                 from_host_id,
                                 purpose=None,
                                 remove=False):
        """
        Get all Qubits from a specific host id.
        These qubits are not removed from storage!

        Args:
            from_host_id (str): The host who the qubits are from
            purpose (int): The purpose of the qubits
            remove (bool): Also remove from storage

        Returns:
            (list): The list of qubits
        """

        if from_host_id in self._host_dict:
            out = []
            self.lock.acquire_write()
            flag = False
            for q in self._host_dict[from_host_id]:
                if self._check_qubit_in_system(q, from_host_id, purpose):
                    if not remove:
                        out.append(q)
                else:
                    flag = True
                    if remove:
                        break
            if not flag and remove:
                num_qubits = len(self._host_dict[from_host_id])
                for _ in range(num_qubits):
                    out.append(
                        self._get_qubit_from_host(from_host_id,
                                                  purpose=purpose))
            self.lock.release_write()
            return out
        return []

    def reset_qubits_from_host(self, from_host_id, purpose=None):
        """
        Remove all stored qubits from the host *from_host_id*.

        Args:
            from_host_id (str): The host who the qubits are from
            purpose (int):
        """
        self.lock.acquire_write()
        if from_host_id in self._host_dict:
            for q in self._host_dict[from_host_id]:
                if self._check_qubit_in_system(q, from_host_id, purpose):
                    self._get_qubit_from_host(from_host_id, purpose=purpose)
        self.lock.release_write()

    def _check_all_requests(self):
        """
        Checks if any of the pending requests is now fulfilled.

        Returns:
            If a request is fulfilled, the request is handled and the function
            returns the qubit of this request.
        """
        for req_id, args in self._pending_request_dict.items():
            ret = self._get_qubit_from_host(args[1], args[2], args[3])
            if ret is not None:
                args[0].put(ret)
                self._remove_request(req_id)
                return ret

    def _add_request(self, args):
        """
        Adds a new request to the quantum storage. If a new qubit arrives, it
        is checked if the request for the qubit is satisfied.

        Args:
            args (list): [Queue, from_host_id, q_id, purpose]
        """
        self._pending_request_dict[self._request_id] = args
        self._request_id += 1
        self._amount_pending_requests += 1
        return self._request_id

    def _remove_request(self, req_id):
        """
        Removes a pending request from the request dict.

        Args:
            req_id (int): The id of the request to remove.
        """
        if req_id in self._pending_request_dict:
            del self._pending_request_dict[req_id]
        self._amount_pending_requests -= 1

    def get_qubit_from_host(self,
                            from_host_id,
                            q_id=None,
                            purpose=None,
                            wait=0):
        """
        Returns next qubit which has been received from a host. If the qubit has
        not been receives yet, the thread is blocked for a maxiumum of the wait time,
        till the qubit arrives (The default is 0). If the id is given, the exact qubit with the id
        is returned, or None if it does not exist.
        The qubit is removed from the quantum storage.

        Args:
            from_host_id (str): Host id from who the qubit has been received.
            q_id (str): Optional Id, to return the exact qubit with the Id.
            purpose (str): Optional, purpose of the Qubit.
            wait (int): Default is 0. The maximum blocking time. -1 if blocking forever.

        Returns:
            (bool): If such a qubit exists, it returns the qubit. Otherwise, None
            is returned.
        """
        # Block forever if wait is -1
        if wait == -1:
            wait = None

        self.lock.acquire_write()
        ret = self._get_qubit_from_host(from_host_id, q_id, purpose)
        if ret is not None or wait == 0:
            self.lock.release_write()
            return ret
        q = queue.Queue()
        args = [q, from_host_id, q_id, purpose]
        req_id = self._add_request(args)
        self.lock.release_write()
        ret = None
        try:
            ret = q.get(timeout=wait)
        except queue.Empty:
            pass
        if ret is None:
            self.lock.acquire_write()
            self._remove_request(req_id)
            self.lock.release_write()
        return ret

    def _get_qubit_from_host(self, from_host_id, q_id=None, purpose=None):
        if q_id is not None:
            qubit = self._pop_qubit_with_id_and_host_from_qubit_dict(
                q_id, from_host_id, purpose=purpose)
            if qubit is not None:
                qubit, purp = qubit
                if from_host_id not in self._host_dict or \
                        qubit not in self._host_dict[from_host_id]:
                    # Qubit with the ID exists, but does not belong to the host requested
                    self._add_qubit_to_qubit_dict(qubit, purp, from_host_id)
                    return None
                self._host_dict[from_host_id].remove(qubit)
                self._decrease_qubit_counter(from_host_id)
            return qubit

        if from_host_id not in self._host_dict:
            return None

        if self._host_dict[from_host_id]:
            # check purposes of all qubits
            for _ in range(len(self._host_dict[from_host_id])):
                qubit = self._host_dict[from_host_id].pop(0)
                out = self._pop_qubit_with_id_and_host_from_qubit_dict(
                    qubit.id, from_host_id, purpose=purpose)
                if out is not None:
                    self._decrease_qubit_counter(from_host_id)
                    return out[0]
                self._host_dict[from_host_id].append(qubit)
        return None

    def _pop_qubit_with_id_and_host_from_qubit_dict(self,
                                                    q_id,
                                                    from_host_id,
                                                    purpose=None):
        def _pop_purpose_from_purpose_dict():
            nonlocal q_id, from_host_id

            if q_id not in self._purpose_dict:
                return None
            pur = self._purpose_dict[q_id].pop(from_host_id, None)
            if pur is not None:
                if not self._purpose_dict[q_id]:
                    del self._purpose_dict[q_id]
                return pur
            return None

        purp = _pop_purpose_from_purpose_dict()
        if purp is not None:
            if purpose is None or purpose == purp:
                qubit = self._qubit_dict[q_id].pop(from_host_id, None)
                if qubit is not None:
                    if not self._qubit_dict[q_id]:
                        del self._qubit_dict[q_id]
                return qubit, purp
            else:
                if q_id not in self._purpose_dict:
                    self._purpose_dict[q_id] = {}
                self._purpose_dict[q_id][from_host_id] = purp
        return None

    def _add_qubit_to_qubit_dict(self, qubit, purpose, from_host_id):
        def _add_purpose_to_purpose_dict(q_id):
            nonlocal purpose, from_host_id
            if q_id not in self._purpose_dict:
                self._purpose_dict[q_id] = {}
            self._purpose_dict[q_id][from_host_id] = purpose

        if qubit.id not in self._qubit_dict:
            self._qubit_dict[qubit.id] = {}
        self._qubit_dict[qubit.id][from_host_id] = qubit
        _add_purpose_to_purpose_dict(qubit.id)

    def _add_new_host(self, host_id):
        if host_id not in self._host_dict:
            self._host_dict[host_id] = []
            if host_id not in self._storage_limits_per_host:
                self._storage_limits_per_host[
                    host_id] = self._default_storage_limit_per_host
            self._amount_qubits_stored_per_host[host_id] = 0

    def _check_qubit_in_system(self, qubit, from_host_id, purpose=None):
        """
        True if qubit with same parameters already in the systems

        Args:
            qubit (Qubit): The qubit in question
            from_host_id (str): The ID of the sending host
            purpose (int): Qubit's purpose

        Returns:
            (bool): If the qubit is in the system.
        """
        if qubit.id in self._qubit_dict and \
                from_host_id in self._qubit_dict[qubit.id]:
            if purpose is None or (
                    purpose == self._purpose_dict[qubit.id][from_host_id]):
                return True
        return False

    def _check_memory_limits(self, host_id):
        """
        Checks if another qubit can be added to the storage.

        Args:
            host_id (str): The host_id the qubit should be added to.

        Returns:
            True if no storage limit has been reached, False if a memory
            limit has occurred.
        """
        if self._storage_mode == QuantumStorage.STORAGE_LIMIT_ALL:
            if self._storage_limit == -1:
                return True
            if self._storage_limit <= self._amount_qubit_stored:
                return False
            else:
                return True
        elif self._storage_mode == QuantumStorage.STORAGE_LIMIT_PER_HOST:
            if self._storage_limit == -1:
                return True
            if self._storage_limit <= self._amount_qubits_stored_per_host[
                    host_id]:
                return False
            else:
                return True
        elif self._storage_mode == QuantumStorage.STORAGE_LIMIT_INDIVIDUALLY_PER_HOST:
            if self._storage_limits_per_host[host_id] == -1:
                return True
            if self._storage_limits_per_host[
                    host_id] <= self._amount_qubits_stored_per_host[host_id]:
                return False
            else:
                return True
        else:
            raise ValueError(
                "Internal Value Error, this storage mode does not exist.")

    def _increase_qubit_counter(self, host_id):
        """
        Checks if the qubit counter can be increased, because of memory limits,
        and increases the counter.

        Args:
            host_id (str): From who the qubit comes from.

        Returns:
            True, if the counter could be increased, False if not.
        """
        if not self._check_memory_limits(host_id):
            return False
        self._amount_qubits_stored_per_host[host_id] += 1
        self._amount_qubit_stored += 1
        return True

    def _reset_qubit_counter(self, host_id):
        """

        Args:
            host_id (str):

        Returns:
            (bool): True, if the counter could be decreased, False if not.
        """
        if self._amount_qubits_stored_per_host[host_id] <= 0 or \
                self._amount_qubit_stored <= 0:
            return False
        num_qubits = self._amount_qubits_stored_per_host[host_id]
        self._amount_qubits_stored_per_host[host_id] = 0
        self._amount_qubit_stored -= num_qubits

    def _decrease_qubit_counter(self, host_id):
        """
        Checks if the qubit counter can be decreased
        and decreases the counter.

        Args:
            host_id (str): From who the qubit comes from.

        Returns:
            (bool): True, if the counter could be decreased, False if not.
        """
        if self._amount_qubits_stored_per_host[host_id] <= 0 or \
                self._amount_qubit_stored <= 0:
            return False
        self._amount_qubits_stored_per_host[host_id] -= 1
        self._amount_qubit_stored -= 1
Ejemplo n.º 4
0
 def __init__(self, name):
     # initialize as a qubit in state |0>
     self._rwlock = RWLock()
     self.N = 1
     self._qubit_names = [name]
     self.data = qutip.qutip.fock_dm(2, 0)
Ejemplo n.º 5
0
    class QubitCollection(object):

        def __init__(self, name):
            # initialize as a qubit in state |0>
            self._rwlock = RWLock()
            self.N = 1
            self._qubit_names = [name]
            self.data = qutip.qutip.fock_dm(2, 0)

        def add_qubit(self, qubit):
            """
            Calculates the tensor product using the implementation
            of QuTip.
            Modified vesion of qutip tensor function,
            See http://qutip.org/docs/4.0.2/modules/qutip/tensor.html
            """
            self._lock()
            self.data = qutip.tensor(self.data, qubit.data)
            self.N = self.N + qubit.N
            self._qubit_names = self._qubit_names + qubit._qubit_names
            self._unlock()

        def apply_single_gate(self, gate, qubit_name):
            if not isinstance(gate, qutip.Qobj):
                raise TypeError("Gate has to be of type Qobject.")
            self._lock()
            target = self._qubit_names.index(qubit_name)
            gate = qutip.gate_expand_1toN(gate, self.N, target)
            self.data = gate * self.data * gate.dag()
            self._unlock()

        def apply_double_gate(self, gate, control_name, target_name):
            if not isinstance(gate, qutip.Qobj):
                raise TypeError("Gate has to be of type Qobject.")
            self._lock()
            control = self._qubit_names.index(control_name)
            target = self._qubit_names.index(target_name)
            gate = qutip.gate_expand_2toN(gate, self.N, control, target)
            self.data = gate * self.data * gate.dag()
            self._unlock()

        def measure(self, qubit_name, non_destructive):
            res = None
            M_0 = qutip.fock(2, 0).proj()
            M_1 = qutip.fock(2, 1).proj()
            self._lock()
            target = self._qubit_names.index(qubit_name)
            if self.N > 1:
                M_0 = qutip.gate_expand_1toN(M_0, self.N, target)
                M_1 = qutip.gate_expand_1toN(M_1, self.N, target)
            pr_0 = qutip.expect(M_0, self.data)
            pr_1 = qutip.expect(M_1, self.data)
            outcome = int(np.random.choice([0, 1], 1, p=[pr_0, pr_1]))
            if outcome == 0:
                self.data = M_0 * self.data * M_0.dag() / pr_0
                res = 0
            else:
                # M_1 = qutip.gate_expand_1toN(M_1, self.N, target)
                self.data = M_1 * self.data * M_1.dag() / pr_1
                res = 1
            if non_destructive is False:
                i_list = [x for x in range(self.N)]
                i_list.remove(target)
                self._qubit_names.remove(qubit_name)
                self.N = self.N - 1
                if len(i_list) > 0:
                    self.data = self.data.ptrace(i_list)
                else:
                    self.data = qutip.Qobj()
            self._unlock()
            return res

        def _lock(self):
            self._rwlock.acquire_write()

        def _unlock(self):
            self._rwlock.release_write()
Ejemplo n.º 6
0
 def __init__(self):
     self.lock = RWLock()
     self.dict = {}
Ejemplo n.º 7
0
class SafeDict(object):
    def __init__(self):
        self.lock = RWLock()
        self.dict = {}

    def __str__(self):
        self.lock.acquire_read()
        ret = str(self.dict)
        self.lock.release_read()
        return ret

    def add_to_dict(self, key, value):
        self.lock.acquire_write()
        self.dict[key] = value
        self.lock.release_write()

    def get_from_dict(self, key):
        ret = None
        self.lock.acquire_read()
        if key in self.dict:
            ret = self.dict[key]
        self.lock.release_read()
        return ret
Ejemplo n.º 8
0
class ClassicalStorage(object):
    GET_NEXT = 1
    GET_ALL = 2
    GET_WITH_SEQ_NUM = 3

    """
    A classical storage for messages.
    """

    def __init__(self):
        self._host_to_msg_dict = {}
        self._host_to_read_index = {}

        # read write lock, for threaded access
        self._lock = RWLock()

        # for tracking pending requests
        # dictionary tracks the request made by a pending request.
        self._pending_request_dict = {}
        # Determines a unique ID for a pending request.
        self._request_id = 0
        # Amount of pending requests
        self._amount_pending_requests = 0

    def _check_all_requests(self):
        """
        Checks if any of the pending requests is now fulfilled.

        Returns:
            If a request is fulfilled, the request is handled and the function
            returns the message of this request.
        """
        for req_id, args in self._pending_request_dict.items():
            ret = None
            if args[2] == ClassicalStorage.GET_NEXT:
                ret = self._get_next_from_sender(args[1])
            elif args[2] == ClassicalStorage.GET_ALL:
                ret = self._get_all_from_sender(args[1])
            elif args[2] == ClassicalStorage.GET_WITH_SEQ_NUM:
                ret = self._get_with_seq_num_from_sender(args[1], args[3])
            else:
                raise ValueError("Internal Error, this request does not exist!")

            if ret is not None:
                args[0].put(ret)
                self._remove_request(req_id)
                return ret

    def _add_request(self, args):
        """
        Adds a new request to the classical storage. If a new message arrives, it
        is checked if the request for the qubit is satisfied.

        Args:
            args (list): [Queue, from_host_id, type, ...]
        Returns:
            (int): ID of the request
        """
        self._pending_request_dict[self._request_id] = args
        self._request_id += 1
        self._amount_pending_requests += 1
        return self._request_id

    def _remove_request(self, req_id):
        """
        Removes a pending request from the request dict.

        Args:
            req_id (int): The id of the request to remove.
        """
        if req_id in self._pending_request_dict:
            del self._pending_request_dict[req_id]
        self._amount_pending_requests -= 1

    def empty(self):
        """
        Empty the classical storage.
        """
        self._lock.acquire_write()
        self._host_to_msg_dict = {}
        self._host_to_read_index = {}
        self._lock.release_write()

    def _add_new_host_id(self, host_id):
        """
        Add a new host to the storage.

        Args:
            host_id (str): The host ID to store.
        """
        self._host_to_msg_dict[host_id] = []
        self._host_to_read_index[host_id] = 0

    def remove_all_ack(self, from_sender=None):
        """
        Removes all ACK messages stored. If from sender is given, only ACKs from
        this sender are removed.

        Args:
            from_sender (String): Host id of the sender, whos ACKs should be delted.
        """

        self._lock.acquire_write()

        def delete_all_ack_for_sender(sender_id):
            for c, msg in enumerate(self._host_to_msg_dict[sender_id]):
                if msg.content == Constants.ACK:
                    del self._host_to_msg_dict[sender_id][c]

        if from_sender is None:
            for sender in list(self._host_to_msg_dict):
                delete_all_ack_for_sender(sender)
        elif from_sender in self._host_to_msg_dict:
            delete_all_ack_for_sender(from_sender)
        self._lock.release_write()

    # TODO: refactor to "add_msg"
    def add_msg_to_storage(self, message):
        """
        Adds a message to the storage.
        """
        sender_id = message.sender
        self._lock.acquire_write()
        if sender_id not in list(self._host_to_msg_dict):
            self._add_new_host_id(sender_id)
        self._host_to_msg_dict[sender_id].append(message)
        self._check_all_requests()
        self._lock.release_write()

    def get_all_from_sender(self, sender_id, wait=0):
        """
        Get all stored messages from a sender. If delete option is set,
        the returned messages are removed from the storage.

        Args:
            sender_id (String): The host id of the host.
            wait (int): Default is 0. The maximum blocking time. -1 to block forever.

        Returns:
            List of messages of the sender. If there are none, an empty list is
            returned.
        """
        # Block forever if wait is -1
        if wait == -1:
            wait = None

        self._lock.acquire_write()
        msg = self._get_all_from_sender(sender_id)
        if msg is not None or wait == 0:
            self._lock.release_write()
            return msg if msg is not None else []

        q = queue.Queue()
        request = [q, sender_id, ClassicalStorage.GET_ALL]
        req_id = self._add_request(request)
        self._lock.release_write()

        try:
            msg = q.get(timeout=wait)
        except queue.Empty:
            pass

        if msg is None:
            self._lock.acquire_write()
            self._remove_request(req_id)
            self._lock.release_write()
            return []
        return msg

    def _get_all_from_sender(self, sender_id):
        if sender_id in list(self._host_to_msg_dict):
            return self._host_to_msg_dict[sender_id]
        return None

    def get_next_from_sender(self, sender_id, wait=0):
        """
        Gets the next, unread, message from the sender. If there is no message
        yet, it is waited for the waiting time till a message is arrived. If
        there is still no message, than None is returned.

        Args:
            sender_id (String): The sender id of the message to get.
            wait (int): Default is 0. The maximum blocking time. -1 to block forever.
        Returns:
            Message object, if such a message exists, or none.
        """
        # Block forever if wait is -1
        if wait == -1:
            wait = None

        self._lock.acquire_write()
        next_msg = self._get_next_from_sender(sender_id)
        if next_msg is not None or wait == 0:
            self._lock.release_write()
            return next_msg

        q = queue.Queue()
        request = [q, sender_id, ClassicalStorage.GET_NEXT]
        req_id = self._add_request(request)
        self._lock.release_write()

        try:
            next_msg = q.get(timeout=wait)
        except queue.Empty:
            pass

        if next_msg is None:
            self._lock.acquire_write()
            self._remove_request(req_id)
            self._lock.release_write()
        return next_msg

    def _get_next_from_sender(self, sender_id):
        if sender_id not in list(self._host_to_msg_dict):
            return None
        if len(self._host_to_msg_dict[sender_id]) <= self._host_to_read_index[sender_id]:
            return None
        msg = self._host_to_msg_dict[sender_id][self._host_to_read_index[sender_id]]
        self._host_to_read_index[sender_id] += 1
        return msg

    def get_with_seq_num_from_sender(self, sender_id, seq_num, wait=0):
        """
        Gets the next, unread, message from the sender. If there is no message
        yet, it is waited for the waiting time till a message is arrived. If
        there is still no message, than None is returned.

        Args:
            sender_id (String): The sender id of the message to get.
            wait (int): Default is 0. The maximum blocking time. -1 to block forever.
        Returns:
            Message object, if such a message exists, or none.
        """
        # Block forever if wait is -1
        if wait == -1:
            wait = None

        self._lock.acquire_write()
        next_msg = self._get_with_seq_num_from_sender(sender_id, seq_num)
        if next_msg is not None or wait == 0:
            self._lock.release_write()
            return next_msg

        q = queue.Queue()
        request = [q, sender_id, ClassicalStorage.GET_NEXT, seq_num]
        req_id = self._add_request(request)
        self._lock.release_write()

        try:
            next_msg = q.get(timeout=wait)
        except queue.Empty:
            pass

        if next_msg is None:
            self._lock.acquire_write()
            self._remove_request(req_id)
            self._lock.release_write()
        return next_msg

    def _get_with_seq_num_from_sender(self, sender_id, seq_num):
        if sender_id not in list(self._host_to_msg_dict):
            return None
        if len(self._host_to_msg_dict[sender_id]) <= seq_num:
            return None
        msg = self._host_to_msg_dict[sender_id][seq_num]
        return msg

    def get_all(self):
        """
        Get all Messages as a list.

        Returns:
            (list) messages: All Messages as a list.
        """
        self._lock.acquire_write()
        ret = []
        for host_id in list(self._host_to_msg_dict):
            ret += self._host_to_msg_dict[host_id]
        self._lock.release_write()
        return ret
Ejemplo n.º 9
0
class CQCBackend(object):
    """
    The SimulaQron CQC backend
    """
    class Hosts(SafeDict):
        # There only should be one instance of Hosts
        __instance = None

        @staticmethod
        def get_instance():
            if CQCBackend.Hosts.__instance is not None:
                return CQCBackend.Hosts.__instance
            else:
                return CQCBackend.Hosts()

        def __init__(self):
            if CQCBackend.Hosts.__instance is not None:
                raise Exception("Call get instance to get this class!")
            CQCBackend.Hosts.__instance = self
            SafeDict.__init__(self)

    class CQCConnections(SafeDict):
        # There only should be one instance of Hosts
        __instance = None

        @staticmethod
        def get_instance():
            if CQCBackend.CQCConnections.__instance is not None:
                return CQCBackend.CQCConnections.__instance
            else:
                return CQCBackend.CQCConnections()

        def __init__(self):
            if CQCBackend.CQCConnections.__instance is not None:
                raise Exception("Call get instance to get this class!")
            CQCBackend.CQCConnections.__instance = self
            SafeDict.__init__(self)

    class EntanglementIDs(SafeDict):
        # There only should be one instance of Hosts
        __instance = None

        @staticmethod
        def get_instance():
            if CQCBackend.EntanglementIDs.__instance is not None:
                return CQCBackend.EntanglementIDs.__instance
            else:
                return CQCBackend.EntanglementIDs()

        def __init__(self):
            if CQCBackend.EntanglementIDs.__instance is not None:
                raise Exception("Call get instance to get this class!")
            CQCBackend.EntanglementIDs.__instance = self
            SafeDict.__init__(self)

    # SimulaQron comes with an own network simulator
    # has to be kept in sync with QuNetSim network
    backend_network = None
    backend_network_lock = RWLock()

    def __init__(self):
        self._hosts = CQCBackend.Hosts.get_instance()
        self._cqc_connections = CQCBackend.CQCConnections.get_instance()
        # keys are from : to, where from is the host calling create EPR
        self._entaglement_ids = CQCBackend.EntanglementIDs.get_instance()
        self._stopped = False

    def start(self, **kwargs):
        """
        Starts Backends which have to run in an own thread or process before they
        can be used.

        Args:
            nodes(List): A list of hosts in the network.
        """
        print('Starting SimulaQron Network...')
        nodes = kwargs['nodes']
        CQCBackend.backend_network_lock.acquire_write()
        simulaqron_settings.default_settings()
        CQCBackend.backend_network = SimulaNetwork(nodes=nodes, force=True)
        CQCBackend.backend_network.start()
        CQCBackend.backend_network_lock.release_write()

    def stop(self):
        """
        Stops Backends which are running in an own thread or process.
        """
        if not self._stopped:
            CQCBackend.backend_network_lock.acquire_write()
            CQCBackend.backend_network.stop()
            self._stopped = True
            CQCBackend.backend_network_lock.release_write()

    def add_host(self, host):
        """
        Adds a host to the backend.

        Args:
            host (Host): New Host which should be added.
        """
        connection = cqc.CQCConnection(host.host_id)
        self._cqc_connections.add_to_dict(host.host_id, connection)
        self._hosts.add_to_dict(host.host_id, host)

    def create_qubit(self, host_id):
        """
        Creates a new Qubit of the type of the backend.

        Args:
            host_id (String): Id of the host to whom the qubit belongs.

        Returns:
            Qubit of backend type.
        """
        return cqc.qubit(self._cqc_connections.get_from_dict(host_id))

    def send_qubit_to(self, qubit, from_host_id, to_host_id):
        """
        Sends a qubit to a new host.

        Args:
            qubit (Qubit): Qubit to be send.
            from_host_id (String): From the starting host.
            to_host_id (String): New host of the qubit.
        """
        cqc_from_host = self._cqc_connections.get_from_dict(from_host_id)
        cqc_to_host = self._cqc_connections.get_from_dict(to_host_id)
        cqc_from_host.sendQubit(qubit.qubit, cqc_to_host.name)
        qubit.qubit = cqc_to_host.recvQubit()
        qubit.host = self._hosts.get_from_dict(to_host_id)

    def create_EPR(self, host_a_id, host_b_id, q_id=None, block=False):
        """
        Creates an EPR pair for two qubits and returns one of the qubits.

        Args:
            host_a_id (String): ID of the first host who gets the EPR state.
            host_b_id (String): ID of the second host who gets the EPR state.
            q_id (String): Optional id which both qubits should have.
            block (bool): Determines if the created pair should be blocked or not.
        Returns:
            Returns a qubit. The qubit belongs to host a. To get the second
            qubit of host b, the receive_epr function has to be called.
        """
        cqc_host_a = self._cqc_connections.get_from_dict(host_a_id)
        cqc_host_b = self._cqc_connections.get_from_dict(host_b_id)
        host_a = self._hosts.get_from_dict(host_a_id)
        q = cqc_host_a.createEPR(cqc_host_b.name)
        qubit = Qubit(host_a, qubit=q, q_id=q_id, blocked=block)
        # add the ID to a list, so the next returned qubit from recv EPR
        # gets assigned the right id
        self.store_ent_id(cqc_host_a, cqc_host_b, qubit)
        return qubit

    def store_ent_id(self, cqc_host_a, cqc_host_b, qubit):
        key = cqc_host_a.name + ':' + cqc_host_b.name
        ent_list = self._entaglement_ids.get_from_dict(key)
        if ent_list is not None:
            ent_list.append(qubit.id)
        else:
            ent_list = [qubit.id]
        self._entaglement_ids.add_to_dict(key, ent_list)

    def receive_epr(self, host_id, sender_id, q_id=None, block=False):
        """
        Called after create EPR in the receiver, to receive the other EPR pair.

        Args:
            host_id (String): ID of the first host who gets the EPR state.
            sender_id (String): ID of the sender of the EPR pair.
            q_id (String): Optional id which both qubits should have.
            block (bool): Determines if the created pair should be blocked or not.
        Returns:
            Returns an EPR qubit with the other Host.
        """
        cqc_host = self._cqc_connections.get_from_dict(host_id)
        host = self._hosts.get_from_dict(host_id)
        q = cqc_host.recvEPR()
        key = sender_id + ':' + cqc_host.name
        ent_list = self._entaglement_ids.get_from_dict(key)
        if ent_list is None:
            raise Exception("Internal Error!")
        id = None
        id = ent_list.pop(0)
        if q_id is not None and q_id != id:
            raise ValueError("q_id doesn't match id!")
        self._entaglement_ids.add_to_dict(key, ent_list)
        return Qubit(host, qubit=q, q_id=id, blocked=block)

    def flush(self, host_id):
        """
        CQC specific function.
        """
        self._cqc_connections.get_from_dict(host_id).flush()

    ##########################
    #   Gate definitions    #
    #########################

    def I(self, qubit):
        """
        Perform Identity gate on a qubit.

        Args:
            qubit (Qubit): Qubit on which gate should be applied to.
        """
        qubit.qubit.I()

    def X(self, qubit):
        """
        Perform pauli X gate on a qubit.

        Args:
            qubit (Qubit): Qubit on which gate should be applied to.
        """
        qubit.qubit.X()

    def Y(self, qubit):
        """
        Perform pauli Y gate on a qubit.

        Args:
            qubit (Qubit): Qubit on which gate should be applied to.
        """
        qubit.qubit.Y()

    def Z(self, qubit):
        """
        Perform pauli Z gate on a qubit.

        Args:
            qubit (Qubit): Qubit on which gate should be applied to.
        """
        qubit.qubit.Z()

    def H(self, qubit):
        """
        Perform Hadamard gate on a qubit.

        Args:
            qubit (Qubit): Qubit on which gate should be applied to.
        """
        qubit.qubit.H()

    def T(self, qubit):
        """
        Perform T gate on a qubit.

        Args:
            qubit (Qubit): Qubit on which gate should be applied to.
        """
        qubit.qubit.T()

    def K(self, qubit):
        """
        Perform K gate on a qubit.

        Args:
            qubit (Qubit): Qubit on which gate should be applied to.
        """
        qubit.qubit.K()

    def rx(self, qubit, steps):
        """
        Perform a rotation pauli x gate with an angle of phi.

        Args:
            qubit (Qubit): Qubit on which gate should be applied to.
            steps (int): Amount of rotation in Rad.
        """
        # convert to cqc unit
        if steps < 0:
            steps = 256 + steps
        qubit.qubit.rot_X(steps)

    def ry(self, qubit, steps):
        """
        Perform a rotation pauli y gate with an angle of phi.

        Args:
            qubit (Qubit): Qubit on which gate should be applied to.
            steps (int): Amount of rotation in Rad.
        """
        # convert to cqc unit
        # steps = phi * 256.0 / (2.0 * np.pi)
        if steps < 0:
            steps = 256 + steps
        qubit.qubit.rot_Y(steps)

    def rz(self, qubit, steps):
        """
        Perform a rotation pauli z gate with an angle of phi.

        Args:
            qubit (Qubit): Qubit on which gate should be applied to.
            phi (float): Amount of rotation in Rad.
        """
        # convert to cqc unit
        # steps = phi * 256.0 / (2.0 * np.pi)
        if steps < 0:
            steps = 256 + steps
        qubit.qubit.rot_Z(steps)

    def cnot(self, qubit, target):
        """
        Applies a controlled x gate to the target qubit.

        Args:
            qubit (Qubit): Qubit to control cnot.
            target (Qubit): Qubit on which the cnot gate should be applied.
        """
        qubit.qubit.cnot(target.qubit)

    def cphase(self, qubit, target):
        """
        Applies a controlled z gate to the target qubit.

        Args:
            qubit (Qubit): Qubit to control cphase.
            target (Qubit): Qubit on which the cphase gate should be applied.
        """
        qubit.qubit.cphase(target.qubit)

    def custom_gate(self, qubit, gate):
        """
        Applies a custom gate to the qubit.

        Args:
            qubit(Qubit): Qubit to which the gate is applied.
            gate(np.ndarray): 2x2 array of the gate.
        """
        raise (EnvironmentError("Not implemented for this backend!"))

    def custom_controlled_gate(self, qubit, target, gate):
        """
        Applies a custom gate to the target qubit, controlled by the qubit.

        Args:
            qubit(Qubit): Qubit to control the gate.
            target(Qubit): Qubit on which the gate is applied.
            gate(nd.array): 2x2 array for the gate applied to target.
        """
        raise (EnvironmentError("Not implemented for this backend!"))

    def custom_two_qubit_gate(self, qubit1, qubit2, gate):
        """
        Applies a custom two qubit gate to qubit1 \\otimes qubit2.

        Args:
            qubit1(Qubit): First qubit of the gate.
            qubit2(Qubit): Second qubit of the gate.
            gate(np.ndarray): 4x4 array for the gate applied.
        """
        raise (EnvironmentError("Not implemented for this backend!"))

    def custom_controlled_two_qubit_gate(self, qubit, target_1, target_2,
                                         gate):
        """
        Applies a custom gate to the target qubit, controlled by the qubit.

        Args:
            qubit (Qubit): Qubit to control the gate.
            target_1 (Qubit): Qubit on which the gate is applied.
            target_2 (Qubit): Qubit on which the gate is applied.
            gate (nd.array): 4x4 array for the gate applied to target.
        """
        raise (EnvironmentError("Not implemented for this backend!"))

    def measure(self, qubit, non_destructive):
        """
        Perform a measurement on a qubit.

        Args:
            qubit (Qubit): Qubit which should be measured.
            non_destructive (bool): Determines if the Qubit should stay in the
                                    system or be eliminated.

        Returns:
            The value which has been measured.
        """
        return qubit.qubit.measure(inplace=non_destructive)

    def release(self, qubit):
        """
        Releases the qubit.

        Args:
            qubit (Qubit): The qubit which should be released.
        """
        qubit.qubit.release()