Skip to content

Commit

Permalink
* Expanded API documentation to include utility methods and routines
Browse files Browse the repository at this point in the history
* Expanded tutorial documentation with a section for multiple nodes
* Added utility method create_complete_graph_network
* Various fixes for properly displaying API documentation
* Reworked the tutorial example 4.3_multi-node to a completly different example
* Edited the GHZ example to use the new squidASM utility method for generating the complete graph network instead of the netbuilder utility method
  • Loading branch information
mkvanhooft committed Mar 6, 2024
1 parent 3864cf8 commit 639ede3
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 74 deletions.
2 changes: 2 additions & 0 deletions docs/source/api_overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ These objects have their documentation on `NETQASM SDK documentation <https://ne
modules/logger
modules/program
modules/classical_socket
modules/util
modules/routines



10 changes: 10 additions & 0 deletions docs/source/modules/routines.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Routines
==========

.. automodule:: squidasm.util.routines
:members:
:undoc-members:

.. automodule:: squidasm.util.qkd_routine
:members:
:undoc-members:
9 changes: 9 additions & 0 deletions docs/source/modules/util.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.. _label_API_util:


Util
==========

.. automodule:: squidasm.util.util
:members:
:undoc-members:
24 changes: 24 additions & 0 deletions docs/source/tutorial/section4_NetworkConfiguration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,30 @@ the configuration.
:caption: examples/tutorial/4.2_network-configuration/1_perfect.yaml
:lines: 19-24

Multiple nodes
==================
SquidASM is capable of simulating networks that consist of more than two nodes.
This extends straightforwardly from two node networks.
To add an extra node, Charlie, the network configuration must be extended with a stack for Charlie
and the desired connections must be added.

.. literalinclude:: ../../../examples/tutorial/4.3_multi-node/config.yaml
:language: yaml
:caption: examples/tutorial/4.3_multi-node/config.yaml

While the previous example has connected the Charlie stack to both Alice and Bob, this is not mandatory,
as long as the application does not attempt to use a non-existent link.
For larger networks it is useful to create the network configuration object via :ref:`label_API_util` methods or create it programmatically yourself.

To write programs for networks with more than two nodes in the network, one must register each of the nodes for which a connection is used to the `ProgramMeta` object.
An example of a program where we have three nodes in the network: Alice, Bob and Charlie, is shown below.
In this example both Bob and Charlie generate a EPR pair with Alice. Alice will then perform a bell state measurement and send the corrections to Charlie.
This will result in Bob and Charlie sharing an EPR pair.

.. literalinclude:: ../../../examples/tutorial/4.3_multi-node/application.py
:language: python
:caption: examples/tutorial/4.3_multi-node/application.py AliceProgram
:pyobject: AliceProgram

Parameter sweeping
=====================
Expand Down
2 changes: 1 addition & 1 deletion examples/advanced/ghz/example_ghz.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

from netsquid_netbuilder.modules.clinks.default import DefaultCLinkConfig
from netsquid_netbuilder.modules.links.perfect import PerfectLinkConfig
from netsquid_netbuilder.util.network_generation import create_complete_graph_network

from squidasm.run.stack.run import run
from squidasm.sim.stack.program import Program, ProgramContext, ProgramMeta
from squidasm.util.routines import create_ghz
from squidasm.util.util import create_complete_graph_network


class GHZProgram(Program):
Expand Down
144 changes: 98 additions & 46 deletions examples/tutorial/4.3_multi-node/application.py
Original file line number Diff line number Diff line change
@@ -1,82 +1,134 @@
from typing import List

import netsquid as ns
from netqasm.sdk.classical_communication.socket import Socket
from netqasm.sdk.connection import BaseNetQASMConnection
from netqasm.sdk.epr_socket import EPRSocket

from squidasm.sim.stack.program import Program, ProgramContext, ProgramMeta


class ClientProgram(Program):
def __init__(self, name: str, server_name: str):
self.name = name
self.server_name = server_name
class AliceProgram(Program):
PEER_BOB = "Bob"
PEER_CHARLIE = "Charlie"

@property
def meta(self) -> ProgramMeta:
return ProgramMeta(
name="tutorial_program",
csockets=[self.server_name],
epr_sockets=[self.server_name],
max_qubits=1,
csockets=[self.PEER_BOB, self.PEER_CHARLIE],
epr_sockets=[self.PEER_BOB, self.PEER_CHARLIE],
max_qubits=2,
)

def run(self, context: ProgramContext):
# get classical socket to peer
csocket = context.csockets[self.server_name]
# get EPR socket to peer
epr_socket = context.epr_sockets[self.server_name]
# get classical sockets
csocket_bob = context.csockets[self.PEER_BOB]
csocket_charlie = context.csockets[self.PEER_CHARLIE]
# get EPR sockets
epr_socket_bob = context.epr_sockets[self.PEER_BOB]
epr_socket_charlie = context.epr_sockets[self.PEER_CHARLIE]
# get connection to quantum network processing unit
connection = context.connection

# Bob listens for messages on his classical socket
message = yield from csocket.recv()
print(f"{ns.sim_time()} ns: Client: {self.name} receives message: {message}")

# Listen for request to create EPR pair, apply a Hadamard gate on the epr qubit and measure
epr_qubit = epr_socket.recv_keep()[0]
epr_qubit.H()
result = epr_qubit.measure()
# send a message to both nodes
msg = "Hello from Alice"
csocket_bob.send(msg)
csocket_charlie.send(msg)
print(f"{ns.sim_time()} ns: Alice sends: {msg} to Bob and Charlie")

# Generate EPR pairs with both Bob and Charlie
epr_qubit_bob = epr_socket_bob.create_keep()[0]
epr_qubit_charlie = epr_socket_charlie.create_keep()[0]
# Perform a bell state measurement on the qubit from Bob and the qubit from Charlie
epr_qubit_bob.cnot(epr_qubit_charlie)
epr_qubit_bob.H()
m2 = epr_qubit_bob.measure()
m1 = epr_qubit_charlie.measure()
yield from connection.flush()

print(
f"{ns.sim_time()} ns: Client: {self.name} measures local EPR qubit: {result}"
f"{ns.sim_time()} ns: Alice finished EPR generation and local quantum operations"
)
csocket_charlie.send(str(int(m2)))
csocket_charlie.send(str(int(m1)))

print(
f"{ns.sim_time()} ns: Alice sends corrections m1: {m1}, m2: {m2} to Charlie"
)

return {}


class ServerProgram(Program):
def __init__(self, clients: List[str]):
self.clients = clients
class BobProgram(Program):
PEER = "Alice"

@property
def meta(self) -> ProgramMeta:
return ProgramMeta(
name="tutorial_program",
csockets=self.clients,
epr_sockets=self.clients,
csockets=[self.PEER],
epr_sockets=[self.PEER],
max_qubits=1,
)

def run(self, context: ProgramContext):
connection: BaseNetQASMConnection = context.connection
# get classical sockets
csocket = context.csockets[self.PEER]
# get EPR sockets
epr_socket = context.epr_sockets[self.PEER]
# get connection to quantum network processing unit
connection = context.connection

for client in self.clients:
# get classical socket to peer
csocket: Socket = context.csockets[client]
epr_socket: EPRSocket = context.epr_sockets[client]
msg = yield from csocket.recv()
print(f"{ns.sim_time()} ns: Bob receives: {msg}")

# send a string message via a classical channel
message = f"Client: {client} you may start"
csocket.send(message)
print(f"{ns.sim_time()} ns: Server sends message: {message}")
# Generate and measure EPR pair with Alice
qubit = epr_socket.recv_keep()[0]
result = qubit.measure()
yield from connection.flush()
print(
f"{ns.sim_time()} ns: Bob finished EPR generation and measures his qubit: {result}"
)

# Register a request to create an EPR pair, then apply a Hadamard gate on the epr qubit and measure
epr_qubit = epr_socket.create_keep()[0]
epr_qubit.H()
result = epr_qubit.measure()
yield from connection.flush()
print(f"{ns.sim_time()} ns: Server measures local EPR qubit: {result}")

return {}
class CharlieProgram(Program):
PEER = "Alice"

@property
def meta(self) -> ProgramMeta:
return ProgramMeta(
name="tutorial_program",
csockets=[self.PEER],
epr_sockets=[self.PEER],
max_qubits=1,
)

def run(self, context: ProgramContext):
# get classical sockets
csocket = context.csockets[self.PEER]
# get EPR sockets
epr_socket = context.epr_sockets[self.PEER]
# get connection to quantum network processing unit
connection = context.connection

msg = yield from csocket.recv()
print(f"{ns.sim_time()} ns: Charlie receives: {msg}")

# Generate EPR pair with Alice
qubit = epr_socket.recv_keep()[0]
yield from connection.flush()
print(f"{ns.sim_time()} ns: Carlie finished EPR generation")

# Receive corrections from Alice
m2 = yield from csocket.recv()
m1 = yield from csocket.recv()
print(f"{ns.sim_time()} ns: Carlie received corrections: m1: {m1}, m2: {m2}")

# Apply corrections
if int(m1):
qubit.X()
if int(m2):
qubit.Z()

# Measure the EPR qubit
result = qubit.measure()
yield from connection.flush()
print(
f"{ns.sim_time()} ns: Carlie applied corrections and measures his qubit {result}"
)
73 changes: 73 additions & 0 deletions examples/tutorial/4.3_multi-node/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# 3 node network, all the sources of noise have been disabled for this example
qdevice_cfg: &qdevice_cfg
num_qubits: 2

# coherence times (same for each qubit)
T1: 0
T2: 0

# gate execution times
init_time: 10_000
single_qubit_gate_time: 1_000
two_qubit_gate_time: 100_000
measure_time: 10_000

# noise model
single_qubit_gate_depolar_prob: 0.
two_qubit_gate_depolar_prob: 0.

stacks:
- name: Alice
qdevice_typ: generic
qdevice_cfg:
<<: *qdevice_cfg

- name: Bob
qdevice_typ: generic
qdevice_cfg:
<<: *qdevice_cfg

- name: Charlie
qdevice_typ: generic
qdevice_cfg:
<<: *qdevice_cfg


link_cfg: &link_cfg
fidelity: 1
prob_success: 0.3
t_cycle: 1e5

links:
- stack1: Alice
stack2: Bob
typ: depolarise
cfg:
<<: *link_cfg
- stack1: Alice
stack2: Charlie
typ: depolarise
cfg:
<<: *link_cfg
- stack1: Bob
stack2: Charlie
typ: depolarise
cfg:
<<: *link_cfg

clinks:
- stack1: Alice
stack2: Bob
typ: default
cfg:
delay: 5e3
- stack1: Alice
stack2: Charlie
typ: default
cfg:
delay: 1e4
- stack1: Bob
stack2: Charlie
typ: default
cfg:
delay: 1e4
32 changes: 11 additions & 21 deletions examples/tutorial/4.3_multi-node/run_simulation.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
from application import ClientProgram, ServerProgram
from netsquid_netbuilder.modules.clinks.default import DefaultCLinkConfig
from netsquid_netbuilder.modules.links.perfect import PerfectLinkConfig
from netsquid_netbuilder.util.network_generation import create_complete_graph_network
from application import AliceProgram, BobProgram, CharlieProgram

from squidasm.run.stack.config import StackNetworkConfig
from squidasm.run.stack.run import run

num_nodes = 6
node_names = [f"Node_{i}" for i in range(num_nodes)]

# import network configuration from file
cfg = create_complete_graph_network(
node_names,
"perfect",
PerfectLinkConfig(state_delay=100),
clink_typ="default",
clink_cfg=DefaultCLinkConfig(delay=100),
)
cfg = StackNetworkConfig.from_file("config.yaml")

server_name = node_names[0]
client_names = node_names[1:]
# Create instances of programs to run
alice_program = AliceProgram()
bob_program = BobProgram()
charlie_program = CharlieProgram()

programs = {server_name: ServerProgram(clients=client_names)}
for client in client_names:
programs[client] = ClientProgram(client, server_name)

# Run the simulation. Programs argument is a mapping of network node labels to programs to run on that node
run(config=cfg, programs=programs, num_times=1)
run(
config=cfg,
programs={"Alice": alice_program, "Bob": bob_program, "Charlie": charlie_program},
num_times=1,
)
Loading

0 comments on commit 639ede3

Please sign in to comment.