Skip to content

Commit 39d4d57

Browse files
committed
Adapt the "distributed" version of the teleport to use the new SDK to run both in a single machine, and distributed.
1 parent 379a126 commit 39d4d57

10 files changed

Lines changed: 227 additions & 121 deletions

File tree

examples/distributed/teleport/README.txt

Lines changed: 0 additions & 63 deletions
This file was deleted.

examples/distributed/teleport/teleport-alice.py

Lines changed: 0 additions & 26 deletions
This file was deleted.

examples/distributed/teleport/teleport-bob.py

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Quantum teleport example
2+
Quantum teleport example, using simple NetQASM code both for Alice and Bob.
3+
4+
5+
# How to run
6+
7+
First of all, make sure you are not already running existing simulaqron programs.
8+
9+
10+
## Initial cleanup
11+
12+
You can run
13+
14+
```shell
15+
simulaqron stop
16+
sh terminate.sh
17+
```
18+
19+
which should get rid of all things running for the teleport example itself. If you want to be a bit
20+
more radical and confident you are not running other things to preserve you can run:
21+
22+
```shell
23+
simulaqron stop
24+
pkill -9 python
25+
```
26+
27+
This will kill ALL python processes run by you so beware. If you have a debugging enabled, you may also
28+
wish to wipe old log files by running:
29+
30+
```shell
31+
rm /tmp/simulaqron*
32+
```
33+
34+
This should leave you a clear slate. We can now start the application.
35+
36+
37+
## On a single machine
38+
If you are running everything on the same machine, first we start the simulaqron backend by typing
39+
40+
```shell
41+
simulaqron start --node Alice,Bob
42+
```
43+
44+
This needs to be done only once. Now type
45+
46+
```shell
47+
sh run.sh
48+
```
49+
50+
WARNING: It seems restarting the simulaqron backend is needed! - There is no need to restart the simulaqron backend again if you want to re-run your example.
51+
52+
53+
## On multiple machines
54+
55+
If you are starting on two different machines run, you first need to update the network configuration
56+
file. To run this example on different machines, it is necessary that both machines can reach each
57+
other via a network (or the internet). Additionally, you need to know the IP addresses of both
58+
machines.
59+
60+
Assuming that the server (alice) will run on a machine with IP `192.168.0.1` and the client (bob)
61+
will run on the machine with IP `192.168.0.2`, modify the `simulaqron_network.json` file *on both*
62+
the server and the client to look like this:
63+
64+
```json
65+
[
66+
{
67+
"name": "default",
68+
"nodes": [
69+
{
70+
"Alice": {
71+
"app_socket": ["192.168.0.1", 8821],
72+
"qnodeos_socket": ["192.168.0.1", 8822],
73+
"vnode_socket": ["192.168.0.1", 8823]
74+
}
75+
},
76+
{
77+
"Bob": {
78+
"app_socket": ["192.168.0.2", 9831],
79+
"qnodeos_socket": ["192.168.0.2", 9832],
80+
"vnode_socket": ["192.168.0.2", 9833]
81+
}
82+
}
83+
],
84+
"topology": null
85+
}
86+
]
87+
```
88+
89+
Note that the `localhost` entries from Alice were changed to `192.168.0.1`. Similarly, the
90+
`localhost` entries from Bob were changed to `192.168.0.2`.
91+
92+
After these modifications, yu can start the simulaqron backends by invoking:
93+
94+
```shell
95+
simulaqron start --node Alice
96+
simulaqron start --node Bob
97+
```
98+
99+
on the machines you will use as Alice and Bob respectively. Again this needs to be run only once.
100+
Now you can run:
101+
102+
* on Bob:
103+
```shell
104+
python teleport-bob.py
105+
```
106+
107+
* on Alice:
108+
```shell
109+
python teleport-alice.py
110+
```
111+
112+
The code assumes you start Bob before starting Alice. Using your knowledge of network programming
113+
from our ping pong example - do you have an idea to make this more robust?
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ if [ ! -f ~/.simulaqron_pids/simulaqron_network_default.pid ]; then
77
fi
88

99
python3 teleport-bob.py &
10+
SERVERPID=$!
11+
sleep 1
1012
python3 teleport-alice.py
1113

12-
13-
14-
15-
14+
# Kill the server after it was used
15+
kill -9 $SERVERPID
File renamed without changes.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from asyncio import StreamReader, StreamWriter
2+
from pathlib import Path
3+
4+
from simulaqron.general.host_config import SocketsConfig
5+
from simulaqron.sdk.protocol import SimulaQronClassicalClient
6+
from simulaqron.settings import network_config
7+
from simulaqron.settings.network_config import NodeConfigType
8+
9+
# This is recipe to use NetQASM with simulaqron backend.
10+
from netqasm.runtime.settings import set_simulator
11+
set_simulator("simulaqron")
12+
13+
# Importing NetQASM connection, Qubit and EPR socket must be *after*
14+
# setting the simulator for NetQASM
15+
from netqasm.sdk.external import NetQASMConnection # noqa: E402
16+
from netqasm.sdk import Qubit, EPRSocket # noqa: E402
17+
18+
19+
async def run_alice(reader: StreamReader, writer: StreamWriter):
20+
epr_socket = EPRSocket("Bob")
21+
with NetQASMConnection("Alice", epr_sockets=[epr_socket]) as alice:
22+
# Create a qubit
23+
q = Qubit(alice)
24+
q.H()
25+
# Create entanglement
26+
epr = epr_socket.create_keep()[0]
27+
# Teleport
28+
q.cnot(epr)
29+
q.H()
30+
m1 = q.measure()
31+
m2 = epr.measure()
32+
# Any value that comes from NetQASm *need* to be retrieved ("casted" to int)
33+
# *after* the connection is closed (or after flushing the connection, untested)
34+
m1_val = int(m1)
35+
m2_val = int(m2)
36+
message = f"{m1_val}:{m2_val}"
37+
writer.write(message.encode("utf-8"))
38+
return m1_val, m2_val
39+
40+
41+
if __name__ == "__main__":
42+
# Load the file network configuration file
43+
network_config_file = Path("simulaqron_network.json")
44+
network_config.read_from_file(network_config_file)
45+
46+
# Get the socket configuration for the sockets used for the application layer
47+
sockets_config = SocketsConfig(network_config, "default", NodeConfigType.APP)
48+
49+
# Create the client
50+
client = SimulaQronClassicalClient(sockets_config)
51+
results = client.run_client("Bob", run_alice)
52+
print(f"Alice measurements: m1={results[0]}, m2={results[1]}")
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from asyncio import StreamReader, StreamWriter
2+
from pathlib import Path
3+
4+
from simulaqron.general.host_config import SocketsConfig
5+
from simulaqron.sdk.protocol import SimulaQronClassicalServer
6+
from simulaqron.settings import network_config
7+
from simulaqron.settings.network_config import NodeConfigType
8+
9+
from netqasm.runtime.settings import set_simulator
10+
set_simulator("simulaqron")
11+
12+
from netqasm.sdk.external import NetQASMConnection # noqa: E402
13+
from netqasm.sdk import EPRSocket # noqa: E402
14+
15+
16+
async def run_bob(reader: StreamReader, writer: StreamWriter):
17+
# We wait for the classical message first
18+
corrections_bytes = await reader.read(255)
19+
corrections = corrections_bytes.decode("utf-8").split(":")
20+
epr_socket = EPRSocket("Alice")
21+
with NetQASMConnection("Bob", epr_sockets=[epr_socket]):
22+
entangled_qubit = epr_socket.recv_keep()[0]
23+
24+
if int(corrections[0]) == 1:
25+
entangled_qubit.X()
26+
if int(corrections[1]) == 1:
27+
entangled_qubit.Z()
28+
meas = entangled_qubit.measure()
29+
# Any value that comes from NetQASm *need* to be retrieved ("casted" to int)
30+
# *after* the connection is closed (or after flushing the connection, untested)
31+
meas_val = int(meas)
32+
print(f"Bob measurement: {meas_val}")
33+
34+
if __name__ == "__main__":
35+
# Load the file network configuration file
36+
network_config_file = Path("simulaqron_network.json")
37+
network_config.read_from_file(network_config_file)
38+
39+
# Get the socket configuration for the sockets used for the application layer
40+
sockets_config = SocketsConfig(network_config, "default", NodeConfigType.APP)
41+
42+
# Create the client
43+
server = SimulaQronClassicalServer(sockets_config, "Bob")
44+
server.register_client_handler(run_bob)
45+
server.start_serving()
File renamed without changes.

simulaqron/sdk/protocol.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import asyncio
22
from asyncio import StreamWriter, StreamReader
3-
from typing import Awaitable, Optional, Callable
3+
from typing import Awaitable, Optional, Callable, Coroutine, Any, TypeVar
44

55
from simulaqron.general.host_config import SocketsConfig
66

7+
_T = TypeVar("_T")
8+
79

810
class SimulaQronClassicalClient:
911
def __init__(self, sockets_config: SocketsConfig):
@@ -16,16 +18,17 @@ def __init__(self, sockets_config: SocketsConfig):
1618
"""
1719
self._sockets_config = sockets_config
1820

19-
async def _run_client(self, hostname: str, port: int, callback: Callable[[StreamReader, StreamWriter], Awaitable[None]]):
21+
async def _run_client(self, hostname: str, port: int, callback: Coroutine[Any, Any, _T]) -> _T:
2022
"""
2123
Python coroutine that opens the connection and runs the function provided by the user.
2224
"""
2325
reader, writer = await asyncio.open_connection(hostname, port)
24-
await callback(reader, writer)
26+
result = await callback(reader, writer)
2527
writer.close()
28+
return result
2629

2730

28-
def run_client(self, node_name: str, callback: Callable[[StreamReader, StreamWriter], Awaitable[None]]) -> None:
31+
def run_client(self, server_name: str, callback: Coroutine[Any, Any, _T]) -> _T:
2932
"""
3033
Runs a function implementing a client that connects to the node with the given name.
3134
Once the connection has been established, the given callback will be executed to start
@@ -44,18 +47,18 @@ async def connected_handler(reader: StreamReader, writer: StreamWriter):
4447
Once the execution of the given function, the client will close the connection to
4548
the server.
4649
47-
:param node_name: The name of the node to connect to. The name *must* exist in the
50+
:param server_name: The name of the server to connect to. The name *must* exist in the
4851
configuration file given when constructing this client.
49-
:type node_name: str
52+
:type server_name: str
5053
:param callback: The function to be called when the connection is established. This function
5154
implements the logic for interacting with the server. The passed function
5255
*must* be a python "async" function.
5356
:type callback: Callable[[StreamReader, StreamWriter], Awaitable[None]]
5457
"""
55-
if node_name not in self._sockets_config.hostDict:
56-
raise RuntimeError(f"The node with name '{node_name}' is not on the network configuration.")
57-
socket_config = self._sockets_config.hostDict[node_name]
58-
asyncio.run(self._run_client(socket_config.hostname, socket_config.port, callback))
58+
if server_name not in self._sockets_config.hostDict:
59+
raise RuntimeError(f"The node with name '{server_name}' is not on the network configuration.")
60+
socket_config = self._sockets_config.hostDict[server_name]
61+
return asyncio.run(self._run_client(socket_config.hostname, socket_config.port, callback))
5962

6063

6164
class SimulaQronClassicalServer:

0 commit comments

Comments
 (0)