Beispiel #1
0
    def start(self, nodes=None, backend=None):
        """
        Starts the network.

        """
        if backend is None:
            self._backend = CQCBackend()
        else:
            self._backend = backend
        if nodes is not None:
            self._backend.start(nodes=nodes)
        self._queue_processor_thread = DaemonThread(target=self._process_queue)
Beispiel #2
0
def main():
    backend = CQCBackend()
    network = Network.get_instance()
    nodes = ["Alice", "Bob", "Eve", "Dean"]
    network.start(nodes, backend)
    network.delay = 0.0
    hosts = {'alice': Host('Alice', backend), 'bob': Host('Bob', backend)}

    # A <-> B
    hosts['alice'].add_connection('Bob')
    hosts['bob'].add_connection('Alice')

    hosts['alice'].start()
    hosts['bob'].start()

    for h in hosts.values():
        network.add_host(h)

    q = Qubit(hosts['alice'])
    q.X()

    hosts['alice'].send_teleport(hosts['bob'].host_id, q)

    q2 = hosts['bob'].get_data_qubit(hosts['alice'].host_id)
    i = 0
    while q2 is None and i < 5:
        q2 = hosts['bob'].get_data_qubit(hosts['alice'].host_id)
        i += 1
        time.sleep(1)

    assert q2 is not None
    assert q2.measure() == 1
    print("All tests succesfull!")
    network.stop(True)
    exit()
Beispiel #3
0
def main():
    network = Network.get_instance()
    nodes = ["Alice", "Bob", "Eve", "Dean"]
    backend = CQCBackend()
    network.start(nodes, backend)
    network.delay = 0.7

    hosts = {'alice': Host('Alice', backend),
             'bob': Host('Bob', backend)}

    network.delay = 0
    # A <-> B
    hosts['alice'].add_connection('Bob')
    hosts['bob'].add_connection('Alice')

    hosts['alice'].start()
    hosts['bob'].start()

    for h in hosts.values():
        network.add_host(h)

    q1 = Qubit(hosts['alice'])
    hosts['alice'].send_qubit('Bob', q1, await_ack=True)
    q1 = Qubit(hosts['alice'])
    hosts['alice'].send_qubit('Bob', q1, await_ack=True)
    q1 = Qubit(hosts['alice'])
    hosts['alice'].send_qubit('Bob', q1, await_ack=True)
    q1 = Qubit(hosts['bob'])
    hosts['bob'].send_qubit('Alice', q1, await_ack=True)
    network.stop(True)
    exit()
def main():
    network = Network.get_instance()

    # backend = ProjectQBackend()
    backend = CQCBackend()

    nodes = ['A', 'B', 'C']
    network.start(nodes, backend)
    network.delay = 0.1

    host_A = Host('A', backend)
    host_A.add_connection('B')
    host_A.delay = 0
    host_A.start()

    host_B = Host('B', backend)
    host_B.add_connections(['A', 'C'])
    host_B.delay = 0
    host_B.start()

    host_C = Host('C', backend)
    host_C.add_connection('B')
    host_C.delay = 0
    host_C.start()

    network.add_host(host_A)
    network.add_host(host_B)
    network.add_host(host_C)

    t1 = host_A.run_protocol(protocol_1, (host_C.host_id, ))
    t2 = host_C.run_protocol(protocol_2, (host_A.host_id, ))

    t1.join()
    t2.join()
    network.stop(True)
Beispiel #5
0
def main():
    backend = CQCBackend()
    network = Network.get_instance()
    nodes = ["Alice", "Bob", "Eve", "Dean"]
    network.start(nodes, backend)
    network.delay = 0.7

    hosts = {'alice': Host('Alice', backend), 'bob': Host('Bob', backend)}

    # A <-> B
    hosts['alice'].add_connection('Bob')
    hosts['bob'].add_connection('Alice')

    hosts['alice'].start()
    hosts['bob'].start()

    for h in hosts.values():
        network.add_host(h)

    ack_received_1 = hosts['alice'].send_classical(hosts['bob'].host_id,
                                                   'hello bob one',
                                                   await_ack=True)
    hosts['alice'].max_ack_wait = 0.0
    ack_received_2 = hosts['alice'].send_classical(hosts['bob'].host_id,
                                                   'hello bob one',
                                                   await_ack=True)
    assert ack_received_1
    assert not ack_received_2
    print("All tests succesfull!")
    network.stop(True)
    exit()
Beispiel #6
0
def main():
    backend = CQCBackend()
    network = Network.get_instance()
    nodes = ["Alice", "Bob", "Eve", "Dean"]
    network.start(nodes, backend)
    network.delay = 0.7
    hosts = {'alice': Host('Alice', backend),
             'bob': Host('Bob', backend)}

    # A <-> B
    hosts['alice'].add_connection('Bob')
    hosts['bob'].add_connection('Alice')

    hosts['alice'].start()
    hosts['bob'].start()

    for h in hosts.values():
        network.add_host(h)

    hosts['alice'].send_superdense(hosts['bob'].host_id, '01')

    messages = hosts['bob'].classical
    i = 0
    while i < 5 and len(messages) == 0:
        messages = hosts['bob'].classical
        i += 1
        time.sleep(1)

    assert messages is not None
    assert len(messages) > 0
    assert (messages[0].sender == hosts['alice'].host_id)
    assert (messages[0].content == '01')
    print("All tests succesfull!")
    network.stop(True)
    exit()
Beispiel #7
0
    def __init__(self, host_id, backend=None):
        """
        Return the most important thing about a person.

        Args:
            host_id: The ID of the host
            backend: The backend to use for this host

        """
        self._host_id = host_id
        self._packet_queue = Queue()
        self._stop_thread = False
        self._queue_processor_thread = None
        self._data_qubit_store = QuantumStorage()
        self._EPR_store = QuantumStorage()
        self._classical_messages = ClassicalStorage()
        self._classical_connections = []
        self._quantum_connections = []
        if backend is None:
            self._backend = CQCBackend()
        else:
            self._backend = backend
        # add this host to the backend

        self._backend.add_host(self)
        self._max_ack_wait = None
        # Frequency of queue processing
        self._delay = 0.1
        self.logger = Logger.get_instance()
        # Packet sequence numbers per connection
        self._max_window = 10
        # [Queue, sender, seq_num, timeout, start_time]
        self._ack_receiver_queue = []
        # sender: host -> int
        self._seq_number_sender = {}
        # sender_ack: host->[received_list, low_number]
        self._seq_number_sender_ack = {}
        # receiver: host->[received_list, low_number]
        self._seq_number_receiver = {}
        self.qkd_keys = {}
Beispiel #8
0
def main():
    network = Network.get_instance()
    nodes = ["Alice", "Bob", "Eve", "Dean"]
    backend = CQCBackend()
    network.start(nodes, backend)
    network.delay = 0.2
    network.packet_drop_rate = 0
    print('')

    host_alice = Host('Alice', backend)
    host_alice.add_connection('Bob')
    host_alice.start()

    host_bob = Host('Bob', backend)
    host_bob.add_connection('Alice')
    host_bob.add_connection('Eve')
    host_bob.start()

    host_eve = Host('Eve', backend)
    host_eve.add_connection('Bob')
    host_eve.add_connection('Dean')
    host_eve.start()

    host_dean = Host('Dean', backend)
    host_dean.add_connection('Eve')
    host_dean.start()

    network.add_host(host_alice)
    network.add_host(host_bob)
    network.add_host(host_eve)
    network.add_host(host_dean)

    print('alice sends message')

    host_alice.send_classical('Bob', 'hello1')
    host_alice.send_classical('Bob', 'hello2')
    host_alice.send_classical('Bob', 'hello3')
    host_alice.send_classical('Bob', 'hello4')
    host_alice.send_classical('Bob', 'hello5')
    host_alice.send_classical('Bob', 'hello6')
    host_alice.send_classical('Bob', 'hello7')
    host_alice.send_classical('Bob', 'hello8')
    host_alice.send_classical('Bob', 'hello9')
    host_alice.send_classical('Bob', 'hello10')

    start_time = time.time()
    while time.time() - start_time < 10:
        pass

    network.stop(True)
    exit()
def main():
    print("Skip test, this test has to be updated!")
    return
    backend = CQCBackend()
    network = Network.get_instance()
    nodes = ["Alice", "Bob", "Eve", "Dean"]
    network.start(nodes, backend)
    network.delay = 0.7

    hosts = {'alice': Host('Alice', backend), 'bob': Host('Bob', backend)}

    network.delay = 0
    # A <-> B
    hosts['alice'].add_connection('Bob')
    hosts['bob'].add_connection('Alice')

    hosts['alice'].start()
    hosts['bob'].start()

    for h in hosts.values():
        network.add_host(h)

    # print(f"ack test - SEND CLASSICAL - started at {time.strftime('%X')}")
    hosts['alice'].send_classical(hosts['bob'].host_id,
                                  'hello bob one',
                                  await_ack=True)
    hosts['alice'].send_classical(hosts['bob'].host_id,
                                  'hello bob two',
                                  await_ack=True)
    # print(f"ack test - SEND CLASSICAL - finished at {time.strftime('%X')}")

    saw_ack_1 = False
    saw_ack_2 = False
    messages = hosts['alice'].classical
    # print([m.seq_num for m in messages])
    for m in messages:
        if m.content == protocols.ACK and m.seq_num == 0:
            saw_ack_1 = True
        if m.content == protocols.ACK and m.seq_num == 1:
            saw_ack_2 = True
        if saw_ack_1 and saw_ack_2:
            break

    assert saw_ack_1
    assert saw_ack_2
    print("All tests succesfull!")
    network.stop(True)
    exit()
def main():
    backend = CQCBackend()
    network = Network.get_instance()
    nodes = ["Alice", "Bob", "Eve", "Dean"]
    network.start(nodes, backend)
    network.delay = 0.7
    hosts = {'alice': Host('Alice', backend), 'bob': Host('Bob', backend)}

    network.delay = 0

    # A <-> B
    hosts['alice'].add_connection('Bob')
    hosts['bob'].add_connection('Alice')

    hosts['alice'].start()
    hosts['bob'].start()

    for h in hosts.values():
        network.add_host(h)

    hosts['alice'].send_classical(hosts['bob'].host_id, 'Hello Bob', False)
    hosts['bob'].send_classical(hosts['alice'].host_id, 'Hello Alice', False)

    i = 0
    bob_messages = hosts['bob'].classical
    while i < 5 and len(bob_messages) == 0:
        bob_messages = hosts['bob'].classical
        i += 1
        time.sleep(1)

    i = 0
    alice_messages = hosts['alice'].classical
    while i < 5 and len(alice_messages) == 0:
        alice_messages = hosts['alice'].classical
        i += 1
        time.sleep(1)

    assert len(alice_messages) > 0
    assert alice_messages[0].sender == hosts['bob'].host_id
    assert alice_messages[0].content == 'Hello Alice'

    assert (len(bob_messages) > 0)
    assert (bob_messages[0].sender == hosts['alice'].host_id)
    assert (bob_messages[0].content == 'Hello Bob')
    print("All tests succesfull!")
    network.stop(True)
    exit()
Beispiel #11
0
def main():
    backend = CQCBackend()
    network = Network.get_instance()
    nodes = ["Alice", "Bob", "Eve", "Dean"]
    network.start(nodes, backend)
    network.delay = 0.7
    hosts = {'alice': Host('Alice', backend), 'bob': Host('Bob', backend)}

    # A <-> B
    hosts['alice'].add_connection('Bob')
    hosts['bob'].add_connection('Alice')

    hosts['alice'].start()
    hosts['bob'].start()

    for h in hosts.values():
        network.add_host(h)

    q_id = hosts['alice'].send_epr(hosts['bob'].host_id)
    q1 = hosts['alice'].shares_epr(hosts['bob'].host_id)
    i = 0
    while not q1 and i < MAX_WAIT:
        q1 = hosts['alice'].shares_epr(hosts['bob'].host_id)
        i += 1
        time.sleep(1)

    i = 0
    q2 = hosts['bob'].shares_epr(hosts['alice'].host_id)
    while not q2 and i < 5:
        q2 = hosts['bob'].shares_epr(hosts['alice'].host_id)
        i += 1
        time.sleep(1)

    assert hosts['alice'].shares_epr(hosts['bob'].host_id)
    assert hosts['bob'].shares_epr(hosts['alice'].host_id)
    q_alice = hosts['alice'].get_epr(hosts['bob'].host_id, q_id)
    q_bob = hosts['bob'].get_epr(hosts['alice'].host_id, q_id)
    assert q_alice is not None
    assert q_bob is not None
    assert q_alice.measure() == q_bob.measure()
    assert not hosts['alice'].shares_epr(hosts['bob'].host_id)
    assert not hosts['bob'].shares_epr(hosts['alice'].host_id)
    print("All tests succesfull!")
    network.stop(True)
    exit()
Beispiel #12
0
def main():
    backend = CQCBackend()
    network = Network.get_instance()
    nodes = ["Alice", "Bob", "Eve", "Dean"]
    network.start(nodes, backend)
    network.delay = 0.7

    hosts = {'alice': Host('Alice', backend), 'bob': Host('Bob', backend)}

    network.start(nodes, backend)
    network.packet_drop_rate = 0.75
    network.delay = 0

    hosts['alice'].add_connection('Bob')
    hosts['bob'].add_connection('Alice')

    hosts['alice'].start()
    hosts['bob'].start()

    for h in hosts.values():
        network.add_host(h)

    # ACKs for 1 hop take at most 2 seconds
    hosts['alice'].max_ack_wait = 3
    num_acks = 0
    num_messages = 15
    for _ in range(num_messages):
        ack = hosts['alice'].send_classical(hosts['bob'].host_id,
                                            'Hello Bob',
                                            await_ack=True)
        if ack:
            num_acks += 1

    num_messages_bob_received = len(hosts['bob'].classical)
    assert num_acks != num_messages
    assert num_acks < num_messages
    assert num_messages_bob_received < num_messages

    # ACKs can also get dropped
    assert num_messages_bob_received > num_acks
    assert float(num_acks) / num_messages < 0.9
    print("All tests succesfull!")
    network.stop(True)
    exit()
def main():
    backend = CQCBackend()
    network = Network.get_instance()
    nodes = ["Alice", "Bob", "Eve", "Dean"]
    network.start(nodes, backend)

    hosts = {'alice': Host('Alice', backend), 'bob': Host('Bob', backend)}

    network.delay = 0
    # A <-> B
    hosts['alice'].add_connection('Bob')
    hosts['bob'].add_connection('Alice')

    hosts['alice'].storage_epr_limit = 1
    hosts['bob'].storage_epr_limit = 1

    hosts['alice'].start()
    hosts['bob'].start()

    for h in hosts.values():
        network.add_host(h)

    hosts['alice'].max_ack_wait = 10

    hosts['alice'].send_epr(hosts['bob'].host_id, await_ack=True)
    hosts['alice'].send_epr(hosts['bob'].host_id, await_ack=True)

    assert hosts['alice'].shares_epr(hosts['bob'].host_id)
    print(hosts['alice'].get_epr_pairs(hosts['bob'].host_id))
    assert len(hosts['alice'].get_epr_pairs(hosts['bob'].host_id)) == 1
    assert hosts['bob'].shares_epr(hosts['alice'].host_id)
    assert len(hosts['bob'].get_epr_pairs(hosts['alice'].host_id)) == 1

    hosts['alice'].set_epr_memory_limit(2, hosts['bob'].host_id)
    hosts['bob'].set_epr_memory_limit(2)

    hosts['alice'].send_epr(hosts['bob'].host_id, await_ack=True)
    hosts['alice'].send_epr(hosts['bob'].host_id, await_ack=True)

    assert len(hosts['alice'].get_epr_pairs(hosts['bob'].host_id)) == 2
    assert len(hosts['bob'].get_epr_pairs(hosts['alice'].host_id)) == 2
    print("All tests succesfull!")
    network.stop(True)
    exit()
Beispiel #14
0
class Host:
    """ Host object acting as either a router node or an application host node. """

    WAIT_TIME = 10

    def __init__(self, host_id, backend=None):
        """
        Return the most important thing about a person.

        Args:
            host_id: The ID of the host
            backend: The backend to use for this host

        """
        self._host_id = host_id
        self._packet_queue = Queue()
        self._stop_thread = False
        self._queue_processor_thread = None
        self._data_qubit_store = QuantumStorage()
        self._EPR_store = QuantumStorage()
        self._classical_messages = ClassicalStorage()
        self._classical_connections = []
        self._quantum_connections = []
        if backend is None:
            self._backend = CQCBackend()
        else:
            self._backend = backend
        # add this host to the backend

        self._backend.add_host(self)
        self._max_ack_wait = None
        # Frequency of queue processing
        self._delay = 0.1
        self.logger = Logger.get_instance()
        # Packet sequence numbers per connection
        self._max_window = 10
        # [Queue, sender, seq_num, timeout, start_time]
        self._ack_receiver_queue = []
        # sender: host -> int
        self._seq_number_sender = {}
        # sender_ack: host->[received_list, low_number]
        self._seq_number_sender_ack = {}
        # receiver: host->[received_list, low_number]
        self._seq_number_receiver = {}
        self.qkd_keys = {}

    @property
    def host_id(self):
        """
        Get the *host_id* of the host.

        Returns:
            (string): The host ID of the host.
        """
        return self._host_id

    @property
    def backend(self):
        return self._backend

    @property
    def classical_connections(self):
        """
        Gets the classical connections of the host.

        Returns:
            classical connections
        """
        return self._classical_connections

    def get_connections(self):
        """
        Get a list of the connections with the types.

        Returns:

        """
        connection_list = []
        for c in self._classical_connections:
            connection_list.append({'type': 'classical', 'connection': c})
        for q in self._quantum_connections:
            connection_list.append({'type': 'quantum', 'connection': q})
        return connection_list

    @property
    def classical(self):
        """
        Gets the received classical messages sorted with the sequence number.

        Returns:
             Array: Sorted array of classical messages.
        """
        return sorted(self._classical_messages.get_all(),
                      key=lambda x: x.seq_num,
                      reverse=True)

    @property
    def EPR_store(self):
        return self._EPR_store

    @property
    def data_qubit_store(self):
        return self._data_qubit_store

    @property
    def delay(self):
        """
        Get the delay of the queue processor.

        Returns:
            The delay per tick for the queue processor.
        """
        return self._delay

    @delay.setter
    def delay(self, delay):
        """
        Set the delay of the queue processor.

        Args:
            delay (float): The delay per tick for the queue processor.

        """
        if not (isinstance(delay, int) or isinstance(delay, float)):
            raise Exception('delay should be a number')

        if delay < 0:
            raise Exception('Delay should not be negative')

        self._delay = delay

    @property
    def max_ack_wait(self):
        """
        Get the maximum amount of time to wait for an ACK

        Returns:
            (float): The maximum amount of time to wait for an ACK
        """
        return self._max_ack_wait

    @max_ack_wait.setter
    def max_ack_wait(self, max_ack_wait):
        """
        Set the maximum amount of time to wait for an ACK

        Args:
            max_ack_wait (float): The maximum amount of time to wait for an ACK
        """

        if not (isinstance(max_ack_wait, int)
                or isinstance(max_ack_wait, float)):
            raise Exception('max ack wait should be a number')

        if max_ack_wait < 0:
            raise Exception('max ack wait should be non-negative')

        self._max_ack_wait = max_ack_wait

    @property
    def storage_epr_limit(self):
        """
        Get the maximum number of qubits that can be held in EPR memory.

        Returns:
            (int): The maximum number of qubits that can be held in EPR memory.
        """
        return self._EPR_store.storage_limit

    @storage_epr_limit.setter
    def storage_epr_limit(self, storage_limit):
        """
        Set the maximum number of qubits that can be held in EPR memory.

        Args:
            storage_limit (int): The maximum number of qubits that can be held in EPR memory
        """

        if not isinstance(storage_limit, int):
            raise Exception('memory limit should be an integer')

        self._EPR_store.set_storage_limit(storage_limit, None)

    @property
    def storage_limit(self):
        """
        Get the maximum number of qubits that can be held in data qubit memory.

        Returns:
            (int): The maximum number of qubits that can be held in data qubit memory.
        """
        return self._data_qubit_store.storage_limit

    @storage_limit.setter
    def storage_limit(self, storage_limit):
        """
        Set the maximum number of qubits that can be held in data qubit memory.

        Args:
            storage_limit (int): The maximum number of qubits that can be held in data qubit memory
        """

        if not isinstance(storage_limit, int):
            raise Exception('memory limit should be an integer')

        self._data_qubit_store.set_storage_limit(storage_limit, None)

    @property
    def quantum_connections(self):
        """
        Get the quantum connections for the host.

        Returns:
            (list): The quantum connections for the host.
        """
        return self._quantum_connections

    def _get_sequence_number(self, host):
        """
        Get and set the next sequence number of connection with a receiver.

        Args:
            host(string): The ID of the receiver

        Returns:
            (int): The next sequence number of connection with a receiver.

        """
        if host not in self._seq_number_sender:
            self._seq_number_sender[host] = 0
        else:
            self._seq_number_sender[host] += 1
        return self._seq_number_sender[host]

    def get_sequence_number(self, host):
        """
        Get and set the next sequence number of connection with a receiver.

        Args:
            host(string): The ID of the receiver

        Returns:
            (int): The next sequence number of connection with a receiver.

        """
        if host not in self.seq_number:
            return 0

        return self.seq_number[host]

    def get_message_w_seq_num(self, sender_id, seq_num, wait=-1):
        """
        Get a message from a sender with a specific sequence number.
        Args:
            sender_id (str): The ID of the sender
            seq_num (int): The sequence number
            wait (int):

        Returns:

        """
        def _wait():
            nonlocal m
            nonlocal wait
            wait_start_time = time.time()
            while time.time() - wait_start_time < wait and m is None:
                filter_messages()

        def filter_messages():
            nonlocal m
            for message in self.classical:
                if message.sender == sender_id and message.seq_num == seq_num:
                    m = message

        m = None
        if wait > 0:
            DaemonThread(_wait).join()
            return m
        else:
            filter_messages()
            return m

    def _log_ack(self, protocol, receiver, seq):
        """
        Logs acknowledgement messages.
        Args:
            protocol (string): The protocol for the ACK
            receiver (string): The sender of the ACK
            seq (int): The sequence number of the packet
        """
        self.logger.log(self.host_id + ' awaits ' + protocol + ' ACK from ' +
                        receiver + ' with sequence ' + str(seq))

    def is_idle(self):
        """
        Returns if the host has packets to process or is idle.

        Returns:
            (boolean): If the host is idle or not.
        """
        return self._packet_queue.empty()

    def _process_packet(self, packet):
        """
        Processes the received packet.

        Args:
            packet (Packet): The received packet
        """
        def check_task(q, sender, seq_num, timeout, start_time):
            if timeout is not None and time.time() - timeout > start_time:
                q.put(False)
                return True
            if sender not in self._seq_number_sender_ack.keys():
                return False
            if seq_num < self._seq_number_sender_ack[sender][1]:
                q.put(True)
                return True
            if seq_num in self._seq_number_sender_ack[sender][0]:
                q.put(True)
                return True
            return False

        sender = packet.sender
        result = protocols.process(packet)
        if result is not None:
            msg = Message(sender, result['message'], result['sequence_number'])
            self._classical_messages.add_msg_to_storage(msg)
            if msg.content != protocols.ACK:
                self.logger.log(self.host_id + ' received ' +
                                str(result['message']) +
                                ' with sequence number ' +
                                str(result['sequence_number']))
            else:
                # Is ack msg
                sender = msg.sender
                if sender not in self._seq_number_sender_ack.keys():
                    self._seq_number_sender_ack[sender] = [[], 0]
                seq_num = msg.seq_num
                expected_seq = self._seq_number_sender_ack[sender][1]
                if seq_num == expected_seq:
                    self._seq_number_sender_ack[sender][1] += 1
                    expected_seq = self._seq_number_sender_ack[sender][1]
                    while len(self._seq_number_sender_ack[sender][0]) > 0 \
                            and expected_seq in self._seq_number_sender_ack[sender][0]:
                        self._seq_number_sender_ack[sender][0].remove(
                            expected_seq)
                        self._seq_number_sender_ack[sender][1] += 1
                        expected_seq += 1
                elif seq_num > expected_seq:
                    self._seq_number_sender_ack[sender][0].append(seq_num)
                else:
                    raise Exception("Should never happen!")
                for t in self._ack_receiver_queue:
                    res = check_task(*t)
                    if res is True:
                        self._ack_receiver_queue.remove(t)

    def _process_queue(self):
        """
        Runs a thread for processing the packets in the packet queue.
        """

        self.logger.log('Host ' + self.host_id + ' started processing')
        while True:
            if self._stop_thread:
                break

            time.sleep(self.delay)
            if not self._packet_queue.empty():
                packet = self._packet_queue.get()
                if not packet:
                    raise Exception('empty message')
                DaemonThread(self._process_packet, args=(packet, ))

    def rec_packet(self, packet):
        """
        Puts the packet into the packet queue of the host.

        Args:
            packet: Received packet.
        """
        self._packet_queue.put(packet)

    def add_c_connection(self, receiver_id):
        """
        Adds the classical connection to host with ID *receiver_id*.

        Args:
            receiver_id (string): The ID of the host to connect with.
        """
        self.classical_connections.append(receiver_id)

    def add_q_connection(self, receiver_id):
        """
        Adds the quantum connection to host with ID *receiver_id*.

        Args:
            receiver_id (string): The ID of the host to connect with.
        """
        self.quantum_connections.append(receiver_id)

    def add_connection(self, receiver_id):
        """
        Adds the classical and quantum connection to host with ID *receiver_id*.

        Args:
            receiver_id (string): The ID of the host to connect with.

        """
        self.classical_connections.append(receiver_id)
        self.quantum_connections.append(receiver_id)

    def send_ack(self, receiver, seq_number):
        """
        Sends the classical message to the receiver host with
        ID:receiver

        Args:
            receiver (string): The ID of the host to send the message.
            seq_number (int): Sequence number of the acknowleged packet.

        """
        packet = protocols.encode(sender=self.host_id,
                                  receiver=receiver,
                                  protocol=protocols.SEND_CLASSICAL,
                                  payload=protocols.ACK,
                                  payload_type=protocols.SIGNAL,
                                  sequence_num=seq_number,
                                  await_ack=False)
        if receiver not in self._seq_number_receiver:
            self._seq_number_receiver[receiver] = [[], 0]
        expected_seq = self._seq_number_receiver[receiver][1]
        if expected_seq + self._max_window < seq_number:
            raise Exception(
                "Message with seq number %d did not come before the receiver window closed!"
                % expected_seq)
        elif expected_seq < seq_number:
            self._seq_number_receiver[receiver][0].append(seq_number)
        else:
            self._seq_number_receiver[receiver][1] += 1
            expected_seq = self._seq_number_receiver[receiver][1]
            while len(self._seq_number_receiver[receiver][0]) > 0 and expected_seq in \
                    self._seq_number_receiver[receiver][0]:
                self._seq_number_receiver[receiver][0].remove(expected_seq)
                self._seq_number_receiver[receiver][1] += 1
                expected_seq += 1
        self._packet_queue.put(packet)

    def await_ack(self, sequence_number, sender):
        """
        Block until an ACK for packet with sequence number arrives.
        Args:
            sequence_number: The sequence number to wait for.
            sender: The sender of the ACK
        Returns:
            The status of the ACK
        """
        def wait():
            nonlocal did_ack
            start_time = time.time()
            q = Queue()
            task = (q, sender, sequence_number, self.max_ack_wait, start_time)
            self._ack_receiver_queue.append(task)
            res = q.get()
            did_ack = res
            return

        did_ack = False
        wait()
        return did_ack

    def send_classical(self, receiver_id, message, await_ack=False):
        """
        Sends the classical message to the receiver host with
        ID:receiver

        Args:
            receiver_id (string): The ID of the host to send the message.
            message (string): The classical message to send.
            await_ack (bool): If sender should wait for an ACK.
        Returns:
            boolean: If await_ack=True, return the status of the ACK
        """
        seq_num = self._get_sequence_number(receiver_id)
        packet = protocols.encode(sender=self.host_id,
                                  receiver=receiver_id,
                                  protocol=protocols.SEND_CLASSICAL,
                                  payload=message,
                                  payload_type=protocols.CLASSICAL,
                                  sequence_num=seq_num,
                                  await_ack=await_ack)
        self.logger.log(self.host_id + " sends CLASSICAL to " + receiver_id +
                        " with sequence " + str(seq_num))
        self._packet_queue.put(packet)

        if packet.await_ack:
            self._log_ack('classical', receiver_id, seq_num)
            return self.await_ack(packet.seq_num, receiver_id)

    def send_epr(self, receiver_id, q_id=None, await_ack=False, block=False):
        """
        Establish an EPR pair with the receiver and return the qubit
        ID of pair.

        Args:
            receiver_id (string): The receiver ID
            q_id (string): The ID of the qubit
            await_ack (bool): If sender should wait for an ACK.
            block (bool): If the created EPR pair should be blocked or not.
        Returns:
            string, boolean: If await_ack=True, return the ID of the EPR pair and the status of the ACK
        """
        if q_id is None:
            q_id = str(uuid.uuid4())

        seq_num = self._get_sequence_number(receiver_id)
        packet = protocols.encode(sender=self.host_id,
                                  receiver=receiver_id,
                                  protocol=protocols.SEND_EPR,
                                  payload={
                                      'q_id': q_id,
                                      'blocked': block
                                  },
                                  payload_type=protocols.SIGNAL,
                                  sequence_num=seq_num,
                                  await_ack=await_ack)
        self.logger.log(self.host_id + " sends EPR to " + receiver_id)
        self._packet_queue.put(packet)

        if packet.await_ack:
            self._log_ack('EPR', receiver_id, seq_num)
            return q_id, self.await_ack(seq_num, receiver_id)

        return q_id

    def send_teleport(self,
                      receiver_id,
                      q,
                      await_ack=False,
                      payload=None,
                      generate_epr_if_none=True):
        """
        Teleports the qubit *q* with the receiver with host ID *receiver*

        Args:
            receiver_id (string): The ID of the host to establish the EPR pair with
            q (Qubit): The qubit to teleport
            await_ack (bool): If sender should wait for an ACK.
            payload:
            generate_epr_if_none: Generate an EPR pair with receiver if one doesn't exist
        Returns:
            boolean: If await_ack=True, return the status of the ACK
        """
        packet = protocols.encode(
            sender=self.host_id,
            receiver=receiver_id,
            protocol=protocols.SEND_TELEPORT,
            payload={
                'q': q,
                'generate_epr_if_none': generate_epr_if_none
            },
            payload_type=protocols.CLASSICAL,
            sequence_num=self._get_sequence_number(receiver_id),
            await_ack=await_ack)
        if payload is not None:
            packet.payload = payload

        self.logger.log(self.host_id + " sends TELEPORT to " + receiver_id)
        self._packet_queue.put(packet)

        if packet.await_ack:
            self._log_ack('TELEPORT', receiver_id, packet.seq_num)
            return self.await_ack(packet.seq_num, receiver_id)

    def send_superdense(self, receiver_id, message, await_ack=False):
        """
        Send the two bit binary (i.e. '00', '01', '10', '11) message via superdense
        coding to the receiver with receiver ID *receiver_id*.

        Args:
            receiver_id (string): The receiver ID to send the message to
            message (string): The two bit binary message
            await_ack (bool): If sender should wait for an ACK.
        Returns:
           boolean: If await_ack=True, return the status of the ACK
        """
        packet = protocols.encode(
            sender=self.host_id,
            receiver=receiver_id,
            protocol=protocols.SEND_SUPERDENSE,
            payload=message,
            payload_type=protocols.CLASSICAL,
            sequence_num=self._get_sequence_number(receiver_id),
            await_ack=await_ack)
        self.logger.log(self.host_id + " sends SUPERDENSE to " + receiver_id)
        self._packet_queue.put(packet)

        if packet.await_ack:
            self._log_ack('SUPERDENSE', receiver_id, packet.seq_num)
            return self.await_ack(packet.seq_num, receiver_id)

    def send_qubit(self, receiver_id, q, await_ack=False):
        """
        Send the qubit *q* to the receiver with ID *receiver_id*.
        Args:
            receiver_id (string): The receiver ID to send the message to
            q (Qubit): The qubit to send
            await_ack (bool): If sender should wait for an ACK.
        Returns:
            string, boolean: If await_ack=True, return the ID of the qubit and the status of the ACK
        """
        q.set_blocked_state(True)
        q_id = q.id
        seq_num = self._get_sequence_number(receiver_id)
        packet = protocols.encode(sender=self.host_id,
                                  receiver=receiver_id,
                                  protocol=protocols.SEND_QUBIT,
                                  payload=q,
                                  payload_type=protocols.QUANTUM,
                                  sequence_num=seq_num,
                                  await_ack=await_ack)

        self.logger.log(self.host_id + " sends QUBIT to " + receiver_id)
        self._packet_queue.put(packet)

        if packet.await_ack:
            self._log_ack('SEND QUBIT', receiver_id, packet.seq_num)
            return q_id, self.await_ack(packet.seq_num, receiver_id)
        return q_id

    def shares_epr(self, receiver_id):
        """
        Returns boolean value dependent on if the host shares an EPR pair
        with receiver with ID *receiver_id*

        Args:
            receiver_id (string): The receiver ID to check.

        Returns:
             boolean: Whether the host shares an EPR pair with receiver with ID *receiver_id*
        """
        return self._EPR_store.check_qubit_from_host_exists(receiver_id)

    def receive_epr(self, receiver_id):
        pass

    def change_epr_qubit_id(self, host_id, new_id, old_id=None):
        """
        Change an EPR pair ID to another. If *old_id* is set, then change that specific
        EPR half, otherwise change the first unblocked EPR half to the *new_id*.
        Args:
            host_id (string): The partner ID of the EPR pair.
            new_id (string): The new ID to change the qubit too
            old_id (string):  The old ID of the qubit

        Returns:
            Old if of the qubit which has been changed.
        """
        return self._EPR_store.change_qubit_id(host_id, new_id, old_id)

    def get_epr_pairs(self, host_id):
        """
        Return the dictionary of EPR pairs stored, just for the information regarding which qubits are stored.
        Does not remove the qubits from storage like *get_epr_pair* does.

        Args:
            host_id (optional): If set,

        Returns:
            dict: If *host_id* is not set, then return the entire dictionary of EPR pairs.
                  Else If *host_id* is set, then return the EPRs for that particular host if there are any.
                  Return an empty list otherwise.
        """
        if host_id is None:
            raise ValueError("Host id has to be specified!")
        return self._EPR_store.get_all_qubits_from_host(host_id)

    def get_data_qubits(self, host_id):
        """
        Return the dictionary of data qubits stored, just for the information regarding which qubits are stored.
        Does not remove the qubits from storage like *get_data_qubit* does.

        Args:
            host_id (int): The host id from which the data qubit have been received.

        Returns:
            dict: If *host_id* is not set, then return the entire dictionary of data qubits.
                  Else If *host_id* is set, then return the data qubits for that particular host if there are any.
                  Return an empty list otherwise.
        """
        return self._data_qubit_store.get_all_qubits_from_host(host_id)

    def set_epr_memory_limit(self, limit, partner_id=None):
        """
        Set the limit to how many EPR pair halves can be stored from partner_id, or if partner_id is not set,
        use the limit for all connections.

        Args:
            limit (int): The maximum number of qubits for the memory
            partner_id (str): (optional) The partner ID to set the limit with
        """
        self._EPR_store.set_storage_limit(limit, partner_id)

    def set_data_qubit_memory_limit(self, limit, partner_id=None):
        """
        Set the limit to how many data qubits can be stored from partner_id, or if partner_id is not set,
        use the limit for all connections.

        Args:
            limit (int): The maximum number of qubits for the memory
            partner_id (str): (optional) The partner ID to set the limit with
        """
        self._data_qubit_store.set_storage_limit(limit, partner_id)

    def add_epr(self, partner_id, qubit, q_id=None, blocked=False):
        """
        Adds the EPR to the EPR store of a host. If the EPR has an ID, adds the EPR with it,
        otherwise generates an ID for the EPR and adds the qubit with that ID.

        Args:
            partner_id (String): The ID of the host to pair the qubit
            qubit(Qubit): The data Qubit to be added.
            q_id(string): The ID of the qubit to be added.
            blocked: If the qubit should be stored as blocked or not
        Returns:
             (string) *q_id*: The qubit ID
        """
        if q_id is not None:
            qubit.set_new_id(q_id)
        qubit.set_blocked_state(blocked)
        self._EPR_store.add_qubit_from_host(qubit, partner_id)
        return qubit.id

    def add_data_qubit(self, partner_id, qubit, q_id=None):
        """
        Adds the data qubit to the data qubit store of a host. If the qubit has an ID, adds the qubit with it,
        otherwise generates an ID for the qubit and adds the qubit with that ID.

        Args:
            partner_id: The ID of the host to pair the qubit
            qubit (Qubit): The data Qubit to be added.
        Returns:
            (string) *q_id*: The qubit ID
        """
        if q_id is not None:
            qubit.set_new_id(q_id)

        self._data_qubit_store.add_qubit_from_host(qubit, partner_id)
        return qubit.id

    def add_checksum(self, qubits, size_per_qubit=2):
        """
        Generate a set of qubits that represent a quantum checksum for the set of qubits *qubits*
        Args:
            qubits: The set of qubits to encode
            size_per_qubit (int): The size of the checksum per qubit (i.e. 1 qubit encoded into *size*)

        Returns:
            list: A list of qubits that are encoded for *qubits*
        """
        i = 0
        check_qubits = []
        while i < len(qubits):
            check = Qubit(self.host_id)
            j = 0
            while j < size_per_qubit:
                qubits[i + j].cnot(check)
                j += 1

            check_qubits.append(check)
            i += size_per_qubit
        return check_qubits

    def get_classical(self, partner_id, wait=-1):
        """
        Get the classical messages from partner host *partner_id*.

        Args:
            partner_id (string): The ID of the partner who sent the clasical messages
            wait (float): How long in seconds to wait for the messages if none are set.

        Returns:
            A list of classical messages from Host with ID *partner_id*.
        """
        if not isinstance(wait, float) and not isinstance(wait, int):
            raise Exception('wait parameter should be a number')

        def process_messages():
            nonlocal cla
            cla = self._classical_messages.get_all_from_sender(partner_id)

        def _wait():
            nonlocal cla
            nonlocal wait
            wait_start_time = time.time()
            while time.time() - wait_start_time < wait and len(cla) == 0:
                process_messages()
            return cla

        if wait > 0:
            cla = []
            DaemonThread(_wait).join()
            return sorted(cla, key=lambda x: x.seq_num, reverse=True)
        else:
            cla = []
            process_messages()
            return sorted(cla, key=lambda x: x.seq_num, reverse=True)

    def get_epr(self, partner_id, q_id=None, wait=-1):
        """
        Gets the EPR that is entangled with another host in the network. If qubit ID is specified,
        EPR with that ID is returned, else, the last EPR added is returned.

        Args:
            partner_id (string): The ID of the host that returned EPR is entangled to.
            q_id (string): The qubit ID of the EPR to get.
            wait (float): the amount of time to wait
        Returns:
             Qubit: Qubit shared with the host with *partner_id* and *q_id*.
        """
        if not isinstance(wait, float) and not isinstance(wait, int):
            raise Exception('wait parameter should be a number')

        def _wait():
            nonlocal q
            nonlocal wait
            wait_start_time = time.time()
            while time.time() - wait_start_time < wait and q is None:
                q = _get_qubit(self._EPR_store, partner_id, q_id)
            return q

        if wait > 0:
            q = None
            DaemonThread(_wait).join()
            return q
        else:
            return _get_qubit(self._EPR_store, partner_id, q_id)

    def get_data_qubit(self, partner_id, q_id=None, wait=-1):
        """
        Gets the data qubit received from another host in the network. If qubit ID is specified,
        qubit with that ID is returned, else, the last qubit received is returned.

        Args:
            partner_id (string): The ID of the host that data qubit to be returned is received from.
            q_id (string): The qubit ID of the data qubit to get.
            wait (float): The amount of time to wait for the a qubit to arrive
        Returns:
             Qubit: Qubit received from the host with *partner_id* and *q_id*.
        """
        if not isinstance(wait, float) and not isinstance(wait, int):
            raise Exception('wait parameter should be a number')

        def _wait():
            nonlocal q
            nonlocal wait
            wait_start_time = time.time()
            while time.time() - wait_start_time < wait and q is None:
                q = _get_qubit(self._data_qubit_store, partner_id, q_id)
            return q

        if wait > 0:
            q = None
            DaemonThread(_wait).join()
            return q
        else:
            return _get_qubit(self._data_qubit_store, partner_id, q_id)

    def stop(self, release_qubits=True):
        """
        Stops the host. If release_qubit is true, clear the quantum memories.

        Args:
            release_qubits (boolean): If release_qubit is true, clear the quantum memories.
        """
        self.logger.log('Host ' + self.host_id + " stopped")
        if release_qubits:
            self._data_qubit_store.release_storage()
            self._EPR_store.release_storage()

        self._backend.stop()
        self._stop_thread = True

    def start(self):
        """
        Starts the host.
        """
        self._queue_processor_thread = DaemonThread(target=self._process_queue)

    def run_protocol(self, protocol, arguments=(), blocking=False):
        """
        Run the protocol *protocol*.
        Args:
            protocol (function): The protocol that the host should run.
            arguments (tuple): The set of (ordered) arguments for the protocol
            blocking (bool): Wait for thread to stop before proceeding
        """
        arguments = (self, ) + arguments
        if blocking:
            DaemonThread(protocol, args=arguments).join()
        else:
            DaemonThread(protocol, args=arguments)

    def get_next_classical_message(self, receive_from_id, buffer, sequence_nr):
        """

        Args:
            receive_from_id:
            buffer:
            sequence_nr:

        Returns:

        """
        buffer = buffer + self.get_classical(receive_from_id,
                                             wait=Host.WAIT_TIME)
        msg = "ACK"
        while msg == "ACK" or (msg.split(':')[0] != ("%d" % sequence_nr)):
            if len(buffer) == 0:
                buffer = buffer + self.get_classical(receive_from_id,
                                                     wait=Host.WAIT_TIME)
            ele = buffer.pop(0)
            msg = ele.content
        return msg

    def send_key(self, receiver_id, key_size, await_ack=True):
        """

        Args:
            receiver_id:
            key_size:
            await_ack:

        Returns:
            bool
        """

        seq_num = self._get_sequence_number(receiver_host.host_id)
        packet = protocols.encode(sender=self.host_id,
                                  receiver=receiver_id,
                                  protocol=protocols.SEND_KEY,
                                  payload={'keysize': key_size},
                                  payload_type=protocols.CLASSICAL,
                                  sequence_num=seq_num,
                                  await_ack=await_ack)
        self.logger.log(self.host_id + " sends KEY to " + receiver_id)
        self._packet_queue.put(packet)

        if packet.await_ack:
            self._log_ack('EPR', receiver_id, seq_num)
            return self.await_ack(seq_num, receiver_id)
Beispiel #15
0
def main():
    global thread_1_return
    global thread_2_return

    network = Network.get_instance()
    nodes = ["Alice", "Bob", "Eve", "Dean"]
    back = CQCBackend()
    network.start(nodes, back)
    network.delay = 0.0

    host_alice = Host('Alice', back)
    host_alice.add_connection('Bob')
    host_alice.max_ack_wait = 30
    host_alice.delay = 0.0
    host_alice.start()

    host_bob = Host('Bob', back)
    host_bob.max_ack_wait = 30
    host_bob.delay = 0.0
    host_bob.add_connection('Alice')
    host_bob.add_connection('Eve')
    host_bob.start()

    host_eve = Host('Eve', back)
    host_eve.max_ack_wait = 30
    host_eve.delay = 0.0
    host_eve.add_connection('Bob')
    host_eve.add_connection('Dean')
    host_eve.start()

    host_dean = Host('Dean', back)
    host_dean.max_ack_wait = 30
    host_dean.delay = 0.0
    host_dean.add_connection('Eve')
    host_dean.start()

    network.add_host(host_alice)
    network.add_host(host_bob)
    network.add_host(host_eve)
    network.add_host(host_dean)

    network.x_error_rate = 0
    network.packet_drop_rate = 0

    q_size = 6
    checksum_per_qubit = 2

    host_alice.run_protocol(qtcp_sender,
                            (q_size, host_dean.host_id, checksum_per_qubit))
    host_dean.run_protocol(qtcp_receiver,
                           (q_size, host_alice.host_id, checksum_per_qubit))

    while thread_1_return is None or thread_2_return is None:
        if thread_1_return is False or thread_2_return is False:
            print('TCP Connection not successful : EXITING')
            sys.exit(1)
        pass

    start_time = time.time()
    while time.time() - start_time < 150:
        pass

    network.stop(stop_hosts=True)
    exit()
Beispiel #16
0
def main():
    print("Test maximum data qubit has been skipped.")
    return
    backend = CQCBackend()
    network = Network.get_instance()
    nodes = ["Alice", "Bob", "Eve", "Dean"]
    network.start(nodes, backend)
    network.delay = 0.7

    hosts = {'alice': Host('Alice', backend), 'bob': Host('Bob', backend)}

    network.delay = 0
    # A <-> B
    hosts['alice'].add_connection('Bob')
    hosts['bob'].add_connection('Alice')

    hosts['alice'].memory_limit = 1
    hosts['bob'].memory_limit = 1

    hosts['alice'].start()
    hosts['bob'].start()

    for h in hosts.values():
        network.add_host(h)

    q_alice_id_1 = hosts['alice'].send_qubit(hosts['bob'].host_id,
                                             Qubit(hosts['alice']))
    time.sleep(2)
    q_alice_id_2 = hosts['alice'].send_qubit(hosts['bob'].host_id,
                                             Qubit(hosts['alice']))
    time.sleep(2)

    q_bob_id_1 = hosts['bob'].send_qubit(hosts['alice'].host_id,
                                         Qubit(hosts['bob']))
    time.sleep(2)
    q_bob_id_2 = hosts['bob'].send_qubit(hosts['alice'].host_id,
                                         Qubit(hosts['bob']))
    time.sleep(2)

    # Allow the network to process the requests
    # TODO: remove the need for this
    time.sleep(2)

    i = 0
    while len(hosts['alice'].get_data_qubits(
            hosts['bob'].host_id)) < 1 and i < 5:
        time.sleep(1)
        i += 1

    i = 0
    while len(hosts['bob'].get_data_qubits(
            hosts['alice'].host_id)) < 1 and i < 5:
        time.sleep(1)
        i += 1

    assert len(hosts['alice'].get_data_qubits(hosts['bob'].host_id)) == 1
    assert hosts['alice'].get_data_qubit(hosts['bob'].host_id,
                                         q_bob_id_1).measure() == 0
    assert hosts['alice'].get_data_qubit(hosts['bob'].host_id,
                                         q_bob_id_2) == None
    assert len(hosts['bob'].get_data_qubits(hosts['alice'].host_id)) == 1
    assert hosts['bob'].get_data_qubit(hosts['alice'].host_id,
                                       q_alice_id_1).measure() == 0
    assert hosts['bob'].get_data_qubit(hosts['alice'].host_id,
                                       q_alice_id_2) == None

    hosts['alice'].set_data_qubit_memory_limit(2, hosts['bob'].host_id)
    hosts['bob'].set_data_qubit_memory_limit(2)

    q_alice_id_1 = hosts['alice'].send_qubit(hosts['bob'].host_id,
                                             Qubit(hosts['alice']))
    time.sleep(2)
    q_alice_id_2 = hosts['alice'].send_qubit(hosts['bob'].host_id,
                                             Qubit(hosts['alice']))
    time.sleep(2)
    q_alice_id_3 = hosts['alice'].send_qubit(hosts['bob'].host_id,
                                             Qubit(hosts['alice']))
    time.sleep(2)

    q_bob_id_1 = hosts['bob'].send_qubit(hosts['alice'].host_id,
                                         Qubit(hosts['bob']))
    time.sleep(2)
    q_bob_id_2 = hosts['bob'].send_qubit(hosts['alice'].host_id,
                                         Qubit(hosts['bob']))
    time.sleep(2)
    q_bob_id_3 = hosts['bob'].send_qubit(hosts['alice'].host_id,
                                         Qubit(hosts['bob']))
    time.sleep(2)

    # Allow the network to process the requests
    time.sleep(3)

    i = 0
    while len(hosts['alice'].get_data_qubits(
            hosts['bob'].host_id)) < 2 and i < 5:
        time.sleep(1)
        i += 1

    i = 0
    while len(hosts['bob'].get_data_qubits(
            hosts['alice'].host_id)) < 2 and i < 5:
        time.sleep(1)
        i += 1

    assert len(hosts['alice'].get_data_qubits(hosts['bob'].host_id)) == 2
    assert hosts['alice'].get_data_qubit(hosts['bob'].host_id,
                                         q_bob_id_1).measure() == 0
    assert hosts['alice'].get_data_qubit(hosts['bob'].host_id,
                                         q_bob_id_2).measure() == 0
    assert hosts['alice'].get_data_qubit(hosts['bob'].host_id,
                                         q_bob_id_3) == None

    assert len(hosts['bob'].get_data_qubits(hosts['alice'].host_id)) == 2
    assert hosts['bob'].get_data_qubit(hosts['alice'].host_id,
                                       q_alice_id_1).measure() == 0
    assert hosts['bob'].get_data_qubit(hosts['alice'].host_id,
                                       q_alice_id_2).measure() == 0
    assert hosts['bob'].get_data_qubit(hosts['alice'].host_id,
                                       q_alice_id_3) == None
    print("All tests succesfull!")
    network.stop(True)
    exit()
Beispiel #17
0
class Network:
    """ A network control singleton object. """
    __instance = None

    @staticmethod
    def get_instance():
        if Network.__instance is None:
            Network()
        return Network.__instance

    def __init__(self):
        if Network.__instance is None:
            self.ARP = {}
            # The directed graph for the connections
            self.classical_network = nx.DiGraph()
            self.quantum_network = nx.DiGraph()
            self._quantum_routing_algo = nx.shortest_path
            self._classical_routing_algo = nx.shortest_path
            self._use_hop_by_hop = True
            self._packet_queue = Queue()
            self._stop_thread = False
            self._queue_processor_thread = None
            self._delay = 0.2
            self._packet_drop_rate = 0
            self._x_error_rate = 0
            self._z_error_rate = 0
            self._backend = None
            Network.__instance = self
        else:
            raise Exception('this is a singleton class')

    @property
    def use_hop_by_hop(self):
        """
        Get the routing style for the network.

        Returns:
            If the network uses hop by hop routing.
        """
        return self._use_hop_by_hop

    @use_hop_by_hop.setter
    def use_hop_by_hop(self, should_use):
        """
        Set the routing style for the network.
        Args:
            should_use (bool): If the network should use hop by hop routing or not
        """
        if not isinstance(should_use, bool):
            raise Exception('use_hop_by_hop should be a boolean value.')

        self._use_hop_by_hop = should_use

    @property
    def classical_routing_algo(self):
        """
        Get the routing algorithm for the network.
        """
        return self._classical_routing_algo

    @classical_routing_algo.setter
    def classical_routing_algo(self, algorithm):
        """
        Set the routing algorithm for the network.

        Args:
             algorithm (function): The routing function. Should return a list of host_ids which represents the route
        """
        self._classical_routing_algo = algorithm

    @property
    def quantum_routing_algo(self):
        """
        Gets the quantum routing algorithm of the network.

        Returns:
           algorithm (function): The quantum routing algorithm of the network
        """
        return self._quantum_routing_algo

    @quantum_routing_algo.setter
    def quantum_routing_algo(self, algorithm):
        """
        Sets the quantum routing algorithm of the network.

        Args:
            algorithm (function): The routing algorithm of the network. Should take an input and an output
        """
        if not callable(algorithm):
            raise Exception("The quantum routing algorithm must be a function.")

        num_algo_params = len(signature(algorithm).parameters)
        if num_algo_params != 3:
            raise Exception("The quantum routing algorithm function should take three parameters: " +
                            "the (nx) graph representation of the network, the sender address and the " +
                            "receiver address.")

        self._quantum_routing_algo = algorithm

    def set_routing_algo(self, algorithm):
        """
        Set the routing algorithm for the network.

        Args:
             algorithm (function): The routing function. Should return a list of host_ids which represents the route
        """
        self._classical_routing_algo = algorithm

    @property
    def delay(self):
        """
        Get the delay interval of the network.
        """
        return self._delay

    @delay.setter
    def delay(self, delay):
        """
        Set the delay interval of the network.

        Args:
             delay (float): Delay in network tick in seconds
        """
        if not (isinstance(delay, int) or isinstance(delay, float)):
            raise Exception('delay should be a number')

        if delay < 0:
            raise Exception('Delay should not be negative')

        self._delay = delay

    @property
    def packet_drop_rate(self):
        """
        Get the drop rate of the network.
        """
        return self._packet_drop_rate

    @packet_drop_rate.setter
    def packet_drop_rate(self, drop_rate):
        """
        Set the drop rate of the network.

        Args:
             drop_rate (float): Probability of dropping a packet in the network
        """

        if drop_rate < 0 or drop_rate > 1:
            raise Exception('Packet drop rate should be between 0 and 1')

        if not (isinstance(drop_rate, int) or isinstance(drop_rate, float)):
            raise Exception('Packet drop rate should be a number')

        self._packet_drop_rate = drop_rate

    @property
    def x_error_rate(self):
        """
        Get the X error rate of the network.
        Returns:
              The *x_error_rate* of the network.
        """

        return self._x_error_rate

    @x_error_rate.setter
    def x_error_rate(self, error_rate):
        """
        Set the X error rate of the network.

        Args:
             error_rate (float): Probability of a X error during a qubit transmission in the network
        """

        if error_rate < 0 or error_rate > 1:
            raise Exception('Error rate should be between 0 and 1.')

        if not (isinstance(error_rate, int) or isinstance(error_rate, float)):
            raise Exception('X error rate should be a number')

        self._x_error_rate = error_rate

    @property
    def z_error_rate(self):
        """
        Get the Z error rate of the network.

        Returns:
            (float): The Z error rate of the network.
        """
        return self._z_error_rate

    @z_error_rate.setter
    def z_error_rate(self, error_rate):
        """
        Set the Z error rate of the network.

        Args:
             error_rate (float): Probability of a Z error during a qubit transmission in the network
        """

        if error_rate < 0 or error_rate > 1:
            raise Exception('Error rate should be between 0 and 1.')

        if not (isinstance(error_rate, int) or isinstance(error_rate, float)):
            raise Exception('Z error rate should be a number')

        self._z_error_rate = error_rate

    def add_host(self, host):
        """
        Adds the *host* to ARP table and updates the network graph.

        Args:
            host (Host): The host to be added to the network.
        """

        Logger.get_instance().debug('host added: ' + host.host_id)
        self.ARP[host.host_id] = host
        self._update_network_graph(host)

    def remove_host(self, host):
        """
        Removes the host from the ARP table.

        Args:
            host (Host): The host to be removed from the network.
        """

        if host.host_id in self.ARP:
            del self.ARP[host.host_id]

    def _remove_network_node(self, host):
        """
        Removes the host from the ARP table.

        Args:
            host (Host): The host to be removed from the network.
        """

        try:
            self.classical_network.remove_node(host.host_id)
        except nx.NetworkXError:
            Logger.get_instance().error('attempted to remove a non-exiting node from network')

    def _update_network_graph(self, host):
        """
        Add host *host* to the network and update the graph representation of the network

        Args:
            host: The host to be added
        """
        self.classical_network.add_node(host.host_id)
        self.quantum_network.add_node(host.host_id)

        for connection in host.classical_connections:
            if not self.classical_network.has_edge(host.host_id, connection):
                edge = (host.host_id, connection, {'weight': 1})
                self.classical_network.add_edges_from([edge])

        for connection in host.quantum_connections:
            if not self.quantum_network.has_edge(host.host_id, connection):
                edge = (host.host_id, connection, {'weight': 1})
                self.quantum_network.add_edges_from([edge])

    def shares_epr(self, sender, receiver):
        """
        Returns boolean value dependent on if the sender and receiver share an EPR pair.

        Args:
            receiver (Host): The receiver
            sender (Host): The sender

        Returns:
             boolean: whether the sender and receiver share an EPR pair.
        """
        host_sender = self.get_host(sender)
        host_receiver = self.get_host(receiver)
        return host_sender.shares_epr(receiver) and host_receiver.shares_epr(sender)

    def get_host(self, host_id):
        """
        Returns the host with the *host_id*.

        Args:
            host_id (string): ID number of the host that is returned.

        Returns:
             Host: Host with the *host_id*
        """
        if host_id not in self.ARP:
            return None
        return self.ARP[host_id]

    def get_ARP(self):
        """
        Returns the ARP table.

        Returns:
             dict: The ARP table
        """
        return self.ARP

    def get_host_name(self, host_id):
        """
        Args:
            host_id (string): ID number of the host whose name is returned if it is in ARP table

        Returns the name of the host with *host_id* if the host is in ARP table , otherwise returns None.

        Returns:
             dict or None: Name of the host
        """
        if host_id not in self.ARP:
            return None
        return self.ARP[host_id].cqc.name

    def get_quantum_route(self, source, dest):
        """
        Gets the route for quantum information from source to destination.

        Args:
            source (string): ID of the source host
            dest (string): ID of the destination host
        Returns:
            route (list): An ordered list of ID numbers on the shortest path from source to destination.
        """
        return self.quantum_routing_algo(self.quantum_network, source, dest)

    def get_classical_route(self, source, dest):
        """
        Gets the route for classical information from source to destination.

        Args:
            source (string): ID of the source host
            dest (string): ID of the destination host

        Returns:
            route (list): An ordered list of ID numbers on the shortest path from source to destination.
        """
        return self.classical_routing_algo(self.classical_network, source, dest)

    def _entanglement_swap(self, sender, receiver, route, q_id, o_seq_num, blocked):
        """
        Performs a chain of entanglement swaps with the hosts between sender and receiver to create a shared EPR pair
        between sender and receiver.

        Args:
            sender (Host): Sender of the EPR pair
            receiver (Host): Receiver of the EPR pair
            route (list): Route between the sender and receiver
            q_id (string): Qubit ID of the sent EPR pair
            blocked (bool): If the pair being distributed is blocked or not
        """
        host_sender = self.get_host(sender)
        # TODO: Multiprocess this
        # Create EPR pairs on the route, where all EPR qubits have the id q_id
        for i in range(len(route) - 1):
            if not self.shares_epr(route[i], route[i + 1]):
                self.get_host(route[i]).send_epr(route[i + 1], q_id, await_ack=True)
            else:
                old_id = self.get_host(route[i]).change_epr_qubit_id(route[i + 1], q_id)
                self.get_host(route[i + 1]).change_epr_qubit_id(route[i], q_id, old_id)

        for i in range(len(route) - 2):
            host = self.get_host(route[i + 1])
            q = host.get_epr(route[0], q_id, wait=10)
            if q is None:
                print("Host is %s" % host.host_id)
                print("Search host is %s" % route[0])
                print("Search id is %s" % q_id)
                print("EPR storage is")
                print(host._EPR_store)
                Logger.get_instance().error('Entanglement swap failed')
                return
            data = {'q': q,
                    'q_id': q_id,
                    'node': sender,
                    'o_seq_num': o_seq_num,
                    'type': protocols.EPR}

            if route[i + 2] == route[-1]:
                data = {'q': q,
                        'q_id': q_id,
                        'node': sender,
                        'ack': True,
                        'o_seq_num': o_seq_num,
                        'type': protocols.EPR}

            host.send_teleport(route[i + 2], None, await_ack=True, payload=data, generate_epr_if_none=False)

        # Change in the storage that the EPR qubit is shared with the receiver
        q2 = host_sender.get_epr(route[1], q_id=q_id)
        host_sender.add_epr(receiver, q2, q_id, blocked)
        Logger.get_instance().log('Entanglement swap was successful for pair with id '
                                  + q_id + ' between ' + sender + ' and ' + receiver)

    def _route_quantum_info(self, sender, receiver, qubits):
        """
        Routes qubits from sender to receiver.

        Args:
            sender (Host): Sender of qubits
            receiver (Host): Receiver qubits
            qubits (List of Qubits): The qubits to be sent
        """

        def transfer_qubits(r, store=False, original_sender=None):
            for q in qubits:
                Logger.get_instance().log('transfer qubits - sending qubit ' + q.id)
                x_err_var = random.random()
                z_err_var = random.random()
                if x_err_var > (1 - self.x_error_rate):
                    q.X()
                if z_err_var > (1 - self.z_error_rate):
                    q.Z()

                q.send_to(self.ARP[r].host_id)
                Logger.get_instance().log('transfer qubits - received ' + q.id)

                # Unblock qubits in case they were blocked
                q.set_blocked_state(False)

                if store and original_sender is not None:
                    self.ARP[r].add_data_qubit(original_sender, q)

        route = self.get_quantum_route(sender, receiver)
        i = 0
        while i < len(route) - 1:
            Logger.get_instance().log('sending qubits from ' + route[i] + ' to ' + route[i + 1])
            if len(route[i:]) != 2:
                transfer_qubits(route[i + 1])
            else:
                transfer_qubits(route[i + 1], store=True, original_sender=route[0])
            i += 1

    def _process_queue(self):
        """
        Runs a thread for processing the packets in the packet queue.
        """

        while True:
            if self._stop_thread:
                break

            if not self._packet_queue.empty():
                # Artificially delay the network
                if self.delay > 0:
                    time.sleep(self.delay)

                packet = self._packet_queue.get()

                # Simulate packet loss
                packet_drop_var = random.random()
                if packet_drop_var > (1 - self.packet_drop_rate):
                    Logger.get_instance().log("PACKET DROPPED")
                    continue

                sender, receiver = packet.sender, packet.receiver

                if packet.payload_type == protocols.QUANTUM:
                    self._route_quantum_info(sender, receiver, [packet.payload])

                try:
                    if packet.protocol == protocols.RELAY and not self.use_hop_by_hop:
                        full_route = packet.route
                        route = full_route[full_route.index(sender):]
                    else:
                        if packet.protocol == protocols.REC_EPR:
                            route = self.get_quantum_route(sender, receiver)
                        else:
                            route = self.get_classical_route(sender, receiver)

                    if len(route) < 2:
                        raise Exception('No route exists')

                    elif len(route) == 2:
                        if packet.protocol != protocols.RELAY:
                            if packet.protocol == protocols.REC_EPR:
                                host_sender = self.get_host(sender)
                                q = host_sender \
                                    .backend \
                                    .create_EPR(host_sender.host_id,
                                                receiver,
                                                q_id=packet.payload['q_id'],
                                                block=packet.payload['blocked'])
                                host_sender.add_epr(receiver, q)
                            self.ARP[receiver].rec_packet(packet)
                        else:
                            self.ARP[receiver].rec_packet(packet.payload)
                    else:
                        if packet.protocol == protocols.REC_EPR:
                            q_id = packet.payload['q_id']
                            blocked = packet.payload['blocked']
                            q_route = self.get_quantum_route(sender, receiver)
                            DaemonThread(self._entanglement_swap,
                                         args=(sender, receiver, q_route, q_id,
                                               packet.seq_num, blocked))
                        else:
                            network_packet = self._encode(route, packet)
                            self.ARP[route[1]].rec_packet(network_packet)

                except nx.NodeNotFound:
                    Logger.get_instance().error("route couldn't be calculated, node doesn't exist")
                except ValueError:
                    Logger.get_instance().error("route couldn't be calculated, value error")
                except Exception as e:
                    Logger.get_instance().error('Error in network: ' + str(e))

    def send(self, packet):
        """
        Puts the packet to the packet queue of the network.

        Args:
            packet (Packet): Packet to be sent
        """

        self._packet_queue.put(packet)

    def stop(self, stop_hosts=False):
        """
        Stops the network.
        """

        Logger.get_instance().log("Network stopped")
        if stop_hosts:
            for host in self.ARP:
                self.ARP[host].stop(release_qubits=True)

        self._stop_thread = True
        if self._backend is not None:
            self._backend.stop()

    def start(self, nodes=None, backend=None):
        """
        Starts the network.

        """
        if backend is None:
            self._backend = CQCBackend()
        else:
            self._backend = backend
        if nodes is not None:
            self._backend.start(nodes=nodes)
        self._queue_processor_thread = DaemonThread(target=self._process_queue)

    def draw_network(self):
        """
        Draws a plot of the network.
        """
        nx.draw_networkx(self.classical_network, pos=nx.spring_layout(self.classical_network),
                         with_labels=True, hold=False)
        plt.show()

    def _encode(self, route, payload, ttl=10):
        """
        Adds another layer to the packet if route length between sender and receiver is greater than 2. Sets the
        protocol flag in this layer to RELAY and payload_type as SIGNAL and adds a variable
        Time-To-Live information in this layer.

        Args:
            route: route of the packet from sender to receiver
            payload (Object): Lower layers of the packet
            ttl(int): Time-to-Live parameter

        Returns:
            dict: Encoded RELAY packet
        """
        if payload.protocol != protocols.RELAY:
            packet = RoutingPacket(route[1], '', protocols.RELAY, protocols.SIGNAL,
                                   payload, ttl, route)
        else:
            packet = payload
            packet.sender = route[1]

        if self.use_hop_by_hop:
            packet.receiver = route[-1]
        else:
            packet.receiver = route[2]

        return packet