Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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},
Expand Down
33 changes: 33 additions & 0 deletions examples/network_generated/send_qubit.py
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions qunetsim/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .components import Host, Network
from .objects import *
from .generator import *
4 changes: 4 additions & 0 deletions qunetsim/generator/__init__.py
Original file line number Diff line number Diff line change
@@ -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
37 changes: 37 additions & 0 deletions qunetsim/generator/conn_type.py
Original file line number Diff line number Diff line change
@@ -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
75 changes: 75 additions & 0 deletions qunetsim/generator/connection.py
Original file line number Diff line number Diff line change
@@ -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}'
28 changes: 28 additions & 0 deletions qunetsim/generator/direction.py
Original file line number Diff line number Diff line change
@@ -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
136 changes: 136 additions & 0 deletions qunetsim/generator/generate.py
Original file line number Diff line number Diff line change
@@ -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