def start(self, nodes=None, backend=None): """ Starts the network. """ if backend is None: self._backend = EQSNBackend() 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 = EQSNBackend() 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) 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(): # Initialize a network network = Network.get_instance() backend = EQSNBackend() # Define the host IDs in the network nodes = ['Alice', 'Bob'] network.delay = 0.0 # Start the network with the defined hosts network.start(nodes, backend) # Initialize the host Alice host_alice = Host('Alice', backend) # Add a one-way connection (classical and quantum) to Bob host_alice.add_connection('Bob') host_alice.delay = 0.0 # Start listening host_alice.start() host_bob = Host('Bob', backend) # Bob adds his own one-way connection to Alice host_bob.add_connection('Alice') host_bob.delay = 0.0 host_bob.start() # Add the hosts to the network # The network is: Alice <--> Bob network.add_host(host_alice) network.add_host(host_bob) # Generate random key key_size = 20 # the size of the key in bit secret_key = np.random.randint(2, size=key_size) # Concatentate functions def alice_func(alice): msg_buff = [] alice_qkd(alice, msg_buff, secret_key, host_bob.host_id) alice_send_message(alice, secret_key, host_bob.host_id) def bob_func(eve): msg_buff = [] eve_key = eve_qkd(eve, msg_buff, key_size, host_alice.host_id) eve_receive_message(eve, msg_buff, eve_key, host_alice.host_id) # Run Bob and Alice host_alice.run_protocol(alice_func, ()) host_bob.run_protocol(bob_func, (), blocking=True) time.sleep(1) network.stop(True)
def setUpClass(cls): global network global hosts nodes = ["Alice", "Bob"] backend = EQSNBackend() network.start(nodes=nodes, backend=backend) hosts = {'alice': Host('Alice', backend), 'bob': Host('Bob', backend)} 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)
def main(): backend = EQSNBackend() 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) # send messages to Bob without waiting for ACKs hosts['alice'].send_classical(hosts['bob'].host_id, 'hello bob one', await_ack=False) hosts['alice'].send_classical(hosts['bob'].host_id, 'hello bob two', await_ack=False) hosts['alice'].send_classical(hosts['bob'].host_id, 'hello bob three', await_ack=False) hosts['alice'].send_classical(hosts['bob'].host_id, 'hello bob four', await_ack=False) # Wait for all Acks from Bob hosts['alice'].await_remaining_acks(hosts['bob'].host_id) saw_ack = [False, False, False, False] messages = hosts['alice'].classical for m in messages: if m.content == protocols.ACK: saw_ack[m.seq_num] = True for ack in saw_ack: assert ack print("All tests succesfull!") network.stop(True)
def main(): backend = EQSNBackend() 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)} network.start(nodes, backend) network.packet_drop_rate = 0.5 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 = 0.5 num_acks = 0 # don't make more then 10 attempts, since of receiver window. num_messages = 20 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 = EQSNBackend() 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'].get_epr(hosts['bob'].host_id, q_id) i = 0 while q1 is None and i < 5: q1 = hosts['alice'].get_epr(hosts['bob'].host_id, q_id) i += 1 time.sleep(1) assert q1 is not None i = 0 q2 = hosts['bob'].get_epr(hosts['alice'].host_id, q_id) while q2 is None and i < 5: q2 = hosts['bob'].get_epr(hosts['alice'].host_id, q_id) i += 1 time.sleep(1) assert q2 is not None assert (q1.measure() == q2.measure()) print("All tests succesfull!") network.stop(True) exit()
def main(): network = Network.get_instance() # backend = ProjectQBackend() # backend = CQCBackend() backend = EQSNBackend() nodes = ['A', 'B'] network.delay = 0.1 network.start(nodes, backend) 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_connection('A') host_B.delay = 0 host_B.start() network.add_host(host_A) network.add_host(host_B) m = 2 n = 4 rot_angle = np.pi / 9 t1 = host_A.run_protocol(quantum_coin_flipping, arguments=(m, n, host_B.host_id, rot_angle)) t2 = host_B.run_protocol(quantum_coin_flipping, arguments=(m, n, host_A.host_id, rot_angle)) t1.join() t2.join() network.stop(True)
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._use_ent_swap = False self._queue_processor_thread = None self._delay = 0.1 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_ent_swap(self): return self._use_ent_swap @use_ent_swap.setter def use_ent_swap(self, use_ent_swap): self._use_ent_swap = use_ent_swap @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 @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 add_hosts(self, hosts): """ Adds the *hosts* to ARP table and updates the network graph. Args: hosts (list): The hosts to be added to the network. """ for host in hosts: self.add_host(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 update_host(self, host): """ Update the connections of a host in the network. Args: host: Returns: """ self.remove_host(host) self.add_host(host) 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 (str): ID number of the host that is returned. Returns: Host (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 (str): 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 (str): ID of the source host dest (str): 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 (str): ID of the source host dest (str): 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 (str): Qubit ID of the sent EPR pair o_seq_num (int): The original sequence number blocked (bool): If the pair being distributed is blocked or not """ host_sender = self.get_host(sender) def establish_epr(net, s, r): if not net.shares_epr(s, r): self.get_host(s).send_epr(r, q_id, await_ack=True) else: old_id = self.get_host(s).change_epr_qubit_id(r, q_id) net.get_host(r).change_epr_qubit_id(route[i], q_id, old_id) # Create EPR pairs on the route, where all EPR qubits have the id q_id threads = [] for i in range(len(route) - 1): threads.append( DaemonThread(establish_epr, args=(self, route[i], route[i + 1]))) for t in threads: t.join() 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, 'eq_id': q_id, 'node': sender, 'o_seq_num': o_seq_num, 'type': protocols.EPR } if route[i + 2] == route[-1]: data = { 'q': q, 'eq_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 _establish_epr(self, sender, receiver, q_id, o_seq_num, blocked): """ Instead doing an entanglement swap, for efficiency we establish EPR pairs directly for simulation, if an entanglement swap would have been possible. Args: sender (Host): Sender of the EPR pair receiver (Host): Receiver of the EPR pair q_id (str): Qubit ID of the sent EPR pair o_seq_num (int): The original sequence number blocked (bool): If the pair being distributed is blocked or not """ host_sender = self.get_host(sender) host_receiver = self.get_host(receiver) q1 = Qubit(host_sender) q2 = Qubit(host_sender) q1.H() q1.cnot(q2) host_sender.add_epr(receiver, q1, q_id, blocked) host_receiver.add_epr(sender, q2, q_id, blocked) host_receiver.send_ack(sender, o_seq_num) 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.blocked = False if not store and self.ARP[r].q_relay_sniffing: self.ARP[r].q_relay_sniffing_fn(original_sender, receiver, q) 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], original_sender=route[0]) 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") if packet.payload_type == protocols.QUANTUM: packet.payload.release() 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_classical_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) if self.use_ent_swap: DaemonThread(self._entanglement_swap, args=(sender, receiver, q_route, q_id, packet.seq_num, blocked)) else: DaemonThread(self._establish_epr, args=(sender, receiver, 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") try: 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() except Exception as e: Logger.get_instance().error(e) def start(self, nodes=None, backend=None): """ Starts the network. """ if backend is None: self._backend = EQSNBackend() 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_classical_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 draw_quantum_network(self): """ Draws a plot of the network. """ nx.draw_networkx(self.quantum_network, pos=nx.spring_layout(self.quantum_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: RoutingPacket: 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