diff --git a/README.md b/README.md index 898098e..7b2fdd1 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The QuNetSim pip package comes with a templater. After installing the library, s ### Quick Example -``` +```python from qunetsim.components import Host, Network network = Network.get_instance() @@ -46,12 +46,55 @@ print("EPR is in state: %d, %d" % (q_alice.measure(), q_bob.measure())) network.stop(True) ``` +### Quick Example: generate a network from a string + +```python +from qunetsim.generator import network_generate +from qunetsim.objects import Qubit + +# Initialize the network and hosts +# Note: we use 'A<==>B' to represent a classical and quantum connection +# we use 'A<-->B' to represent a classical only connection +# we use 'A<~~>B' to represent a quantum only connection +# All connections are added uni-directionally, so '<' and '>' +# represent the direction of the flow of traffic. +network, hosts = network_generate("Alice<=>Bob<~>Eve<=>Dean<->Alice") + +network.start(list(hosts.keys())) + +# Initialize the hosts +for host in hosts.values(): + host.start() + +for _ in range(10): + # Create a qubit owned by Alice + q = Qubit(hosts['Alice']) + # Put the qubit in the excited state + q.H() + # Send the qubit and await an ACK from Dean + q_id, ack_arrived = hosts['Alice'].send_qubit('Dean', q, await_ack=True) + + # Get the qubit on Dean's side from Alice + q_rec = hosts['Dean'].get_qubit('Alice', q_id, wait=0) + + # Ensure the qubit arrived and then measure and print the results. + if q_rec is not None: + m = q_rec.measure() + print("Results of the measurements for q_id are ", str(m)) + else: + print('Qubit did not arrive.') + +network.stop(stop_hosts=True) +``` + + + ## Contributing Feel free to contribute by adding Github issues and pull requests. Adding test cases for any contributions is a requirement for any pull request to be merged. ## Citation -``` +```bibtex @article{diadamo2020qunetsim, title={QuNetSim: A Software Framework for Quantum Networks}, author={DiAdamo, Stephen and N{\"o}tzel, Janis and Zanger, Benjamin and Be{\c{s}}e, Mehmet Mert}, diff --git a/examples/network_generated/send_qubit.py b/examples/network_generated/send_qubit.py new file mode 100644 index 0000000..328d7bd --- /dev/null +++ b/examples/network_generated/send_qubit.py @@ -0,0 +1,33 @@ +from qunetsim.generator import network_generate +from qunetsim.objects import Qubit + +network, hosts = network_generate("Alice<==>Bob<~~>Eve<==>Dean<-->Alice") + +network.draw_quantum_network() +network.draw_classical_network() + +network.start(list(hosts.keys())) + +# Initialize the hosts +for host in hosts.values(): + host.start() + +for _ in range(10): + # Create a qubit owned by Alice + q = Qubit(hosts['Alice']) + # Put the qubit in the excited state + q.H() + # Send the qubit and await an ACK from Dean + q_id, ack_arrived = hosts['Alice'].send_qubit('Dean', q, await_ack=True) + + # Get the qubit on Dean's side from Alice + q_rec = hosts['Dean'].get_qubit('Alice', q_id, wait=0) + + # Ensure the qubit arrived and then measure and print the results. + if q_rec is not None: + m = q_rec.measure() + print("Results of the measurements for q_id are ", str(m)) + else: + print('Qubit did not arrive.') + +network.stop(stop_hosts=True) diff --git a/qunetsim/__init__.py b/qunetsim/__init__.py index 28e5016..89c70a5 100644 --- a/qunetsim/__init__.py +++ b/qunetsim/__init__.py @@ -1,2 +1,3 @@ from .components import Host, Network from .objects import * +from .generator import * \ No newline at end of file diff --git a/qunetsim/generator/__init__.py b/qunetsim/generator/__init__.py new file mode 100644 index 0000000..bc037d0 --- /dev/null +++ b/qunetsim/generator/__init__.py @@ -0,0 +1,4 @@ +""" +The generator package provides functions and classes to translate a string and generate the specified network. +""" +from .generate import network_generate diff --git a/qunetsim/generator/conn_type.py b/qunetsim/generator/conn_type.py new file mode 100644 index 0000000..9b7680a --- /dev/null +++ b/qunetsim/generator/conn_type.py @@ -0,0 +1,37 @@ +from qunetsim.components import Host + + +class ConnType: + """ + Enumeration for the connection types. + """ + CLASSICAL_AND_QUANTUM = 1 + CLASSICAL = 2 + QUANTUM = 3 + + """ + Function to add the connection between the hosts, depending on the connection type. + """ + add_connection_function = { + CLASSICAL_AND_QUANTUM: Host.add_connection, + CLASSICAL: Host.add_c_connection, + QUANTUM: Host.add_q_connection + } + + @staticmethod + def get_conn_type(string): + """ + Get the connection type from the string. + + Args: + string (str): The string representing the connection type. + + Returns: + ConnType: The connection type. + """ + if string == '==': + return ConnType.CLASSICAL_AND_QUANTUM + elif string == '--': + return ConnType.CLASSICAL + elif string == '~~': + return ConnType.QUANTUM diff --git a/qunetsim/generator/connection.py b/qunetsim/generator/connection.py new file mode 100644 index 0000000..7bcd9cf --- /dev/null +++ b/qunetsim/generator/connection.py @@ -0,0 +1,75 @@ +from qunetsim.components import Host +from .conn_type import ConnType +from .direction import Direction + + +class Connection: + """ + Class representing a connection between two hosts. + """ + + def __init__(self, hostA: Host, hostB: Host, connection_string: str): + """ + Initialize the connection. + + Args: + hostA (Host): The first host. + hostB (Host): The second host. + connection_string (str): The string representing the connection. + """ + self.hostA = hostA + self.hostB = hostB + + self.direction = Direction.get_direction(connection_string) + + if connection_string[0] == '<': + connection_string = connection_string[1:] + if connection_string[-1] == '>': + connection_string = connection_string[:-1] + + self.type = ConnType.get_conn_type(connection_string) + + def add_connection(self): + """ + Add the connection between the hosts. + """ + if self.direction == Direction.UNIDIRECTIONAL_A_B: + ConnType.add_connection_function[self.type]( + self.hostA, self.hostB.host_id) + elif self.direction == Direction.UNIDIRECTIONAL_B_A: + ConnType.add_connection_function[self.type]( + self.hostB, self.hostA.host_id) + else: + ConnType.add_connection_function[self.type]( + self.hostA, self.hostB.host_id) + ConnType.add_connection_function[self.type]( + self.hostB, self.hostA.host_id) + + def __str__(self): + """ + Get the string representation of the connection. + + Returns: + str: The string representation of the connection. + """ + # Direction to str + start = '' + end = '' + connection_type = '' + if self.direction == Direction.UNIDIRECTIONAL_A_B: + end = '>' + elif self.direction == Direction.UNIDIRECTIONAL_B_A: + start = '<' + else: + start = '<' + end = '>' + + # Connection type to str + if self.type == ConnType.CLASSICAL_AND_QUANTUM: + connection_type = '==' + elif self.type == ConnType.CLASSICAL: + connection_type = '--' + else: + connection_type = '~~' + + return f'{self.hostA.host_id} {start}{connection_type}{end} {self.hostB.host_id}' diff --git a/qunetsim/generator/direction.py b/qunetsim/generator/direction.py new file mode 100644 index 0000000..0c7ef56 --- /dev/null +++ b/qunetsim/generator/direction.py @@ -0,0 +1,28 @@ +class Direction: + """ + Enumeration for the direction of the connection. + """ + UNIDIRECTIONAL_A_B = 1 # A --> B + UNIDIRECTIONAL_B_A = 2 # A <-- B + BIDIRECTIONAL = 3 # A <-> B + + @staticmethod + def get_direction(string): + """ + Get the direction from the string. + + Args: + string (str): The string representing the connection. + + Returns: + Direction: The direction. + """ + start = string[0] == '<' + end = string[-1] == '>' + + if not (start ^ end): + return Direction.BIDIRECTIONAL + elif start: + return Direction.UNIDIRECTIONAL_B_A + else: + return Direction.UNIDIRECTIONAL_A_B diff --git a/qunetsim/generator/generate.py b/qunetsim/generator/generate.py new file mode 100644 index 0000000..6be093c --- /dev/null +++ b/qunetsim/generator/generate.py @@ -0,0 +1,136 @@ +from qunetsim.components import Host, Network +from .connection import Connection + +import re + + +def get_connections(hosts: dict[str, Host], string: str) -> list[Connection]: + """ + Get the connections from the string. + + Args: + hosts (dict[str, Host]): The dictionary of hosts. + string (str): The string representing the connections. + + Returns: + list[Connection]: The list of connections. + """ + # Use the regular expression to get the connections + regex = r'(\{(?:\w+,)*\w+\}|\w+)(<{0,1}(?:==|--|~~)>{0,1})(\{(?:\w+,)*\w+\}|\w+)' + + connections = [] + start = 0 + + while True: + match = re.search(regex, string[start:]) + if not match: + break + + # Get the hosts from the left and right side + hosts_left = get_hosts(match.group(1)) + hosts_right = get_hosts(match.group(3)) + connection_type = match.group(2) + + # Append the new connections + for host_left in hosts_left: + for host_right in hosts_right: + host1 = hosts[host_left] + host2 = hosts[host_right] + connections.append(Connection(host1, host2, connection_type)) + + # Update the start index + start += match.start(3) + + # Remove the duplicates + connections = list(dict.fromkeys(connections)) + + return connections + + +def get_hosts(string: str) -> list[str]: + """ + Get the hosts from the string. + + Args: + string (str): The string representing the hosts. + + Returns: + list[str]: The list of hosts. + """ + # Use the regular expression (\w+) + regex = r'(\w+)' + + hosts = [] + + for match in re.finditer(regex, string): + hosts.append(match.group()) + + # remove the duplicates + hosts = list(dict.fromkeys(hosts)) + + return hosts + + +def array_to_host(array): + """ + Convert the array of host string to a host object. + + Args: + array (list): The array representing the host. + + Returns: + list (Host): The list with the host objects. + """ + + # Create one host object for each host in the array + hosts = {host: Host(host) for host in array} + + return hosts + + +def network_parser(string) -> tuple[dict[str, Host], list[Connection]]: + """ + Parse the network string and return the network object. + + Args: + string (str): The string representing the network. + + Returns: + tuple[dict[str, Host], list[Connection]]: The hosts and connections. + """ + # Remove the spaces and line breaks + string = string.replace(' ', '') + + # Get the hostnames + string_hosts = get_hosts(string) + + # Create the host objects + hosts = array_to_host(string_hosts) + + # Get the connections + connections = get_connections(hosts, string) + + return hosts, connections + + +def network_generate(string : str) -> tuple[Network, dict[str, Host]]: + """ + Generate the network from the string. + + Args: + string (str): The string representing the network. + + Returns: + tuple[Network, dict[str, Host]]: The network and the hosts. + """ + network = Network.get_instance() + hosts, connections = network_parser(string) + + for connection in connections: + connection.add_connection() + + # Append the hosts to the network + for host in hosts.values(): + network.add_host(host) + + return network, hosts