Skip to content

Commit

Permalink
* Added example for creating a fully functional server for one of the…
Browse files Browse the repository at this point in the history
… nodes
  • Loading branch information
mkvanhooft committed Jan 22, 2024
1 parent 44acb17 commit 2cf1b67
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 0 deletions.
4 changes: 4 additions & 0 deletions BETA-CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
This is a changelog that is separate from the main changelog, this is to log changes on the beta branch

2024-01-22 (b0.0.5)
-------------------
- Added an advanced example for creating a server that can handle simultaneous incoming requests from multiple clients

2023-12-04 (b0.0.4)
-------------------
- Changed interplay between `LinkLayer` and `MagicDistributor` so that completion of a delivery in the `LinkLayer` happens on completion of label_delivery instead of state_delivery
Expand Down
106 changes: 106 additions & 0 deletions examples/advanced/server/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from typing import List

from netqasm.sdk.connection import BaseNetQASMConnection
from netsquid_netbuilder.logger import LogManager
from netsquid_protocols import CSocketListener, QueueProtocol, SleepingProtocol

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

REQUEST_EPR_PAIR_GENERATION_MSG = "Request to initiate EPR pair generation"
CONFIRM_EPR_PAIR_GENERATION_MSG = "confirmation to initiate EPR pair generation"


class ClientProgram(Program):
def __init__(self, node_name: str, server_name: str, request_start_time=0):
self.node_name = node_name
self.server_name = server_name
self.logger = LogManager.get_stack_logger(f"Program {self.node_name}")
self.request_start_time = request_start_time

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

def run(self, context: ProgramContext):
csocket = context.csockets[self.server_name]
epr_socket = context.epr_sockets[self.server_name]
connection = context.connection

# Wait to the desired time using a WaitingProtocol
waiting_protocol = SleepingProtocol()
yield from waiting_protocol.sleep(end_time=self.request_start_time)

# submit the request to the server
message = REQUEST_EPR_PAIR_GENERATION_MSG
csocket.send(message)
self.logger.info(f"sending message {message} to {self.server_name}")

# wait for confirmation from the server
message = yield from csocket.recv()
self.logger.info(f"received message {message} from {self.server_name}")
if message != CONFIRM_EPR_PAIR_GENERATION_MSG:
raise RuntimeError(f"Expected confirmation but received: {message}")

# Execute the request
epr_qubit = epr_socket.create_keep()[0]
epr_qubit.H()
result = epr_qubit.measure()
yield from connection.flush()
self.logger.info(f"measures local EPR qubit: {result}")

return {}


class ServerProgram(Program):
def __init__(self, clients: List[str]):
self.clients = clients
self.node_name = "server"
self.logger = LogManager.get_stack_logger(f"Program {self.node_name}")

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

def run(self, context: ProgramContext):
connection: BaseNetQASMConnection = context.connection

# We use the Queue protocol to manage the queue and signal for new queue items
queue_protocol = QueueProtocol()
queue_protocol.start()

# Set up a CSocketListener for each client that will forward requests to the queue
for client in self.clients:
thread = CSocketListener(context, client, queue_protocol, self.logger)
thread.start()

while True:
# Wait for a new request if applicable and process the next item in the queue
client_name, msg = yield from queue_protocol.pop()
csocket = context.csockets[client_name]
epr_socket = context.epr_sockets[client_name]

if msg != REQUEST_EPR_PAIR_GENERATION_MSG:
raise RuntimeError(f"Received unsupported request: {msg}")
self.logger.info(f"start processing request from {client_name}")

# Send confirmation to the client
message = CONFIRM_EPR_PAIR_GENERATION_MSG
csocket.send(message)
self.logger.info(f"sending message {message} to {client_name}")

# Start processing the request
epr_qubit = epr_socket.recv_keep()[0]
epr_qubit.H()
result = epr_qubit.measure()
yield from connection.flush()
self.logger.info(f"measures local EPR qubit: {result}")
81 changes: 81 additions & 0 deletions examples/advanced/server/netsquid_protocols.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from logging import Logger
from typing import Generator, Tuple

from netqasm.sdk.classical_communication.socket import Socket
from netsquid.protocols import Protocol

from squidasm.sim.stack.program import ProgramContext


class SleepingProtocol(Protocol):
"""Netsquid protocol to support waiting or sleeping in a Program."""

def sleep(self, duration=0, end_time=0):
"""
Sleep for a certain duration or until a certain end time.
Specify either duration or end time, but not both.
Requires usage of `yield from` before method call to function.
:param duration: Time to sleep in nanoseconds.
:param end_time: Simulation time to sleep to in nanoseconds.
"""
yield self.await_timer(duration=duration, end_time=end_time)


class QueueProtocol(Protocol):
"""Netsquid protocol for a queue, that supports waiting for a new item, when the queue is empty."""

QUEUE_STATUS_CHANGE_SIGNAL = "Queue has been updated"

def __init__(self):
self._queue = []
self.add_signal(self.QUEUE_STATUS_CHANGE_SIGNAL)

def push(self, msg_source: str, msg: str):
"""
Put a new item on the queue.
:param msg_source: Source of the message.
:param msg: The message.
"""
self._queue.append((msg_source, msg))
self.send_signal(self.QUEUE_STATUS_CHANGE_SIGNAL)

def pop(self) -> Generator[None, None, Tuple[str, str]]:
"""
Take an item of the queue. Waits for a new item if the queue is empty.
:return: A tuple with the source of the message and the message.
"""
if len(self._queue) == 0:
yield self.await_signal(sender=self, signal_label=self.QUEUE_STATUS_CHANGE_SIGNAL) # fmt: skip
return self._queue.pop()


class CSocketListener(Protocol):
"""Netsquid protocol that listens to messages on a CSocket and forwards them to the QueueProtocol."""

def __init__(
self,
context: ProgramContext,
peer_name: str,
queue_protocol: QueueProtocol,
logger: Logger,
):
"""
:param context: ProgramContext of the current program.
:param peer_name: Name of the remote node this Listener should listen to.
:param queue_protocol: The QueueProtocol where messages are forwarded to.
:param logger: A Logger.
"""
self._context = context
self._peer_name = peer_name
self._queue_protocol = queue_protocol
self.logger = logger.getChild(f"PortListener({peer_name})")

def run(self):
csocket: Socket = self._context.csockets[self._peer_name]
while True:
message = yield from csocket.recv()
self._queue_protocol.push(self._peer_name, str(message))
self.logger.info(f"Received message: {message}")
39 changes: 39 additions & 0 deletions examples/advanced/server/run_simulation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import logging

import numpy.random
from application import ClientProgram, ServerProgram
from netsquid_magic.models.perfect import PerfectLinkConfig
from netsquid_netbuilder.modules.clinks.default import DefaultCLinkConfig
from netsquid_netbuilder.util.network_generation import create_complete_graph_network

from squidasm.run.stack.run import run

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

cfg = create_complete_graph_network(
node_names,
"perfect",
PerfectLinkConfig(state_delay=100),
clink_typ="default",
clink_cfg=DefaultCLinkConfig(delay=100),
)

server_name = node_names[0]
client_names = node_names[1:]

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

# Set log level for all programs
for program in programs.values():
program.logger.setLevel(logging.INFO)

if random_request_start_times:
# Set the clients to start their requests at a random time between 0 and 400 ns
for client in client_names:
programs[client].request_start_time = numpy.random.randint(0, 400)

run(config=cfg, programs=programs, num_times=1)

0 comments on commit 2cf1b67

Please sign in to comment.