Skip to content

Commit c89b723

Browse files
committed
Add template for creating client-server applications using the new SDK
1 parent 7fca7b4 commit c89b723

14 files changed

Lines changed: 410 additions & 27 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env bash
2+
3+
./terminate.sh
4+
./run.sh
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env sh
2+
TEST_PIDS=$(ps aux | grep python | grep -E "example" | awk {'print $2'})
3+
if [ "$TEST_PIDS" != "" ]
4+
then
5+
kill -9 $TEST_PIDS
6+
fi
7+
8+
# Check if SimulaQron is running
9+
if [ -f ~/.simulaqron_pids/simulaqron_network_default.pid ]; then
10+
if ! simulaqron stop
11+
then
12+
# Kill the process, only if simulaqron could not be stopped gracefully
13+
cat $HOME/.simulaqron_pids/simulaqron_network_default.pid | xargs kill -9
14+
fi
15+
fi
16+

examples/new-sdk/teleport/README.md

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,11 @@ First of all, make sure you are not already running existing simulaqron programs
1212
You can run
1313

1414
```shell
15-
simulaqron stop
16-
sh terminate.sh
15+
./terminate.sh
1716
```
1817

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:
18+
which should get rid of all things running for the teleport example itself. If you have a
19+
debugging enabled, you may also wish to wipe old log files by running:
2920

3021
```shell
3122
rm /tmp/simulaqron*
@@ -35,16 +26,10 @@ This should leave you a clear slate. We can now start the application.
3526

3627

3728
## 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
29+
If you are running everything on the same machine, simply type:
4530

4631
```shell
47-
sh run.sh
32+
./run.sh
4833
```
4934

5035
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.

examples/new-sdk/teleport/aliceTest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ async def run_alice(reader: StreamReader, writer: StreamWriter):
2929
q.H()
3030
m1 = q.measure()
3131
m2 = epr.measure()
32-
# Any value that comes from NetQASm *need* to be retrieved ("casted" to int)
32+
# Any value that comes from NetQASM *need* to be retrieved ("casted" to int)
3333
# *after* the connection is closed (or after flushing the connection, untested)
3434
m1_val = int(m1)
3535
m2_val = int(m2)

examples/new-sdk/teleport/bobTest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async def run_bob(reader: StreamReader, writer: StreamWriter):
4040
# Get the socket configuration for the sockets used for the application layer
4141
sockets_config = SocketsConfig(network_config, "default", NodeConfigType.APP)
4242

43-
# Create the client
43+
# Create the server
4444
server = SimulaQronClassicalServer(sockets_config, "Bob")
4545
server.register_client_handler(run_bob)
4646
server.start_serving()

examples/new-sdk/teleport/run.sh

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
1-
#!/bin/sh
1+
#!/usr/bin/env bash
22

3-
# Check if SimulaQron is already running
4-
if [ ! -f ~/.simulaqron_pids/simulaqron_network_default.pid ]; then
5-
# If not, start simulaqron backend for both nodes
6-
simulaqron start --nodes=Alice,Bob --network-config-file simulaqron_network.json
3+
# Flag used to determine whether to start simulaqron backend or not
4+
# This is useful when using this script to run the application in different machines.
5+
START_SIMULAQRON=true
6+
7+
# Process the arguments, if any
8+
while [[ $# -gt 0 ]]; do
9+
case $1 in
10+
-n|--no-start-simulaqron)
11+
START_SIMULAQRON=false
12+
shift
13+
;;
14+
esac
15+
done
16+
17+
if [ "$START_SIMULAQRON" = true ]; then
18+
# Check if SimulaQron is already running
19+
if [ ! -f ~/.simulaqron_pids/simulaqron_network_default.pid ]; then
20+
# If not, start simulaqron backend for both nodes
21+
simulaqron start --nodes=Alice,Bob --network-config-file simulaqron_network.json
22+
fi
723
fi
824

925
python3 bobTest.py &
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# SimulaQron Client-Server template
2+
This folder can be used as a template for creating client-server applications.
3+
This template uses SimulaQron's new SDK, to register a handler when a new client
4+
connects to the server. Additionally, it also provides a simple way to execute
5+
client code once the connection to the server has been established.
6+
7+
8+
# How to run
9+
10+
First of all, make sure you are not already running existing simulaqron programs.
11+
12+
13+
## Initial cleanup
14+
15+
You can run
16+
17+
```shell
18+
sh terminate.sh
19+
```
20+
21+
which should get rid of all things running for the teleport example itself. If you have a
22+
debugging enabled, you may also wish to wipe old log files by running:
23+
24+
```shell
25+
rm /tmp/simulaqron*
26+
```
27+
28+
This should leave you a clear slate. We can now start the application.
29+
30+
31+
## On a single machine
32+
If you are running everything on the same machine, simply type:
33+
34+
```shell
35+
./run.sh
36+
```
37+
38+
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.
39+
40+
41+
## On multiple machines
42+
43+
If you are starting on two different machines run, you first need to update the network configuration
44+
file. To run this example on different machines, it is necessary that both machines can reach each
45+
other via a network (or the internet). Additionally, you need to know the IP addresses of both
46+
machines.
47+
48+
Assuming that the server (alice) will run on a machine with IP `192.168.0.1` and the client (bob)
49+
will run on the machine with IP `192.168.0.2`, modify the `simulaqron_network.json` file *on both*
50+
the server and the client to look like this:
51+
52+
```json
53+
[
54+
{
55+
"name": "default",
56+
"nodes": [
57+
{
58+
"Alice": {
59+
"app_socket": ["192.168.0.1", 8821],
60+
"qnodeos_socket": ["192.168.0.1", 8822],
61+
"vnode_socket": ["192.168.0.1", 8823]
62+
}
63+
},
64+
{
65+
"Bob": {
66+
"app_socket": ["192.168.0.2", 9831],
67+
"qnodeos_socket": ["192.168.0.2", 9832],
68+
"vnode_socket": ["192.168.0.2", 9833]
69+
}
70+
}
71+
],
72+
"topology": null
73+
}
74+
]
75+
```
76+
77+
Note that the `localhost` entries from Alice were changed to `192.168.0.1`. Similarly, the
78+
`localhost` entries from Bob were changed to `192.168.0.2`.
79+
80+
After these modifications, yu can start the simulaqron backends by invoking:
81+
82+
```shell
83+
simulaqron start --node Alice
84+
simulaqron start --node Bob
85+
```
86+
87+
on the machines you will use as Alice and Bob respectively. Again this needs to be run only once.
88+
Now you can run:
89+
90+
* on Bob:
91+
```shell
92+
python bobTest.py
93+
```
94+
95+
* on Alice:
96+
```shell
97+
python aliceTest.py
98+
```
99+
100+
The code assumes you start Bob before starting Alice. Using your knowledge of network programming
101+
from our ping pong example - do you have an idea to make this more robust?
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env bash
2+
3+
./terminate.sh
4+
./run.sh
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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, simulaqron_settings
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+
# This function contains the code of the classical client
20+
# "reader" is an object connected to the server, which can be used to read data from the server
21+
# "writer" is an object connected to the server, which can be used to send data to the server
22+
async def run_client(reader: StreamReader, writer: StreamWriter):
23+
# To send a messsage, we can simply use the "wirte" method from the "writer" object
24+
# The argument *must* be a python bytes object, which we can get by encoding (using
25+
# the UTF-8 charmap) any python string
26+
message = "Hello World"
27+
writer.write(message.encode("utf-8"))
28+
29+
# If you want to receive a message (such a response) from the server, you can
30+
# use the "read" method from the "reader" object. The argument is an integer that
31+
# configures the maximum bytes that we are allowed to read in a single operation.
32+
# Note: Since "read" is a python coroutine, we need to "await" it, so python can
33+
# execute other coroutines until the data becomes available to read.
34+
answer = await reader.read(100)
35+
36+
this_node_name = "Alice"
37+
other_node_name = "Bob"
38+
39+
# We ca create an EPR socket with the other node
40+
epr_socket = EPRSocket(other_node_name)
41+
# To start executing quantum operations, we need to create a NetQASM connection
42+
with NetQASMConnection(this_node_name, epr_sockets=[epr_socket]) as alice:
43+
# Create a qubit
44+
q = Qubit(alice)
45+
q.H()
46+
# Create an entangled qubit with the other node
47+
epr = epr_socket.create_keep()[0]
48+
# Teleport
49+
q.cnot(epr)
50+
q.H()
51+
m1 = q.measure()
52+
m2 = epr.measure()
53+
# Any value that comes from NetQASM *need* to be retrieved ("casted" to int)
54+
# *after* the connection is closed (or after flushing the connection, untested)
55+
m1_val = int(m1)
56+
m2_val = int(m2)
57+
return m1_val, m2_val
58+
59+
60+
if __name__ == "__main__":
61+
# Load the simulaqron settings file
62+
simulaqron_config_file = Path("simulaqron_settings.json")
63+
simulaqron_settings.read_from_file(simulaqron_config_file)
64+
65+
# Load the file network configuration file
66+
network_config_file = Path("simulaqron_network.json")
67+
network_config.read_from_file(network_config_file)
68+
69+
# Some data for this node:
70+
network_name = "default" # A network with this name *must* exist in "simulaqron_network.json"
71+
node_name = "YourName" # A node with this name *must* exist in "simulaqron_network.json"
72+
73+
# Get the socket configuration for the sockets used for the application layer
74+
sockets_config = SocketsConfig(network_config, network_name, NodeConfigType.APP)
75+
76+
# Name of one node to classically connect to
77+
server_name = "Bob"
78+
79+
# Create the client
80+
client = SimulaQronClassicalClient(sockets_config)
81+
# Run a classical client invoking the `run_client` method. This also has the effect to
82+
# immediately execute the `run_client` method.
83+
results = client.run_client(server_name, run_client)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
# This function contains the code to serve *one client ocnnected to this server*.
20+
# "reader" is an object connected to the client, which can be used to read data from the client
21+
# "writer" is an object connected to the client, which can be used to send data to the client
22+
async def serve_client(reader: StreamReader, writer: StreamWriter):
23+
# If you want to receive a message (such a query) from the client, you can
24+
# use the "read" method from the "reader" object. The argument is an integer that
25+
# configures the maximum bytes that we are allowed to read in a single operation.
26+
# Note: Since "read" is a python coroutine, we need to "await" it, so python can
27+
# execute other coroutines (such as serving a new client) until the data becomes
28+
# available to read.
29+
answer = await reader.read(100)
30+
31+
# To send a messsage, we can simply use the "wirte" method from the "writer" object
32+
# The argument *must* be a python bytes object, which we can get by encoding (using
33+
# the UTF-8 charmap) any python string
34+
message = "Hello World"
35+
writer.write(message.encode("utf-8"))
36+
37+
this_node_name = "Bob"
38+
other_node_name = "Alice"
39+
40+
epr_socket = EPRSocket(other_node_name)
41+
with NetQASMConnection(this_node_name, epr_sockets=[epr_socket]) as bob:
42+
# Create a qubit
43+
q = Qubit(bob)
44+
q.H()
45+
# Create entanglement
46+
epr = epr_socket.create_keep()[0]
47+
# Teleport
48+
q.cnot(epr)
49+
q.H()
50+
m1 = q.measure()
51+
m2 = epr.measure()
52+
# Any value that comes from NetQASM *need* to be retrieved ("casted" to int)
53+
# *after* the connection is closed (or after flushing the connection, untested)
54+
m1_val = int(m1)
55+
m2_val = int(m2)
56+
return m1_val, m2_val
57+
58+
59+
if __name__ == "__main__":
60+
# Load the simulaqron settings file
61+
simulaqron_config_file = Path("simulaqron_settings.json")
62+
simulaqron_settings.read_from_file(simulaqron_config_file)
63+
64+
# Load the file network configuration file
65+
network_config_file = Path("simulaqron_network.json")
66+
network_config.read_from_file(network_config_file)
67+
68+
# Some data for this node:
69+
network_name = "default" # A network with this name *must* exist in "simulaqron_network.json"
70+
node_name = "YourName" # A node with this name *must* exist in "simulaqron_network.json"
71+
72+
# Get the socket configuration for the sockets used for the application layer
73+
sockets_config = SocketsConfig(network_config, network_name, NodeConfigType.APP)
74+
75+
# Create the server
76+
server = SimulaQronClassicalServer(sockets_config, node_name)
77+
server.register_client_handler(serve_client)
78+
server.start_serving()

0 commit comments

Comments
 (0)