diff --git a/docs/source/api_overview.rst b/docs/source/api_overview.rst index 8708f7f..e4b39a0 100644 --- a/docs/source/api_overview.rst +++ b/docs/source/api_overview.rst @@ -16,6 +16,8 @@ These objects have their documentation on `NETQASM SDK documentation 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}" + ) diff --git a/examples/tutorial/4.3_multi-node/config.yaml b/examples/tutorial/4.3_multi-node/config.yaml new file mode 100644 index 0000000..30c2500 --- /dev/null +++ b/examples/tutorial/4.3_multi-node/config.yaml @@ -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 \ No newline at end of file diff --git a/examples/tutorial/4.3_multi-node/run_simulation.py b/examples/tutorial/4.3_multi-node/run_simulation.py index 4ce312b..95b8e24 100644 --- a/examples/tutorial/4.3_multi-node/run_simulation.py +++ b/examples/tutorial/4.3_multi-node/run_simulation.py @@ -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, +) diff --git a/squidasm/util/util.py b/squidasm/util/util.py index 2624a9a..8e6a816 100644 --- a/squidasm/util/util.py +++ b/squidasm/util/util.py @@ -1,4 +1,4 @@ -"""Utility functions for examples""" +import itertools from typing import List import netsquid.qubits @@ -6,6 +6,9 @@ from netqasm.sdk.qubit import Qubit from netsquid.qubits import operators from netsquid.qubits import qubitapi as qapi +from netsquid_netbuilder.modules.clinks import ICLinkConfig +from netsquid_netbuilder.modules.links import ILinkConfig +from netsquid_netbuilder.modules.qdevices import IQDeviceConfig import squidasm.sim.stack.globals from squidasm.run.stack.config import ( @@ -28,6 +31,7 @@ def create_two_node_network( ) -> StackNetworkConfig: """ Create a network configuration with two nodes, with simple noise models. + :param node_names: List of str with the names of the two nodes :param link_noise: A number between 0 and 1 that indicates how noisy the generated EPR pairs are. :param qdevice_noise: A number between 0 and 1 that indicates how noisy the qubit operations on the nodes are. @@ -61,18 +65,65 @@ def create_two_node_network( return StackNetworkConfig(stacks=processing_nodes, links=[link], clinks=[clink]) +def create_complete_graph_network( + node_names: List[str], + link_typ: str, + link_cfg: ILinkConfig, + clink_typ: str = "instant", + clink_cfg: ICLinkConfig = None, + qdevice_typ: str = "generic", + qdevice_cfg: IQDeviceConfig = None, +) -> StackNetworkConfig: + """ + Create a complete graph network configuration. + The network generated will connect each node to each other node directly using the link and clink models provided. + + :param node_names: List of str with the names of the nodes. The amount of names will determine the amount of nodes. + :param link_typ: str specification of the link model to use for quantum links. + :param link_cfg: Configuration of the link model. + :param clink_typ: str specification of the clink model to use for classical communication. + :param clink_cfg: Configuration of the clink model. + :param qdevice_typ: str specification of the qdevice model to use for quantum devices. + :param qdevice_cfg: Configuration of qdevice. + :return: StackNetworkConfig object with a network. + """ + network_config = StackNetworkConfig(stacks=[], links=[], clinks=[]) + + assert len(node_names) > 0 + + for node_name in node_names: + qdevice_cfg = ( + GenericQDeviceConfig.perfect_config() + if qdevice_cfg is None + else qdevice_cfg + ) + node = StackConfig( + name=node_name, qdevice_typ=qdevice_typ, qdevice_cfg=qdevice_cfg + ) + network_config.stacks.append(node) + + for s1, s2 in itertools.combinations(node_names, 2): + link = LinkConfig(stack1=s1, stack2=s2, typ=link_typ, cfg=link_cfg) + network_config.links.append(link) + + clink = CLinkConfig(stack1=s1, stack2=s2, typ=clink_typ, cfg=clink_cfg) + network_config.clinks.append(clink) + + return network_config + + def get_qubit_state(q: Qubit, node_name, full_state=False) -> np.ndarray: """ Retrieves the underlying quantum state from a qubit in density matrix formalism. - This is only possible in simulation. + This is only possible in simulation. .. note:: The function gets the *current* qubit. So make sure the subroutine is flushed before calling the method. :param q: The qubit to get the state of or list of qubits. - :param node_name: Node name of current node. - Requirement for this parameter is due to software limitation, - can be made unnecessary in future version of SquidASM. + :param node_name: Node name of current node. + Requirement for this parameter is due to software limitation, + can be made unnecessary in future version of SquidASM. :param full_state: Flag to retrieve the full underlying entangled state and not only this qubit subspace. :return: An array that is the density matrix description of the quantum state """ @@ -101,7 +152,7 @@ def get_qubit_state(q: Qubit, node_name, full_state=False) -> np.ndarray: def get_reference_state(phi: float, theta: float) -> np.ndarray: """ Gives the reference quantum state for a qubit in density matrix formalism, - that is in a pure state matching a state on the Bloch sphere described by the angles phi and theta. + that is in a pure state matching a state on the Bloch sphere described by the angles phi and theta. :param phi: Angle on Bloch sphere between state and x-axis :param theta: Angle on Bloch sphere between state and z-axis