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 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()
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)
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()
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()
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 = {}
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()
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()
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()
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)
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()
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()
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