From 7a99cdb3290acec4906edfdda38a3dd8bd855dfd Mon Sep 17 00:00:00 2001 From: Bart van der Vecht Date: Mon, 30 May 2022 10:00:55 +0200 Subject: [PATCH 1/8] Initial qoala runtime implementation --- examples/lir/bqc/bqc_5_5.lhr | 99 +++++ examples/lir/bqc/bqc_5_5.py | 217 ++++++++++ examples/lir/bqc/bqc_5_5_nv.py | 217 ++++++++++ examples/lir/bqc/bqc_5_5_nv_raw_lhr.py | 157 ++++++++ examples/lir/bqc/example_bqc.py | 299 ++++++++++++++ examples/lir/teleport/example_teleport.py | 155 ++++++++ squidasm/run/stack/lhrprogram.py | 465 ++++++++++++++++++++++ squidasm/sim/stack/host.py | 365 ++++++++++++++--- tests/lir/test_parse_lir.py | 190 +++++++++ 9 files changed, 2107 insertions(+), 57 deletions(-) create mode 100644 examples/lir/bqc/bqc_5_5.lhr create mode 100644 examples/lir/bqc/bqc_5_5.py create mode 100644 examples/lir/bqc/bqc_5_5_nv.py create mode 100644 examples/lir/bqc/bqc_5_5_nv_raw_lhr.py create mode 100644 examples/lir/bqc/example_bqc.py create mode 100644 examples/lir/teleport/example_teleport.py create mode 100644 squidasm/run/stack/lhrprogram.py create mode 100644 tests/lir/test_parse_lir.py diff --git a/examples/lir/bqc/bqc_5_5.lhr b/examples/lir/bqc/bqc_5_5.lhr new file mode 100644 index 00000000..676cb4a3 --- /dev/null +++ b/examples/lir/bqc/bqc_5_5.lhr @@ -0,0 +1,99 @@ +run_subroutine(vec<>) : + NETQASM_START + set C0 0 + set C1 1 + set C10 10 + array C10 @0 + array C1 @1 + store C0 @1[C0] + set R5 0 + set R6 0 + set R7 1 + set R8 0 + recv_epr C0 C0 C1 C0 + set R0 0 +LABEL7: + set R5 1 + beq R0 R5 LABEL1 + set R1 0 + set R2 0 + set R3 0 + set R4 0 +LABEL6: + set R5 10 + beq R4 R5 LABEL5 + add R1 R1 R0 + set R5 1 + add R4 R4 R5 + jmp LABEL6 +LABEL5: + set R5 1 + add R2 R0 R5 + set R4 0 +LABEL4: + set R5 10 + beq R4 R5 LABEL3 + add R3 R3 R2 + set R5 1 + add R4 R4 R5 + jmp LABEL4 +LABEL3: + wait_all @0[R1:R3] + set R5 0 + beq R0 R5 LABEL2 + set R5 0 + sub R1 R5 R0 + rot_y C0 8 4 + crot_y C0 R1 24 4 + rot_x C0 24 4 + crot_x C0 R1 8 4 + qfree C0 +LABEL2: + set R5 1 + add R0 R0 R5 + jmp LABEL7 +LABEL1: + qalloc C1 + init C1 + rot_y C1 8 4 + rot_x C1 16 4 + rot_y C1 8 4 + crot_x C0 C1 8 4 + rot_z C0 24 4 + rot_x C1 24 4 + rot_y C1 24 4 + ret_arr @0 + ret_arr @1 + NETQASM_END + +delta1 = recv_cmsg() + +run_subroutine(vec) : + return M0 -> m1 + NETQASM_START + set Q0 0 + rot_z Q0 {delta1} 4 + rot_y Q0 8 4 + rot_x Q0 16 4 + meas Q0 M0 + qfree Q0 + ret_reg M0 + NETQASM_END + +send_cmsg(m1) +delta2 = recv_cmsg() + +run_subroutine(vec) : + return M0 -> m2 + NETQASM_START + set Q1 1 + rot_z Q1 {delta2} 4 + rot_y Q1 8 4 + rot_x Q1 16 4 + meas Q1 M0 + qfree Q1 + ret_reg M0 + NETQASM_END + +return_result(m1) +return_result(m2) diff --git a/examples/lir/bqc/bqc_5_5.py b/examples/lir/bqc/bqc_5_5.py new file mode 100644 index 00000000..5e94ea05 --- /dev/null +++ b/examples/lir/bqc/bqc_5_5.py @@ -0,0 +1,217 @@ +from __future__ import annotations + +import math +from typing import List + +from netqasm.lang.operand import Template +from netqasm.sdk.qubit import Qubit + +from squidasm.run.stack import lhrprogram as lp +from squidasm.run.stack.config import ( + GenericQDeviceConfig, + LinkConfig, + StackConfig, + StackNetworkConfig, +) +from squidasm.run.stack.run import run +from squidasm.sim.stack.common import LogManager +from squidasm.sim.stack.program import ProgramContext, ProgramMeta + + +class ClientProgram(lp.SdkProgram): + PEER = "server" + + def __init__( + self, + alpha: float, + beta: float, + theta1: float, + r1: int, + ): + self._alpha = alpha + self._beta = beta + self._theta1 = theta1 + self._r1 = r1 + + @property + def meta(self) -> ProgramMeta: + return ProgramMeta( + name="client_program", + parameters={ + "alpha": self._alpha, + "beta": self._beta, + "theta1": self._theta1, + "r1": self._r1, + }, + csockets=[self.PEER], + epr_sockets=[self.PEER], + max_qubits=2, + ) + + def compile(self, context: ProgramContext) -> lp.LhrProgram: + conn = context.connection + epr_socket = context.epr_sockets[self.PEER] + + epr = epr_socket.create_keep()[0] + + epr.rot_Z(angle=self._theta1) + epr.H() + p1 = epr.measure(store_array=True) + + subrt = conn.compile() + subroutines = {"subrt": subrt} + + lhr_subrt = lp.LhrSubroutine(subrt, return_map={"p1": lp.LhrSharedMemLoc("M0")}) + instrs: List[lp.ClassicalLirOp] = [] + instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt)) + instrs.append(lp.AssignCValueOp("p1", p1)) + + delta1 = self._alpha - self._theta1 + self._r1 * math.pi + delta1_discrete = delta1 / (math.pi / 16) + + instrs.append(lp.AssignCValueOp("delta1", delta1_discrete)) + instrs.append(lp.MultiplyConstantCValueOp("p1", "p1", 16)) + instrs.append(lp.AddCValueOp("delta1", "delta1", "p1")) + instrs.append(lp.SendCMsgOp("delta1")) + + instrs.append(lp.ReceiveCMsgOp("m1")) + + beta = math.pow(-1, self._r1) * self._beta + delta2_discrete = beta / (math.pi / 16) + instrs.append(lp.AssignCValueOp("delta2", delta2_discrete)) + instrs.append( + lp.BitConditionalMultiplyConstantCValueOp( + result="delta2", value0="delta2", value1=-1, cond="m1" + ) + ) + instrs.append(lp.SendCMsgOp("delta2")) + + instrs.append(lp.ReturnResultOp("p1")) + + return lp.LhrProgram(instrs, subroutines, meta=self.meta) + + +class ServerProgram(lp.SdkProgram): + PEER = "client" + + def __init__(self) -> None: + pass + + @property + def meta(self) -> ProgramMeta: + return ProgramMeta( + name="server_program", + parameters={}, + csockets=[self.PEER], + epr_sockets=[self.PEER], + max_qubits=2, + ) + + def compile(self, context: ProgramContext) -> lp.LhrProgram: + conn = context.connection + epr_socket = context.epr_sockets[self.PEER] + + # Create EPR Pair + epr = epr_socket.recv_keep()[0] + q = Qubit(conn) + q.H() + + epr.cphase(q) + + subrt1 = conn.compile() + subroutines = {"subrt1": subrt1} + + instrs: List[lp.ClassicalLirOp] = [] + lhr_subrt1 = lp.LhrSubroutine(subrt1, return_map={}) + instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt1)) + + instrs.append(lp.ReceiveCMsgOp("delta1")) + + epr.rot_Z(n=Template("delta1"), d=4) + epr.H() + m1 = epr.measure(store_array=False) + + subrt2 = conn.compile() + subroutines["subrt2"] = subrt2 + + lhr_subrt2 = lp.LhrSubroutine( + subrt2, return_map={"m1": lp.LhrSharedMemLoc("M0")} + ) + instrs.append(lp.RunSubroutineOp(lp.LhrVector(["delta1"]), lhr_subrt2)) + instrs.append(lp.AssignCValueOp("m1", m1)) + instrs.append(lp.SendCMsgOp("m1")) + + instrs.append(lp.ReceiveCMsgOp("delta2")) + + q.rot_Z(n=Template("delta2"), d=4) + q.H() + m2 = q.measure(store_array=False) + subrt3 = conn.compile() + subroutines["subrt3"] = subrt3 + + lhr_subrt3 = lp.LhrSubroutine( + subrt3, return_map={"m2": lp.LhrSharedMemLoc("M0")} + ) + instrs.append(lp.RunSubroutineOp(lp.LhrVector(["delta2"]), lhr_subrt3)) + instrs.append(lp.AssignCValueOp("m2", m2)) + instrs.append(lp.ReturnResultOp("m1")) + instrs.append(lp.ReturnResultOp("m2")) + + return lp.LhrProgram(instrs, subroutines, meta=self.meta) + + +PI = math.pi +PI_OVER_2 = math.pi / 2 + + +def computation_round( + cfg: StackNetworkConfig, + num_times: int = 1, + alpha: float = 0.0, + beta: float = 0.0, + theta1: float = 0.0, +) -> None: + client_program = ClientProgram( + alpha=alpha, + beta=beta, + theta1=theta1, + r1=0, + ) + server_program = ServerProgram() + + _, server_results = run( + cfg, {"client": client_program, "server": server_program}, num_times=num_times + ) + + m2s = [result["m2"] for result in server_results] + num_zeros = len([m for m in m2s if m == 0]) + frac0 = round(num_zeros / num_times, 2) + frac1 = 1 - frac0 + print(f"dist (0, 1) = ({frac0}, {frac1})") + + +if __name__ == "__main__": + num_times = 1 + + LogManager.set_log_level("WARNING") + LogManager.log_to_file("dump.log") + + sender_stack = StackConfig( + name="client", + qdevice_typ="generic", + qdevice_cfg=GenericQDeviceConfig.perfect_config(), + ) + receiver_stack = StackConfig( + name="server", + qdevice_typ="generic", + qdevice_cfg=GenericQDeviceConfig.perfect_config(), + ) + link = LinkConfig( + stack1="client", + stack2="server", + typ="perfect", + ) + + cfg = StackNetworkConfig(stacks=[sender_stack, receiver_stack], links=[link]) + + computation_round(cfg, num_times, alpha=PI_OVER_2, beta=PI_OVER_2) diff --git a/examples/lir/bqc/bqc_5_5_nv.py b/examples/lir/bqc/bqc_5_5_nv.py new file mode 100644 index 00000000..7c5f9d3c --- /dev/null +++ b/examples/lir/bqc/bqc_5_5_nv.py @@ -0,0 +1,217 @@ +from __future__ import annotations + +import math +from typing import List + +from netqasm.lang.operand import Template +from netqasm.sdk.qubit import Qubit + +from squidasm.run.stack import lhrprogram as lp +from squidasm.run.stack.config import ( + LinkConfig, + NVQDeviceConfig, + StackConfig, + StackNetworkConfig, +) +from squidasm.run.stack.run import run +from squidasm.sim.stack.common import LogManager +from squidasm.sim.stack.program import ProgramContext, ProgramMeta + + +class ClientProgram(lp.SdkProgram): + PEER = "server" + + def __init__( + self, + alpha: float, + beta: float, + theta1: float, + r1: int, + ): + self._alpha = alpha + self._beta = beta + self._theta1 = theta1 + self._r1 = r1 + + @property + def meta(self) -> ProgramMeta: + return ProgramMeta( + name="client_program", + parameters={ + "alpha": self._alpha, + "beta": self._beta, + "theta1": self._theta1, + "r1": self._r1, + }, + csockets=[self.PEER], + epr_sockets=[self.PEER], + max_qubits=2, + ) + + def compile(self, context: ProgramContext) -> lp.LhrProgram: + conn = context.connection + epr_socket = context.epr_sockets[self.PEER] + + epr = epr_socket.create_keep()[0] + + epr.rot_Z(angle=self._theta1) + epr.H() + p1 = epr.measure(store_array=True) + + subrt = conn.compile() + subroutines = {"subrt": subrt} + + lhr_subrt = lp.LhrSubroutine(subrt, return_map={"p1": lp.LhrSharedMemLoc("M0")}) + instrs: List[lp.ClassicalLirOp] = [] + instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt)) + instrs.append(lp.AssignCValueOp("p1", p1)) + + delta1 = self._alpha - self._theta1 + self._r1 * math.pi + delta1_discrete = delta1 / (math.pi / 16) + + instrs.append(lp.AssignCValueOp("delta1", delta1_discrete)) + instrs.append(lp.MultiplyConstantCValueOp("p1", "p1", 16)) + instrs.append(lp.AddCValueOp("delta1", "delta1", "p1")) + instrs.append(lp.SendCMsgOp("delta1")) + + instrs.append(lp.ReceiveCMsgOp("m1")) + + beta = math.pow(-1, self._r1) * self._beta + delta2_discrete = beta / (math.pi / 16) + instrs.append(lp.AssignCValueOp("delta2", delta2_discrete)) + instrs.append( + lp.BitConditionalMultiplyConstantCValueOp( + result="delta2", value0="delta2", value1=-1, cond="m1" + ) + ) + instrs.append(lp.SendCMsgOp("delta2")) + + instrs.append(lp.ReturnResultOp("p1")) + + return lp.LhrProgram(instrs, subroutines, meta=self.meta) + + +class ServerProgram(lp.SdkProgram): + PEER = "client" + + def __init__(self) -> None: + pass + + @property + def meta(self) -> ProgramMeta: + return ProgramMeta( + name="server_program", + parameters={}, + csockets=[self.PEER], + epr_sockets=[self.PEER], + max_qubits=2, + ) + + def compile(self, context: ProgramContext) -> lp.LhrProgram: + conn = context.connection + epr_socket = context.epr_sockets[self.PEER] + + # Create EPR Pair + epr = epr_socket.recv_keep()[0] + q = Qubit(conn) + q.H() + + epr.cphase(q) + + subrt1 = conn.compile() + subroutines = {"subrt1": subrt1} + + instrs: List[lp.ClassicalLirOp] = [] + lhr_subrt1 = lp.LhrSubroutine(subrt1, return_map={}) + instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt1)) + + instrs.append(lp.ReceiveCMsgOp("delta1")) + + epr.rot_Z(n=Template("delta1"), d=4) + epr.H() + m1 = epr.measure(store_array=False) + + subrt2 = conn.compile() + subroutines["subrt2"] = subrt2 + + lhr_subrt2 = lp.LhrSubroutine( + subrt2, return_map={"m1": lp.LhrSharedMemLoc("M0")} + ) + instrs.append(lp.RunSubroutineOp(lp.LhrVector(["delta1"]), lhr_subrt2)) + instrs.append(lp.AssignCValueOp("m1", m1)) + instrs.append(lp.SendCMsgOp("m1")) + + instrs.append(lp.ReceiveCMsgOp("delta2")) + + q.rot_Z(n=Template("delta2"), d=4) + q.H() + m2 = q.measure(store_array=False) + subrt3 = conn.compile() + subroutines["subrt3"] = subrt3 + + lhr_subrt3 = lp.LhrSubroutine( + subrt3, return_map={"m2": lp.LhrSharedMemLoc("M0")} + ) + instrs.append(lp.RunSubroutineOp(lp.LhrVector(["delta2"]), lhr_subrt3)) + instrs.append(lp.AssignCValueOp("m2", m2)) + instrs.append(lp.ReturnResultOp("m1")) + instrs.append(lp.ReturnResultOp("m2")) + + return lp.LhrProgram(instrs, subroutines, meta=self.meta) + + +PI = math.pi +PI_OVER_2 = math.pi / 2 + + +def computation_round( + cfg: StackNetworkConfig, + num_times: int = 1, + alpha: float = 0.0, + beta: float = 0.0, + theta1: float = 0.0, +) -> None: + client_program = ClientProgram( + alpha=alpha, + beta=beta, + theta1=theta1, + r1=0, + ) + server_program = ServerProgram() + + _, server_results = run( + cfg, {"client": client_program, "server": server_program}, num_times=num_times + ) + + m2s = [result["m2"] for result in server_results] + num_zeros = len([m for m in m2s if m == 0]) + frac0 = round(num_zeros / num_times, 2) + frac1 = 1 - frac0 + print(f"dist (0, 1) = ({frac0}, {frac1})") + + +if __name__ == "__main__": + num_times = 1 + + LogManager.set_log_level("WARNING") + LogManager.log_to_file("dump.log") + + sender_stack = StackConfig( + name="client", + qdevice_typ="nv", + qdevice_cfg=NVQDeviceConfig.perfect_config(), + ) + receiver_stack = StackConfig( + name="server", + qdevice_typ="nv", + qdevice_cfg=NVQDeviceConfig.perfect_config(), + ) + link = LinkConfig( + stack1="client", + stack2="server", + typ="perfect", + ) + + cfg = StackNetworkConfig(stacks=[sender_stack, receiver_stack], links=[link]) + + computation_round(cfg, num_times, alpha=PI_OVER_2, beta=PI_OVER_2) diff --git a/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py b/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py new file mode 100644 index 00000000..d919a246 --- /dev/null +++ b/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py @@ -0,0 +1,157 @@ +from __future__ import annotations + +import math +import os +from typing import List + +from squidasm.run.stack import lhrprogram as lp +from squidasm.run.stack.config import ( + LinkConfig, + NVQDeviceConfig, + StackConfig, + StackNetworkConfig, +) +from squidasm.run.stack.run import run +from squidasm.sim.stack.common import LogManager +from squidasm.sim.stack.program import ProgramContext, ProgramMeta + + +class ClientProgram(lp.SdkProgram): + PEER = "server" + + def __init__( + self, + alpha: float, + beta: float, + theta1: float, + r1: int, + ): + self._alpha = alpha + self._beta = beta + self._theta1 = theta1 + self._r1 = r1 + + @property + def meta(self) -> ProgramMeta: + return ProgramMeta( + name="client_program", + parameters={ + "alpha": self._alpha, + "beta": self._beta, + "theta1": self._theta1, + "r1": self._r1, + }, + csockets=[self.PEER], + epr_sockets=[self.PEER], + max_qubits=2, + ) + + def compile(self, context: ProgramContext) -> lp.LhrProgram: + conn = context.connection + epr_socket = context.epr_sockets[self.PEER] + + epr = epr_socket.create_keep()[0] + + epr.rot_Z(angle=self._theta1) + epr.H() + _ = epr.measure(store_array=True) # p1 + + subrt = conn.compile() + subroutines = {"subrt": subrt} + + lhr_subrt = lp.LhrSubroutine(subrt, return_map={"p1": lp.LhrSharedMemLoc("M0")}) + instrs: List[lp.ClassicalLirOp] = [] + instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt)) + # instrs.append(lp.AssignCValueOp("p1", p1)) + + delta1 = self._alpha - self._theta1 + self._r1 * math.pi + delta1_discrete = delta1 / (math.pi / 16) + + instrs.append(lp.AssignCValueOp("delta1", delta1_discrete)) + instrs.append(lp.MultiplyConstantCValueOp("p1", "p1", 16)) + instrs.append(lp.AddCValueOp("delta1", "delta1", "p1")) + instrs.append(lp.SendCMsgOp("delta1")) + + instrs.append(lp.ReceiveCMsgOp("m1")) + + beta = math.pow(-1, self._r1) * self._beta + delta2_discrete = beta / (math.pi / 16) + instrs.append(lp.AssignCValueOp("delta2", delta2_discrete)) + instrs.append( + lp.BitConditionalMultiplyConstantCValueOp( + result="delta2", value0="delta2", value1=-1, cond="m1" + ) + ) + instrs.append(lp.SendCMsgOp("delta2")) + + instrs.append(lp.ReturnResultOp("p1")) + + return lp.LhrProgram(instrs, subroutines, meta=self.meta) + + +PI = math.pi +PI_OVER_2 = math.pi / 2 + + +def computation_round( + cfg: StackNetworkConfig, + num_times: int = 1, + alpha: float = 0.0, + beta: float = 0.0, + theta1: float = 0.0, +) -> None: + client_program = ClientProgram( + alpha=alpha, + beta=beta, + theta1=theta1, + r1=0, + ) + # server_program = ServerProgram() + lhr_file = os.path.join(os.path.dirname(__file__), "bqc_5_5.lhr") + with open(lhr_file) as file: + lhr_text = file.read() + server_program = lp.LhrParser(lhr_text).parse() + server_program.meta = ProgramMeta( + name="server_program", + parameters={}, + csockets=["client"], + epr_sockets=["client"], + max_qubits=2, + ) + + _, server_results = run( + cfg, {"client": client_program, "server": server_program}, num_times=num_times + ) + + m2s = [result["m2"] for result in server_results] + num_zeros = len([m for m in m2s if m == 0]) + frac0 = round(num_zeros / num_times, 2) + frac1 = 1 - frac0 + print(f"dist (0, 1) = ({frac0}, {frac1})") + + +if __name__ == "__main__": + num_times = 1 + + LogManager.set_log_level("DEBUG") + LogManager.log_to_file("dump.log") + + sender_stack = StackConfig( + name="client", + qdevice_typ="nv", + qdevice_cfg=NVQDeviceConfig.perfect_config(), + ) + receiver_stack = StackConfig( + name="server", + qdevice_typ="nv", + qdevice_cfg=NVQDeviceConfig.perfect_config(), + ) + link = LinkConfig( + stack1="client", + stack2="server", + typ="perfect", + ) + + cfg = StackNetworkConfig(stacks=[sender_stack, receiver_stack], links=[link]) + + computation_round(cfg, num_times, alpha=PI_OVER_2, beta=PI_OVER_2) diff --git a/examples/lir/bqc/example_bqc.py b/examples/lir/bqc/example_bqc.py new file mode 100644 index 00000000..1eb5d0f9 --- /dev/null +++ b/examples/lir/bqc/example_bqc.py @@ -0,0 +1,299 @@ +from __future__ import annotations + +import math +from typing import List + +from netqasm.lang.operand import Template + +from squidasm.run.stack import lhrprogram as lp +from squidasm.run.stack.config import ( + GenericQDeviceConfig, + LinkConfig, + StackConfig, + StackNetworkConfig, +) +from squidasm.run.stack.run import run +from squidasm.sim.stack.program import ProgramContext, ProgramMeta + + +class ClientProgram(lp.LirProgram): + PEER = "server" + + def __init__( + self, + alpha: float, + beta: float, + trap: bool, + dummy: int, + theta1: float, + theta2: float, + r1: int, + r2: int, + ): + self._alpha = alpha + self._beta = beta + self._trap = trap + self._dummy = dummy + self._theta1 = theta1 + self._theta2 = theta2 + self._r1 = r1 + self._r2 = r2 + + super().__init__([], {}) + + @property + def meta(self) -> ProgramMeta: + return ProgramMeta( + name="client_program", + parameters={ + "alpha": self._alpha, + "beta": self._beta, + "trap": self._trap, + "dummy": self._dummy, + "theta1": self._theta1, + "theta2": self._theta2, + "r1": self._r1, + "r2": self._r2, + }, + csockets=[self.PEER], + epr_sockets=[self.PEER], + max_qubits=2, + ) + + def compile(self, context: ProgramContext) -> None: + conn = context.connection + epr_socket = context.epr_sockets[self.PEER] + + # Create EPR pair + epr1 = epr_socket.create_keep()[0] + + # RSP + if self._trap and self._dummy == 2: + # remotely-prepare a dummy state + p2 = epr1.measure(store_array=False) + else: + epr1.rot_Z(angle=self._theta2) + epr1.H() + p2 = epr1.measure(store_array=False) + + # Create EPR pair + epr2 = epr_socket.create_keep()[0] + + # RSP + if self._trap and self._dummy == 1: + # remotely-prepare a dummy state + p1 = epr2.measure(store_array=False) + else: + epr2.rot_Z(angle=self._theta1) + epr2.H() + p1 = epr2.measure(store_array=False) + + subrt = conn.compile() + subroutines = {"subrt": subrt} + + instrs: List[lp.ClassicalLirOp] = [] + instrs.append(lp.RunSubroutineOp("subrt")) + instrs.append(lp.AssignCValueOp("p1", p1)) + instrs.append(lp.AssignCValueOp("p2", p2)) + + if self._trap and self._dummy == 2: + delta1 = -self._theta1 + self._r1 * math.pi + else: + delta1 = self._alpha - self._theta1 + self._r1 * math.pi + delta1_discrete = delta1 / (math.pi / 16) + + instrs.append(lp.AssignCValueOp("delta1", delta1_discrete)) + instrs.append(lp.MultiplyConstantCValueOp("p1", "p1", 16)) + instrs.append(lp.AddCValueOp("delta1", "delta1", "p1")) + instrs.append(lp.SendCMsgOp("delta1")) + + instrs.append(lp.ReceiveCMsgOp("m1")) + + if self._trap and self._dummy == 1: + delta2 = -self._theta2 + self._r2 * math.pi + delta2_discrete = delta2 / (math.pi / 16) + instrs.append(lp.AssignCValueOp("delta2", delta2_discrete)) + else: + beta = math.pow(-1, self._r1) * self._beta + beta_discrete = beta / (math.pi / 16) + instrs.append(lp.AssignCValueOp("beta", beta_discrete)) + instrs.append( + lp.BitConditionalMultiplyConstantCValueOp( + result="beta", value0="beta", value1=16, cond="m1" + ) + ) + delta2 = self._theta2 + self._r2 * math.pi + delta2_discrete = delta2 / (math.pi / 16) + instrs.append(lp.AssignCValueOp("delta2", delta2_discrete)) + instrs.append(lp.AddCValueOp("delta2", "delta2", "beta")) + + instrs.append(lp.MultiplyConstantCValueOp("p2", "p2", 16)) + instrs.append(lp.AddCValueOp("delta2", "delta2", "p2")) + instrs.append(lp.SendCMsgOp("delta2")) + + instrs.append(lp.ReturnResultOp("p1")) + instrs.append(lp.ReturnResultOp("p2")) + + self.instructions = instrs + self.subroutines = subroutines + + +class ServerProgram(lp.LirProgram): + PEER = "client" + + def __init__(self) -> None: + super().__init__([], {}) + + @property + def meta(self) -> ProgramMeta: + return ProgramMeta( + name="server_program", + parameters={}, + csockets=[self.PEER], + epr_sockets=[self.PEER], + max_qubits=2, + ) + + def compile(self, context: ProgramContext) -> None: + conn = context.connection + epr_socket = context.epr_sockets[self.PEER] + + # Create EPR Pair + epr1 = epr_socket.recv_keep()[0] + epr2 = epr_socket.recv_keep()[0] + epr2.cphase(epr1) + + subrt1 = conn.compile() + subroutines = {"subrt1": subrt1} + + instrs: List[lp.ClassicalLirOp] = [] + instrs.append(lp.RunSubroutineOp("subrt1")) + + instrs.append(lp.ReceiveCMsgOp("delta1")) + + epr2.rot_Z(n=Template("delta1"), d=4) + epr2.H() + m1 = epr2.measure(store_array=False) + + subrt2 = conn.compile() + subroutines["subrt2"] = subrt2 + + instrs.append(lp.RunSubroutineOp("subrt2")) + instrs.append(lp.AssignCValueOp("m1", m1)) + instrs.append(lp.SendCMsgOp("m1")) + + instrs.append(lp.ReceiveCMsgOp("delta2")) + + epr1.rot_Z(n=Template("delta2"), d=4) + epr1.H() + m2 = epr1.measure(store_array=False) + subrt3 = conn.compile() + subroutines["subrt3"] = subrt3 + + instrs.append(lp.RunSubroutineOp("subrt3")) + instrs.append(lp.AssignCValueOp("m2", m2)) + instrs.append(lp.ReturnResultOp("m1")) + instrs.append(lp.ReturnResultOp("m2")) + + self.instructions = instrs + self.subroutines = subroutines + + +PI = math.pi +PI_OVER_2 = math.pi / 2 + + +def computation_round( + cfg: StackNetworkConfig, + num_times: int = 1, + alpha: float = 0.0, + beta: float = 0.0, + theta1: float = 0.0, + theta2: float = 0.0, +) -> None: + client_program = ClientProgram( + alpha=alpha, + beta=beta, + trap=False, + dummy=-1, + theta1=theta1, + theta2=theta2, + r1=0, + r2=0, + ) + server_program = ServerProgram() + + _, server_results = run( + cfg, {"client": client_program, "server": server_program}, num_times=num_times + ) + + m2s = [result["m2"] for result in server_results] + num_zeros = len([m for m in m2s if m == 0]) + frac0 = round(num_zeros / num_times, 2) + frac1 = 1 - frac0 + print(f"dist (0, 1) = ({frac0}, {frac1})") + + +def trap_round( + cfg: StackNetworkConfig, + num_times: int = 1, + alpha: float = 0.0, + beta: float = 0.0, + theta1: float = 0.0, + theta2: float = 0.0, + dummy: int = 1, +) -> None: + client_program = ClientProgram( + alpha=alpha, + beta=beta, + trap=True, + dummy=dummy, + theta1=theta1, + theta2=theta2, + r1=0, + r2=0, + ) + server_program = ServerProgram() + + client_results, server_results = run( + cfg, {"client": client_program, "server": server_program}, num_times=num_times + ) + + p1s = [result["p1"] for result in client_results] + p2s = [result["p2"] for result in client_results] + m1s = [result["m1"] for result in server_results] + m2s = [result["m2"] for result in server_results] + + assert dummy in [1, 2] + if dummy == 1: + num_fails = len([(p, m) for (p, m) in zip(p1s, m2s) if p != m]) + else: + num_fails = len([(p, m) for (p, m) in zip(p2s, m1s) if p != m]) + + frac_fail = round(num_fails / num_times, 2) + print(f"fail rate: {frac_fail}") + + +if __name__ == "__main__": + num_times = 1 + + sender_stack = StackConfig( + name="client", + qdevice_typ="generic", + qdevice_cfg=GenericQDeviceConfig.perfect_config(), + ) + receiver_stack = StackConfig( + name="server", + qdevice_typ="generic", + qdevice_cfg=GenericQDeviceConfig.perfect_config(), + ) + link = LinkConfig( + stack1="client", + stack2="server", + typ="perfect", + ) + + cfg = StackNetworkConfig(stacks=[sender_stack, receiver_stack], links=[link]) + + # computation_round(cfg, num_times, alpha=PI_OVER_2, beta=PI_OVER_2) + trap_round(cfg, num_times, dummy=1) diff --git a/examples/lir/teleport/example_teleport.py b/examples/lir/teleport/example_teleport.py new file mode 100644 index 00000000..eefb276d --- /dev/null +++ b/examples/lir/teleport/example_teleport.py @@ -0,0 +1,155 @@ +from __future__ import annotations + +import math +from typing import List + +from netqasm.lang.operand import Template +from netqasm.sdk.qubit import Qubit +from netqasm.sdk.toolbox import set_qubit_state + +from squidasm.run.stack import lhrprogram as lp +from squidasm.run.stack.config import ( + GenericQDeviceConfig, + LinkConfig, + StackConfig, + StackNetworkConfig, +) +from squidasm.run.stack.run import run +from squidasm.sim.stack.common import LogManager +from squidasm.sim.stack.program import ProgramContext, ProgramMeta + + +class SenderProgram(lp.SdkProgram): + PEER = "receiver" + + def __init__( + self, + theta: float, + phi: float, + ): + self._theta = theta + self._phi = phi + + super().__init__([], {}) + + @property + def meta(self) -> ProgramMeta: + return ProgramMeta( + name="sender_program", + parameters={ + "theta": self._theta, + "phi": self._phi, + }, + csockets=[self.PEER], + epr_sockets=[self.PEER], + max_qubits=2, + ) + + def compile(self, context: ProgramContext) -> lp.LhrProgram: + conn = context.connection + epr_socket = context.epr_sockets[self.PEER] + + q = Qubit(conn) + set_qubit_state(q, self._phi, self._theta) + + e = epr_socket.create_keep()[0] + q.cnot(e) + q.H() + m1 = q.measure() + m2 = e.measure() + + subrt = conn.compile() + subroutines = {"subrt": subrt} + + instrs: List[lp.ClassicalLhrOp] = [] + instrs.append(lp.RunSubroutineOp("subrt")) + instrs.append(lp.AssignCValueOp("m1", m1)) + instrs.append(lp.AssignCValueOp("m2", m2)) + instrs.append(lp.SendCMsgOp("m1")) + instrs.append(lp.SendCMsgOp("m2")) + instrs.append(lp.ReturnResultOp("m1")) + instrs.append(lp.ReturnResultOp("m2")) + + self.instructions = instrs + self.subroutines = subroutines + return lp.LhrProgram(instrs, subroutines, self.meta) + + +class ReceiverProgram(lp.SdkProgram): + PEER = "sender" + + def __init__(self) -> None: + super().__init__([], {}) + + @property + def meta(self) -> ProgramMeta: + return ProgramMeta( + name="receiver_program", + parameters={}, + csockets=[self.PEER], + epr_sockets=[self.PEER], + max_qubits=1, + ) + + def compile(self, context: ProgramContext) -> lp.LhrProgram: + conn = context.connection + epr_socket = context.epr_sockets[self.PEER] + + e = epr_socket.recv_keep()[0] + subrt1 = conn.compile() + + subroutines = {"subrt1": subrt1} + + instrs: List[lp.ClassicalLhrOp] = [] + instrs.append(lp.RunSubroutineOp("subrt1")) + instrs.append(lp.ReceiveCMsgOp("m1")) + instrs.append(lp.ReceiveCMsgOp("m2")) + + m1 = conn.builder.new_register(init_value=Template("m1"), return_reg=False) + m2 = conn.builder.new_register(init_value=Template("m2"), return_reg=False) + + with m2.if_eq(1): + e.X() + with m1.if_eq(1): + e.Z() + + m = e.measure(store_array=False) + + subrt2 = conn.compile() + subroutines["subrt2"] = subrt2 + + instrs.append(lp.RunSubroutineOp("subrt2")) + instrs.append(lp.AssignCValueOp("m", m)) + instrs.append(lp.ReturnResultOp("m")) + + self.instructions = instrs + self.subroutines = subroutines + return lp.LhrProgram(instrs, subroutines, self.meta) + + +if __name__ == "__main__": + LogManager.set_log_level("INFO") + + sender_stack = StackConfig( + name="sender", + qdevice_typ="generic", + qdevice_cfg=GenericQDeviceConfig.perfect_config(), + ) + receiver_stack = StackConfig( + name="receiver", + qdevice_typ="generic", + qdevice_cfg=GenericQDeviceConfig.perfect_config(), + ) + link = LinkConfig( + stack1="sender", + stack2="receiver", + typ="perfect", + ) + + cfg = StackNetworkConfig(stacks=[sender_stack, receiver_stack], links=[link]) + + sender_program = SenderProgram(theta=math.pi, phi=0) + receiver_program = ReceiverProgram() + + results = run(cfg, {"sender": sender_program, "receiver": receiver_program}) + print(results) diff --git a/squidasm/run/stack/lhrprogram.py b/squidasm/run/stack/lhrprogram.py new file mode 100644 index 00000000..11d8251e --- /dev/null +++ b/squidasm/run/stack/lhrprogram.py @@ -0,0 +1,465 @@ +import abc +from typing import Dict, List, Optional, Union + +from netqasm.lang.instr.flavour import NVFlavour +from netqasm.lang.operand import Template +from netqasm.lang.parsing.text import parse_text_subroutine +from netqasm.lang.subroutine import Subroutine +from netqasm.sdk.futures import Future + +from squidasm.sim.stack.program import ProgramContext, ProgramMeta + +LhrValue = Union[int, Template, Future] + + +class LhrAttribute: + def __init__(self, value: LhrValue) -> None: + self._value = value + + @property + def value(self) -> LhrValue: + return self._value + + +class LhrSharedMemLoc: + def __init__(self, loc: str) -> None: + self._loc = loc + + @property + def loc(self) -> str: + return self._loc + + def __str__(self) -> str: + return str(self.loc) + + +class LhrVector: + def __init__(self, values: List[str]) -> None: + self._values = values + + @property + def values(self) -> List[str]: + return self._values + + def __str__(self) -> str: + return f"vec<{','.join(v for v in self.values)}>" + + +class LhrSubroutine: + def __init__( + self, subrt: Subroutine, return_map: Dict[str, LhrSharedMemLoc] + ) -> None: + self._subrt = subrt + self._return_map = return_map + + @property + def subroutine(self) -> Subroutine: + return self._subrt + + @property + def return_map(self) -> Dict[str, LhrSharedMemLoc]: + return self._return_map + + def __str__(self) -> str: + s = "\n" + for key, value in self.return_map.items(): + s += f"return {str(value)} -> {key}\n" + s += "NETQASM_START\n" + s += self.subroutine.print_instructions() + s += "\nNETQASM_END" + return s + + +class ClassicalLhrOp: + def __init__( + self, + arguments: Optional[List[str]] = None, + results: Optional[List[str]] = None, + attributes: Optional[List[LhrValue]] = None, + ) -> None: + self._arguments: List[str] + self._results: List[str] + self._attributes: List[LhrValue] + + if arguments is None: + self._arguments = [] + else: + self._arguments = arguments + + if results is None: + self._results = [] + else: + self._results = results + + if attributes is None: + self._attributes = [] + else: + self._attributes = attributes + + def __str__(self) -> str: + results = ", ".join(str(r) for r in self.results) + args = ", ".join(str(a) for a in self.arguments) + attrs = ", ".join(str(a) for a in self.attributes) + s = "" + if len(results) > 0: + s += f"{results} = " + + s += f"{self.op_name}({args})" + + if len(attrs) > 0: + s += f" : {attrs}" + return s + + @property + def op_name(self) -> str: + return self.__class__.OP_NAME + + @property + def arguments(self) -> List[str]: + return self._arguments + + @property + def results(self) -> List[str]: + return self._results + + @property + def attributes(self) -> List[LhrValue]: + return self._attributes + + +class SendCMsgOp(ClassicalLhrOp): + OP_NAME = "send_cmsg" + + def __init__(self, value: str) -> None: + super().__init__(arguments=[value]) + + @classmethod + def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): + assert result is None + assert len(args) == 1 + assert attr is None + return cls(args[0]) + + +class ReceiveCMsgOp(ClassicalLhrOp): + OP_NAME = "recv_cmsg" + + def __init__(self, result: str) -> None: + super().__init__(results=[result]) + + @classmethod + def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): + assert result is not None + assert len(args) == 0 + assert attr is None + return cls(result) + + +class AddCValueOp(ClassicalLhrOp): + OP_NAME = "add_cval_c" + + def __init__(self, result: str, value0: str, value1: str) -> None: + super().__init__(arguments=[value0, value1], results=[result]) + + @classmethod + def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): + assert result is not None + assert len(args) == 2 + assert attr is None + return cls(result, args[0], args[1]) + + +class MultiplyConstantCValueOp(ClassicalLhrOp): + OP_NAME = "mult_const" + + def __init__(self, result: str, value0: str, value1: LhrAttribute) -> None: + super().__init__(arguments=[value0, value1], results=[result]) + + @classmethod + def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): + assert result is not None + assert len(args) == 2 + assert attr is None + return cls(result, args[0], args[1]) + + +class BitConditionalMultiplyConstantCValueOp(ClassicalLhrOp): + OP_NAME = "bcond_mult_const" + + def __init__( + self, result: str, value0: str, value1: LhrAttribute, cond: str + ) -> None: + super().__init__(arguments=[value0, value1, cond], results=[result]) + + @classmethod + def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): + assert result is not None + assert len(args) == 3 + assert attr is None + return cls(result, args[0], args[1], args[2]) + + +class AssignCValueOp(ClassicalLhrOp): + OP_NAME = "assign_cval" + + def __init__(self, result: str, value: LhrValue) -> None: + super().__init__(results=[result], attributes=[value]) + + @classmethod + def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): + assert result is not None + assert len(args) == 0 + assert attr is not None + return cls(result, attr) + + +class RunSubroutineOp(ClassicalLhrOp): + OP_NAME = "run_subroutine" + + def __init__(self, values: LhrVector, subrt: LhrSubroutine) -> None: + super().__init__(arguments=[values], attributes=[subrt]) + + @classmethod + def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): + assert result is None + assert len(args) == 1 + assert isinstance(args[0], LhrVector) + assert attr is not None + return cls(args[0], attr) + + def __str__(self) -> str: + return super().__str__() + + +class ReturnResultOp(ClassicalLhrOp): + OP_NAME = "return_result" + + def __init__(self, value: str) -> None: + super().__init__(arguments=[value]) + + @classmethod + def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): + assert result is None + assert len(args) == 1 + assert attr is None + return cls(args[0]) + + +LIR_OP_NAMES = { + cls.OP_NAME: cls + for cls in [ + SendCMsgOp, + ReceiveCMsgOp, + AddCValueOp, + MultiplyConstantCValueOp, + BitConditionalMultiplyConstantCValueOp, + AssignCValueOp, + RunSubroutineOp, + ReturnResultOp, + ] +} + + +class LhrProgram: + def __init__( + self, + instructions: List[ClassicalLhrOp], + subroutines: Dict[str, Subroutine], + meta: Optional[ProgramMeta] = None, + ) -> None: + self._instructions: List[ClassicalLhrOp] = instructions + self._subroutines: Dict[str, Subroutine] = subroutines + self._meta: Optional[ProgramMeta] = meta + + @property + def meta(self) -> ProgramMeta: + if self._meta is None: + raise NotImplementedError + return self._meta + + @meta.setter + def meta(self, new_meta: ProgramMeta) -> None: + self._meta = new_meta + + @property + def instructions(self) -> List[ClassicalLhrOp]: + return self._instructions + + @instructions.setter + def instructions(self, new_instrs) -> None: + self._instructions = new_instrs + + @property + def subroutines(self) -> Dict[str, Subroutine]: + return self._subroutines + + @subroutines.setter + def subroutines(self, new_subroutines) -> None: + self._subroutines = new_subroutines + + def __str__(self) -> str: + # instrs = [ + # f"{str(i)}\n{self.subroutines[i.arguments[0]]}" # inline subroutine contents + # if isinstance(i, RunSubroutineOp) + # else str(i) + # for i in self.instructions + # ] + + # return "\n".join(" " + i for i in instrs) + return "\n".join(" " + str(i) for i in self.instructions) + + def compile(self, context: ProgramContext) -> None: + raise NotImplementedError + + +class EndOfTextException(Exception): + pass + + +class LhrParser: + def __init__(self, text: str) -> None: + self._text = text + lines = [line.strip() for line in text.split("\n")] + self._lines = [line for line in lines if len(line) > 0] + self._lineno: int = 0 + + def _next_line(self) -> None: + self._lineno += 1 + if self._lineno >= len(self._lines): + raise EndOfTextException + + def _parse_lir(self) -> ClassicalLhrOp: + line = self._lines[self._lineno] + + assign_parts = [x.strip() for x in line.split("=")] + assert len(assign_parts) <= 2 + if len(assign_parts) == 1: + value = assign_parts[0] + result = None + elif len(assign_parts) == 2: + value = assign_parts[1] + result = assign_parts[0] + value_parts = [x.strip() for x in value.split(":")] + assert len(value_parts) <= 2 + if len(value_parts) == 2: + value = value_parts[0] + attr = value_parts[1] + try: + attr = int(attr) + except ValueError: + pass + else: + value = value_parts[0] + attr = None + + op_parts = [x.strip() for x in value.split("(")] + assert len(op_parts) == 2 + op = op_parts[0] + arguments = op_parts[1].rstrip(")") + if len(arguments) == 0: + args = [] + else: + args = [x.strip() for x in arguments.split(",")] + + def parse_arg(arg): + if arg.startswith("vec<"): + vec_values_str = arg[4:-1] + if len(vec_values_str) == 0: + vec_values = [] + else: + vec_values = [x.strip() for x in vec_values_str.split(",")] + return LhrVector(vec_values) + return arg + + args = [parse_arg(arg) for arg in args] + + # print(f"result = {result}, op = {op}, args = {args}, attr = {attr}") + + lir_op = LIR_OP_NAMES[op].from_generic_args(result, args, attr) + return lir_op + + def _read_line(self) -> str: + self._next_line() + return self._lines[self._lineno] + + def _parse_subroutine(self) -> LhrSubroutine: + return_dict: Dict[str, LhrSharedMemLoc] = {} + while (line := self._read_line()) != "NETQASM_START": + ret_text = "return " + assert line.startswith(ret_text) + map_text = line[len(ret_text) :] + map_parts = [x.strip() for x in map_text.split("->")] + assert len(map_parts) == 2 + shared_loc = map_parts[0] + variable = map_parts[1] + return_dict[variable] = LhrSharedMemLoc(shared_loc) + subrt_lines = [] + while True: + line = self._read_line() + if line == "NETQASM_END": + break + subrt_lines.append(line) + subrt_text = "\n".join(subrt_lines) + try: + subrt = parse_text_subroutine(subrt_text) + except KeyError: + subrt = parse_text_subroutine(subrt_text, flavour=NVFlavour()) + return LhrSubroutine(subrt, return_dict) + + def parse(self) -> LhrProgram: + instructions = [] + subroutines = {} + + try: + while True: + instr = self._parse_lir() + if isinstance(instr, RunSubroutineOp): + subrt = self._parse_subroutine() + instr = RunSubroutineOp(instr.arguments[0], subrt) + instructions.append(instr) + self._next_line() + except EndOfTextException: + pass + + return LhrProgram(instructions, subroutines) + + +class SdkProgram(abc.ABC): + @property + def meta(self) -> ProgramMeta: + raise NotImplementedError + + def compile(self, context: ProgramContext) -> LhrProgram: + raise NotImplementedError + + +if __name__ == "__main__": + ops = [] + ops.append(SendCMsgOp("my_value")) + ops.append(ReceiveCMsgOp("received_value")) + ops.append(AssignCValueOp("new_value", 3)) + ops.append(AddCValueOp("my_value", "new_value", "new_value")) + + subrt_text = """ + set Q0 0 + rot_z Q0 {my_value} 4 + meas Q0 M0 + ret_reg M0 + """ + subrt = parse_text_subroutine(subrt_text) + lhr_subrt = LhrSubroutine(subrt, {"m": LhrSharedMemLoc("M0")}) + ops.append(RunSubroutineOp(LhrVector(["my_value"]), lhr_subrt)) + ops.append(ReturnResultOp("m")) + + program = LhrProgram( + instructions=ops, + subroutines={"subrt1": subrt}, + ) + print("original program:") + print(program) + + text = str(program) + parsed_program = LhrParser(text).parse() + + print("\nto text and parsed back:") + print(parsed_program) diff --git a/squidasm/sim/stack/host.py b/squidasm/sim/stack/host.py index 4e22c305..44b23747 100644 --- a/squidasm/sim/stack/host.py +++ b/squidasm/sim/stack/host.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging from typing import Any, Dict, Generator, List, Optional, Type from netqasm.backend.messages import ( @@ -7,13 +8,16 @@ OpenEPRSocketMessage, StopAppMessage, ) +from netqasm.lang.operand import Register +from netqasm.lang.parsing.text import NetQASMSyntaxError, parse_register from netqasm.sdk.epr_socket import EPRSocket from netqasm.sdk.transpile import NVSubroutineTranspiler, SubroutineTranspiler from netsquid.components.component import Component, Port from netsquid.nodes import Node from pydynaa import EventExpression -from squidasm.sim.stack.common import ComponentProtocol, PortListener +from squidasm.run.stack import lhrprogram as lp +from squidasm.sim.stack.common import ComponentProtocol, LogManager, PortListener from squidasm.sim.stack.connection import QnosConnection from squidasm.sim.stack.context import NetSquidContext from squidasm.sim.stack.csocket import ClassicalSocket @@ -21,6 +25,177 @@ from squidasm.sim.stack.signals import SIGNAL_HAND_HOST_MSG, SIGNAL_HOST_HOST_MSG +class LhrProcess: + def __init__(self, host: Host, program: lp.LhrProgram) -> None: + self._host = host + self._name = f"{host._comp.name}_Lhr" + self._logger: logging.Logger = LogManager.get_stack_logger( + f"{self.__class__.__name__}({self._name})" + ) + self._program = program + self._program_results: List[Dict[str, Any]] = [] + + def setup(self) -> Generator[EventExpression, None, None]: + program = self._program + prog_meta = program.meta + + # Register the new program (called 'application' by QNodeOS) with QNodeOS. + self._host.send_qnos_msg( + bytes(InitNewAppMessage(max_qubits=prog_meta.max_qubits)) + ) + self._app_id = yield from self._host.receive_qnos_msg() + self._logger.debug(f"got app id from qnos: {self._app_id}") + + # Set up the connection with QNodeOS. + conn = QnosConnection( + host=self._host, + app_id=self._app_id, + app_name=prog_meta.name, + max_qubits=prog_meta.max_qubits, + compiler=self._host._compiler, + ) + + # Create EPR sockets that can be used by the program SDK code. + epr_sockets: Dict[int, EPRSocket] = {} + for i, remote_name in enumerate(prog_meta.epr_sockets): + remote_id = None + nodes = NetSquidContext.get_nodes() + for id, name in nodes.items(): + if name == remote_name: + remote_id = id + assert remote_id is not None + self._host.send_qnos_msg( + bytes(OpenEPRSocketMessage(self._app_id, i, remote_id)) + ) + epr_sockets[remote_name] = EPRSocket(remote_name, i) + epr_sockets[remote_name].conn = conn + + # Create classical sockets that can be used by the program SDK code. + classical_sockets: Dict[int, ClassicalSocket] = {} + for i, remote_name in enumerate(prog_meta.csockets): + remote_id = None + nodes = NetSquidContext.get_nodes() + for id, name in nodes.items(): + if name == remote_name: + remote_id = id + assert remote_id is not None + classical_sockets[remote_name] = ClassicalSocket( + self._host, prog_meta.name, remote_name + ) + + self._context = ProgramContext( + netqasm_connection=conn, + csockets=classical_sockets, + epr_sockets=epr_sockets, + app_id=self._app_id, + ) + + self._memory: Dict[str, Any] = {} + + def run(self, num_times: int) -> Generator[EventExpression, None, None]: + for _ in range(num_times): + yield from self.setup() + result = yield from self.execute_program() + self._program_results.append(result) + self._host.send_qnos_msg(bytes(StopAppMessage(self._app_id))) + + def run_with_context( + self, context: ProgramContext, num_times: int + ) -> Generator[EventExpression, None, None]: + self._memory: Dict[str, Any] = {} + self._context = context + for _ in range(num_times): + result = yield from self.execute_program() + self._program_results.append(result) + self._host.send_qnos_msg(bytes(StopAppMessage(context.app_id))) + + @property + def context(self) -> ProgramContext: + return self._context + + @property + def memory(self) -> Dict[str, Any]: + return self._memory + + @property + def program(self) -> ProgramContext: + return self._program + + def execute_program(self) -> Generator[EventExpression, None, Dict[str, Any]]: + context = self._context + memory = self._memory + program = self._program + + csockets = list(context.csockets.values()) + csck = csockets[0] if len(csockets) > 0 else None + conn = context.connection + + results: Dict[str, Any] = {} + + for instr in program.instructions: + self._logger.info(f"Interpreting LIR instruction {instr}") + if isinstance(instr, lp.SendCMsgOp): + value = memory[instr.arguments[0]] + self._logger.info(f"sending msg {value}") + csck.send(value) + elif isinstance(instr, lp.ReceiveCMsgOp): + msg = yield from csck.recv() + msg = int(msg) + memory[instr.results[0]] = msg + self._logger.info(f"received msg {msg}") + elif isinstance(instr, lp.AddCValueOp): + arg0 = memory[instr.arguments[0]] + arg1 = memory[instr.arguments[1]] + memory[instr.results[0]] = arg0 + arg1 + elif isinstance(instr, lp.MultiplyConstantCValueOp): + arg0 = memory[instr.arguments[0]] + arg1 = instr.arguments[1] + memory[instr.results[0]] = arg0 * arg1 + elif isinstance(instr, lp.BitConditionalMultiplyConstantCValueOp): + arg0 = memory[instr.arguments[0]] + arg1 = instr.arguments[1] + cond = memory[instr.arguments[2]] + if cond == 1: + memory[instr.results[0]] = arg0 * arg1 + elif isinstance(instr, lp.AssignCValueOp): + value = instr.attributes[0] + # if isinstance(value, str) and value.startswith("RegFuture__"): + # reg_str = value[len("RegFuture__") :] + memory[instr.results[0]] = instr.attributes[0] + elif isinstance(instr, lp.RunSubroutineOp): + arg_vec: lp.LirVector = instr.arguments[0] + args = arg_vec.values + lhr_subrt: lp.LhrSubroutine = instr.attributes[0] + subrt = lhr_subrt.subroutine + self._logger.info(f"executing subroutine {subrt}") + + arg_values = {arg: memory[arg] for arg in args} + + self._logger.warning( + f"instantiating subroutine with values {arg_values}" + ) + subrt.instantiate(conn.app_id, arg_values) + + yield from conn.commit_subroutine(subrt) + + for key, mem_loc in lhr_subrt.return_map.items(): + try: + reg: Register = parse_register(mem_loc.loc) + value = conn.shared_memory.get_register(reg) + self._logger.debug( + f"writing shared memory value {value} from location " + f"{mem_loc} to variable {key}" + ) + memory[key] = value + except NetQASMSyntaxError: + pass + elif isinstance(instr, lp.ReturnResultOp): + value = instr.arguments[0] + results[value] = int(memory[value]) + + return results + + class HostComponent(Component): """NetSquid compmonent representing a Host. @@ -111,6 +286,132 @@ def send_peer_msg(self, msg: str) -> None: def receive_peer_msg(self) -> Generator[EventExpression, None, str]: return (yield from self._receive_msg("peer", SIGNAL_HOST_HOST_MSG)) + def run_sdk_program( + self, program: Program + ) -> Generator[EventExpression, None, None]: + prog_meta = program.meta + + # Register the new program (called 'application' by QNodeOS) with QNodeOS. + self.send_qnos_msg(bytes(InitNewAppMessage(max_qubits=prog_meta.max_qubits))) + app_id = yield from self.receive_qnos_msg() + self._logger.debug(f"got app id from qnos: {app_id}") + + # Set up the Connection object to be used by the program SDK code. + conn = QnosConnection( + self, + app_id, + prog_meta.name, + max_qubits=prog_meta.max_qubits, + compiler=self._compiler, + ) + + # Create EPR sockets that can be used by the program SDK code. + epr_sockets: Dict[int, EPRSocket] = {} + for i, remote_name in enumerate(prog_meta.epr_sockets): + remote_id = None + nodes = NetSquidContext.get_nodes() + for id, name in nodes.items(): + if name == remote_name: + remote_id = id + assert remote_id is not None + self.send_qnos_msg(bytes(OpenEPRSocketMessage(app_id, i, remote_id))) + epr_sockets[remote_name] = EPRSocket(remote_name, i) + epr_sockets[remote_name].conn = conn + + # Create classical sockets that can be used by the program SDK code. + classical_sockets: Dict[int, ClassicalSocket] = {} + for i, remote_name in enumerate(prog_meta.csockets): + remote_id = None + nodes = NetSquidContext.get_nodes() + for id, name in nodes.items(): + if name == remote_name: + remote_id = id + assert remote_id is not None + classical_sockets[remote_name] = ClassicalSocket( + self, prog_meta.name, remote_name + ) + + context = ProgramContext( + netqasm_connection=conn, + csockets=classical_sockets, + epr_sockets=epr_sockets, + app_id=app_id, + ) + + # Run the program by evaluating its run() method. + result = yield from program.run(context) + self._program_results.append(result) + + # Tell QNodeOS the program has finished. + self.send_qnos_msg(bytes(StopAppMessage(app_id))) + + def run_lhr_sdk_program( + self, + program: lp.LhrProgram, + ) -> Generator[EventExpression, None, None]: + prog_meta = program.meta + + # Register the new program (called 'application' by QNodeOS) with QNodeOS. + self.send_qnos_msg(bytes(InitNewAppMessage(max_qubits=prog_meta.max_qubits))) + app_id = yield from self.receive_qnos_msg() + self._logger.debug(f"got app id from qnos: {app_id}") + + # Set up the Connection object to be used by the program SDK code. + conn = QnosConnection( + self, + app_id, + prog_meta.name, + max_qubits=prog_meta.max_qubits, + compiler=self._compiler, + ) + + # Create EPR sockets that can be used by the program SDK code. + epr_sockets: Dict[int, EPRSocket] = {} + for i, remote_name in enumerate(prog_meta.epr_sockets): + remote_id = None + nodes = NetSquidContext.get_nodes() + for id, name in nodes.items(): + if name == remote_name: + remote_id = id + assert remote_id is not None + self.send_qnos_msg(bytes(OpenEPRSocketMessage(app_id, i, remote_id))) + epr_sockets[remote_name] = EPRSocket(remote_name, i) + epr_sockets[remote_name].conn = conn + + # Create classical sockets that can be used by the program SDK code. + classical_sockets: Dict[int, ClassicalSocket] = {} + for i, remote_name in enumerate(prog_meta.csockets): + remote_id = None + nodes = NetSquidContext.get_nodes() + for id, name in nodes.items(): + if name == remote_name: + remote_id = id + assert remote_id is not None + classical_sockets[remote_name] = ClassicalSocket( + self, prog_meta.name, remote_name + ) + + context = ProgramContext( + netqasm_connection=conn, + csockets=classical_sockets, + epr_sockets=epr_sockets, + app_id=app_id, + ) + + lhr_program = program.compile(context) + self._logger.warning(f"Runnign compiled SDK program:\n{lhr_program}") + process = LhrProcess(self, lhr_program) + yield from process.run_with_context(context, 1) + self._program_results = process._program_results + + def run_lhr_program( + self, program: lp.LhrProgram, num_times: int + ) -> Generator[EventExpression, None, None]: + self._logger.warning(f"Creating LHR process for program:\n{program}") + process = LhrProcess(self, program) + result = yield from process.run(num_times) + return result + def run(self) -> Generator[EventExpression, None, None]: """Run this protocol. Automatically called by NetSquid during simulation.""" @@ -120,63 +421,13 @@ def run(self) -> Generator[EventExpression, None, None]: self._num_pending -= 1 assert self._program is not None - prog_meta = self._program.meta - - # Register the new program (called 'application' by QNodeOS) with QNodeOS. - self.send_qnos_msg( - bytes(InitNewAppMessage(max_qubits=prog_meta.max_qubits)) - ) - app_id = yield from self.receive_qnos_msg() - self._logger.debug(f"got app id from qnos: {app_id}") - - # Set up the Connection object to be used by the program SDK code. - conn = QnosConnection( - self, - app_id, - prog_meta.name, - max_qubits=prog_meta.max_qubits, - compiler=self._compiler, - ) - - # Create EPR sockets that can be used by the program SDK code. - epr_sockets: Dict[int, EPRSocket] = {} - for i, remote_name in enumerate(prog_meta.epr_sockets): - remote_id = None - nodes = NetSquidContext.get_nodes() - for id, name in nodes.items(): - if name == remote_name: - remote_id = id - assert remote_id is not None - self.send_qnos_msg(bytes(OpenEPRSocketMessage(app_id, i, remote_id))) - epr_sockets[remote_name] = EPRSocket(remote_name, i) - epr_sockets[remote_name].conn = conn - - # Create classical sockets that can be used by the program SDK code. - classical_sockets: Dict[int, ClassicalSocket] = {} - for i, remote_name in enumerate(prog_meta.csockets): - remote_id = None - nodes = NetSquidContext.get_nodes() - for id, name in nodes.items(): - if name == remote_name: - remote_id = id - assert remote_id is not None - classical_sockets[remote_name] = ClassicalSocket( - self, prog_meta.name, remote_name - ) - - context = ProgramContext( - netqasm_connection=conn, - csockets=classical_sockets, - epr_sockets=epr_sockets, - app_id=app_id, - ) - - # Run the program by evaluating its run() method. - result = yield from self._program.run(context) - self._program_results.append(result) - # Tell QNodeOS the program has finished. - self.send_qnos_msg(bytes(StopAppMessage(app_id))) + if isinstance(self._program, lp.LhrProgram): + yield from self.run_lhr_program(self._program, 1) + elif isinstance(self._program, lp.SdkProgram): + yield from self.run_lhr_sdk_program(self._program) + else: + self.run_sdk_program(self._program) def enqueue_program(self, program: Program, num_times: int = 1): """Queue a program to be run the given number of times. diff --git a/tests/lir/test_parse_lir.py b/tests/lir/test_parse_lir.py new file mode 100644 index 00000000..bb3f5d6a --- /dev/null +++ b/tests/lir/test_parse_lir.py @@ -0,0 +1,190 @@ +from squidasm.run.stack import lhrprogram as lp +from squidasm.run.stack.config import ( + GenericQDeviceConfig, + LinkConfig, + StackConfig, + StackNetworkConfig, +) +from squidasm.run.stack.run import run +from squidasm.sim.stack.common import LogManager +from squidasm.sim.stack.program import ProgramMeta + + +def test_parse(): + program_text = """ +my_value = assign_cval() : 1 +send_cmsg(my_value) +received_value = recv_cmsg() +new_value = assign_cval() : 3 +my_value = add_cval_c(new_value, new_value) +run_subroutine(vec) : + return M0 -> m + NETQASM_START + set Q0 0 + rot_z Q0 {my_value} 4 + meas Q0 M0 + ret_reg M0 + NETQASM_END +return_result(m) + """ + parsed_program = lp.LhrParser(program_text).parse() + + print(parsed_program) + + +def test_run(): + program_text = """ +new_value = assign_cval() : 8 +my_value = add_cval_c(new_value, new_value) +run_subroutine(vec) : + return M0 -> m + NETQASM_START + set Q0 0 + qalloc Q0 + init Q0 + rot_x Q0 {my_value} 4 + meas Q0 M0 + ret_reg M0 + NETQASM_END +return_result(m) + """ + parsed_program = lp.LhrParser(program_text).parse() + parsed_program.meta = ProgramMeta( + name="client", parameters={}, csockets=[], epr_sockets=[], max_qubits=1 + ) + + sender_stack = StackConfig( + name="client", + qdevice_typ="generic", + qdevice_cfg=GenericQDeviceConfig.perfect_config(), + ) + + cfg = StackNetworkConfig(stacks=[sender_stack], links=[]) + result = run(cfg, programs={"client": parsed_program}) + print(result) + + +def test_run_two_nodes_classical(): + program_text_client = """ +new_value = assign_cval() : 8 +send_cmsg(new_value) + """ + program_client = lp.LhrParser(program_text_client).parse() + program_client.meta = ProgramMeta( + name="client", parameters={}, csockets=["server"], epr_sockets=[], max_qubits=1 + ) + client_stack = StackConfig( + name="client", + qdevice_typ="generic", + qdevice_cfg=GenericQDeviceConfig.perfect_config(), + ) + + program_text_server = """ +new_value = assign_cval() : 8 +value = recv_cmsg() +return_result(value) + """ + program_server = lp.LhrParser(program_text_server).parse() + program_server.meta = ProgramMeta( + name="client", parameters={}, csockets=["client"], epr_sockets=[], max_qubits=1 + ) + server_stack = StackConfig( + name="server", + qdevice_typ="generic", + qdevice_cfg=GenericQDeviceConfig.perfect_config(), + ) + + cfg = StackNetworkConfig(stacks=[client_stack, server_stack], links=[]) + result = run(cfg, programs={"client": program_client, "server": program_server}) + print(result) + + +def test_run_two_nodes_epr(): + program_text_client = """ +run_subroutine(vec<>) : + return M0 -> m + NETQASM_START + set R0 0 + set R1 1 + set R2 2 + set R3 20 + set R10 10 + array R10 @0 + array R1 @1 + array R3 @2 + store R0 @1[R0] + store R0 @2[R0] + store R1 @2[R1] + create_epr R1 R0 R1 R2 R0 + wait_all @0[R0:R10] + set Q0 0 + meas Q0 M0 + qfree Q0 + ret_reg M0 + NETQASM_END +return_result(m) + """ + program_client = lp.LhrParser(program_text_client).parse() + program_client.meta = ProgramMeta( + name="client", + parameters={}, + csockets=["server"], + epr_sockets=["server"], + max_qubits=1, + ) + client_stack = StackConfig( + name="client", + qdevice_typ="generic", + qdevice_cfg=GenericQDeviceConfig.perfect_config(), + ) + + program_text_server = """ +run_subroutine(vec<>) : + return M0 -> m + NETQASM_START + set R0 0 + set R1 1 + set R2 2 + set R10 10 + array R10 @0 + array R1 @1 + store R0 @1[R0] + recv_epr R0 R0 R1 R0 + wait_all @0[R0:R10] + set Q0 0 + meas Q0 M0 + qfree Q0 + ret_reg M0 + NETQASM_END +return_result(m) + """ + program_server = lp.LhrParser(program_text_server).parse() + program_server.meta = ProgramMeta( + name="client", + parameters={}, + csockets=["client"], + epr_sockets=["client"], + max_qubits=1, + ) + server_stack = StackConfig( + name="server", + qdevice_typ="generic", + qdevice_cfg=GenericQDeviceConfig.perfect_config(), + ) + link = LinkConfig( + stack1="client", + stack2="server", + typ="perfect", + ) + + cfg = StackNetworkConfig(stacks=[client_stack, server_stack], links=[link]) + result = run(cfg, programs={"client": program_client, "server": program_server}) + print(result) + + +if __name__ == "__main__": + LogManager.set_log_level("DEBUG") + # test_parse() + test_run() + # test_run_two_nodes_classical() + # test_run_two_nodes_epr() From 71b3bdd5ab33a90dea67fa03498089adc49ac6d8 Mon Sep 17 00:00:00 2001 From: Bart van der Vecht Date: Mon, 30 May 2022 22:10:41 +0200 Subject: [PATCH 2/8] Rename files to qoala --- examples/lir/bqc/bqc_5_5.py | 6 +- examples/lir/bqc/bqc_5_5_nv.py | 6 +- examples/lir/bqc/bqc_5_5_nv_raw_lhr.py | 4 +- examples/lir/bqc/example_bqc.py | 10 +- examples/lir/teleport/example_teleport.py | 2 +- .../run/{stack/lhrprogram.py => qoala/lhr.py} | 69 +- squidasm/sim/qoala/__init__.py | 0 .../sim/qoala/arch/qoala-runtime-data.drawio | 67 ++ squidasm/sim/qoala/arch/qoala2.drawio | 25 + squidasm/sim/qoala/common.py | 393 ++++++++ squidasm/sim/qoala/connection.py | 129 +++ squidasm/sim/qoala/context.py | 59 ++ squidasm/sim/qoala/csocket.py | 54 ++ squidasm/sim/qoala/egp.py | 110 +++ squidasm/sim/qoala/globals.py | 51 + squidasm/sim/qoala/handler.py | 289 ++++++ squidasm/sim/qoala/host.py | 440 +++++++++ squidasm/sim/qoala/netstack.py | 779 ++++++++++++++++ squidasm/sim/qoala/processor.py | 870 ++++++++++++++++++ squidasm/sim/qoala/program.py | 55 ++ squidasm/sim/qoala/qnos.py | 200 ++++ squidasm/sim/qoala/signals.py | 10 + squidasm/sim/qoala/stack.py | 233 +++++ squidasm/sim/stack/host.py | 34 +- .../test_parse_lhr.py} | 14 +- 25 files changed, 3867 insertions(+), 42 deletions(-) rename squidasm/run/{stack/lhrprogram.py => qoala/lhr.py} (88%) create mode 100644 squidasm/sim/qoala/__init__.py create mode 100644 squidasm/sim/qoala/arch/qoala-runtime-data.drawio create mode 100644 squidasm/sim/qoala/arch/qoala2.drawio create mode 100644 squidasm/sim/qoala/common.py create mode 100644 squidasm/sim/qoala/connection.py create mode 100644 squidasm/sim/qoala/context.py create mode 100644 squidasm/sim/qoala/csocket.py create mode 100644 squidasm/sim/qoala/egp.py create mode 100644 squidasm/sim/qoala/globals.py create mode 100644 squidasm/sim/qoala/handler.py create mode 100644 squidasm/sim/qoala/host.py create mode 100644 squidasm/sim/qoala/netstack.py create mode 100644 squidasm/sim/qoala/processor.py create mode 100644 squidasm/sim/qoala/program.py create mode 100644 squidasm/sim/qoala/qnos.py create mode 100644 squidasm/sim/qoala/signals.py create mode 100644 squidasm/sim/qoala/stack.py rename tests/{lir/test_parse_lir.py => qoala/test_parse_lhr.py} (94%) diff --git a/examples/lir/bqc/bqc_5_5.py b/examples/lir/bqc/bqc_5_5.py index 5e94ea05..5601b7d8 100644 --- a/examples/lir/bqc/bqc_5_5.py +++ b/examples/lir/bqc/bqc_5_5.py @@ -6,7 +6,7 @@ from netqasm.lang.operand import Template from netqasm.sdk.qubit import Qubit -from squidasm.run.stack import lhrprogram as lp +from squidasm.run.qoala import lhr as lp from squidasm.run.stack.config import ( GenericQDeviceConfig, LinkConfig, @@ -62,7 +62,7 @@ def compile(self, context: ProgramContext) -> lp.LhrProgram: subroutines = {"subrt": subrt} lhr_subrt = lp.LhrSubroutine(subrt, return_map={"p1": lp.LhrSharedMemLoc("M0")}) - instrs: List[lp.ClassicalLirOp] = [] + instrs: List[lp.ClassicalLhrOp] = [] instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt)) instrs.append(lp.AssignCValueOp("p1", p1)) @@ -121,7 +121,7 @@ def compile(self, context: ProgramContext) -> lp.LhrProgram: subrt1 = conn.compile() subroutines = {"subrt1": subrt1} - instrs: List[lp.ClassicalLirOp] = [] + instrs: List[lp.ClassicalLhrOp] = [] lhr_subrt1 = lp.LhrSubroutine(subrt1, return_map={}) instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt1)) diff --git a/examples/lir/bqc/bqc_5_5_nv.py b/examples/lir/bqc/bqc_5_5_nv.py index 7c5f9d3c..ff8dd743 100644 --- a/examples/lir/bqc/bqc_5_5_nv.py +++ b/examples/lir/bqc/bqc_5_5_nv.py @@ -6,7 +6,7 @@ from netqasm.lang.operand import Template from netqasm.sdk.qubit import Qubit -from squidasm.run.stack import lhrprogram as lp +from squidasm.run.qoala import lhr as lp from squidasm.run.stack.config import ( LinkConfig, NVQDeviceConfig, @@ -62,7 +62,7 @@ def compile(self, context: ProgramContext) -> lp.LhrProgram: subroutines = {"subrt": subrt} lhr_subrt = lp.LhrSubroutine(subrt, return_map={"p1": lp.LhrSharedMemLoc("M0")}) - instrs: List[lp.ClassicalLirOp] = [] + instrs: List[lp.ClassicalLhrOp] = [] instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt)) instrs.append(lp.AssignCValueOp("p1", p1)) @@ -121,7 +121,7 @@ def compile(self, context: ProgramContext) -> lp.LhrProgram: subrt1 = conn.compile() subroutines = {"subrt1": subrt1} - instrs: List[lp.ClassicalLirOp] = [] + instrs: List[lp.ClassicalLhrOp] = [] lhr_subrt1 = lp.LhrSubroutine(subrt1, return_map={}) instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt1)) diff --git a/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py b/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py index d919a246..59db8c74 100644 --- a/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py +++ b/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py @@ -4,7 +4,7 @@ import os from typing import List -from squidasm.run.stack import lhrprogram as lp +from squidasm.run.qoala import lhr as lp from squidasm.run.stack.config import ( LinkConfig, NVQDeviceConfig, @@ -60,7 +60,7 @@ def compile(self, context: ProgramContext) -> lp.LhrProgram: subroutines = {"subrt": subrt} lhr_subrt = lp.LhrSubroutine(subrt, return_map={"p1": lp.LhrSharedMemLoc("M0")}) - instrs: List[lp.ClassicalLirOp] = [] + instrs: List[lp.ClassicalLhrOp] = [] instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt)) # instrs.append(lp.AssignCValueOp("p1", p1)) diff --git a/examples/lir/bqc/example_bqc.py b/examples/lir/bqc/example_bqc.py index 1eb5d0f9..aeab47d5 100644 --- a/examples/lir/bqc/example_bqc.py +++ b/examples/lir/bqc/example_bqc.py @@ -5,7 +5,7 @@ from netqasm.lang.operand import Template -from squidasm.run.stack import lhrprogram as lp +from squidasm.run.qoala import lhr as lp from squidasm.run.stack.config import ( GenericQDeviceConfig, LinkConfig, @@ -16,7 +16,7 @@ from squidasm.sim.stack.program import ProgramContext, ProgramMeta -class ClientProgram(lp.LirProgram): +class ClientProgram(lp.LhrProgram): PEER = "server" def __init__( @@ -91,7 +91,7 @@ def compile(self, context: ProgramContext) -> None: subrt = conn.compile() subroutines = {"subrt": subrt} - instrs: List[lp.ClassicalLirOp] = [] + instrs: List[lp.ClassicalLhrOp] = [] instrs.append(lp.RunSubroutineOp("subrt")) instrs.append(lp.AssignCValueOp("p1", p1)) instrs.append(lp.AssignCValueOp("p2", p2)) @@ -138,7 +138,7 @@ def compile(self, context: ProgramContext) -> None: self.subroutines = subroutines -class ServerProgram(lp.LirProgram): +class ServerProgram(lp.LhrProgram): PEER = "client" def __init__(self) -> None: @@ -166,7 +166,7 @@ def compile(self, context: ProgramContext) -> None: subrt1 = conn.compile() subroutines = {"subrt1": subrt1} - instrs: List[lp.ClassicalLirOp] = [] + instrs: List[lp.ClassicalLhrOp] = [] instrs.append(lp.RunSubroutineOp("subrt1")) instrs.append(lp.ReceiveCMsgOp("delta1")) diff --git a/examples/lir/teleport/example_teleport.py b/examples/lir/teleport/example_teleport.py index eefb276d..098dc1fe 100644 --- a/examples/lir/teleport/example_teleport.py +++ b/examples/lir/teleport/example_teleport.py @@ -7,7 +7,7 @@ from netqasm.sdk.qubit import Qubit from netqasm.sdk.toolbox import set_qubit_state -from squidasm.run.stack import lhrprogram as lp +from squidasm.run.qoala import lhr as lp from squidasm.run.stack.config import ( GenericQDeviceConfig, LinkConfig, diff --git a/squidasm/run/stack/lhrprogram.py b/squidasm/run/qoala/lhr.py similarity index 88% rename from squidasm/run/stack/lhrprogram.py rename to squidasm/run/qoala/lhr.py index 11d8251e..61b36e62 100644 --- a/squidasm/run/stack/lhrprogram.py +++ b/squidasm/run/qoala/lhr.py @@ -1,6 +1,9 @@ import abc +from dataclasses import dataclass +from enum import Enum, auto from typing import Dict, List, Optional, Union +from netqasm.lang.instr import NetQASMInstruction from netqasm.lang.instr.flavour import NVFlavour from netqasm.lang.operand import Template from netqasm.lang.parsing.text import parse_text_subroutine @@ -12,6 +15,27 @@ LhrValue = Union[int, Template, Future] +class LhrInstructionType(Enum): + CC = 0 + CL = auto() + QC = auto() + QL = auto() + + +@dataclass +class LhrInstructionSignature: + typ: LhrInstructionType + duration: int = 0 + + +class StaticLhrProgramInfo: + pass + + +class DynamicLhrProgramInfo: + pass + + class LhrAttribute: def __init__(self, value: LhrValue) -> None: self._value = value @@ -129,6 +153,7 @@ def attributes(self) -> List[LhrValue]: class SendCMsgOp(ClassicalLhrOp): OP_NAME = "send_cmsg" + TYP = LhrInstructionType.CC def __init__(self, value: str) -> None: super().__init__(arguments=[value]) @@ -143,6 +168,7 @@ def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): class ReceiveCMsgOp(ClassicalLhrOp): OP_NAME = "recv_cmsg" + TYP = LhrInstructionType.CC def __init__(self, result: str) -> None: super().__init__(results=[result]) @@ -157,6 +183,7 @@ def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): class AddCValueOp(ClassicalLhrOp): OP_NAME = "add_cval_c" + TYP = LhrInstructionType.CL def __init__(self, result: str, value0: str, value1: str) -> None: super().__init__(arguments=[value0, value1], results=[result]) @@ -171,6 +198,7 @@ def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): class MultiplyConstantCValueOp(ClassicalLhrOp): OP_NAME = "mult_const" + TYP = LhrInstructionType.CL def __init__(self, result: str, value0: str, value1: LhrAttribute) -> None: super().__init__(arguments=[value0, value1], results=[result]) @@ -185,6 +213,7 @@ def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): class BitConditionalMultiplyConstantCValueOp(ClassicalLhrOp): OP_NAME = "bcond_mult_const" + TYP = LhrInstructionType.CL def __init__( self, result: str, value0: str, value1: LhrAttribute, cond: str @@ -201,6 +230,7 @@ def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): class AssignCValueOp(ClassicalLhrOp): OP_NAME = "assign_cval" + TYP = LhrInstructionType.CL def __init__(self, result: str, value: LhrValue) -> None: super().__init__(results=[result], attributes=[value]) @@ -215,6 +245,7 @@ def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): class RunSubroutineOp(ClassicalLhrOp): OP_NAME = "run_subroutine" + TYP = LhrInstructionType.CL def __init__(self, values: LhrVector, subrt: LhrSubroutine) -> None: super().__init__(arguments=[values], attributes=[subrt]) @@ -227,12 +258,17 @@ def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): assert attr is not None return cls(args[0], attr) + @property + def subroutine(self) -> LhrSubroutine: + return self.attributes[0] + def __str__(self) -> str: return super().__str__() class ReturnResultOp(ClassicalLhrOp): OP_NAME = "return_result" + TYP = LhrInstructionType.CL def __init__(self, value: str) -> None: super().__init__(arguments=[value]) @@ -245,7 +281,7 @@ def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): return cls(args[0]) -LIR_OP_NAMES = { +LHR_OP_NAMES = { cls.OP_NAME: cls for cls in [ SendCMsgOp, @@ -260,6 +296,13 @@ def from_generic_args(cls, result: str, args: List[str], attr: LhrValue): } +def netqasm_instr_to_type(instr: NetQASMInstruction) -> LhrInstructionType: + if instr.mnemonic in ["create_epr", "recv_epr"]: + return LhrInstructionType.QC + else: + return LhrInstructionType.QL + + class LhrProgram: def __init__( self, @@ -289,6 +332,18 @@ def instructions(self) -> List[ClassicalLhrOp]: def instructions(self, new_instrs) -> None: self._instructions = new_instrs + def get_instr_signatures(self) -> List[LhrInstructionSignature]: + sigs: List[LhrInstructionSignature] = [] + for instr in self.instructions: + if isinstance(instr, RunSubroutineOp): + subrt = instr.subroutine + for nq_instr in subrt.subroutine.instructions: + typ = netqasm_instr_to_type(nq_instr) + sigs.append(typ) + else: + sigs.append(instr.TYP) + return sigs + @property def subroutines(self) -> Dict[str, Subroutine]: return self._subroutines @@ -328,7 +383,7 @@ def _next_line(self) -> None: if self._lineno >= len(self._lines): raise EndOfTextException - def _parse_lir(self) -> ClassicalLhrOp: + def _parse_lhr(self) -> ClassicalLhrOp: line = self._lines[self._lineno] assign_parts = [x.strip() for x in line.split("=")] @@ -367,7 +422,7 @@ def parse_arg(arg): if len(vec_values_str) == 0: vec_values = [] else: - vec_values = [x.strip() for x in vec_values_str.split(",")] + vec_values = [x.strip() for x in vec_values_str.split(";")] return LhrVector(vec_values) return arg @@ -375,8 +430,8 @@ def parse_arg(arg): # print(f"result = {result}, op = {op}, args = {args}, attr = {attr}") - lir_op = LIR_OP_NAMES[op].from_generic_args(result, args, attr) - return lir_op + lhr_op = LHR_OP_NAMES[op].from_generic_args(result, args, attr) + return lhr_op def _read_line(self) -> str: self._next_line() @@ -412,7 +467,7 @@ def parse(self) -> LhrProgram: try: while True: - instr = self._parse_lir() + instr = self._parse_lhr() if isinstance(instr, RunSubroutineOp): subrt = self._parse_subroutine() instr = RunSubroutineOp(instr.arguments[0], subrt) @@ -463,3 +518,5 @@ def compile(self, context: ProgramContext) -> LhrProgram: print("\nto text and parsed back:") print(parsed_program) + + print(parsed_program.get_instr_signatures()) diff --git a/squidasm/sim/qoala/__init__.py b/squidasm/sim/qoala/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/squidasm/sim/qoala/arch/qoala-runtime-data.drawio b/squidasm/sim/qoala/arch/qoala-runtime-data.drawio new file mode 100644 index 00000000..7e98d2be --- /dev/null +++ b/squidasm/sim/qoala/arch/qoala-runtime-data.drawio @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/squidasm/sim/qoala/arch/qoala2.drawio b/squidasm/sim/qoala/arch/qoala2.drawio new file mode 100644 index 00000000..9ebee61a --- /dev/null +++ b/squidasm/sim/qoala/arch/qoala2.drawio @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/squidasm/sim/qoala/common.py b/squidasm/sim/qoala/common.py new file mode 100644 index 00000000..8fb2aab2 --- /dev/null +++ b/squidasm/sim/qoala/common.py @@ -0,0 +1,393 @@ +import logging +from dataclasses import dataclass +from typing import Dict, Generator, List, Optional, Set, Tuple, Union + +import netsquid as ns +from netqasm.lang import operand +from netqasm.lang.encoding import RegisterName +from netqasm.sdk.shared_memory import Arrays, RegisterGroup, setup_registers +from netsquid.components.component import Component, Port +from netsquid.protocols import Protocol + +from pydynaa import EventExpression + + +class SimTimeFilter(logging.Filter): + def filter(self, record): + record.simtime = ns.sim_time() + return True + + +class LogManager: + STACK_LOGGER = "Stack" + _LOGGER_HAS_BEEN_SETUP = False + + @classmethod + def _setup_stack_logger(cls) -> None: + logger = logging.getLogger(cls.STACK_LOGGER) + formatter = logging.Formatter( + "%(levelname)s:%(simtime)s ns:%(name)s:%(message)s" + ) + syslog = logging.StreamHandler() + syslog.setFormatter(formatter) + syslog.addFilter(SimTimeFilter()) + logger.addHandler(syslog) + logger.propagate = False + cls._LOGGER_HAS_BEEN_SETUP = True + + @classmethod + def get_stack_logger(cls, sub_logger: Optional[str] = None) -> logging.Logger: + if not cls._LOGGER_HAS_BEEN_SETUP: + cls._setup_stack_logger() + logger = logging.getLogger(cls.STACK_LOGGER) + if sub_logger is None: + return logger + else: + return logger.getChild(sub_logger) + + @classmethod + def set_log_level(cls, level: Union[int, str]) -> None: + logger = cls.get_stack_logger() + logger.setLevel(level) + + @classmethod + def get_log_level(cls) -> int: + return cls.get_stack_logger().level + + @classmethod + def log_to_file(cls, path: str) -> None: + fileHandler = logging.FileHandler(path, mode="w") + formatter = logging.Formatter( + "%(levelname)s:%(simtime)s ns:%(name)s:%(message)s" + ) + fileHandler.setFormatter(formatter) + fileHandler.addFilter(SimTimeFilter()) + cls.get_stack_logger().addHandler(fileHandler) + + +class PortListener(Protocol): + def __init__(self, port: Port, signal_label: str) -> None: + self._buffer: List[bytes] = [] + self._port: Port = port + self._signal_label = signal_label + self.add_signal(signal_label) + + @property + def buffer(self) -> List[bytes]: + return self._buffer + + def run(self) -> Generator[EventExpression, None, None]: + while True: + # Wait for an event saying that there is new input. + yield self.await_port_input(self._port) + + counter = 0 + # Read all inputs and count them. + while True: + input = self._port.rx_input() + if input is None: + break + self._buffer += input.items + counter += 1 + # If there are n inputs, there have been n events, but we yielded only + # on one of them so far. "Flush" these n-1 additional events: + while counter > 1: + yield self.await_port_input(self._port) + counter -= 1 + + # Only after having yielded on all current events, we can schedule a + # notification event, so that its reactor can handle all inputs at once. + self.send_signal(self._signal_label) + + +class RegisterMeta: + @classmethod + def prefixes(cls) -> List[str]: + return ["R", "C", "Q", "M"] + + @classmethod + def parse(cls, name: str) -> Tuple[RegisterName, int]: + assert len(name) >= 2 + assert name[0] in cls.prefixes() + group = RegisterName[name[0]] + index = int(name[1:]) + assert index < 16 + return group, index + + +class ComponentProtocol(Protocol): + def __init__(self, name: str, comp: Component) -> None: + super().__init__(name) + self._listeners: Dict[str, PortListener] = {} + self._logger: logging.Logger = LogManager.get_stack_logger( + f"{self.__class__.__name__}({comp.name})" + ) + + def add_listener(self, name, listener: PortListener) -> None: + self._listeners[name] = listener + + def _receive_msg( + self, listener_name: str, wake_up_signal: str + ) -> Generator[EventExpression, None, str]: + listener = self._listeners[listener_name] + if len(listener.buffer) == 0: + yield self.await_signal(sender=listener, signal_label=wake_up_signal) + return listener.buffer.pop(0) + + def start(self) -> None: + super().start() + for listener in self._listeners.values(): + listener.start() + + def stop(self) -> None: + for listener in self._listeners.values(): + listener.stop() + super().stop() + + +class AppMemory: + def __init__(self, app_id: int, max_qubits: int) -> None: + self._app_id: int = app_id + self._registers: Dict[RegisterName, RegisterGroup] = setup_registers() + self._arrays: Arrays = Arrays() + self._virt_qubits: Dict[int, Optional[int]] = { + i: None for i in range(max_qubits) + } + self._prog_counter: int = 0 + + @property + def prog_counter(self) -> int: + return self._prog_counter + + def increment_prog_counter(self) -> None: + self._prog_counter += 1 + + def set_prog_counter(self, value: int) -> None: + self._prog_counter = value + + def map_virt_id(self, virt_id: int, phys_id: int) -> None: + self._virt_qubits[virt_id] = phys_id + + def unmap_virt_id(self, virt_id: int) -> None: + self._virt_qubits[virt_id] = None + + def unmap_all(self) -> None: + for virt_id in self._virt_qubits: + self._virt_qubits[virt_id] = None + + @property + def qubit_mapping(self) -> Dict[int, Optional[int]]: + return self._virt_qubits + + def phys_id_for(self, virt_id: int) -> int: + return self._virt_qubits[virt_id] + + def virt_id_for(self, phys_id: int) -> Optional[int]: + for virt, phys in self._virt_qubits.items(): + if phys == phys_id: + return virt + return None + + def set_reg_value(self, register: Union[str, operand.Register], value: int) -> None: + if isinstance(register, str): + name, index = RegisterMeta.parse(register) + else: + name, index = register.name, register.index + self._registers[name][index] = value + + def get_reg_value(self, register: Union[str, operand.Register]) -> int: + if isinstance(register, str): + name, index = RegisterMeta.parse(register) + else: + name, index = register.name, register.index + return self._registers[name][index] + + # for compatibility with netqasm Futures + def get_register(self, register: Union[str, operand.Register]) -> Optional[int]: + return self.get_reg_value(register) + + # for compatibility with netqasm Futures + def get_array_part( + self, address: int, index: Union[int, slice] + ) -> Union[None, int, List[Optional[int]]]: + if isinstance(index, int): + return self.get_array_value(address, index) + elif isinstance(index, slice): + return self.get_array_values(address, index.start, index.stop) + + def init_new_array(self, address: int, length: int) -> None: + self._arrays.init_new_array(address, length) + + def get_array(self, address: int) -> List[Optional[int]]: + return self._arrays._get_array(address) + + def get_array_entry(self, array_entry: operand.ArrayEntry) -> Optional[int]: + address, index = self.expand_array_part(array_part=array_entry) + result = self._arrays[address, index] + assert (result is None) or isinstance(result, int) + return result + + def get_array_value(self, addr: int, offset: int) -> Optional[int]: + address, index = self.expand_array_part( + array_part=operand.ArrayEntry(operand.Address(addr), offset) + ) + result = self._arrays[address, index] + assert (result is None) or isinstance(result, int) + return result + + def get_array_values( + self, addr: int, start_offset: int, end_offset + ) -> List[Optional[int]]: + values = self.get_array_slice( + operand.ArraySlice(operand.Address(addr), start_offset, end_offset) + ) + assert values is not None + return values + + def set_array_entry( + self, array_entry: operand.ArrayEntry, value: Optional[int] + ) -> None: + address, index = self.expand_array_part(array_part=array_entry) + self._arrays[address, index] = value + + def set_array_value(self, addr: int, offset: int, value: Optional[int]) -> None: + address, index = self.expand_array_part( + array_part=operand.ArrayEntry(operand.Address(addr), offset) + ) + self._arrays[address, index] = value + + def get_array_slice( + self, array_slice: operand.ArraySlice + ) -> Optional[List[Optional[int]]]: + address, index = self.expand_array_part(array_part=array_slice) + result = self._arrays[address, index] + assert (result is None) or isinstance(result, list) + return result + + def expand_array_part( + self, array_part: Union[operand.ArrayEntry, operand.ArraySlice] + ) -> Tuple[int, Union[int, slice]]: + address: int = array_part.address.address + index: Union[int, slice] + if isinstance(array_part, operand.ArrayEntry): + if isinstance(array_part.index, int): + index = array_part.index + else: + index_from_reg = self.get_reg_value(register=array_part.index) + if index_from_reg is None: + raise RuntimeError( + f"Trying to use register {array_part.index} " + "to index an array but its value is None" + ) + index = index_from_reg + elif isinstance(array_part, operand.ArraySlice): + startstop: List[int] = [] + for raw_s in [array_part.start, array_part.stop]: + if isinstance(raw_s, int): + startstop.append(raw_s) + elif isinstance(raw_s, operand.Register): + s = self.get_reg_value(register=raw_s) + if s is None: + raise RuntimeError( + f"Trying to use register {raw_s} to " + "index an array but its value is None" + ) + startstop.append(s) + else: + raise RuntimeError( + f"Something went wrong: raw_s should be int " + f"or Register but is {type(raw_s)}" + ) + index = slice(*startstop) + else: + raise RuntimeError( + f"Something went wrong: array_part is a {type(array_part)}" + ) + return address, index + + +@dataclass +class NetstackCreateRequest: + app_id: int + remote_node_id: int + epr_socket_id: int + qubit_array_addr: int + arg_array_addr: int + result_array_addr: int + + +@dataclass +class NetstackReceiveRequest: + app_id: int + remote_node_id: int + epr_socket_id: int + qubit_array_addr: int + result_array_addr: int + + +@dataclass +class NetstackBreakpointCreateRequest: + app_id: int + + +@dataclass +class NetstackBreakpointReceiveRequest: + app_id: int + + +class AllocError(Exception): + pass + + +class PhysicalQuantumMemory: + def __init__(self, qubit_count: int) -> None: + self._qubit_count = qubit_count + self._allocated_ids: Set[int] = set() + self._comm_qubit_ids: Set[int] = {i for i in range(qubit_count)} + + @property + def qubit_count(self) -> int: + return self._qubit_count + + @property + def comm_qubit_count(self) -> int: + return len(self._comm_qubit_ids) + + def allocate(self) -> int: + """Allocate a qubit (communcation or memory).""" + for i in range(self._qubit_count): + if i not in self._allocated_ids: + self._allocated_ids.add(i) + return i + raise AllocError("No more qubits available") + + def allocate_comm(self) -> int: + """Allocate a communication qubit.""" + for i in range(self._qubit_count): + if i not in self._allocated_ids and i in self._comm_qubit_ids: + self._allocated_ids.add(i) + return i + raise AllocError("No more comm qubits available") + + def allocate_mem(self) -> int: + """Allocate a memory qubit.""" + for i in range(self._qubit_count): + if i not in self._allocated_ids and i not in self._comm_qubit_ids: + self._allocated_ids.add(i) + return i + raise AllocError("No more mem qubits available") + + def free(self, id: int) -> None: + self._allocated_ids.remove(id) + + def is_allocated(self, id: int) -> bool: + return id in self._allocated_ids + + def clear(self) -> None: + self._allocated_ids = {} + + +class NVPhysicalQuantumMemory(PhysicalQuantumMemory): + def __init__(self, qubit_count: int) -> None: + super().__init__(qubit_count) + self._comm_qubit_ids: Set[int] = {0} diff --git a/squidasm/sim/qoala/connection.py b/squidasm/sim/qoala/connection.py new file mode 100644 index 00000000..d551d8da --- /dev/null +++ b/squidasm/sim/qoala/connection.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, Callable, Generator, Optional, Type + +from netqasm.backend.messages import SubroutineMessage +from netqasm.lang.subroutine import Subroutine +from netqasm.sdk.build_types import GenericHardwareConfig, HardwareConfig +from netqasm.sdk.builder import Builder +from netqasm.sdk.connection import ( + BaseNetQASMConnection, + NetworkInfo, + ProtoSubroutine, + T_Message, +) +from netqasm.sdk.shared_memory import SharedMemory + +from pydynaa import EventExpression + +if TYPE_CHECKING: + from netqasm.sdk.transpile import SubroutineTranspiler + from squidasm.sim.stack.host import Host + +from squidasm.sim.stack.common import LogManager + +from .context import NetSquidNetworkInfo + + +class QnosConnection(BaseNetQASMConnection): + def __init__( + self, + host: Host, + app_id: int, + app_name: str, + max_qubits: int = 5, + hardware_config: Optional[HardwareConfig] = None, + compiler: Optional[Type[SubroutineTranspiler]] = None, + **kwargs, + ) -> None: + self._app_name = app_name + self._app_id = app_id + self._node_name = app_name + self._max_qubits = max_qubits + + self._host = host + + self._shared_memory = None + self._logger: logging.Logger = LogManager.get_stack_logger( + f"{self.__class__.__name__}({self.app_name})" + ) + + if hardware_config is None: + hardware_config = GenericHardwareConfig(max_qubits) + + self._builder = Builder( + connection=self, + app_id=self._app_id, + hardware_config=hardware_config, + compiler=compiler, + ) + + @property + def shared_memory(self) -> SharedMemory: + return self._shared_memory + + def __enter__(self) -> QnosConnection: + pass + + def __exit__(self, exc_type, exc_val, exc_tb): + self.flush() + + def _commit_message( + self, msg: T_Message, block: bool = True, callback: Optional[Callable] = None + ) -> None: + assert isinstance(msg, SubroutineMessage) + self._logger.debug(f"Committing message {msg}") + self._host.send_qnos_msg(bytes(msg)) + + def commit_protosubroutine( + self, + protosubroutine: ProtoSubroutine, + block: bool = True, + callback: Optional[Callable] = None, + ) -> Generator[EventExpression, None, None]: + self._logger.info(f"Flushing protosubroutine:\n{protosubroutine}") + + subroutine = self._builder.subrt_compile_subroutine(protosubroutine) + self._logger.info(f"Flushing compiled subroutine:\n{subroutine}") + + yield from self.commit_subroutine(subroutine, block, callback) + self._builder._reset() + + def commit_subroutine( + self, + subroutine: Subroutine, + block: bool = True, + callback: Optional[Callable] = None, + ) -> Generator[EventExpression, None, None]: + self._logger.info(f"Commiting compiled subroutine:\n{subroutine}") + + self._commit_message( + msg=SubroutineMessage(subroutine=subroutine), + block=block, + callback=callback, + ) + + result = yield from self._host.receive_qnos_msg() + self._shared_memory = result + + def flush( + self, block: bool = True, callback: Optional[Callable] = None + ) -> Generator[EventExpression, None, None]: + subroutine = self._builder.subrt_pop_pending_subroutine() + if subroutine is None: + return + + yield from self.commit_protosubroutine( + protosubroutine=subroutine, + block=block, + callback=callback, + ) + + def _commit_serialized_message( + self, raw_msg: bytes, block: bool = True, callback: Optional[Callable] = None + ) -> None: + pass + + def _get_network_info(self) -> Type[NetworkInfo]: + return NetSquidNetworkInfo diff --git a/squidasm/sim/qoala/context.py b/squidasm/sim/qoala/context.py new file mode 100644 index 00000000..6375d606 --- /dev/null +++ b/squidasm/sim/qoala/context.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Dict + +from netqasm.sdk.network import NetworkInfo + +if TYPE_CHECKING: + from squidasm.sim.stack.host import Host + + +class NetSquidNetworkInfo(NetworkInfo): + @classmethod + def _get_node_id(cls, node_name: str) -> int: + nodes = NetSquidContext.get_nodes() + for id, name in nodes.items(): + if name == node_name: + return id + raise ValueError(f"Node with name {node_name} not found") + + @classmethod + def _get_node_name(cls, node_id: int) -> str: + return NetSquidContext.get_nodes()[node_id] + + @classmethod + def get_node_id_for_app(cls, app_name: str) -> int: + return cls._get_node_id(node_name=app_name) + + @classmethod + def get_node_name_for_app(cls, app_name: str) -> str: + raise NotImplementedError + + +class NetSquidContext: + _protocols: Dict[str, Host] = {} + _nodes: Dict[int, str] = {} + + @classmethod + def get_nodes(cls) -> Dict[int, str]: + return cls._nodes + + @classmethod + def set_nodes(cls, nodes: Dict[int, str]) -> None: + cls._nodes = nodes + + @classmethod + def add_node(cls, id: int, node: str) -> None: + cls._nodes[id] = node + + @classmethod + def get_protocols(cls) -> Dict[str, Host]: + return cls._protocols + + @classmethod + def set_protocols(cls, protocols: Dict[str, Host]) -> None: + cls._protocols = protocols + + @classmethod + def add_protocol(cls, name: str, protocol: Host) -> None: + cls._protocols[name] = protocol diff --git a/squidasm/sim/qoala/csocket.py b/squidasm/sim/qoala/csocket.py new file mode 100644 index 00000000..617f8861 --- /dev/null +++ b/squidasm/sim/qoala/csocket.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Generator + +from netqasm.sdk.classical_communication.message import StructuredMessage +from netqasm.sdk.classical_communication.socket import Socket + +from pydynaa import EventExpression + +if TYPE_CHECKING: + from squidasm.sim.stack.host import Host + + +class ClassicalSocket(Socket): + def __init__( + self, + host: Host, + app_name: str, + remote_app_name: str, + socket_id: int = 0, + ): + """Socket used to communicate classical data between applications.""" + super().__init__( + app_name=app_name, remote_app_name=remote_app_name, socket_id=socket_id + ) + self._host = host + + def send(self, msg: str) -> None: + """Sends a message to the remote node.""" + self._host.send_peer_msg(msg) + + def recv(self) -> Generator[EventExpression, None, str]: + return (yield from self._host.receive_peer_msg()) + + def send_int(self, value: int) -> None: + self.send(str(value)) + + def recv_int(self) -> Generator[EventExpression, None, int]: + value = yield from self.recv() + return int(value) + + def send_float(self, value: float) -> None: + self.send(str(value)) + + def recv_float(self) -> Generator[EventExpression, None, float]: + value = yield from self.recv() + return float(value) + + def send_structured(self, msg: StructuredMessage) -> None: + self.send(msg) + + def recv_structured(self) -> Generator[EventExpression, None, StructuredMessage]: + value = yield from self.recv() + return value diff --git a/squidasm/sim/qoala/egp.py b/squidasm/sim/qoala/egp.py new file mode 100644 index 00000000..b0549d02 --- /dev/null +++ b/squidasm/sim/qoala/egp.py @@ -0,0 +1,110 @@ +from abc import ABCMeta, abstractmethod + +from netsquid import BellIndex +from netsquid.protocols import ServiceProtocol +from netsquid_magic.link_layer import TranslationUnit +from qlink_interface import ( + ReqCreateAndKeep, + ReqMeasureDirectly, + ReqReceive, + ReqRemoteStatePrep, + ReqStopReceive, + ResCreateAndKeep, + ResError, + ResMeasureDirectly, + ResRemoteStatePrep, +) + +# (Mostly) copied from the nlblueprint repo. +# This is done to not have nlblueprint as a dependency. + + +class EGPService(ServiceProtocol, metaclass=ABCMeta): + def __init__(self, node, name=None): + super().__init__(node=node, name=name) + self.register_request(ReqCreateAndKeep, self.create_and_keep) + self.register_request(ReqMeasureDirectly, self.measure_directly) + self.register_request(ReqReceive, self.receive) + self.register_request(ReqStopReceive, self.stop_receive) + self.register_response(ResCreateAndKeep) + self.register_response(ResMeasureDirectly) + self.register_response(ResRemoteStatePrep) + self.register_response(ResError) + self._current_create_id = 0 + + @abstractmethod + def create_and_keep(self, req): + assert isinstance(req, ReqCreateAndKeep) + + @abstractmethod + def measure_directly(self, req): + assert isinstance(req, ReqMeasureDirectly) + + @abstractmethod + def remote_state_preparation(self, req): + assert isinstance(req, ReqRemoteStatePrep) + + @abstractmethod + def receive(self, req): + assert isinstance(req, ReqReceive) + + @abstractmethod + def stop_receive(self, req): + assert isinstance(req, ReqStopReceive) + + def _get_create_id(self): + create_id = self._current_create_id + self._current_create_id += 1 + return create_id + + +class EgpProtocol(EGPService): + def __init__(self, node, magic_link_layer_protocol, name=None): + super().__init__(node=node, name=name) + self._ll_prot = magic_link_layer_protocol + + def run(self): + while True: + yield self.await_signal( + sender=self._ll_prot, + signal_label="react_to_{}".format(self.node.ID), + ) + result = self._ll_prot.get_signal_result( + label="react_to_{}".format(self.node.ID), receiver=self + ) + if result.node_id == self.node.ID: + try: + BellIndex(result.msg.bell_state) + except AttributeError: + pass + except ValueError: + raise TypeError( + f"{result.msg.bell_state}, which was obtained from magic link layer protocol," + f"is not a :class:`netsquid.qubits.ketstates.BellIndex`." + ) + self.send_response(response=result.msg) + + def create_and_keep(self, req): + super().create_and_keep(req) + return self._ll_prot.put_from(self.node.ID, req) + + def measure_directly(self, req): + super().measure_directly(req) + return self._ll_prot.put_from(self.node.ID, req) + + def remote_state_preparation(self, req): + super().remote_state_preparation(req) + return self._ll_prot.put_from(self.node.ID, req) + + def receive(self, req): + super().receive(req) + self._ll_prot.put_from(self.node.ID, req) + + def stop_receive(self, req): + super().stop_receive(req) + self._ll_prot.put_from(self.node.ID, req) + + +class EgpTranslationUnit(TranslationUnit): + def request_to_parameters(self, request, **fixed_parameters): + return {} diff --git a/squidasm/sim/qoala/globals.py b/squidasm/sim/qoala/globals.py new file mode 100644 index 00000000..c56297db --- /dev/null +++ b/squidasm/sim/qoala/globals.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Dict, List, Optional + +import numpy as np +from netsquid.qubits import qubitapi +from netsquid.qubits.qubit import Qubit + +if TYPE_CHECKING: + from squidasm.sim.stack.stack import StackNetwork + +T_QubitData = Dict[str, Dict[int, Qubit]] +T_StateData = Dict[str, Dict[int, np.ndarray]] + + +class GlobalSimData: + _NETWORK: Optional[StackNetwork] = None + _BREAKPOINT_STATES: List[np.ndarray] = [] + + @classmethod + def set_network(cls, network: StackNetwork) -> None: + cls._NETWORK = network + + @classmethod + def get_network(cls) -> Optional[StackNetwork]: + return cls._NETWORK + + @classmethod + def get_quantum_state(cls, save: bool = False) -> T_QubitData: + network = cls.get_network() + assert network is not None + + qubits: T_QubitData = {} + states: T_StateData = {} + for name, qdevice in network.qdevices.items(): + qubits[name] = {} + states[name] = {} + for i in range(qdevice.num_positions): + if qdevice.mem_positions[i].in_use: + [q] = qdevice.peek(i, skip_noise=True) + qubits[name][i] = q + if save: + states[name][i] = qubitapi.reduced_dm(q) + if save: + cls._BREAKPOINT_STATES.append(states) + return qubits + + @classmethod + def get_last_breakpoint_state(cls) -> np.ndarray: + assert len(cls._BREAKPOINT_STATES) > 0 + return cls._BREAKPOINT_STATES[-1] diff --git a/squidasm/sim/qoala/handler.py b/squidasm/sim/qoala/handler.py new file mode 100644 index 00000000..c8b1beea --- /dev/null +++ b/squidasm/sim/qoala/handler.py @@ -0,0 +1,289 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional + +from netqasm.backend.messages import ( + InitNewAppMessage, + Message, + OpenEPRSocketMessage, + StopAppMessage, + SubroutineMessage, + deserialize_host_msg, +) +from netqasm.lang.instr import flavour +from netqasm.lang.parsing import deserialize as deser_subroutine +from netqasm.lang.subroutine import Subroutine +from netsquid.components.component import Component, Port +from netsquid.nodes import Node + +from pydynaa import EventExpression +from squidasm.sim.stack.common import ( + AppMemory, + ComponentProtocol, + PhysicalQuantumMemory, + PortListener, +) +from squidasm.sim.stack.netstack import Netstack, NetstackComponent +from squidasm.sim.stack.signals import SIGNAL_HOST_HAND_MSG, SIGNAL_PROC_HAND_MSG + +if TYPE_CHECKING: + from squidasm.sim.stack.processor import ProcessorComponent + from squidasm.sim.stack.qnos import Qnos, QnosComponent + + +class HandlerComponent(Component): + """NetSquid component representing a QNodeOS handler. + + Subcomponent of a QnosComponent. + + The "QnodeOS handler" represents the combination of the following components + within QNodeOS: + - interface with the Host + - scheduler + + Has communications ports with + - the processor component of this QNodeOS + - the Host component on this node + + This is a static container for handler-related components and ports. + Behavior of a QNodeOS handler is modeled in the `Handler` class, + which is a subclass of `Protocol`. + """ + + def __init__(self, node: Node) -> None: + super().__init__(f"{node.name}_handler") + self._node = node + self.add_ports(["proc_out", "proc_in"]) + self.add_ports(["host_out", "host_in"]) + + @property + def processor_in_port(self) -> Port: + return self.ports["proc_in"] + + @property + def processor_out_port(self) -> Port: + return self.ports["proc_out"] + + @property + def host_in_port(self) -> Port: + return self.ports["host_in"] + + @property + def host_out_port(self) -> Port: + return self.ports["host_out"] + + @property + def node(self) -> Node: + return self._node + + @property + def processor_comp(self) -> ProcessorComponent: + return self.supercomponent.processor + + @property + def netstack_comp(self) -> NetstackComponent: + return self.supercomponent.netstack + + @property + def qnos_comp(self) -> QnosComponent: + return self.supercomponent + + +class RunningApp: + def __init__(self, app_id: int) -> None: + self._id = app_id + self._pending_subroutines: List[Subroutine] = [] + + def add_subroutine(self, subroutine: Subroutine) -> None: + self._pending_subroutines.append(subroutine) + + def next_subroutine(self) -> Optional[Subroutine]: + if len(self._pending_subroutines) > 0: + return self._pending_subroutines.pop() + return None + + @property + def id(self) -> int: + return self._id + + +class Handler(ComponentProtocol): + """NetSquid protocol representing a QNodeOS handler.""" + + def __init__( + self, comp: HandlerComponent, qnos: Qnos, qdevice_type: Optional[str] = "nv" + ) -> None: + """Processor handler constructor. Typically created indirectly through + constructing a `Qnos` instance. + + :param comp: NetSquid component representing the handler + :param qnos: `Qnos` protocol that owns this protocol + """ + super().__init__(name=f"{comp.name}_protocol", comp=comp) + self._comp = comp + self._qnos = qnos + + self.add_listener( + "host", + PortListener(self._comp.ports["host_in"], SIGNAL_HOST_HAND_MSG), + ) + self.add_listener( + "processor", + PortListener(self._comp.ports["proc_in"], SIGNAL_PROC_HAND_MSG), + ) + + # Number of applications that were handled so far. Used as a unique ID for + # the next application. + self._app_counter = 0 + + # Currently active (running or waiting) applications. + self._applications: Dict[int, RunningApp] = {} + + # Whether the quantum memory for applications should be reset when the + # application finishes. + self._should_clear_memory: bool = True + + # Set the expected flavour such that Host messages are deserialized correctly. + if qdevice_type == "nv": + self._flavour: Optional[flavour.Flavour] = flavour.NVFlavour() + elif qdevice_type == "generic": + self._flavour: Optional[flavour.Flavour] = flavour.VanillaFlavour() + else: + raise ValueError + + @property + def app_memories(self) -> Dict[int, AppMemory]: + return self._qnos.app_memories + + @property + def physical_memory(self) -> PhysicalQuantumMemory: + return self._qnos.physical_memory + + @property + def should_clear_memory(self) -> bool: + return self._should_clear_memory + + @should_clear_memory.setter + def should_clear_memory(self, value: bool) -> None: + self._should_clear_memory = value + + @property + def flavour(self) -> Optional[flavour.Flavour]: + return self._flavour + + @flavour.setter + def flavour(self, flavour: Optional[flavour.Flavour]) -> None: + self._flavour = flavour + + def _send_host_msg(self, msg: Any) -> None: + self._comp.host_out_port.tx_output(msg) + + def _receive_host_msg(self) -> Generator[EventExpression, None, str]: + return (yield from self._receive_msg("host", SIGNAL_HOST_HAND_MSG)) + + def _send_processor_msg(self, msg: str) -> None: + self._comp.processor_out_port.tx_output(msg) + + def _receive_processor_msg(self) -> Generator[EventExpression, None, str]: + return (yield from self._receive_msg("processor", SIGNAL_PROC_HAND_MSG)) + + @property + def qnos(self) -> Qnos: + return self._qnos + + @property + def netstack(self) -> Netstack: + return self.qnos.netstack + + def _next_app(self) -> Optional[RunningApp]: + for app in self._applications.values(): + return app + return None + + def init_new_app(self, max_qubits: int) -> int: + app_id = self._app_counter + self._app_counter += 1 + self.app_memories[app_id] = AppMemory(app_id, self.physical_memory.qubit_count) + self._applications[app_id] = RunningApp(app_id) + self._logger.debug(f"registered app with ID {app_id}") + return app_id + + def open_epr_socket(self, app_id: int, socket_id: int, remote_id: int) -> None: + self._logger.debug(f"Opening EPR socket ({socket_id}, {remote_id})") + self.netstack.open_epr_socket(app_id, socket_id, remote_id) + + def add_subroutine(self, app_id: int, subroutine: Subroutine) -> None: + self._applications[app_id].add_subroutine(subroutine) + + def _deserialize_subroutine(self, msg: SubroutineMessage) -> Subroutine: + # return deser_subroutine(msg.subroutine, flavour=flavour.NVFlavour()) + return deser_subroutine(msg.subroutine, flavour=self._flavour) + + def clear_application(self, app_id: int) -> None: + for virt_id, phys_id in self.app_memories[app_id].qubit_mapping.items(): + self.app_memories[app_id].unmap_virt_id(virt_id) + if phys_id is not None: + self.physical_memory.free(phys_id) + self.app_memories.pop(app_id) + + def stop_application(self, app_id: int) -> None: + self._logger.debug(f"stopping application with ID {app_id}") + if self.should_clear_memory: + self._logger.debug(f"clearing qubits for application with ID {app_id}") + self.clear_application(app_id) + self._applications.pop(app_id) + else: + self._logger.info(f"NOT clearing qubits for application with ID {app_id}") + + def assign_processor( + self, app_id: int, subroutine: Subroutine + ) -> Generator[EventExpression, None, AppMemory]: + """Tell the processor to execute a subroutine and wait for it to finish. + + :param app_id: ID of the application this subroutine is for + :param subroutine: the subroutine to execute + """ + self._send_processor_msg(subroutine) + result = yield from self._receive_processor_msg() + assert result == "subroutine done" + self._logger.debug(f"result: {result}") + app_mem = self.app_memories[app_id] + return app_mem + + def msg_from_host(self, msg: Message) -> None: + """Handle a deserialized message from the Host.""" + if isinstance(msg, InitNewAppMessage): + app_id = self.init_new_app(msg.max_qubits) + self._send_host_msg(app_id) + elif isinstance(msg, OpenEPRSocketMessage): + self.open_epr_socket(msg.app_id, msg.epr_socket_id, msg.remote_node_id) + elif isinstance(msg, SubroutineMessage): + subroutine = self._deserialize_subroutine(msg) + self.add_subroutine(subroutine.app_id, subroutine) + elif isinstance(msg, StopAppMessage): + self.stop_application(msg.app_id) + + def run(self) -> Generator[EventExpression, None, None]: + """Run this protocol. Automatically called by NetSquid during simulation.""" + + # Loop forever acting on messages from the Host. + while True: + # Wait for a new message from the Host. + raw_host_msg = yield from self._receive_host_msg() + self._logger.debug(f"received new msg from host: {raw_host_msg}") + msg = deserialize_host_msg(raw_host_msg) + + # Handle the message. This updates the handler's state and may e.g. + # add a pending subroutine for an application. + self.msg_from_host(msg) + + # Get the next application that needs work. + app = self._next_app() + if app is not None: + # Flush all pending subroutines for this app. + while True: + subrt = app.next_subroutine() + if subrt is None: + break + app_mem = yield from self.assign_processor(app.id, subrt) + self._send_host_msg(app_mem) diff --git a/squidasm/sim/qoala/host.py b/squidasm/sim/qoala/host.py new file mode 100644 index 00000000..5fd1b293 --- /dev/null +++ b/squidasm/sim/qoala/host.py @@ -0,0 +1,440 @@ +from __future__ import annotations + +import logging +from typing import Any, Dict, Generator, List, Optional, Type + +from netqasm.backend.messages import ( + InitNewAppMessage, + OpenEPRSocketMessage, + StopAppMessage, +) +from netqasm.lang.operand import Register +from netqasm.lang.parsing.text import NetQASMSyntaxError, parse_register +from netqasm.sdk.epr_socket import EPRSocket +from netqasm.sdk.transpile import NVSubroutineTranspiler, SubroutineTranspiler +from netsquid.components.component import Component, Port +from netsquid.nodes import Node + +from pydynaa import EventExpression +from squidasm.run.qoala import lhr +from squidasm.sim.stack.common import ComponentProtocol, LogManager, PortListener +from squidasm.sim.stack.connection import QnosConnection +from squidasm.sim.stack.context import NetSquidContext +from squidasm.sim.stack.csocket import ClassicalSocket +from squidasm.sim.stack.program import Program, ProgramContext +from squidasm.sim.stack.signals import SIGNAL_HAND_HOST_MSG, SIGNAL_HOST_HOST_MSG + + +class LhrProcess: + def __init__(self, host: Host, program: lhr.LhrProgram) -> None: + self._host = host + self._name = f"{host._comp.name}_Lhr" + self._logger: logging.Logger = LogManager.get_stack_logger( + f"{self.__class__.__name__}({self._name})" + ) + self._program = program + self._program_results: List[Dict[str, Any]] = [] + + def setup(self) -> Generator[EventExpression, None, None]: + program = self._program + prog_meta = program.meta + + # Register the new program (called 'application' by QNodeOS) with QNodeOS. + self._host.send_qnos_msg( + bytes(InitNewAppMessage(max_qubits=prog_meta.max_qubits)) + ) + self._app_id = yield from self._host.receive_qnos_msg() + self._logger.debug(f"got app id from qnos: {self._app_id}") + + # Set up the connection with QNodeOS. + conn = QnosConnection( + host=self._host, + app_id=self._app_id, + app_name=prog_meta.name, + max_qubits=prog_meta.max_qubits, + compiler=self._host._compiler, + ) + + # Create EPR sockets that can be used by the program SDK code. + epr_sockets: Dict[int, EPRSocket] = {} + for i, remote_name in enumerate(prog_meta.epr_sockets): + remote_id = None + nodes = NetSquidContext.get_nodes() + for id, name in nodes.items(): + if name == remote_name: + remote_id = id + assert remote_id is not None + self._host.send_qnos_msg( + bytes(OpenEPRSocketMessage(self._app_id, i, remote_id)) + ) + epr_sockets[remote_name] = EPRSocket(remote_name, i) + epr_sockets[remote_name].conn = conn + + # Create classical sockets that can be used by the program SDK code. + classical_sockets: Dict[int, ClassicalSocket] = {} + for i, remote_name in enumerate(prog_meta.csockets): + remote_id = None + nodes = NetSquidContext.get_nodes() + for id, name in nodes.items(): + if name == remote_name: + remote_id = id + assert remote_id is not None + classical_sockets[remote_name] = ClassicalSocket( + self._host, prog_meta.name, remote_name + ) + + self._context = ProgramContext( + netqasm_connection=conn, + csockets=classical_sockets, + epr_sockets=epr_sockets, + app_id=self._app_id, + ) + + self._memory: Dict[str, Any] = {} + + def run(self, num_times: int) -> Generator[EventExpression, None, None]: + for _ in range(num_times): + yield from self.setup() + result = yield from self.execute_program() + self._program_results.append(result) + self._host.send_qnos_msg(bytes(StopAppMessage(self._app_id))) + + def run_with_context( + self, context: ProgramContext, num_times: int + ) -> Generator[EventExpression, None, None]: + self._memory: Dict[str, Any] = {} + self._context = context + for _ in range(num_times): + result = yield from self.execute_program() + self._program_results.append(result) + self._host.send_qnos_msg(bytes(StopAppMessage(context.app_id))) + + @property + def context(self) -> ProgramContext: + return self._context + + @property + def memory(self) -> Dict[str, Any]: + return self._memory + + @property + def program(self) -> ProgramContext: + return self._program + + def execute_program(self) -> Generator[EventExpression, None, Dict[str, Any]]: + context = self._context + memory = self._memory + program = self._program + + csockets = list(context.csockets.values()) + csck = csockets[0] if len(csockets) > 0 else None + conn = context.connection + + results: Dict[str, Any] = {} + + for instr in program.instructions: + self._logger.info(f"Interpreting LHR instruction {instr}") + if isinstance(instr, lhr.SendCMsgOp): + value = memory[instr.arguments[0]] + self._logger.info(f"sending msg {value}") + csck.send(value) + elif isinstance(instr, lhr.ReceiveCMsgOp): + msg = yield from csck.recv() + msg = int(msg) + memory[instr.results[0]] = msg + self._logger.info(f"received msg {msg}") + elif isinstance(instr, lhr.AddCValueOp): + arg0 = memory[instr.arguments[0]] + arg1 = memory[instr.arguments[1]] + memory[instr.results[0]] = arg0 + arg1 + elif isinstance(instr, lhr.MultiplyConstantCValueOp): + arg0 = memory[instr.arguments[0]] + arg1 = instr.arguments[1] + memory[instr.results[0]] = arg0 * arg1 + elif isinstance(instr, lhr.BitConditionalMultiplyConstantCValueOp): + arg0 = memory[instr.arguments[0]] + arg1 = instr.arguments[1] + cond = memory[instr.arguments[2]] + if cond == 1: + memory[instr.results[0]] = arg0 * arg1 + elif isinstance(instr, lhr.AssignCValueOp): + value = instr.attributes[0] + # if isinstance(value, str) and value.startswith("RegFuture__"): + # reg_str = value[len("RegFuture__") :] + memory[instr.results[0]] = instr.attributes[0] + elif isinstance(instr, lhr.RunSubroutineOp): + arg_vec: lhr.LhrVector = instr.arguments[0] + args = arg_vec.values + lhr_subrt: lhr.LhrSubroutine = instr.attributes[0] + subrt = lhr_subrt.subroutine + self._logger.info(f"executing subroutine {subrt}") + + arg_values = {arg: memory[arg] for arg in args} + + self._logger.warning( + f"instantiating subroutine with values {arg_values}" + ) + subrt.instantiate(conn.app_id, arg_values) + + yield from conn.commit_subroutine(subrt) + + for key, mem_loc in lhr_subrt.return_map.items(): + try: + reg: Register = parse_register(mem_loc.loc) + value = conn.shared_memory.get_register(reg) + self._logger.debug( + f"writing shared memory value {value} from location " + f"{mem_loc} to variable {key}" + ) + memory[key] = value + except NetQASMSyntaxError: + pass + elif isinstance(instr, lhr.ReturnResultOp): + value = instr.arguments[0] + results[value] = int(memory[value]) + + return results + + +class HostComponent(Component): + """NetSquid compmonent representing a Host. + + Subcomponent of a ProcessingNode. + + This is a static container for Host-related components and ports. Behavior + of a Host is modeled in the `Host` class, which is a subclass of `Protocol`. + """ + + def __init__(self, node: Node) -> None: + super().__init__(f"{node.name}_host") + self.add_ports(["qnos_in", "qnos_out"]) + self.add_ports(["peer_in", "peer_out"]) + + @property + def qnos_in_port(self) -> Port: + return self.ports["qnos_in"] + + @property + def qnos_out_port(self) -> Port: + return self.ports["qnos_out"] + + @property + def peer_in_port(self) -> Port: + return self.ports["peer_in"] + + @property + def peer_out_port(self) -> Port: + return self.ports["peer_out"] + + +class Host(ComponentProtocol): + """NetSquid protocol representing a Host.""" + + def __init__(self, comp: HostComponent, qdevice_type: Optional[str] = "nv") -> None: + """Qnos protocol constructor. + + :param comp: NetSquid component representing the Host + :param qdevice_type: hardware type of the QDevice of this node + """ + super().__init__(name=f"{comp.name}_protocol", comp=comp) + self._comp = comp + + self.add_listener( + "qnos", + PortListener(self._comp.ports["qnos_in"], SIGNAL_HAND_HOST_MSG), + ) + self.add_listener( + "peer", + PortListener(self._comp.ports["peer_in"], SIGNAL_HOST_HOST_MSG), + ) + + if qdevice_type == "nv": + self._compiler: Optional[ + Type[SubroutineTranspiler] + ] = NVSubroutineTranspiler + elif qdevice_type == "generic": + self._compiler: Optional[Type[SubroutineTranspiler]] = None + else: + raise ValueError + + # Program that is currently being executed. + self._program: Optional[Program] = None + + # Number of times the current program still needs to be run. + self._num_pending: int = 0 + + # Results of program runs so far. + self._program_results: List[Dict[str, Any]] = [] + + @property + def compiler(self) -> Optional[Type[SubroutineTranspiler]]: + return self._compiler + + @compiler.setter + def compiler(self, typ: Optional[Type[SubroutineTranspiler]]) -> None: + self._compiler = typ + + def send_qnos_msg(self, msg: bytes) -> None: + self._comp.qnos_out_port.tx_output(msg) + + def receive_qnos_msg(self) -> Generator[EventExpression, None, str]: + return (yield from self._receive_msg("qnos", SIGNAL_HAND_HOST_MSG)) + + def send_peer_msg(self, msg: str) -> None: + self._comp.peer_out_port.tx_output(msg) + + def receive_peer_msg(self) -> Generator[EventExpression, None, str]: + return (yield from self._receive_msg("peer", SIGNAL_HOST_HOST_MSG)) + + def run_sdk_program( + self, program: Program + ) -> Generator[EventExpression, None, None]: + prog_meta = program.meta + + # Register the new program (called 'application' by QNodeOS) with QNodeOS. + self.send_qnos_msg(bytes(InitNewAppMessage(max_qubits=prog_meta.max_qubits))) + app_id = yield from self.receive_qnos_msg() + self._logger.debug(f"got app id from qnos: {app_id}") + + # Set up the Connection object to be used by the program SDK code. + conn = QnosConnection( + self, + app_id, + prog_meta.name, + max_qubits=prog_meta.max_qubits, + compiler=self._compiler, + ) + + # Create EPR sockets that can be used by the program SDK code. + epr_sockets: Dict[int, EPRSocket] = {} + for i, remote_name in enumerate(prog_meta.epr_sockets): + remote_id = None + nodes = NetSquidContext.get_nodes() + for id, name in nodes.items(): + if name == remote_name: + remote_id = id + assert remote_id is not None + self.send_qnos_msg(bytes(OpenEPRSocketMessage(app_id, i, remote_id))) + epr_sockets[remote_name] = EPRSocket(remote_name, i) + epr_sockets[remote_name].conn = conn + + # Create classical sockets that can be used by the program SDK code. + classical_sockets: Dict[int, ClassicalSocket] = {} + for i, remote_name in enumerate(prog_meta.csockets): + remote_id = None + nodes = NetSquidContext.get_nodes() + for id, name in nodes.items(): + if name == remote_name: + remote_id = id + assert remote_id is not None + classical_sockets[remote_name] = ClassicalSocket( + self, prog_meta.name, remote_name + ) + + context = ProgramContext( + netqasm_connection=conn, + csockets=classical_sockets, + epr_sockets=epr_sockets, + app_id=app_id, + ) + + # Run the program by evaluating its run() method. + result = yield from program.run(context) + self._program_results.append(result) + + # Tell QNodeOS the program has finished. + self.send_qnos_msg(bytes(StopAppMessage(app_id))) + + def run_lhr_sdk_program( + self, + program: lhr.LhrProgram, + ) -> Generator[EventExpression, None, None]: + prog_meta = program.meta + + # Register the new program (called 'application' by QNodeOS) with QNodeOS. + self.send_qnos_msg(bytes(InitNewAppMessage(max_qubits=prog_meta.max_qubits))) + app_id = yield from self.receive_qnos_msg() + self._logger.debug(f"got app id from qnos: {app_id}") + + # Set up the Connection object to be used by the program SDK code. + conn = QnosConnection( + self, + app_id, + prog_meta.name, + max_qubits=prog_meta.max_qubits, + compiler=self._compiler, + ) + + # Create EPR sockets that can be used by the program SDK code. + epr_sockets: Dict[int, EPRSocket] = {} + for i, remote_name in enumerate(prog_meta.epr_sockets): + remote_id = None + nodes = NetSquidContext.get_nodes() + for id, name in nodes.items(): + if name == remote_name: + remote_id = id + assert remote_id is not None + self.send_qnos_msg(bytes(OpenEPRSocketMessage(app_id, i, remote_id))) + epr_sockets[remote_name] = EPRSocket(remote_name, i) + epr_sockets[remote_name].conn = conn + + # Create classical sockets that can be used by the program SDK code. + classical_sockets: Dict[int, ClassicalSocket] = {} + for i, remote_name in enumerate(prog_meta.csockets): + remote_id = None + nodes = NetSquidContext.get_nodes() + for id, name in nodes.items(): + if name == remote_name: + remote_id = id + assert remote_id is not None + classical_sockets[remote_name] = ClassicalSocket( + self, prog_meta.name, remote_name + ) + + context = ProgramContext( + netqasm_connection=conn, + csockets=classical_sockets, + epr_sockets=epr_sockets, + app_id=app_id, + ) + + lhr_program = program.compile(context) + self._logger.warning(f"Runnign compiled SDK program:\n{lhr_program}") + process = LhrProcess(self, lhr_program) + yield from process.run_with_context(context, 1) + self._program_results = process._program_results + + def run_lhr_program( + self, program: lhr.LhrProgram, num_times: int + ) -> Generator[EventExpression, None, None]: + self._logger.warning(f"Creating LHR process for program:\n{program}") + process = LhrProcess(self, program) + result = yield from process.run(num_times) + return result + + def run(self) -> Generator[EventExpression, None, None]: + """Run this protocol. Automatically called by NetSquid during simulation.""" + + # Run a single program as many times as requested. + while self._num_pending > 0: + self._logger.info(f"num pending: {self._num_pending}") + self._num_pending -= 1 + + assert self._program is not None + + if isinstance(self._program, lhr.LhrProgram): + yield from self.run_lhr_program(self._program, 1) + elif isinstance(self._program, lhr.SdkProgram): + yield from self.run_lhr_sdk_program(self._program) + else: + self.run_sdk_program(self._program) + + def enqueue_program(self, program: Program, num_times: int = 1): + """Queue a program to be run the given number of times. + + NOTE: At the moment, only a single program can be queued at a time.""" + self._program = program + self._num_pending = num_times + + def get_results(self) -> List[Dict[str, Any]]: + return self._program_results diff --git a/squidasm/sim/qoala/netstack.py b/squidasm/sim/qoala/netstack.py new file mode 100644 index 00000000..c12c17bf --- /dev/null +++ b/squidasm/sim/qoala/netstack.py @@ -0,0 +1,779 @@ +from __future__ import annotations + +import math +from dataclasses import dataclass +from typing import TYPE_CHECKING, Dict, Generator, List, Optional + +import netsquid as ns +from netqasm.sdk.build_epr import ( + SER_CREATE_IDX_NUMBER, + SER_CREATE_IDX_TYPE, + SER_RESPONSE_KEEP_IDX_BELL_STATE, + SER_RESPONSE_KEEP_IDX_GOODNESS, + SER_RESPONSE_KEEP_LEN, + SER_RESPONSE_MEASURE_IDX_MEASUREMENT_BASIS, + SER_RESPONSE_MEASURE_IDX_MEASUREMENT_OUTCOME, + SER_RESPONSE_MEASURE_LEN, +) +from netsquid.components import QuantumProcessor +from netsquid.components.component import Component, Port +from netsquid.components.instructions import INSTR_ROT_X, INSTR_ROT_Z +from netsquid.components.qprogram import QuantumProgram +from netsquid.nodes import Node +from netsquid.qubits.ketstates import BellIndex +from netsquid_magic.link_layer import MagicLinkLayerProtocolWithSignaling +from qlink_interface import ( + ReqCreateAndKeep, + ReqCreateBase, + ReqMeasureDirectly, + ReqReceive, + ResCreateAndKeep, + ResMeasureDirectly, +) +from qlink_interface.interface import ReqRemoteStatePrep + +from pydynaa import EventExpression +from squidasm.sim.stack.common import ( + AllocError, + AppMemory, + ComponentProtocol, + NetstackBreakpointCreateRequest, + NetstackBreakpointReceiveRequest, + NetstackCreateRequest, + NetstackReceiveRequest, + PhysicalQuantumMemory, + PortListener, +) +from squidasm.sim.stack.egp import EgpProtocol +from squidasm.sim.stack.signals import ( + SIGNAL_MEMORY_FREED, + SIGNAL_PEER_NSTK_MSG, + SIGNAL_PROC_NSTK_MSG, +) + +if TYPE_CHECKING: + from squidasm.sim.stack.qnos import Qnos + +PI = math.pi +PI_OVER_2 = math.pi / 2 + + +class NetstackComponent(Component): + """NetSquid component representing the network stack in QNodeOS. + + Subcomponent of a QnosComponent. + + Has communications ports with + - the processor component of this QNodeOS + - the netstack compmonent of the remote node + NOTE: at this moment only a single other node is supported in the network + + This is a static container for network-stack-related components and ports. + Behavior of a QNodeOS network stack is modeled in the `NetStack` class, + which is a subclass of `Protocol`. + """ + + def __init__(self, node: Node) -> None: + super().__init__(f"{node.name}_netstack") + self._node = node + self.add_ports(["proc_out", "proc_in"]) + self.add_ports(["peer_out", "peer_in"]) + + @property + def processor_in_port(self) -> Port: + return self.ports["proc_in"] + + @property + def processor_out_port(self) -> Port: + return self.ports["proc_out"] + + @property + def peer_in_port(self) -> Port: + return self.ports["peer_in"] + + @property + def peer_out_port(self) -> Port: + return self.ports["peer_out"] + + @property + def node(self) -> Node: + return self._node + + +@dataclass +class EprSocket: + """EPR Socket. Allows for EPR pair generation with a single remote node. + + Multiple EPR Sockets may be created for a single pair of nodes. These + sockets have a different ID, and may e.g be used for EPR generation requests + with different parameters.""" + + socket_id: int + remote_id: int + + +class Netstack(ComponentProtocol): + """NetSquid protocol representing the QNodeOS network stack.""" + + def __init__(self, comp: NetstackComponent, qnos: Qnos) -> None: + """Network stack protocol constructor. Typically created indirectly through + constructing a `Qnos` instance. + + :param comp: NetSquid component representing the network stack + :param qnos: `Qnos` protocol that owns this protocol + """ + super().__init__(name=f"{comp.name}_protocol", comp=comp) + self._comp = comp + self._qnos = qnos + + self.add_listener( + "processor", + PortListener(self._comp.processor_in_port, SIGNAL_PROC_NSTK_MSG), + ) + self.add_listener( + "peer", + PortListener(self._comp.peer_in_port, SIGNAL_PEER_NSTK_MSG), + ) + + self._egp: Optional[EgpProtocol] = None + self._epr_sockets: Dict[int, List[EprSocket]] = {} # app ID -> [socket] + + def assign_ll_protocol(self, prot: MagicLinkLayerProtocolWithSignaling) -> None: + """Set the magic link layer protocol that this network stack uses to produce + entangled pairs with the remote node. + + :param prot: link layer protocol instance + """ + self._egp = EgpProtocol(self._comp.node, prot) + + def open_epr_socket(self, app_id: int, socket_id: int, remote_node_id: int) -> None: + """Create a new EPR socket with the specified remote node. + + :param app_id: ID of the application that creates this EPR socket + :param socket_id: ID of the socket + :param remote_node_id: ID of the remote node + """ + if app_id not in self._epr_sockets: + self._epr_sockets[app_id] = [] + self._epr_sockets[app_id].append(EprSocket(socket_id, remote_node_id)) + + def _send_processor_msg(self, msg: str) -> None: + """Send a message to the processor.""" + self._comp.processor_out_port.tx_output(msg) + + def _receive_processor_msg(self) -> Generator[EventExpression, None, str]: + """Receive a message from the processor. Block until there is at least one + message.""" + return (yield from self._receive_msg("processor", SIGNAL_PROC_NSTK_MSG)) + + def _send_peer_msg(self, msg: str) -> None: + """Send a message to the network stack of the other node. + + NOTE: for now we assume there is only one other node, which is 'the' peer.""" + self._comp.peer_out_port.tx_output(msg) + + def _receive_peer_msg(self) -> Generator[EventExpression, None, str]: + """Receive a message from the network stack of the other node. Block until + there is at least one message. + + NOTE: for now we assume there is only one other node, which is 'the' peer.""" + return (yield from self._receive_msg("peer", SIGNAL_PEER_NSTK_MSG)) + + def start(self) -> None: + """Start this protocol. The NetSquid simulator will call and yield on the + `run` method. Also start the underlying EGP protocol.""" + super().start() + if self._egp: + self._egp.start() + + def stop(self) -> None: + """Stop this protocol. The NetSquid simulator will stop calling `run`. + Also stop the underlying EGP protocol.""" + if self._egp: + self._egp.stop() + super().stop() + + def _read_request_args_array(self, app_id: int, array_addr: int) -> List[int]: + app_mem = self.app_memories[app_id] + app_mem.get_array(array_addr) + return app_mem.get_array(array_addr) + + def _construct_request(self, remote_id: int, args: List[int]) -> ReqCreateBase: + """Construct a link layer request from application request info. + + :param remote_id: ID of remote node + :param args: NetQASM array elements from the arguments array specified by the + application + :return: link layer request object + """ + typ = args[SER_CREATE_IDX_TYPE] + assert typ is not None + num_pairs = args[SER_CREATE_IDX_NUMBER] + assert num_pairs is not None + + # TODO + MINIMUM_FIDELITY = 0.99 + + if typ == 0: + request = ReqCreateAndKeep( + remote_node_id=remote_id, + number=num_pairs, + minimum_fidelity=MINIMUM_FIDELITY, + ) + elif typ == 1: + request = ReqMeasureDirectly( + remote_node_id=remote_id, + number=num_pairs, + minimum_fidelity=MINIMUM_FIDELITY, + ) + elif typ == 2: + request = ReqRemoteStatePrep( + remote_node_id=remote_id, + number=num_pairs, + minimum_fidelity=MINIMUM_FIDELITY, + ) + else: + raise ValueError(f"Unsupported create type {typ}") + return request + + @property + def app_memories(self) -> Dict[int, AppMemory]: + return self._qnos.app_memories + + @property + def physical_memory(self) -> PhysicalQuantumMemory: + return self._qnos.physical_memory + + @property + def qdevice(self) -> QuantumProcessor: + return self._comp.node.qdevice + + def find_epr_socket( + self, app_id: int, sck_id: int, rem_id: int + ) -> Optional[EprSocket]: + """Get a specific EPR socket or None if it does not exist. + + :param app_id: app ID + :param sck_id: EPR socket ID + :param rem_id: remote node ID + :return: the corresponding EPR socket or None if it does not exist + """ + if app_id not in self._epr_sockets: + return None + for sck in self._epr_sockets[app_id]: + if sck.socket_id == sck_id and sck.remote_id == rem_id: + return sck + return None + + def handle_create_ck_request( + self, req: NetstackCreateRequest, request: ReqCreateAndKeep + ) -> Generator[EventExpression, None, None]: + """Handle a Create and Keep request as the initiator/creator, until all + pairs have been created. + + This method uses the EGP protocol to create and measure EPR pairs with + the remote node. It will fully complete the request before returning. If + the pair created by the EGP protocol is another Bell state than Phi+, + local gates are applied to do a correction, such that the final + delivered pair is always Phi+. + + The method can however yield (i.e. give control back to the simulator + scheduler) in the following cases: - no communication qubit is + available; this method will resume when a + SIGNAL_MEMORY_FREED is given (currently only the processor can do + this) + - when waiting for the EGP protocol to produce the next pair; this + method resumes when the pair is delivered + - a Bell correction gate is applied + + This method does not return anything. This method has the side effect + that NetQASM array value are written to. + + :param req: application request info (app ID and NetQASM array IDs) + :param request: link layer request object + """ + num_pairs = request.number + + app_mem = self.app_memories[req.app_id] + qubit_ids = app_mem.get_array(req.qubit_array_addr) + + self._logger.info(f"putting CK request to EGP for {num_pairs} pairs") + self._logger.info(f"qubit IDs specified by application: {qubit_ids}") + self._logger.info(f"splitting request into {num_pairs} 1-pair requests") + request.number = 1 + + start_time = ns.sim_time() + + for pair_index in range(num_pairs): + self._logger.info(f"trying to allocate comm qubit for pair {pair_index}") + while True: + try: + phys_id = self.physical_memory.allocate_comm() + break + except AllocError: + self._logger.info("no comm qubit available, waiting...") + + # Wait for a signal indicating the communication qubit might be free + # again. + yield self.await_signal( + sender=self._qnos.processor, signal_label=SIGNAL_MEMORY_FREED + ) + self._logger.info( + "a 'free' happened, trying again to allocate comm qubit..." + ) + + # Put the request to the EGP. + self._logger.info(f"putting CK request for pair {pair_index}") + self._egp.put(request) + + # Wait for a signal from the EGP. + self._logger.info(f"waiting for result for pair {pair_index}") + yield self.await_signal( + sender=self._egp, signal_label=ResCreateAndKeep.__name__ + ) + # Get the EGP's result. + result: ResCreateAndKeep = self._egp.get_signal_result( + ResCreateAndKeep.__name__, receiver=self + ) + self._logger.info(f"got result for pair {pair_index}: {result}") + + # Bell state corrections. Resulting state is always Phi+ (i.e. B00). + if result.bell_state == BellIndex.B00: + pass + elif result.bell_state == BellIndex.B01: + prog = QuantumProgram() + prog.apply(INSTR_ROT_X, qubit_indices=[0], angle=PI) + yield self.qdevice.execute_program(prog) + elif result.bell_state == BellIndex.B10: + prog = QuantumProgram() + prog.apply(INSTR_ROT_Z, qubit_indices=[0], angle=PI) + yield self.qdevice.execute_program(prog) + elif result.bell_state == BellIndex.B11: + prog = QuantumProgram() + prog.apply(INSTR_ROT_X, qubit_indices=[0], angle=PI) + prog.apply(INSTR_ROT_Z, qubit_indices=[0], angle=PI) + yield self.qdevice.execute_program(prog) + + virt_id = app_mem.get_array_value(req.qubit_array_addr, pair_index) + app_mem.map_virt_id(virt_id, phys_id) + self._logger.info( + f"mapping virtual qubit {virt_id} to physical qubit {phys_id}" + ) + + gen_duration_ns_float = ns.sim_time() - start_time + gen_duration_us_int = int(gen_duration_ns_float / 1000) + self._logger.info(f"gen duration (us): {gen_duration_us_int}") + + # Length of response array slice for a single pair. + slice_len = SER_RESPONSE_KEEP_LEN + + # Populate results array. + for i in range(slice_len): + # Write -1 to unused array elements. + value = -1 + + # Write corresponding result value to the other array elements. + if i == SER_RESPONSE_KEEP_IDX_GOODNESS: + value = gen_duration_us_int + if i == SER_RESPONSE_KEEP_IDX_BELL_STATE: + value = result.bell_state + + # Calculate array element location. + arr_index = slice_len * pair_index + i + + app_mem.set_array_value(req.result_array_addr, arr_index, value) + self._logger.debug( + f"wrote to @{req.result_array_addr}[{slice_len * pair_index}:" + f"{slice_len * pair_index + slice_len}] for app ID {req.app_id}" + ) + self._send_processor_msg("wrote to array") + + def handle_create_md_request( + self, req: NetstackCreateRequest, request: ReqMeasureDirectly + ) -> Generator[EventExpression, None, None]: + """Handle a Create and Measure request as the initiator/creator, until all + pairs have been created and measured. + + This method uses the EGP protocol to create EPR pairs with the remote node. + It will fully complete the request before returning. + + No Bell state corrections are done. This means that application code should + use the result information to check, for each pair, the generated Bell state + and possibly post-process the measurement outcomes. + + The method can yield (i.e. give control back to the simulator scheduler) in + the following cases: + - no communication qubit is available; this method will resume when a + SIGNAL_MEMORY_FREED is given (currently only the processor can do this) + - when waiting for the EGP protocol to produce the next pair; this method + resumes when the pair is delivered + + This method does not return anything. + This method has the side effect that NetQASM array value are written to. + + :param req: application request info (app ID and NetQASM array IDs) + :param request: link layer request object + """ + + # Put the reqeust to the EGP. + self._egp.put(request) + + results: List[ResMeasureDirectly] = [] + + # Wait for all pairs to be created. For each pair, the EGP sends a separate + # signal that is awaited here. Only after the last pair, we write the results + # to the array. This is done since the whole request (i.e. all pairs) is + # expected to finish in a short time anyway. However, writing results for a + # pair as soon as they are done may be implemented in the future. + for _ in range(request.number): + phys_id = self.physical_memory.allocate_comm() + + yield self.await_signal( + sender=self._egp, signal_label=ResMeasureDirectly.__name__ + ) + result: ResMeasureDirectly = self._egp.get_signal_result( + ResMeasureDirectly.__name__, receiver=self + ) + self._logger.debug(f"bell index: {result.bell_state}") + results.append(result) + self.physical_memory.free(phys_id) + + app_mem = self.app_memories[req.app_id] + + # Length of response array slice for a single pair. + slice_len = SER_RESPONSE_MEASURE_LEN + + # Populate results array. + for pair_index in range(request.number): + result = results[pair_index] + + for i in range(slice_len): + # Write -1 to unused array elements. + value = -1 + + # Write corresponding result value to the other array elements. + if i == SER_RESPONSE_MEASURE_IDX_MEASUREMENT_OUTCOME: + value = result.measurement_outcome + elif i == SER_RESPONSE_MEASURE_IDX_MEASUREMENT_BASIS: + value = result.measurement_basis.value + elif i == SER_RESPONSE_KEEP_IDX_BELL_STATE: + value = result.bell_state.value + + # Calculate array element location. + arr_index = slice_len * pair_index + i + + app_mem.set_array_value(req.result_array_addr, arr_index, value) + + self._send_processor_msg("wrote to array") + + def handle_create_request( + self, req: NetstackCreateRequest + ) -> Generator[EventExpression, None, None]: + """Issue a request to create entanglement with a remote node. + + :param req: request info + """ + + # EPR socket should exist. + assert ( + self.find_epr_socket(req.app_id, req.epr_socket_id, req.remote_node_id) + is not None + ) + + # Read request parameters from the corresponding NetQASM array. + args = self._read_request_args_array(req.app_id, req.arg_array_addr) + + # Create the link layer request object. + request = self._construct_request(req.remote_node_id, args) + + # Send it to the receiver node and wait for an acknowledgement. + self._send_peer_msg(request) + peer_msg = yield from self._receive_peer_msg() + self._logger.debug(f"received peer msg: {peer_msg}") + + # Handle the request. + if isinstance(request, ReqCreateAndKeep): + yield from self.handle_create_ck_request(req, request) + elif isinstance(request, ReqMeasureDirectly): + yield from self.handle_create_md_request(req, request) + + def handle_receive_ck_request( + self, req: NetstackReceiveRequest, request: ReqCreateAndKeep + ) -> Generator[EventExpression, None, None]: + """Handle a Create and Keep request as the receiver, until all pairs have + been created. + + This method uses the EGP protocol to create EPR pairs with the remote + node. It will fully complete the request before returning. + + If the pair created by the EGP protocol is another Bell state than Phi+, + it is assumed that the *other* node applies local gates such that the + final delivered pair is always Phi+. + + The method can yield (i.e. give control back to the simulator scheduler) + in the following cases: - no communication qubit is available; this + method will resume when a + SIGNAL_MEMORY_FREED is given (currently only the processor can do + this) + - when waiting for the EGP protocol to produce the next pair; this + method resumes when the pair is delivered + + This method does not return anything. This method has the side effect + that NetQASM array value are written to. + + :param req: application request info (app ID and NetQASM array IDs) + :param request: link layer request object + """ + assert isinstance(request, ReqCreateAndKeep) + + num_pairs = request.number + + self._logger.info(f"putting CK request to EGP for {num_pairs} pairs") + self._logger.info(f"splitting request into {num_pairs} 1-pair requests") + + start_time = ns.sim_time() + + for pair_index in range(num_pairs): + self._logger.info(f"trying to allocate comm qubit for pair {pair_index}") + while True: + try: + phys_id = self.physical_memory.allocate_comm() + break + except AllocError: + self._logger.info("no comm qubit available, waiting...") + + # Wait for a signal indicating the communication qubit might be free + # again. + yield self.await_signal( + sender=self._qnos.processor, signal_label=SIGNAL_MEMORY_FREED + ) + self._logger.info( + "a 'free' happened, trying again to allocate comm qubit..." + ) + + # Put the request to the EGP. + self._logger.info(f"putting CK request for pair {pair_index}") + self._egp.put(ReqReceive(remote_node_id=req.remote_node_id)) + self._logger.info(f"waiting for result for pair {pair_index}") + + # Wait for a signal from the EGP. + yield self.await_signal( + sender=self._egp, signal_label=ResCreateAndKeep.__name__ + ) + # Get the EGP's result. + result: ResCreateAndKeep = self._egp.get_signal_result( + ResCreateAndKeep.__name__, receiver=self + ) + self._logger.info(f"got result for pair {pair_index}: {result}") + + app_mem = self.app_memories[req.app_id] + virt_id = app_mem.get_array_value(req.qubit_array_addr, pair_index) + app_mem.map_virt_id(virt_id, phys_id) + self._logger.info( + f"mapping virtual qubit {virt_id} to physical qubit {phys_id}" + ) + + gen_duration_ns_float = ns.sim_time() - start_time + gen_duration_us_int = int(gen_duration_ns_float / 1000) + self._logger.info(f"gen duration (us): {gen_duration_us_int}") + + # Length of response array slice for a single pair. + slice_len = SER_RESPONSE_KEEP_LEN + + for i in range(slice_len): + # Write -1 to unused array elements. + value = -1 + + # Write corresponding result value to the other array elements. + if i == SER_RESPONSE_KEEP_IDX_GOODNESS: + value = gen_duration_us_int + if i == SER_RESPONSE_KEEP_IDX_BELL_STATE: + value = result.bell_state.value + + # Calculate array element location. + arr_index = slice_len * pair_index + i + + app_mem.set_array_value(req.result_array_addr, arr_index, value) + self._logger.debug( + f"wrote to @{req.result_array_addr}[{slice_len * pair_index}:" + f"{slice_len * pair_index + slice_len}] for app ID {req.app_id}" + ) + self._send_processor_msg("wrote to array") + + def handle_receive_md_request( + self, req: NetstackReceiveRequest, request: ReqMeasureDirectly + ) -> Generator[EventExpression, None, None]: + """Handle a Create and Measure request as the receiver, until all + pairs have been created and measured. + + This method uses the EGP protocol to create EPR pairs with the remote node. + It will fully complete the request before returning. + + No Bell state corrections are done. This means that application code should + use the result information to check, for each pair, the generated Bell state + and possibly post-process the measurement outcomes. + + The method can yield (i.e. give control back to the simulator scheduler) + in the following cases: - no communication qubit is available; this + method will resume when a + SIGNAL_MEMORY_FREED is given (currently only the processor can do + this) + - when waiting for the EGP protocol to produce the next pair; this + method resumes when the pair is delivered + + This method does not return anything. This method has the side effect + that NetQASM array value are written to. + + :param req: application request info (app ID and NetQASM array IDs) + :param request: link layer request object + """ + assert isinstance(request, ReqMeasureDirectly) + + self._egp.put(ReqReceive(remote_node_id=req.remote_node_id)) + + results: List[ResMeasureDirectly] = [] + + for _ in range(request.number): + phys_id = self.physical_memory.allocate_comm() + + yield self.await_signal( + sender=self._egp, signal_label=ResMeasureDirectly.__name__ + ) + result: ResMeasureDirectly = self._egp.get_signal_result( + ResMeasureDirectly.__name__, receiver=self + ) + results.append(result) + + self.physical_memory.free(phys_id) + + app_mem = self.app_memories[req.app_id] + + # Length of response array slice for a single pair. + slice_len = SER_RESPONSE_MEASURE_LEN + + # Populate results array. + for pair_index in range(request.number): + result = results[pair_index] + + for i in range(slice_len): + # Write -1 to unused array elements. + value = -1 + + # Write corresponding result value to the other array elements. + if i == SER_RESPONSE_MEASURE_IDX_MEASUREMENT_OUTCOME: + value = result.measurement_outcome + elif i == SER_RESPONSE_MEASURE_IDX_MEASUREMENT_BASIS: + value = result.measurement_basis.value + elif i == SER_RESPONSE_KEEP_IDX_BELL_STATE: + value = result.bell_state.value + + # Calculate array element location. + arr_index = slice_len * pair_index + i + + app_mem.set_array_value(req.result_array_addr, arr_index, value) + + self._send_processor_msg("wrote to array") + + def handle_receive_request( + self, req: NetstackReceiveRequest + ) -> Generator[EventExpression, None, None]: + """Issue a request to receive entanglement from a remote node. + + :param req: request info + """ + + # EPR socket should exist. + assert ( + self.find_epr_socket(req.app_id, req.epr_socket_id, req.remote_node_id) + is not None + ) + + # Wait for the network stack in the remote node to get the corresponding + # 'create' request from its local application and send it to us. + # NOTE: we do not check if the request from the other node matches our own + # request. Also, we simply block until synchronizing with the other node, + # and then fully handle the request. There is no support for queueing + # and/or interleaving multiple different requests. + create_request = yield from self._receive_peer_msg() + self._logger.debug(f"received {create_request} from peer") + + # Acknowledge to the remote node that we received the request and we will + # start handling it. + self._logger.debug("sending 'ready' to peer") + self._send_peer_msg("ready") + + # Handle the request, based on the type that we now know because of the + # other node. + if isinstance(create_request, ReqCreateAndKeep): + yield from self.handle_receive_ck_request(req, create_request) + elif isinstance(create_request, ReqMeasureDirectly): + yield from self.handle_receive_md_request(req, create_request) + + def handle_breakpoint_create_request( + self, + ) -> Generator[EventExpression, None, None]: + # Synchronize with the remote node. + self._send_peer_msg("breakpoint start") + response = yield from self._receive_peer_msg() + assert response == "breakpoint start" + + # Remote node is now ready. Notify the processor. + self._send_processor_msg("breakpoint ready") + + # Wait for the processor to finish handling the breakpoint. + processor_msg = yield from self._receive_processor_msg() + assert processor_msg == "breakpoint end" + + # Tell the remote node that the breakpoint has finished. + self._send_peer_msg("breakpoint end") + + # Wait for the remote node to have finsihed as well. + response = yield from self._receive_peer_msg() + assert response == "breakpoint end" + + # Notify the processor that we are done. + self._send_processor_msg("breakpoint finished") + + def handle_breakpoint_receive_request( + self, + ) -> Generator[EventExpression, None, None]: + # Synchronize with the remote node. + msg = yield from self._receive_peer_msg() + assert msg == "breakpoint start" + self._send_peer_msg("breakpoint start") + + # Notify the processor we are ready to handle the breakpoint. + self._send_processor_msg("breakpoint ready") + + # Wait for the processor to finish handling the breakpoint. + processor_msg = yield from self._receive_processor_msg() + assert processor_msg == "breakpoint end" + + # Wait for the remote node to finish and tell it we are finished as well. + peer_msg = yield from self._receive_peer_msg() + assert peer_msg == "breakpoint end" + self._send_peer_msg("breakpoint end") + + # Notify the processor that we are done. + self._send_processor_msg("breakpoint finished") + + def run(self) -> Generator[EventExpression, None, None]: + # Loop forever acting on messages from the processor. + while True: + # Wait for a new message. + msg = yield from self._receive_processor_msg() + self._logger.debug(f"received new msg from processor: {msg}") + + # Handle it. + if isinstance(msg, NetstackCreateRequest): + yield from self.handle_create_request(msg) + self._logger.debug("create request done") + elif isinstance(msg, NetstackReceiveRequest): + yield from self.handle_receive_request(msg) + self._logger.debug("receive request done") + elif isinstance(msg, NetstackBreakpointCreateRequest): + yield from self.handle_breakpoint_create_request() + self._logger.debug("breakpoint create request done") + elif isinstance(msg, NetstackBreakpointReceiveRequest): + yield from self.handle_breakpoint_receive_request() + self._logger.debug("breakpoint receive request done") diff --git a/squidasm/sim/qoala/processor.py b/squidasm/sim/qoala/processor.py new file mode 100644 index 00000000..0e912eea --- /dev/null +++ b/squidasm/sim/qoala/processor.py @@ -0,0 +1,870 @@ +from __future__ import annotations + +import math +from typing import TYPE_CHECKING, Dict, Generator, Optional, Union + +import netsquid as ns +from netqasm.lang.instr import NetQASMInstruction, core, nv, vanilla +from netqasm.lang.operand import Register +from netqasm.lang.subroutine import Subroutine +from netsquid.components import QuantumProcessor +from netsquid.components.component import Component, Port +from netsquid.components.instructions import ( + INSTR_CNOT, + INSTR_CXDIR, + INSTR_CYDIR, + INSTR_CZ, + INSTR_H, + INSTR_INIT, + INSTR_MEASURE, + INSTR_ROT_X, + INSTR_ROT_Y, + INSTR_ROT_Z, + INSTR_X, + INSTR_Y, + INSTR_Z, +) +from netsquid.components.instructions import Instruction as NsInstr +from netsquid.components.qprogram import QuantumProgram +from netsquid.nodes import Node +from netsquid.qubits import qubitapi + +from pydynaa import EventExpression +from squidasm.sim.stack.common import ( + AllocError, + AppMemory, + ComponentProtocol, + NetstackBreakpointCreateRequest, + NetstackBreakpointReceiveRequest, + NetstackCreateRequest, + NetstackReceiveRequest, + PhysicalQuantumMemory, + PortListener, +) +from squidasm.sim.stack.globals import GlobalSimData +from squidasm.sim.stack.signals import ( + SIGNAL_HAND_PROC_MSG, + SIGNAL_MEMORY_FREED, + SIGNAL_NSTK_PROC_MSG, +) + +if TYPE_CHECKING: + from squidasm.sim.stack.qnos import Qnos + +PI = math.pi +PI_OVER_2 = math.pi / 2 + + +class ProcessorComponent(Component): + """NetSquid component representing a QNodeOS processor. + + Subcomponent of a QnosComponent. + + Has communications ports with + - the netstack component of this QNodeOS + - the handler compmonent of this QNodeOS + + This is a static container for processor-related components and ports. + Behavior of a QNodeOS processor is modeled in the `Processor` class, + which is a subclass of `Protocol`. + """ + + def __init__(self, node: Node) -> None: + super().__init__(f"{node.name}_processor") + self._node = node + self.add_ports(["nstk_out", "nstk_in"]) + self.add_ports(["hand_out", "hand_in"]) + + @property + def netstack_in_port(self) -> Port: + return self.ports["nstk_in"] + + @property + def netstack_out_port(self) -> Port: + return self.ports["nstk_out"] + + @property + def handler_in_port(self) -> Port: + return self.ports["hand_in"] + + @property + def handler_out_port(self) -> Port: + return self.ports["hand_out"] + + @property + def qdevice(self) -> QuantumProcessor: + return self.supercomponent.qdevice + + @property + def node(self) -> Node: + return self._node + + +class Processor(ComponentProtocol): + """NetSquid protocol representing a QNodeOS processor.""" + + def __init__(self, comp: ProcessorComponent, qnos: Qnos) -> None: + """Processor protocol constructor. Typically created indirectly through + constructing a `Qnos` instance. + + :param comp: NetSquid component representing the processor + :param qnos: `Qnos` protocol that owns this protocol + """ + super().__init__(name=f"{comp.name}_protocol", comp=comp) + self._comp = comp + self._qnos = qnos + + self.add_listener( + "handler", + PortListener(self._comp.ports["hand_in"], SIGNAL_HAND_PROC_MSG), + ) + self.add_listener( + "netstack", + PortListener(self._comp.ports["nstk_in"], SIGNAL_NSTK_PROC_MSG), + ) + + self.add_signal(SIGNAL_MEMORY_FREED) + + @property + def app_memories(self) -> Dict[int, AppMemory]: + """Get a dictionary of app IDs to application memories.""" + return self._qnos.app_memories + + @property + def physical_memory(self) -> PhysicalQuantumMemory: + """Get the physical quantum memory object.""" + return self._qnos.physical_memory + + @property + def qdevice(self) -> QuantumProcessor: + """Get the NetSquid `QuantumProcessor` object of this node.""" + return self._comp.qdevice + + def _send_handler_msg(self, msg: str) -> None: + self._comp.handler_out_port.tx_output(msg) + + def _receive_handler_msg(self) -> Generator[EventExpression, None, str]: + return (yield from self._receive_msg("handler", SIGNAL_HAND_PROC_MSG)) + + def _send_netstack_msg(self, msg: str) -> None: + self._comp.netstack_out_port.tx_output(msg) + + def _receive_netstack_msg(self) -> Generator[EventExpression, None, str]: + return (yield from self._receive_msg("netstack", SIGNAL_NSTK_PROC_MSG)) + + def _flush_netstack_msgs(self) -> None: + self._listeners["netstack"].buffer.clear() + + def run(self) -> Generator[EventExpression, None, None]: + """Run this protocol. Automatically called by NetSquid during simulation.""" + while True: + subroutine = yield from self._receive_handler_msg() + # assert isinstance(subroutine, Subroutine) + self._logger.debug(f"received new subroutine from handler: {subroutine}") + + yield from self.execute_subroutine(subroutine) + + self._send_handler_msg("subroutine done") + + def execute_subroutine( + self, subroutine: Subroutine + ) -> Generator[EventExpression, None, None]: + """Execute a NetQASM subroutine on this processor.""" + app_id = subroutine.app_id + assert app_id in self.app_memories + app_mem = self.app_memories[app_id] + app_mem.set_prog_counter(0) + while app_mem.prog_counter < len(subroutine.instructions): + instr = subroutine.instructions[app_mem.prog_counter] + self._logger.debug( + f"{ns.sim_time()} interpreting instruction {instr} at line {app_mem.prog_counter}" + ) + + if ( + isinstance(instr, core.JmpInstruction) + or isinstance(instr, core.BranchUnaryInstruction) + or isinstance(instr, core.BranchBinaryInstruction) + ): + self._interpret_branch_instr(app_id, instr) + else: + generator = self._interpret_instruction(app_id, instr) + if generator: + yield from generator + app_mem.increment_prog_counter() + + def _interpret_instruction( + self, app_id: int, instr: NetQASMInstruction + ) -> Optional[Generator[EventExpression, None, None]]: + if isinstance(instr, core.SetInstruction): + return self._interpret_set(app_id, instr) + elif isinstance(instr, core.QAllocInstruction): + return self._interpret_qalloc(app_id, instr) + elif isinstance(instr, core.QFreeInstruction): + return self._interpret_qfree(app_id, instr) + elif isinstance(instr, core.StoreInstruction): + return self._interpret_store(app_id, instr) + elif isinstance(instr, core.LoadInstruction): + return self._interpret_load(app_id, instr) + elif isinstance(instr, core.LeaInstruction): + return self._interpret_lea(app_id, instr) + elif isinstance(instr, core.UndefInstruction): + return self._interpret_undef(app_id, instr) + elif isinstance(instr, core.ArrayInstruction): + return self._interpret_array(app_id, instr) + elif isinstance(instr, core.InitInstruction): + return self._interpret_init(app_id, instr) + elif isinstance(instr, core.MeasInstruction): + return self._interpret_meas(app_id, instr) + elif isinstance(instr, core.CreateEPRInstruction): + return self._interpret_create_epr(app_id, instr) + elif isinstance(instr, core.RecvEPRInstruction): + return self._interpret_recv_epr(app_id, instr) + elif isinstance(instr, core.WaitAllInstruction): + return self._interpret_wait_all(app_id, instr) + elif isinstance(instr, core.RetRegInstruction): + pass + elif isinstance(instr, core.RetArrInstruction): + pass + elif isinstance(instr, core.SingleQubitInstruction): + return self._interpret_single_qubit_instr(app_id, instr) + elif isinstance(instr, core.TwoQubitInstruction): + return self._interpret_two_qubit_instr(app_id, instr) + elif isinstance(instr, core.RotationInstruction): + return self._interpret_single_rotation_instr(app_id, instr) + elif isinstance(instr, core.ControlledRotationInstruction): + return self._interpret_controlled_rotation_instr(app_id, instr) + elif isinstance(instr, core.ClassicalOpInstruction) or isinstance( + instr, core.ClassicalOpModInstruction + ): + return self._interpret_binary_classical_instr(app_id, instr) + elif isinstance(instr, core.BreakpointInstruction): + return self._interpret_breakpoint(app_id, instr) + else: + raise RuntimeError(f"Invalid instruction {instr}") + + def _interpret_breakpoint( + self, app_id: int, instr: core.BreakpointInstruction + ) -> None: + if instr.action.value == 0: + self._logger.info("BREAKPOINT: no action taken") + elif instr.action.value == 1: + self._logger.info("BREAKPOINT: dumping local state:") + for i in range(self.qdevice.num_positions): + if self.qdevice.mem_positions[i].in_use: + q = self.qdevice.peek(i, skip_noise=True) + qstate = qubitapi.reduced_dm(q) + self._logger.info(f"physical qubit {i}:\n{qstate}") + + GlobalSimData.get_quantum_state(save=True) # TODO: rewrite this + elif instr.action.value == 2: + self._logger.info("BREAKPOINT: dumping global state:") + if instr.role.value == 0: + self._send_netstack_msg(NetstackBreakpointCreateRequest(app_id)) + ready = yield from self._receive_netstack_msg() + assert ready == "breakpoint ready" + + state = GlobalSimData.get_quantum_state(save=True) + self._logger.info(state) + + self._send_netstack_msg("breakpoint end") + finished = yield from self._receive_netstack_msg() + assert finished == "breakpoint finished" + elif instr.role.value == 1: + self._send_netstack_msg(NetstackBreakpointReceiveRequest(app_id)) + ready = yield from self._receive_netstack_msg() + assert ready == "breakpoint ready" + self._send_netstack_msg("breakpoint end") + finished = yield from self._receive_netstack_msg() + assert finished == "breakpoint finished" + else: + raise ValueError + else: + raise ValueError + + def _interpret_set(self, app_id: int, instr: core.SetInstruction) -> None: + self._logger.debug(f"Set register {instr.reg} to {instr.imm}") + self.app_memories[app_id].set_reg_value(instr.reg, instr.imm.value) + + def _interpret_qalloc(self, app_id: int, instr: core.QAllocInstruction) -> None: + app_mem = self.app_memories[app_id] + + virt_id = app_mem.get_reg_value(instr.reg) + if virt_id is None: + raise RuntimeError(f"qubit address in register {instr.reg} is not defined") + self._logger.debug(f"Allocating qubit with virtual ID {virt_id}") + + phys_id = self.physical_memory.allocate() + app_mem.map_virt_id(virt_id, phys_id) + + def _interpret_qfree(self, app_id: int, instr: core.QFreeInstruction) -> None: + app_mem = self.app_memories[app_id] + + virt_id = app_mem.get_reg_value(instr.reg) + assert virt_id is not None + self._logger.debug(f"Freeing virtual qubit {virt_id}") + phys_id = app_mem.phys_id_for(virt_id) + assert phys_id is not None + app_mem.unmap_virt_id(virt_id) + self.physical_memory.free(phys_id) + self.send_signal(SIGNAL_MEMORY_FREED) + self.qdevice.mem_positions[phys_id].in_use = False + + def _interpret_store(self, app_id: int, instr: core.StoreInstruction) -> None: + app_mem = self.app_memories[app_id] + + value = app_mem.get_reg_value(instr.reg) + if value is None: + raise RuntimeError(f"value in register {instr.reg} is not defined") + self._logger.debug( + f"Storing value {value} from register {instr.reg} " + f"to array entry {instr.entry}" + ) + + app_mem.set_array_entry(instr.entry, value) + + def _interpret_load(self, app_id: int, instr: core.LoadInstruction) -> None: + app_mem = self.app_memories[app_id] + + value = app_mem.get_array_entry(instr.entry) + if value is None: + raise RuntimeError(f"array value at {instr.entry} is not defined") + self._logger.debug( + f"Storing value {value} from array entry {instr.entry} " + f"to register {instr.reg}" + ) + + app_mem.set_reg_value(instr.reg, value) + + def _interpret_lea(self, app_id: int, instr: core.LeaInstruction) -> None: + app_mem = self.app_memories[app_id] + self._logger.debug( + f"Storing address of {instr.address} to register {instr.reg}" + ) + app_mem.set_reg_value(instr.reg, instr.address.address) + + def _interpret_undef(self, app_id: int, instr: core.UndefInstruction) -> None: + app_mem = self.app_memories[app_id] + self._logger.debug(f"Unset array entry {instr.entry}") + app_mem.set_array_entry(instr.entry, None) + + def _interpret_array(self, app_id: int, instr: core.ArrayInstruction) -> None: + app_mem = self.app_memories[app_id] + + length = app_mem.get_reg_value(instr.size) + assert length is not None + self._logger.debug( + f"Initializing an array of length {length} at address {instr.address}" + ) + + app_mem.init_new_array(instr.address.address, length) + + def _interpret_branch_instr( + self, + app_id: int, + instr: Union[ + core.BranchUnaryInstruction, + core.BranchBinaryInstruction, + core.JmpInstruction, + ], + ) -> None: + app_mem = self.app_memories[app_id] + a, b = None, None + registers = [] + if isinstance(instr, core.BranchUnaryInstruction): + a = app_mem.get_reg_value(instr.reg) + registers = [instr.reg] + elif isinstance(instr, core.BranchBinaryInstruction): + a = app_mem.get_reg_value(instr.reg0) + b = app_mem.get_reg_value(instr.reg1) + registers = [instr.reg0, instr.reg1] + + if isinstance(instr, core.JmpInstruction): + condition = True + elif isinstance(instr, core.BranchUnaryInstruction): + condition = instr.check_condition(a) + elif isinstance(instr, core.BranchBinaryInstruction): + condition = instr.check_condition(a, b) + + if condition: + jump_address = instr.line + self._logger.debug( + f"Branching to line {jump_address}, since {instr}(a={a}, b={b}) " + f"is True, with values from registers {registers}" + ) + app_mem.set_prog_counter(jump_address.value) + else: + self._logger.debug( + f"Don't branch, since {instr}(a={a}, b={b}) " + f"is False, with values from registers {registers}" + ) + app_mem.increment_prog_counter() + + def _interpret_binary_classical_instr( + self, + app_id: int, + instr: Union[ + core.ClassicalOpInstruction, + core.ClassicalOpModInstruction, + ], + ) -> None: + app_mem = self.app_memories[app_id] + mod = None + if isinstance(instr, core.ClassicalOpModInstruction): + mod = app_mem.get_reg_value(instr.regmod) + if mod is not None and mod < 1: + raise RuntimeError(f"Modulus needs to be greater or equal to 1, not {mod}") + a = app_mem.get_reg_value(instr.regin0) + b = app_mem.get_reg_value(instr.regin1) + assert a is not None + assert b is not None + value = self._compute_binary_classical_instr(instr, a, b, mod=mod) + mod_str = "" if mod is None else f"(mod {mod})" + self._logger.debug( + f"Performing {instr} of a={a} and b={b} {mod_str} " + f"and storing the value {value} at register {instr.regout}" + ) + app_mem.set_reg_value(instr.regout, value) + + def _compute_binary_classical_instr( + self, instr: NetQASMInstruction, a: int, b: int, mod: Optional[int] = 1 + ) -> int: + if isinstance(instr, core.AddInstruction): + return a + b + elif isinstance(instr, core.AddmInstruction): + assert mod is not None + return (a + b) % mod + elif isinstance(instr, core.SubInstruction): + return a - b + elif isinstance(instr, core.SubmInstruction): + assert mod is not None + return (a - b) % mod + else: + raise ValueError(f"{instr} cannot be used as binary classical function") + + def _interpret_init( + self, app_id: int, instr: core.InitInstruction + ) -> Generator[EventExpression, None, None]: + raise NotImplementedError + + def _do_single_rotation( + self, + app_id: int, + instr: core.RotationInstruction, + ns_instr: NsInstr, + ) -> Generator[EventExpression, None, None]: + app_mem = self.app_memories[app_id] + virt_id = app_mem.get_reg_value(instr.reg) + phys_id = app_mem.phys_id_for(virt_id) + angle = self._get_rotation_angle_from_operands( + n=instr.angle_num.value, + d=instr.angle_denom.value, + ) + self._logger.debug( + f"Performing {instr} with angle {angle} on virtual qubit " + f"{virt_id} (physical ID: {phys_id})" + ) + prog = QuantumProgram() + prog.apply(ns_instr, qubit_indices=[phys_id], angle=angle) + yield self.qdevice.execute_program(prog) + + def _interpret_single_rotation_instr( + self, app_id: int, instr: nv.RotXInstruction + ) -> Generator[EventExpression, None, None]: + raise NotImplementedError + + def _do_controlled_rotation( + self, + app_id: int, + instr: core.ControlledRotationInstruction, + ns_instr: NsInstr, + ) -> Generator[EventExpression, None, None]: + app_mem = self.app_memories[app_id] + virt_id0 = app_mem.get_reg_value(instr.reg0) + phys_id0 = app_mem.phys_id_for(virt_id0) + virt_id1 = app_mem.get_reg_value(instr.reg1) + phys_id1 = app_mem.phys_id_for(virt_id1) + angle = self._get_rotation_angle_from_operands( + n=instr.angle_num.value, + d=instr.angle_denom.value, + ) + self._logger.debug( + f"Performing {instr} with angle {angle} on virtual qubits " + f"{virt_id0} and {virt_id1} (physical IDs: {phys_id0} and {phys_id1})" + ) + prog = QuantumProgram() + prog.apply(ns_instr, qubit_indices=[phys_id0, phys_id1], angle=angle) + yield self.qdevice.execute_program(prog) + + def _interpret_controlled_rotation_instr( + self, app_id: int, instr: core.ControlledRotationInstruction + ) -> Generator[EventExpression, None, None]: + raise NotImplementedError + + def _get_rotation_angle_from_operands(self, n: int, d: int) -> float: + return float(n * PI / (2**d)) + + def _interpret_meas( + self, app_id: int, instr: core.MeasInstruction + ) -> Generator[EventExpression, None, None]: + raise NotImplementedError + + def _interpret_create_epr( + self, app_id: int, instr: core.CreateEPRInstruction + ) -> None: + app_mem = self.app_memories[app_id] + remote_node_id = app_mem.get_reg_value(instr.remote_node_id) + epr_socket_id = app_mem.get_reg_value(instr.epr_socket_id) + qubit_array_addr = app_mem.get_reg_value(instr.qubit_addr_array) + arg_array_addr = app_mem.get_reg_value(instr.arg_array) + result_array_addr = app_mem.get_reg_value(instr.ent_results_array) + assert remote_node_id is not None + assert epr_socket_id is not None + # qubit_array_addr can be None + assert arg_array_addr is not None + assert result_array_addr is not None + self._logger.debug( + f"Creating EPR pair with remote node id {remote_node_id} " + f"and EPR socket ID {epr_socket_id}, " + f"using qubit addresses stored in array with address {qubit_array_addr}, " + f"using arguments stored in array with address {arg_array_addr}, " + f"placing the entanglement information in array at " + f"address {result_array_addr}" + ) + + msg = NetstackCreateRequest( + app_id, + remote_node_id, + epr_socket_id, + qubit_array_addr, + arg_array_addr, + result_array_addr, + ) + self._send_netstack_msg(msg) + # result = yield from self._receive_netstack_msg() + # self._logger.debug(f"result from netstack: {result}") + + def _interpret_recv_epr(self, app_id: int, instr: core.RecvEPRInstruction) -> None: + app_mem = self.app_memories[app_id] + remote_node_id = app_mem.get_reg_value(instr.remote_node_id) + epr_socket_id = app_mem.get_reg_value(instr.epr_socket_id) + qubit_array_addr = app_mem.get_reg_value(instr.qubit_addr_array) + result_array_addr = app_mem.get_reg_value(instr.ent_results_array) + assert remote_node_id is not None + assert epr_socket_id is not None + # qubit_array_addr can be None + assert result_array_addr is not None + self._logger.debug( + f"Receiving EPR pair with remote node id {remote_node_id} " + f"and EPR socket ID {epr_socket_id}, " + f"using qubit addresses stored in array with address {qubit_array_addr}, " + f"placing the entanglement information in array at " + f"address {result_array_addr}" + ) + + msg = NetstackReceiveRequest( + app_id, + remote_node_id, + epr_socket_id, + qubit_array_addr, + result_array_addr, + ) + self._send_netstack_msg(msg) + # result = yield from self._receive_netstack_msg() + # self._logger.debug(f"result from netstack: {result}") + + def _interpret_wait_all( + self, app_id: int, instr: core.WaitAllInstruction + ) -> Generator[EventExpression, None, None]: + app_mem = self.app_memories[app_id] + self._logger.debug( + f"Waiting for all entries in array slice {instr.slice} to become defined" + ) + assert isinstance(instr.slice.start, Register) + assert isinstance(instr.slice.stop, Register) + start: int = app_mem.get_reg_value(instr.slice.start) + end: int = app_mem.get_reg_value(instr.slice.stop) + addr: int = instr.slice.address.address + + self._logger.debug( + f"checking if @{addr}[{start}:{end}] has values for app ID {app_id}" + ) + + while True: + values = self.app_memories[app_id].get_array_values(addr, start, end) + if any(v is None for v in values): + self._logger.debug( + f"waiting for netstack to write to @{addr}[{start}:{end}] " + f"for app ID {app_id}" + ) + yield from self._receive_netstack_msg() + self._logger.debug("netstack wrote something") + else: + break + self._flush_netstack_msgs() + self._logger.debug("all entries were written") + + self._logger.info(f"\nFinished waiting for array slice {instr.slice}") + + def _interpret_ret_reg(self, app_id: int, instr: core.RetRegInstruction) -> None: + pass + + def _interpret_ret_arr(self, app_id: int, instr: core.RetArrInstruction) -> None: + pass + + def _interpret_single_qubit_instr( + self, app_id: int, instr: core.SingleQubitInstruction + ) -> Generator[EventExpression, None, None]: + raise NotImplementedError + + def _interpret_two_qubit_instr( + self, app_id: int, instr: core.SingleQubitInstruction + ) -> Generator[EventExpression, None, None]: + raise NotImplementedError + + +class GenericProcessor(Processor): + """A `Processor` for nodes with a generic quantum hardware.""" + + def _interpret_init( + self, app_id: int, instr: core.InitInstruction + ) -> Generator[EventExpression, None, None]: + app_mem = self.app_memories[app_id] + virt_id = app_mem.get_reg_value(instr.reg) + phys_id = app_mem.phys_id_for(virt_id) + self._logger.debug( + f"Performing {instr} on virtual qubit " + f"{virt_id} (physical ID: {phys_id})" + ) + prog = QuantumProgram() + prog.apply(INSTR_INIT, qubit_indices=[phys_id]) + yield self.qdevice.execute_program(prog) + + def _interpret_meas( + self, app_id: int, instr: core.MeasInstruction + ) -> Generator[EventExpression, None, None]: + app_mem = self.app_memories[app_id] + virt_id = app_mem.get_reg_value(instr.qreg) + phys_id = app_mem.phys_id_for(virt_id) + + self._logger.debug( + f"Measuring qubit {virt_id} (physical ID: {phys_id}), " + f"placing the outcome in register {instr.creg}" + ) + + prog = QuantumProgram() + prog.apply(INSTR_MEASURE, qubit_indices=[phys_id]) + yield self.qdevice.execute_program(prog) + outcome: int = prog.output["last"][0] + app_mem.set_reg_value(instr.creg, outcome) + + def _interpret_single_qubit_instr( + self, app_id: int, instr: core.SingleQubitInstruction + ) -> Generator[EventExpression, None, None]: + app_mem = self.app_memories[app_id] + virt_id = app_mem.get_reg_value(instr.qreg) + phys_id = app_mem.phys_id_for(virt_id) + if isinstance(instr, vanilla.GateXInstruction): + prog = QuantumProgram() + prog.apply(INSTR_X, qubit_indices=[phys_id]) + yield self.qdevice.execute_program(prog) + elif isinstance(instr, vanilla.GateYInstruction): + prog = QuantumProgram() + prog.apply(INSTR_Y, qubit_indices=[phys_id]) + yield self.qdevice.execute_program(prog) + elif isinstance(instr, vanilla.GateZInstruction): + prog = QuantumProgram() + prog.apply(INSTR_Z, qubit_indices=[phys_id]) + yield self.qdevice.execute_program(prog) + elif isinstance(instr, vanilla.GateHInstruction): + prog = QuantumProgram() + prog.apply(INSTR_H, qubit_indices=[phys_id]) + yield self.qdevice.execute_program(prog) + else: + raise RuntimeError(f"Unsupported instruction {instr}") + + def _interpret_single_rotation_instr( + self, app_id: int, instr: nv.RotXInstruction + ) -> Generator[EventExpression, None, None]: + if isinstance(instr, vanilla.RotXInstruction): + yield from self._do_single_rotation(app_id, instr, INSTR_ROT_X) + elif isinstance(instr, vanilla.RotYInstruction): + yield from self._do_single_rotation(app_id, instr, INSTR_ROT_Y) + elif isinstance(instr, vanilla.RotZInstruction): + yield from self._do_single_rotation(app_id, instr, INSTR_ROT_Z) + else: + raise RuntimeError(f"Unsupported instruction {instr}") + + def _interpret_controlled_rotation_instr( + self, app_id: int, instr: core.ControlledRotationInstruction + ) -> Generator[EventExpression, None, None]: + raise RuntimeError(f"Unsupported instruction {instr}") + + def _interpret_two_qubit_instr( + self, app_id: int, instr: core.SingleQubitInstruction + ) -> Generator[EventExpression, None, None]: + app_mem = self.app_memories[app_id] + virt_id0 = app_mem.get_reg_value(instr.reg0) + phys_id0 = app_mem.phys_id_for(virt_id0) + virt_id1 = app_mem.get_reg_value(instr.reg1) + phys_id1 = app_mem.phys_id_for(virt_id1) + if isinstance(instr, vanilla.CnotInstruction): + prog = QuantumProgram() + prog.apply(INSTR_CNOT, qubit_indices=[phys_id0, phys_id1]) + yield self.qdevice.execute_program(prog) + elif isinstance(instr, vanilla.CphaseInstruction): + prog = QuantumProgram() + prog.apply(INSTR_CZ, qubit_indices=[phys_id0, phys_id1]) + yield self.qdevice.execute_program(prog) + else: + raise RuntimeError(f"Unsupported instruction {instr}") + + +class NVProcessor(Processor): + """A `Processor` for nodes with a NV hardware.""" + + def _interpret_qalloc(self, app_id: int, instr: core.QAllocInstruction) -> None: + app_mem = self.app_memories[app_id] + + virt_id = app_mem.get_reg_value(instr.reg) + if virt_id is None: + raise RuntimeError(f"qubit address in register {instr.reg} is not defined") + self._logger.debug(f"Allocating qubit with virtual ID {virt_id}") + + # Virtual ID > 0 corresponds to memory qubits + if virt_id > 0: + phys_id = self.physical_memory.allocate_mem() + else: + phys_id = self.physical_memory.allocate_comm() + app_mem.map_virt_id(virt_id, phys_id) + + def _interpret_init( + self, app_id: int, instr: core.InitInstruction + ) -> Generator[EventExpression, None, None]: + app_mem = self.app_memories[app_id] + virt_id = app_mem.get_reg_value(instr.reg) + phys_id = app_mem.phys_id_for(virt_id) + self._logger.debug( + f"Performing {instr} on virtual qubit " + f"{virt_id} (physical ID: {phys_id})" + ) + prog = QuantumProgram() + prog.apply(INSTR_INIT, qubit_indices=[phys_id]) + yield self.qdevice.execute_program(prog) + + def _measure_electron(self) -> Generator[EventExpression, None, int]: + prog = QuantumProgram() + prog.apply(INSTR_MEASURE, qubit_indices=[0]) + yield self.qdevice.execute_program(prog) + outcome: int = prog.output["last"][0] + return outcome + + def _move_carbon_to_electron_for_measure( + self, carbon_id: int + ) -> Generator[EventExpression, None, None]: + prog = QuantumProgram() + prog.apply(INSTR_INIT, qubit_indices=[0]) + prog.apply(INSTR_ROT_Y, qubit_indices=[0], angle=PI_OVER_2) + prog.apply(INSTR_CYDIR, qubit_indices=[0, carbon_id], angle=-PI_OVER_2) + prog.apply(INSTR_ROT_X, qubit_indices=[0], angle=-PI_OVER_2) + prog.apply(INSTR_CXDIR, qubit_indices=[0, carbon_id], angle=PI_OVER_2) + prog.apply(INSTR_ROT_Y, qubit_indices=[0], angle=-PI_OVER_2) + yield self.qdevice.execute_program(prog) + + def _move_electron_to_carbon( + self, carbon_id: int + ) -> Generator[EventExpression, None, None]: + prog = QuantumProgram() + prog.apply(INSTR_INIT, qubit_indices=[carbon_id]) + prog.apply(INSTR_ROT_Y, qubit_indices=[0], angle=PI_OVER_2) + prog.apply(INSTR_CYDIR, qubit_indices=[0, carbon_id], angle=-PI_OVER_2) + prog.apply(INSTR_ROT_X, qubit_indices=[0], angle=-PI_OVER_2) + prog.apply(INSTR_CXDIR, qubit_indices=[0, carbon_id], angle=PI_OVER_2) + yield self.qdevice.execute_program(prog) + + def _interpret_meas( + self, app_id: int, instr: core.MeasInstruction + ) -> Generator[EventExpression, None, None]: + app_mem = self.app_memories[app_id] + virt_id = app_mem.get_reg_value(instr.qreg) + phys_id = app_mem.phys_id_for(virt_id) + + # Only the electron (phys ID 0) can be measured. + # Measuring any other physical qubit (i.e one of the carbons) requires + # freeing up the electron and moving the target qubit to the electron first. + + if phys_id == 0: + # Measuring the electron. This can be done immediately. + outcome = yield from self._measure_electron() + app_mem.set_reg_value(instr.creg, outcome) + else: + # We want to measure a carbon. + # Move it to the electron first. + if self.physical_memory.is_allocated(0): + # Electron is already allocated. Try to move it to a free carbon. + try: + new_qubit = self.physical_memory.allocate() + except AllocError: + self._logger.error( + f"Allocation error. Reason:\n" + f"Measuring virtual qubit {virt_id}.\n" + f"-> Measuring physical qubit {phys_id}.\n" + f"-> Measuring physical qubit with ID > 0 requires " + f"physical qubit 0 to be free." + f"-> Physical qubit 0 is in use.\n" + f"-> Trying to find free physical qubit for qubit 0.\n" + f"-> No physical qubits available." + ) + yield from self._move_electron_to_carbon(new_qubit) + elec_app_id, elec_virt_id = self._qnos.get_virt_qubit_for_phys_id(0) + self._logger.warning( + f"moving virtual qubit {elec_virt_id} from app " + f"{app_id} from physical ID 0 to {new_qubit}" + ) + # Update qubit ID mapping. + self.app_memories[elec_app_id].unmap_virt_id(elec_virt_id) + self.app_memories[elec_app_id].map_virt_id(elec_virt_id, new_qubit) + app_mem.unmap_virt_id(virt_id) + app_mem.map_virt_id(virt_id, 0) + yield from self._move_carbon_to_electron_for_measure(phys_id) + self.physical_memory.free(phys_id) + self.send_signal(SIGNAL_MEMORY_FREED) + self.qdevice.mem_positions[phys_id].in_use = False + outcome = yield from self._measure_electron() + app_mem.set_reg_value(instr.creg, outcome) + else: + self.physical_memory.allocate_comm() + app_mem.unmap_virt_id(virt_id) + app_mem.map_virt_id(virt_id, 0) + yield from self._move_carbon_to_electron_for_measure(phys_id) + self.physical_memory.free(phys_id) + self.send_signal(SIGNAL_MEMORY_FREED) + self.qdevice.mem_positions[phys_id].in_use = False + outcome = yield from self._measure_electron() + app_mem.set_reg_value(instr.creg, outcome) + + self._logger.debug( + f"Measuring qubit {virt_id} (physical ID: {phys_id}), " + f"placing the outcome in register {instr.creg}" + ) + + def _interpret_single_rotation_instr( + self, app_id: int, instr: nv.RotXInstruction + ) -> Generator[EventExpression, None, None]: + if isinstance(instr, nv.RotXInstruction): + yield from self._do_single_rotation(app_id, instr, INSTR_ROT_X) + elif isinstance(instr, nv.RotYInstruction): + yield from self._do_single_rotation(app_id, instr, INSTR_ROT_Y) + elif isinstance(instr, nv.RotZInstruction): + yield from self._do_single_rotation(app_id, instr, INSTR_ROT_Z) + else: + raise RuntimeError(f"Unsupported instruction {instr}") + + def _interpret_controlled_rotation_instr( + self, app_id: int, instr: core.ControlledRotationInstruction + ) -> Generator[EventExpression, None, None]: + if isinstance(instr, nv.ControlledRotXInstruction): + yield from self._do_controlled_rotation(app_id, instr, INSTR_CXDIR) + elif isinstance(instr, nv.ControlledRotYInstruction): + yield from self._do_controlled_rotation(app_id, instr, INSTR_CYDIR) + else: + raise RuntimeError(f"Unsupported instruction {instr}") diff --git a/squidasm/sim/qoala/program.py b/squidasm/sim/qoala/program.py new file mode 100644 index 00000000..2cba790b --- /dev/null +++ b/squidasm/sim/qoala/program.py @@ -0,0 +1,55 @@ +import abc +from dataclasses import dataclass +from typing import Any, Dict, List + +from netqasm.sdk.classical_communication.socket import Socket +from netqasm.sdk.connection import BaseNetQASMConnection +from netqasm.sdk.epr_socket import EPRSocket + + +class ProgramContext: + def __init__( + self, + netqasm_connection: BaseNetQASMConnection, + csockets: Dict[str, Socket], + epr_sockets: Dict[str, EPRSocket], + app_id: int, + ): + self._connection = netqasm_connection + self._csockets = csockets + self._epr_sockets = epr_sockets + self._app_id = app_id + + @property + def connection(self) -> BaseNetQASMConnection: + return self._connection + + @property + def csockets(self) -> Dict[str, Socket]: + return self._csockets + + @property + def epr_sockets(self) -> Dict[str, EPRSocket]: + return self._epr_sockets + + @property + def app_id(self) -> int: + return self._app_id + + +@dataclass +class ProgramMeta: + name: str + parameters: Dict[str, Any] + csockets: List[str] + epr_sockets: List[str] + max_qubits: int + + +class Program(abc.ABC): + @property + def meta(self) -> ProgramMeta: + raise NotImplementedError + + def run(self, context: ProgramContext) -> Dict[str, Any]: + raise NotImplementedError diff --git a/squidasm/sim/qoala/qnos.py b/squidasm/sim/qoala/qnos.py new file mode 100644 index 00000000..23150be4 --- /dev/null +++ b/squidasm/sim/qoala/qnos.py @@ -0,0 +1,200 @@ +from __future__ import annotations + +from typing import Dict, Optional, Tuple + +from netsquid.components import QuantumProcessor +from netsquid.components.component import Component, Port +from netsquid.nodes import Node +from netsquid.protocols import Protocol +from netsquid_magic.link_layer import MagicLinkLayerProtocolWithSignaling + +from squidasm.sim.stack.common import ( + AppMemory, + NVPhysicalQuantumMemory, + PhysicalQuantumMemory, +) +from squidasm.sim.stack.handler import Handler, HandlerComponent +from squidasm.sim.stack.netstack import Netstack, NetstackComponent +from squidasm.sim.stack.processor import ( + GenericProcessor, + NVProcessor, + Processor, + ProcessorComponent, +) + +# TODO: make this a parameter +NUM_QUBITS = 5 + + +class QnosComponent(Component): + """NetSquid component representing a QNodeOS instance. + + Subcomponent of a ProcessingNode. + + This is a static container for QNodeOS-related components and ports. + Behavior of a QNodeOS instance is modeled in the `Qnos` class, + which is a subclass of `Protocol`. + """ + + def __init__(self, node: Node) -> None: + super().__init__(name=f"{node.name}_qnos") + self._node = node + + # Ports for communicating with Host + self.add_ports(["host_out", "host_in"]) + + # Ports for communicating with other nodes + self.add_ports(["peer_out", "peer_in"]) + + comp_handler = HandlerComponent(node) + self.add_subcomponent(comp_handler, "handler") + + comp_processor = ProcessorComponent(node) + self.add_subcomponent(comp_processor, "processor") + + comp_netstack = NetstackComponent(node) + self.add_subcomponent(comp_netstack, "netstack") + + self.netstack_comp.ports["peer_out"].forward_output(self.peer_out_port) + self.peer_in_port.forward_input(self.netstack_comp.ports["peer_in"]) + + self.handler_comp.ports["host_out"].forward_output(self.host_out_port) + self.host_in_port.forward_input(self.handler_comp.ports["host_in"]) + + self.handler_comp.processor_out_port.connect( + self.processor_comp.handler_in_port + ) + self.handler_comp.processor_in_port.connect( + self.processor_comp.handler_out_port + ) + + self.processor_comp.netstack_out_port.connect( + self.netstack_comp.processor_in_port + ) + self.processor_comp.netstack_in_port.connect( + self.netstack_comp.processor_out_port + ) + + @property + def handler_comp(self) -> HandlerComponent: + return self.subcomponents["handler"] + + @property + def processor_comp(self) -> ProcessorComponent: + return self.subcomponents["processor"] + + @property + def netstack_comp(self) -> NetstackComponent: + return self.subcomponents["netstack"] + + @property + def qdevice(self) -> QuantumProcessor: + return self.node.qmemory + + @property + def host_in_port(self) -> Port: + return self.ports["host_in"] + + @property + def host_out_port(self) -> Port: + return self.ports["host_out"] + + @property + def peer_in_port(self) -> Port: + return self.ports["peer_in"] + + @property + def peer_out_port(self) -> Port: + return self.ports["peer_out"] + + @property + def node(self) -> Node: + return self._node + + +class Qnos(Protocol): + """NetSquid protocol representing a QNodeOS instance.""" + + def __init__(self, comp: QnosComponent, qdevice_type: Optional[str] = "nv") -> None: + """Qnos protocol constructor. + + :param comp: NetSquid component representing the QNodeOS instance + :param qdevice_type: hardware type of the QDevice of this node + """ + super().__init__(name=f"{comp.name}_protocol") + self._comp = comp + + # Create internal protocols. + self.handler = Handler(comp.handler_comp, self, qdevice_type) + self.netstack = Netstack(comp.netstack_comp, self) + if qdevice_type == "generic": + self.processor = GenericProcessor(comp.processor_comp, self) + self._physical_memory = PhysicalQuantumMemory(comp.qdevice.num_positions) + elif qdevice_type == "nv": + self.processor = NVProcessor(comp.processor_comp, self) + self._physical_memory = NVPhysicalQuantumMemory(comp.qdevice.num_positions) + else: + raise ValueError + + # Classical memories that are shared (virtually) with the Host. + # Each application has its own `AppMemory`, identified by the application ID. + self._app_memories: Dict[int, AppMemory] = {} # app ID -> app memory + + # TODO: move this to a separate memory manager object + def get_virt_qubit_for_phys_id(self, phys_id: int) -> Tuple[int, int]: + # returns (app_id, virt_id) + for app_id, app_mem in self._app_memories.items(): + virt_id = app_mem.virt_id_for(phys_id) + if virt_id is not None: + return app_id, virt_id + raise RuntimeError(f"no virtual ID found for physical ID {phys_id}") + + def assign_ll_protocol(self, prot: MagicLinkLayerProtocolWithSignaling) -> None: + self.netstack.assign_ll_protocol(prot) + + @property + def handler(self) -> Handler: + return self._handler + + @handler.setter + def handler(self, handler: Handler) -> None: + self._handler = handler + + @property + def processor(self) -> Processor: + return self._processor + + @processor.setter + def processor(self, processor: Processor) -> None: + self._processor = processor + + @property + def netstack(self) -> Netstack: + return self._netstack + + @netstack.setter + def netstack(self, netstack: Netstack) -> None: + self._netstack = netstack + + @property + def app_memories(self) -> Dict[int, AppMemory]: + return self._app_memories + + @property + def physical_memory(self) -> PhysicalQuantumMemory: + return self._physical_memory + + def start(self) -> None: + assert self._handler is not None + assert self._processor is not None + assert self._netstack is not None + super().start() + self._handler.start() + self._processor.start() + self._netstack.start() + + def stop(self) -> None: + self._netstack.stop() + self._processor.stop() + self._handler.stop() + super().stop() diff --git a/squidasm/sim/qoala/signals.py b/squidasm/sim/qoala/signals.py new file mode 100644 index 00000000..d10bd6bf --- /dev/null +++ b/squidasm/sim/qoala/signals.py @@ -0,0 +1,10 @@ +SIGNAL_HOST_HOST_MSG = "EvHostHandMsg" +SIGNAL_HOST_HAND_MSG = "EvHostHandMsg" +SIGNAL_HAND_HOST_MSG = "EvHandHostMsg" +SIGNAL_HAND_PROC_MSG = "EvHandProcMsg" +SIGNAL_PROC_HAND_MSG = "EvProcHandMsg" +SIGNAL_PROC_NSTK_MSG = "EvProcNstkMsg" +SIGNAL_NSTK_PROC_MSG = "EvNstkProcMsg" +SIGNAL_PEER_NSTK_MSG = "EvPeerNstkMsg" + +SIGNAL_MEMORY_FREED = "EvMemoryFreed" diff --git a/squidasm/sim/qoala/stack.py b/squidasm/sim/qoala/stack.py new file mode 100644 index 00000000..8a397ef3 --- /dev/null +++ b/squidasm/sim/qoala/stack.py @@ -0,0 +1,233 @@ +from __future__ import annotations + +from typing import Dict, List, Optional + +from netsquid.components import QuantumProcessor +from netsquid.components.component import Port +from netsquid.nodes import Node +from netsquid.nodes.network import Network +from netsquid.protocols import Protocol +from netsquid_magic.link_layer import ( + MagicLinkLayerProtocol, + MagicLinkLayerProtocolWithSignaling, +) + +from squidasm.sim.stack.host import Host, HostComponent +from squidasm.sim.stack.qnos import Qnos, QnosComponent + + +class ProcessingNode(Node): + """NetSquid component representing a quantum network node containing a software + stack consisting of Host, QNodeOS and QDevice. + + This component has two subcomponents: a QnosComponent and a HostComponent. + + Has communications ports between + - the Host component on this node and the Host component on the peer node + - the QNodeOS component on this node and the QNodeOS component on the peer node + + For now, it is assumed there is only a single other nodes in the network, + which is "the" peer. + + This is a static container for components and ports. + Behavior of the node is modeled in the `NodeStack` class, which is a subclass + of `Protocol`. + """ + + def __init__( + self, + name: str, + qdevice: QuantumProcessor, + node_id: Optional[int] = None, + ) -> None: + """ProcessingNode constructor. Typically created indirectly through + constructing a `NodeStack`.""" + super().__init__(name, ID=node_id) + self.qmemory = qdevice + + qnos_comp = QnosComponent(self) + self.add_subcomponent(qnos_comp, "qnos") + + host_comp = HostComponent(self) + self.add_subcomponent(host_comp, "host") + + self.host_comp.ports["qnos_out"].connect(self.qnos_comp.ports["host_in"]) + self.host_comp.ports["qnos_in"].connect(self.qnos_comp.ports["host_out"]) + + # Ports for communicating with other nodes + self.add_ports(["qnos_peer_out", "qnos_peer_in"]) + self.add_ports(["host_peer_out", "host_peer_in"]) + + self.qnos_comp.peer_out_port.forward_output(self.qnos_peer_out_port) + self.qnos_peer_in_port.forward_input(self.qnos_comp.peer_in_port) + self.host_comp.peer_out_port.forward_output(self.host_peer_out_port) + self.host_peer_in_port.forward_input(self.host_comp.peer_in_port) + + @property + def qnos_comp(self) -> QnosComponent: + return self.subcomponents["qnos"] + + @property + def host_comp(self) -> HostComponent: + return self.subcomponents["host"] + + @property + def qdevice(self) -> QuantumProcessor: + return self.qmemory + + @property + def host_peer_in_port(self) -> Port: + return self.ports["host_peer_in"] + + @property + def host_peer_out_port(self) -> Port: + return self.ports["host_peer_out"] + + @property + def qnos_peer_in_port(self) -> Port: + return self.ports["qnos_peer_in"] + + @property + def qnos_peer_out_port(self) -> Port: + return self.ports["qnos_peer_out"] + + +class NodeStack(Protocol): + """NetSquid protocol representing a node with a software stack. + + The software stack consists of a Host, QNodeOS and a QDevice. + The Host and QNodeOS are each represented by separate subprotocols. + The QDevice is handled/modeled as part of the QNodeOS protocol. + """ + + def __init__( + self, + name: str, + node: Optional[ProcessingNode] = None, + qdevice_type: Optional[str] = "generic", + qdevice: Optional[QuantumProcessor] = None, + node_id: Optional[int] = None, + use_default_components: bool = True, + ) -> None: + """NodeStack constructor. + + :param name: name of this node + :param node: an existing ProcessingNode object containing the static + components or None. If None, a ProcessingNode is automatically + created. + :param qdevice_type: hardware type of the QDevice, defaults to "generic" + :param qdevice: NetSquid `QuantumProcessor` representing the QDevice, + defaults to None. If None, a QuantumProcessor is created + automatically. + :param node_id: ID to use for the internal NetSquid node object + :param use_default_components: whether to automatically create NetSquid + components for the Host and QNodeOS, defaults to True. If False, + this allows for manually creating and adding these components. + """ + super().__init__(name=f"{name}") + if node: + self._node = node + else: + assert qdevice is not None + self._node = ProcessingNode(name, qdevice, node_id) + + self._host: Optional[Host] = None + self._qnos: Optional[Qnos] = None + + # Create internal components. + # If `use_default_components` is False, these components must be manually + # created and added to this NodeStack. + if use_default_components: + self._host = Host(self.host_comp, qdevice_type) + self._qnos = Qnos(self.qnos_comp, qdevice_type) + + def assign_ll_protocol(self, prot: MagicLinkLayerProtocolWithSignaling) -> None: + """Set the link layer protocol to use for entanglement generation. + + The same link layer protocol object is used by both nodes sharing a link in + the network.""" + self.qnos.assign_ll_protocol(prot) + + @property + def node(self) -> ProcessingNode: + return self._node + + @property + def host_comp(self) -> HostComponent: + return self.node.host_comp + + @property + def qnos_comp(self) -> QnosComponent: + return self.node.qnos_comp + + @property + def qdevice(self) -> QuantumProcessor: + return self.node.qdevice + + @property + def host(self) -> Host: + return self._host + + @host.setter + def host(self, host: Host) -> None: + self._host = host + + @property + def qnos(self) -> Qnos: + return self._qnos + + @qnos.setter + def qnos(self, qnos: Qnos) -> None: + self._qnos = qnos + + def connect_to(self, other: NodeStack) -> None: + """Create connections between ports of this NodeStack and those of + another NodeStack.""" + self.node.host_peer_out_port.connect(other.node.host_peer_in_port) + self.node.host_peer_in_port.connect(other.node.host_peer_out_port) + self.node.qnos_peer_out_port.connect(other.node.qnos_peer_in_port) + self.node.qnos_peer_in_port.connect(other.node.qnos_peer_out_port) + + def start(self) -> None: + assert self._host is not None + assert self._qnos is not None + super().start() + self._host.start() + self._qnos.start() + + def stop(self) -> None: + assert self._host is not None + assert self._qnos is not None + self._qnos.stop() + self._host.stop() + super().stop() + + +class StackNetwork(Network): + """A network of `NodeStack`s connected by links, which are + `MagicLinkLayerProtocol`s.""" + + def __init__( + self, stacks: Dict[str, NodeStack], links: List[MagicLinkLayerProtocol] + ) -> None: + """StackNetwork constructor. + + :param stacks: dictionary of node name to `NodeStack` object representing + that node + :param links: list of link layer protocol objects. Each object internally + contains the IDs of the two nodes that this link connects + """ + self._stacks = stacks + self._links = links + + @property + def stacks(self) -> Dict[str, NodeStack]: + return self._stacks + + @property + def links(self) -> List[MagicLinkLayerProtocol]: + return self._links + + @property + def qdevices(self) -> Dict[str, QuantumProcessor]: + return {name: stack.qdevice for name, stack in self._stacks.items()} diff --git a/squidasm/sim/stack/host.py b/squidasm/sim/stack/host.py index 44b23747..5fd1b293 100644 --- a/squidasm/sim/stack/host.py +++ b/squidasm/sim/stack/host.py @@ -16,7 +16,7 @@ from netsquid.nodes import Node from pydynaa import EventExpression -from squidasm.run.stack import lhrprogram as lp +from squidasm.run.qoala import lhr from squidasm.sim.stack.common import ComponentProtocol, LogManager, PortListener from squidasm.sim.stack.connection import QnosConnection from squidasm.sim.stack.context import NetSquidContext @@ -26,7 +26,7 @@ class LhrProcess: - def __init__(self, host: Host, program: lp.LhrProgram) -> None: + def __init__(self, host: Host, program: lhr.LhrProgram) -> None: self._host = host self._name = f"{host._comp.name}_Lhr" self._logger: logging.Logger = LogManager.get_stack_logger( @@ -133,39 +133,39 @@ def execute_program(self) -> Generator[EventExpression, None, Dict[str, Any]]: results: Dict[str, Any] = {} for instr in program.instructions: - self._logger.info(f"Interpreting LIR instruction {instr}") - if isinstance(instr, lp.SendCMsgOp): + self._logger.info(f"Interpreting LHR instruction {instr}") + if isinstance(instr, lhr.SendCMsgOp): value = memory[instr.arguments[0]] self._logger.info(f"sending msg {value}") csck.send(value) - elif isinstance(instr, lp.ReceiveCMsgOp): + elif isinstance(instr, lhr.ReceiveCMsgOp): msg = yield from csck.recv() msg = int(msg) memory[instr.results[0]] = msg self._logger.info(f"received msg {msg}") - elif isinstance(instr, lp.AddCValueOp): + elif isinstance(instr, lhr.AddCValueOp): arg0 = memory[instr.arguments[0]] arg1 = memory[instr.arguments[1]] memory[instr.results[0]] = arg0 + arg1 - elif isinstance(instr, lp.MultiplyConstantCValueOp): + elif isinstance(instr, lhr.MultiplyConstantCValueOp): arg0 = memory[instr.arguments[0]] arg1 = instr.arguments[1] memory[instr.results[0]] = arg0 * arg1 - elif isinstance(instr, lp.BitConditionalMultiplyConstantCValueOp): + elif isinstance(instr, lhr.BitConditionalMultiplyConstantCValueOp): arg0 = memory[instr.arguments[0]] arg1 = instr.arguments[1] cond = memory[instr.arguments[2]] if cond == 1: memory[instr.results[0]] = arg0 * arg1 - elif isinstance(instr, lp.AssignCValueOp): + elif isinstance(instr, lhr.AssignCValueOp): value = instr.attributes[0] # if isinstance(value, str) and value.startswith("RegFuture__"): # reg_str = value[len("RegFuture__") :] memory[instr.results[0]] = instr.attributes[0] - elif isinstance(instr, lp.RunSubroutineOp): - arg_vec: lp.LirVector = instr.arguments[0] + elif isinstance(instr, lhr.RunSubroutineOp): + arg_vec: lhr.LhrVector = instr.arguments[0] args = arg_vec.values - lhr_subrt: lp.LhrSubroutine = instr.attributes[0] + lhr_subrt: lhr.LhrSubroutine = instr.attributes[0] subrt = lhr_subrt.subroutine self._logger.info(f"executing subroutine {subrt}") @@ -189,7 +189,7 @@ def execute_program(self) -> Generator[EventExpression, None, Dict[str, Any]]: memory[key] = value except NetQASMSyntaxError: pass - elif isinstance(instr, lp.ReturnResultOp): + elif isinstance(instr, lhr.ReturnResultOp): value = instr.arguments[0] results[value] = int(memory[value]) @@ -347,7 +347,7 @@ def run_sdk_program( def run_lhr_sdk_program( self, - program: lp.LhrProgram, + program: lhr.LhrProgram, ) -> Generator[EventExpression, None, None]: prog_meta = program.meta @@ -405,7 +405,7 @@ def run_lhr_sdk_program( self._program_results = process._program_results def run_lhr_program( - self, program: lp.LhrProgram, num_times: int + self, program: lhr.LhrProgram, num_times: int ) -> Generator[EventExpression, None, None]: self._logger.warning(f"Creating LHR process for program:\n{program}") process = LhrProcess(self, program) @@ -422,9 +422,9 @@ def run(self) -> Generator[EventExpression, None, None]: assert self._program is not None - if isinstance(self._program, lp.LhrProgram): + if isinstance(self._program, lhr.LhrProgram): yield from self.run_lhr_program(self._program, 1) - elif isinstance(self._program, lp.SdkProgram): + elif isinstance(self._program, lhr.SdkProgram): yield from self.run_lhr_sdk_program(self._program) else: self.run_sdk_program(self._program) diff --git a/tests/lir/test_parse_lir.py b/tests/qoala/test_parse_lhr.py similarity index 94% rename from tests/lir/test_parse_lir.py rename to tests/qoala/test_parse_lhr.py index bb3f5d6a..76c2dd50 100644 --- a/tests/lir/test_parse_lir.py +++ b/tests/qoala/test_parse_lhr.py @@ -1,4 +1,4 @@ -from squidasm.run.stack import lhrprogram as lp +from squidasm.run.qoala import lhr as lp from squidasm.run.stack.config import ( GenericQDeviceConfig, LinkConfig, @@ -101,7 +101,9 @@ def test_run_two_nodes_classical(): def test_run_two_nodes_epr(): program_text_client = """ -run_subroutine(vec<>) : +remote_id = assign_cval() : 1 +epr_sck_id = assign_cval() : 0 +run_subroutine(vec) : return M0 -> m NETQASM_START set R0 0 @@ -109,13 +111,15 @@ def test_run_two_nodes_epr(): set R2 2 set R3 20 set R10 10 + set C0 {remote_id} + set C1 {epr_sck_id} array R10 @0 array R1 @1 array R3 @2 store R0 @1[R0] store R0 @2[R0] store R1 @2[R1] - create_epr R1 R0 R1 R2 R0 + create_epr C0 C1 R1 R2 R0 wait_all @0[R0:R10] set Q0 0 meas Q0 M0 @@ -185,6 +189,6 @@ def test_run_two_nodes_epr(): if __name__ == "__main__": LogManager.set_log_level("DEBUG") # test_parse() - test_run() + # test_run() # test_run_two_nodes_classical() - # test_run_two_nodes_epr() + test_run_two_nodes_epr() From dd484c0ca725f2059156381a191a9ecffd8e0ee2 Mon Sep 17 00:00:00 2001 From: Bart van der Vecht Date: Tue, 7 Jun 2022 14:54:43 +0200 Subject: [PATCH 3/8] Reorganize --- examples/lir/bqc/bqc_5_5.py | 2 +- examples/lir/bqc/bqc_5_5_nv.py | 2 +- examples/lir/bqc/bqc_5_5_nv_raw_lhr.py | 2 +- examples/lir/bqc/example_bqc.py | 2 +- examples/lir/teleport/example_teleport.py | 2 +- squidasm/{sim => }/qoala/__init__.py | 0 .../qoala/arch/qoala-runtime-data.drawio | 0 squidasm/{sim => }/qoala/arch/qoala2.drawio | 0 squidasm/{run/qoala => qoala/lang}/lhr.py | 2 +- squidasm/qoala/runtime/config.py | 185 ++++++++++++++ .../{sim/qoala => qoala/runtime}/context.py | 2 +- squidasm/qoala/runtime/environment.py | 37 +++ .../{sim/qoala => qoala/runtime}/program.py | 0 squidasm/qoala/runtime/run.py | 167 +++++++++++++ squidasm/qoala/sim/build.py | 232 ++++++++++++++++++ squidasm/{sim/qoala => qoala/sim}/common.py | 0 .../{sim/qoala => qoala/sim}/connection.py | 6 +- squidasm/{sim/qoala => qoala/sim}/csocket.py | 2 +- squidasm/{sim/qoala => qoala/sim}/egp.py | 0 squidasm/{sim/qoala => qoala/sim}/globals.py | 2 +- squidasm/{sim/qoala => qoala/sim}/handler.py | 10 +- squidasm/{sim/qoala => qoala/sim}/host.py | 14 +- squidasm/{sim/qoala => qoala/sim}/netstack.py | 8 +- .../{sim/qoala => qoala/sim}/processor.py | 8 +- squidasm/{sim/qoala => qoala/sim}/qnos.py | 8 +- squidasm/{sim/qoala => qoala/sim}/signals.py | 0 squidasm/{sim/qoala => qoala/sim}/stack.py | 4 +- squidasm/sim/stack/host.py | 2 +- tests/qoala/test_parse_lhr.py | 2 +- 29 files changed, 661 insertions(+), 40 deletions(-) rename squidasm/{sim => }/qoala/__init__.py (100%) rename squidasm/{sim => }/qoala/arch/qoala-runtime-data.drawio (100%) rename squidasm/{sim => }/qoala/arch/qoala2.drawio (100%) rename squidasm/{run/qoala => qoala/lang}/lhr.py (99%) create mode 100644 squidasm/qoala/runtime/config.py rename squidasm/{sim/qoala => qoala/runtime}/context.py (97%) create mode 100644 squidasm/qoala/runtime/environment.py rename squidasm/{sim/qoala => qoala/runtime}/program.py (100%) create mode 100644 squidasm/qoala/runtime/run.py create mode 100644 squidasm/qoala/sim/build.py rename squidasm/{sim/qoala => qoala/sim}/common.py (100%) rename squidasm/{sim/qoala => qoala/sim}/connection.py (96%) rename squidasm/{sim/qoala => qoala/sim}/csocket.py (97%) rename squidasm/{sim/qoala => qoala/sim}/egp.py (100%) rename squidasm/{sim/qoala => qoala/sim}/globals.py (96%) rename squidasm/{sim/qoala => qoala/sim}/handler.py (97%) rename squidasm/{sim/qoala => qoala/sim}/host.py (97%) rename squidasm/{sim/qoala => qoala/sim}/netstack.py (99%) rename squidasm/{sim/qoala => qoala/sim}/processor.py (99%) rename squidasm/{sim/qoala => qoala/sim}/qnos.py (96%) rename squidasm/{sim/qoala => qoala/sim}/signals.py (100%) rename squidasm/{sim/qoala => qoala/sim}/stack.py (98%) diff --git a/examples/lir/bqc/bqc_5_5.py b/examples/lir/bqc/bqc_5_5.py index 5601b7d8..93c0499a 100644 --- a/examples/lir/bqc/bqc_5_5.py +++ b/examples/lir/bqc/bqc_5_5.py @@ -6,7 +6,7 @@ from netqasm.lang.operand import Template from netqasm.sdk.qubit import Qubit -from squidasm.run.qoala import lhr as lp +from squidasm.qoala.lang import lhr as lp from squidasm.run.stack.config import ( GenericQDeviceConfig, LinkConfig, diff --git a/examples/lir/bqc/bqc_5_5_nv.py b/examples/lir/bqc/bqc_5_5_nv.py index ff8dd743..9e1c47ef 100644 --- a/examples/lir/bqc/bqc_5_5_nv.py +++ b/examples/lir/bqc/bqc_5_5_nv.py @@ -6,7 +6,7 @@ from netqasm.lang.operand import Template from netqasm.sdk.qubit import Qubit -from squidasm.run.qoala import lhr as lp +from squidasm.qoala.lang import lhr as lp from squidasm.run.stack.config import ( LinkConfig, NVQDeviceConfig, diff --git a/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py b/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py index 59db8c74..aa1e2c18 100644 --- a/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py +++ b/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py @@ -4,7 +4,7 @@ import os from typing import List -from squidasm.run.qoala import lhr as lp +from squidasm.qoala.lang import lhr as lp from squidasm.run.stack.config import ( LinkConfig, NVQDeviceConfig, diff --git a/examples/lir/bqc/example_bqc.py b/examples/lir/bqc/example_bqc.py index aeab47d5..00b39be7 100644 --- a/examples/lir/bqc/example_bqc.py +++ b/examples/lir/bqc/example_bqc.py @@ -5,7 +5,7 @@ from netqasm.lang.operand import Template -from squidasm.run.qoala import lhr as lp +from squidasm.qoala.lang import lhr as lp from squidasm.run.stack.config import ( GenericQDeviceConfig, LinkConfig, diff --git a/examples/lir/teleport/example_teleport.py b/examples/lir/teleport/example_teleport.py index 098dc1fe..5d077a86 100644 --- a/examples/lir/teleport/example_teleport.py +++ b/examples/lir/teleport/example_teleport.py @@ -7,7 +7,7 @@ from netqasm.sdk.qubit import Qubit from netqasm.sdk.toolbox import set_qubit_state -from squidasm.run.qoala import lhr as lp +from squidasm.qoala.lang import lhr as lp from squidasm.run.stack.config import ( GenericQDeviceConfig, LinkConfig, diff --git a/squidasm/sim/qoala/__init__.py b/squidasm/qoala/__init__.py similarity index 100% rename from squidasm/sim/qoala/__init__.py rename to squidasm/qoala/__init__.py diff --git a/squidasm/sim/qoala/arch/qoala-runtime-data.drawio b/squidasm/qoala/arch/qoala-runtime-data.drawio similarity index 100% rename from squidasm/sim/qoala/arch/qoala-runtime-data.drawio rename to squidasm/qoala/arch/qoala-runtime-data.drawio diff --git a/squidasm/sim/qoala/arch/qoala2.drawio b/squidasm/qoala/arch/qoala2.drawio similarity index 100% rename from squidasm/sim/qoala/arch/qoala2.drawio rename to squidasm/qoala/arch/qoala2.drawio diff --git a/squidasm/run/qoala/lhr.py b/squidasm/qoala/lang/lhr.py similarity index 99% rename from squidasm/run/qoala/lhr.py rename to squidasm/qoala/lang/lhr.py index 61b36e62..a1cc83da 100644 --- a/squidasm/run/qoala/lhr.py +++ b/squidasm/qoala/lang/lhr.py @@ -10,7 +10,7 @@ from netqasm.lang.subroutine import Subroutine from netqasm.sdk.futures import Future -from squidasm.sim.stack.program import ProgramContext, ProgramMeta +from squidasm.qoala.runtime.program import ProgramContext, ProgramMeta LhrValue = Union[int, Template, Future] diff --git a/squidasm/qoala/runtime/config.py b/squidasm/qoala/runtime/config.py new file mode 100644 index 00000000..1ec04180 --- /dev/null +++ b/squidasm/qoala/runtime/config.py @@ -0,0 +1,185 @@ +from __future__ import annotations + +from typing import Any, List + +import yaml +from pydantic import BaseModel + + +def _from_file(path: str, typ: Any) -> Any: + with open(path, "r") as f: + raw_config = yaml.load(f, Loader=yaml.Loader) + return typ(**raw_config) + + +class GenericQDeviceConfig(BaseModel): + # total number of qubits + num_qubits: int = 2 + # number of communication qubits + num_comm_qubits: int = 2 + + # coherence times (same for each qubit) + T1: int = 10_000_000_000 + T2: int = 1_000_000_000 + + # gate execution times + init_time: int = 10_000 + single_qubit_gate_time: int = 1_000 + two_qubit_gate_time: int = 100_000 + measure_time: int = 10_000 + + # noise model + single_qubit_gate_depolar_prob: float = 0.0 + two_qubit_gate_depolar_prob: float = 0.01 + + @classmethod + def from_file(cls, path: str) -> GenericQDeviceConfig: + return _from_file(path, GenericQDeviceConfig) # type: ignore + + @classmethod + def perfect_config(cls) -> GenericQDeviceConfig: + cfg = GenericQDeviceConfig() + cfg.single_qubit_gate_depolar_prob = 0.0 + cfg.two_qubit_gate_depolar_prob = 0.0 + return cfg + + +class NVQDeviceConfig(BaseModel): + # number of qubits per NV + num_qubits: int = 2 + + # initialization error of the electron spin + electron_init_depolar_prob: float = 0.05 + + # error of the single-qubit gate + electron_single_qubit_depolar_prob: float = 0.0 + + # measurement errors (prob_error_X is the probability that outcome X is flipped to 1 - X) + prob_error_0: float = 0.05 + prob_error_1: float = 0.005 + + # initialization error of the carbon nuclear spin + carbon_init_depolar_prob: float = 0.05 + + # error of the Z-rotation gate on the carbon nuclear spin + carbon_z_rot_depolar_prob: float = 0.001 + + # error of the native NV two-qubit gate + ec_gate_depolar_prob: float = 0.008 + + # coherence times + electron_T1: int = 1_000_000_000 + electron_T2: int = 300_000_000 + carbon_T1: int = 150_000_000_000 + carbon_T2: int = 1_500_000_000 + + # gate execution times + carbon_init: int = 310_000 + carbon_rot_x: int = 500_000 + carbon_rot_y: int = 500_000 + carbon_rot_z: int = 500_000 + electron_init: int = 2_000 + electron_rot_x: int = 5 + electron_rot_y: int = 5 + electron_rot_z: int = 5 + ec_controlled_dir_x: int = 500_000 + ec_controlled_dir_y: int = 500_000 + measure: int = 3_700 + + @classmethod + def from_file(cls, path: str) -> NVQDeviceConfig: + return _from_file(path, NVQDeviceConfig) # type: ignore + + @classmethod + def perfect_config(cls) -> NVQDeviceConfig: + # get default config + cfg = NVQDeviceConfig() + + # set all error params to 0 + cfg.electron_init_depolar_prob = 0 + cfg.electron_single_qubit_depolar_prob = 0 + cfg.prob_error_0 = 0 + cfg.prob_error_1 = 0 + cfg.carbon_init_depolar_prob = 0 + cfg.carbon_z_rot_depolar_prob = 0 + cfg.ec_gate_depolar_prob = 0 + return cfg + + +class StackConfig(BaseModel): + name: str + qdevice_typ: str + qdevice_cfg: Any + + @classmethod + def from_file(cls, path: str) -> StackConfig: + return _from_file(path, StackConfig) # type: ignore + + @classmethod + def perfect_generic_config(cls, name: str) -> StackConfig: + return StackConfig( + name=name, + qdevice_typ="generic", + qdevice_cfg=GenericQDeviceConfig.perfect_config(), + ) + + +class DepolariseLinkConfig(BaseModel): + fidelity: float + prob_success: float + t_cycle: float + + @classmethod + def from_file(cls, path: str) -> DepolariseLinkConfig: + return _from_file(path, DepolariseLinkConfig) # type: ignore + + +class NVLinkConfig(BaseModel): + length_A: float + length_B: float + full_cycle: float + cycle_time: float + alpha: float + + @classmethod + def from_file(cls, path: str) -> NVLinkConfig: + return _from_file(path, NVLinkConfig) # type: ignore + + +class HeraldedLinkConfig(BaseModel): + length: float + p_loss_init: float = 0 + p_loss_length: float = 0.25 + speed_of_light: float = 200_000 + dark_count_probability: float = 0 + detector_efficiency: float = 1.0 + visibility: float = 1.0 + num_resolving: bool = False + + @classmethod + def from_file(cls, path: str) -> HeraldedLinkConfig: + return _from_file(path, HeraldedLinkConfig) # type: ignore + + +class LinkConfig(BaseModel): + stack1: str + stack2: str + typ: str + cfg: Any + + @classmethod + def from_file(cls, path: str) -> LinkConfig: + return _from_file(path, LinkConfig) # type: ignore + + @classmethod + def perfect_config(cls, stack1: str, stack2: str) -> LinkConfig: + return LinkConfig(stack1=stack1, stack2=stack2, typ="perfect", cfg=None) + + +class StackNetworkConfig(BaseModel): + stacks: List[StackConfig] + links: List[LinkConfig] + + @classmethod + def from_file(cls, path: str) -> StackNetworkConfig: + return _from_file(path, StackNetworkConfig) # type: ignore diff --git a/squidasm/sim/qoala/context.py b/squidasm/qoala/runtime/context.py similarity index 97% rename from squidasm/sim/qoala/context.py rename to squidasm/qoala/runtime/context.py index 6375d606..dfed162b 100644 --- a/squidasm/sim/qoala/context.py +++ b/squidasm/qoala/runtime/context.py @@ -5,7 +5,7 @@ from netqasm.sdk.network import NetworkInfo if TYPE_CHECKING: - from squidasm.sim.stack.host import Host + from squidasm.qoala.sim.host import Host class NetSquidNetworkInfo(NetworkInfo): diff --git a/squidasm/qoala/runtime/environment.py b/squidasm/qoala/runtime/environment.py new file mode 100644 index 00000000..4b94ce17 --- /dev/null +++ b/squidasm/qoala/runtime/environment.py @@ -0,0 +1,37 @@ +from typing import Dict, Tuple + + +class GlobalNodeInfo: + pass + + +class GlobalLinkInfo: + pass + + +class GlobalEnvironment: + # node ID -> node info + _nodes: Dict[int, GlobalNodeInfo] = {} + + # (node A ID, node B ID) -> link info + # for a pair (a, b) there exists no separate (b, a) info (it is the same) + _links: Dict[Tuple[int, int], GlobalLinkInfo] = {} + + +class LocalNodeInfo: + pass + + +class LocalLinkInfo: + pass + + +class LocalEnvironment: + _global_env: GlobalEnvironment + + # node ID of self + _node_id: int + + +class ProgramEnvironment: + pass diff --git a/squidasm/sim/qoala/program.py b/squidasm/qoala/runtime/program.py similarity index 100% rename from squidasm/sim/qoala/program.py rename to squidasm/qoala/runtime/program.py diff --git a/squidasm/qoala/runtime/run.py b/squidasm/qoala/runtime/run.py new file mode 100644 index 00000000..19ee23fd --- /dev/null +++ b/squidasm/qoala/runtime/run.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +import itertools +from typing import Any, Dict, List + +import netsquid as ns +from netsquid_magic.link_layer import ( + MagicLinkLayerProtocol, + MagicLinkLayerProtocolWithSignaling, + SingleClickTranslationUnit, +) +from netsquid_magic.magic_distributor import ( + DepolariseWithFailureMagicDistributor, + DoubleClickMagicDistributor, + PerfectStateMagicDistributor, +) +from netsquid_nv.magic_distributor import NVSingleClickMagicDistributor +from netsquid_physlayer.heralded_connection import MiddleHeraldedConnection + +from squidasm.qoala.runtime.config import ( + DepolariseLinkConfig, + GenericQDeviceConfig, + HeraldedLinkConfig, + NVLinkConfig, + NVQDeviceConfig, + StackNetworkConfig, +) +from squidasm.qoala.runtime.context import NetSquidContext +from squidasm.qoala.runtime.program import Program +from squidasm.qoala.sim.build import build_generic_qdevice, build_nv_qdevice +from squidasm.qoala.sim.globals import GlobalSimData +from squidasm.qoala.sim.stack import NodeStack, StackNetwork + + +def fidelity_to_prob_max_mixed(fid: float) -> float: + return (1 - fid) * 4.0 / 3.0 + + +def _setup_network(config: StackNetworkConfig) -> StackNetwork: + assert len(config.stacks) <= 2 + assert len(config.links) <= 1 + + stacks: Dict[str, NodeStack] = {} + link_prots: List[MagicLinkLayerProtocol] = [] + + for cfg in config.stacks: + if cfg.qdevice_typ == "nv": + qdevice_cfg = cfg.qdevice_cfg + if not isinstance(qdevice_cfg, NVQDeviceConfig): + qdevice_cfg = NVQDeviceConfig(**cfg.qdevice_cfg) + qdevice = build_nv_qdevice(f"qdevice_{cfg.name}", cfg=qdevice_cfg) + stack = NodeStack(cfg.name, qdevice_type="nv", qdevice=qdevice) + elif cfg.qdevice_typ == "generic": + qdevice_cfg = cfg.qdevice_cfg + if not isinstance(qdevice_cfg, GenericQDeviceConfig): + qdevice_cfg = GenericQDeviceConfig(**cfg.qdevice_cfg) + qdevice = build_generic_qdevice(f"qdevice_{cfg.name}", cfg=qdevice_cfg) + stack = NodeStack(cfg.name, qdevice_type="generic", qdevice=qdevice) + NetSquidContext.add_node(stack.node.ID, cfg.name) + stacks[cfg.name] = stack + + for (_, s1), (_, s2) in itertools.combinations(stacks.items(), 2): + s1.connect_to(s2) + + for link in config.links: + stack1 = stacks[link.stack1] + stack2 = stacks[link.stack2] + if link.typ == "perfect": + link_dist = PerfectStateMagicDistributor( + nodes=[stack1.node, stack2.node], state_delay=1000.0 + ) + elif link.typ == "depolarise": + link_cfg = link.cfg + if not isinstance(link_cfg, DepolariseLinkConfig): + link_cfg = DepolariseLinkConfig(**link.cfg) + prob_max_mixed = fidelity_to_prob_max_mixed(link_cfg.fidelity) + link_dist = DepolariseWithFailureMagicDistributor( + nodes=[stack1.node, stack2.node], + prob_max_mixed=prob_max_mixed, + prob_success=link_cfg.prob_success, + t_cycle=link_cfg.t_cycle, + ) + elif link.typ == "nv": + link_cfg = link.cfg + if not isinstance(link_cfg, NVLinkConfig): + link_cfg = NVLinkConfig(**link.cfg) + link_dist = NVSingleClickMagicDistributor( + nodes=[stack1.node, stack2.node], + length_A=link_cfg.length_A, + length_B=link_cfg.length_B, + full_cycle=link_cfg.full_cycle, + cycle_time=link_cfg.cycle_time, + alpha=link_cfg.alpha, + ) + elif link.typ == "heralded": + link_cfg = link.cfg + if not isinstance(link_cfg, HeraldedLinkConfig): + link_cfg = HeraldedLinkConfig(**link.cfg) + connection = MiddleHeraldedConnection( + name="heralded_conn", **link_cfg.dict() + ) + link_dist = DoubleClickMagicDistributor( + [stack1.node, stack2.node], connection + ) + else: + raise ValueError + + link_prot = MagicLinkLayerProtocolWithSignaling( + nodes=[stack1.node, stack2.node], + magic_distributor=link_dist, + translation_unit=SingleClickTranslationUnit(), + ) + stack1.assign_ll_protocol(link_prot) + stack2.assign_ll_protocol(link_prot) + + link_prots.append(link_prot) + + return StackNetwork(stacks, link_prots) + + +def _run(network: StackNetwork) -> List[Dict[str, Any]]: + """Run the protocols of a network and programs running in that network. + + NOTE: For now, only two nodes and a single link are supported. + + :param network: `StackNetwork` representing the nodes and links + :return: final results of the programs + """ + assert len(network.stacks) <= 2 + assert len(network.links) <= 1 + + # Start the link protocols. + for link in network.links: + link.start() + + # Start the node protocols. + for _, stack in network.stacks.items(): + stack.start() + + # Start the NetSquid simulation. + ns.sim_run() + + return [stack.host.get_results() for _, stack in network.stacks.items()] + + +def run( + config: StackNetworkConfig, programs: Dict[str, Program], num_times: int = 1 +) -> List[Dict[str, Any]]: + """Run programs on a network specified by a network configuration. + + :param config: configuration of the network + :param programs: dictionary of node names to programs + :param num_times: numbers of times to run the programs, defaults to 1 + :return: program results + """ + network = _setup_network(config) + + NetSquidContext.set_nodes({}) + for name, stack in network.stacks.items(): + NetSquidContext.add_node(stack.node.ID, name) + + GlobalSimData.set_network(network) + for name, program in programs.items(): + network.stacks[name].host.enqueue_program(program, num_times) + + results = _run(network) + return results diff --git a/squidasm/qoala/sim/build.py b/squidasm/qoala/sim/build.py new file mode 100644 index 00000000..63313bd3 --- /dev/null +++ b/squidasm/qoala/sim/build.py @@ -0,0 +1,232 @@ +import numpy as np +from netsquid.components.instructions import ( + INSTR_CNOT, + INSTR_CXDIR, + INSTR_CYDIR, + INSTR_CZ, + INSTR_H, + INSTR_INIT, + INSTR_MEASURE, + INSTR_ROT_X, + INSTR_ROT_Y, + INSTR_ROT_Z, + INSTR_X, + INSTR_Y, + INSTR_Z, +) +from netsquid.components.models.qerrormodels import DepolarNoiseModel, T1T2NoiseModel +from netsquid.components.qprocessor import PhysicalInstruction, QuantumProcessor +from netsquid.qubits.operators import Operator + +from squidasm.run.stack.config import GenericQDeviceConfig, NVQDeviceConfig + + +def build_generic_qdevice(name: str, cfg: GenericQDeviceConfig) -> QuantumProcessor: + phys_instructions = [] + + single_qubit_gate_noise = DepolarNoiseModel( + depolar_rate=cfg.single_qubit_gate_depolar_prob, time_independent=True + ) + + two_qubit_gate_noise = DepolarNoiseModel( + depolar_rate=cfg.two_qubit_gate_depolar_prob, time_independent=True + ) + + phys_instructions.append( + PhysicalInstruction( + INSTR_INIT, + parallel=False, + duration=cfg.init_time, + ) + ) + + for instr in [ + INSTR_ROT_X, + INSTR_ROT_Y, + INSTR_ROT_Z, + INSTR_X, + INSTR_Y, + INSTR_Z, + INSTR_H, + ]: + phys_instructions.append( + PhysicalInstruction( + instr, + parallel=False, + quantum_noise_model=single_qubit_gate_noise, + apply_q_noise_after=True, + duration=cfg.single_qubit_gate_time, + ) + ) + + for instr in [INSTR_CNOT, INSTR_CZ]: + phys_instructions.append( + PhysicalInstruction( + instr, + parallel=False, + quantum_noise_model=two_qubit_gate_noise, + apply_q_noise_after=True, + duration=cfg.two_qubit_gate_time, + ) + ) + + phys_instr_measure = PhysicalInstruction( + INSTR_MEASURE, + parallel=False, + duration=cfg.measure_time, + ) + phys_instructions.append(phys_instr_measure) + + electron_qubit_noise = T1T2NoiseModel(T1=cfg.T1, T2=cfg.T2) + mem_noise_models = [electron_qubit_noise] * cfg.num_qubits + qmem = QuantumProcessor( + name=name, + num_positions=cfg.num_qubits, + mem_noise_models=mem_noise_models, + phys_instructions=phys_instructions, + ) + return qmem + + +def build_nv_qdevice(name: str, cfg: NVQDeviceConfig) -> QuantumProcessor: + + # noise models for single- and multi-qubit operations + electron_init_noise = DepolarNoiseModel( + depolar_rate=cfg.electron_init_depolar_prob, time_independent=True + ) + + electron_single_qubit_noise = DepolarNoiseModel( + depolar_rate=cfg.electron_single_qubit_depolar_prob, time_independent=True + ) + + carbon_init_noise = DepolarNoiseModel( + depolar_rate=cfg.carbon_init_depolar_prob, time_independent=True + ) + + carbon_z_rot_noise = DepolarNoiseModel( + depolar_rate=cfg.carbon_z_rot_depolar_prob, time_independent=True + ) + + ec_noise = DepolarNoiseModel( + depolar_rate=cfg.ec_gate_depolar_prob, time_independent=True + ) + + electron_qubit_noise = T1T2NoiseModel(T1=cfg.electron_T1, T2=cfg.electron_T2) + + carbon_qubit_noise = T1T2NoiseModel(T1=cfg.carbon_T1, T2=cfg.carbon_T2) + + # defining gates and their gate times + + phys_instructions = [] + + electron_position = 0 + carbon_positions = [pos + 1 for pos in range(cfg.num_qubits - 1)] + + phys_instructions.append( + PhysicalInstruction( + INSTR_INIT, + parallel=False, + topology=carbon_positions, + quantum_noise_model=carbon_init_noise, + apply_q_noise_after=True, + duration=cfg.carbon_init, + ) + ) + + for (instr, dur) in zip( + [INSTR_ROT_X, INSTR_ROT_Y, INSTR_ROT_Z], + [cfg.carbon_rot_x, cfg.carbon_rot_y, cfg.carbon_rot_z], + ): + phys_instructions.append( + PhysicalInstruction( + instr, + parallel=False, + topology=carbon_positions, + quantum_noise_model=carbon_z_rot_noise, + apply_q_noise_after=True, + duration=dur, + ) + ) + + phys_instructions.append( + PhysicalInstruction( + INSTR_INIT, + parallel=False, + topology=[electron_position], + quantum_noise_model=electron_init_noise, + apply_q_noise_after=True, + duration=cfg.electron_init, + ) + ) + + for (instr, dur) in zip( + [INSTR_ROT_X, INSTR_ROT_Y, INSTR_ROT_Z], + [cfg.electron_rot_x, cfg.electron_rot_y, cfg.electron_rot_z], + ): + phys_instructions.append( + PhysicalInstruction( + instr, + parallel=False, + topology=[electron_position], + quantum_noise_model=electron_single_qubit_noise, + apply_q_noise_after=True, + duration=dur, + ) + ) + + electron_carbon_topologies = [ + (electron_position, carbon_pos) for carbon_pos in carbon_positions + ] + phys_instructions.append( + PhysicalInstruction( + INSTR_CXDIR, + parallel=False, + topology=electron_carbon_topologies, + quantum_noise_model=ec_noise, + apply_q_noise_after=True, + duration=cfg.ec_controlled_dir_x, + ) + ) + + phys_instructions.append( + PhysicalInstruction( + INSTR_CYDIR, + parallel=False, + topology=electron_carbon_topologies, + quantum_noise_model=ec_noise, + apply_q_noise_after=True, + duration=cfg.ec_controlled_dir_y, + ) + ) + + M0 = Operator( + "M0", np.diag([np.sqrt(1 - cfg.prob_error_0), np.sqrt(cfg.prob_error_1)]) + ) + M1 = Operator( + "M1", np.diag([np.sqrt(cfg.prob_error_0), np.sqrt(1 - cfg.prob_error_1)]) + ) + + # hack to set imperfect measurements + INSTR_MEASURE._meas_operators = [M0, M1] + + phys_instr_measure = PhysicalInstruction( + INSTR_MEASURE, + parallel=False, + topology=[electron_position], + quantum_noise_model=None, + duration=cfg.measure, + ) + + phys_instructions.append(phys_instr_measure) + + # add qubits + mem_noise_models = [electron_qubit_noise] + [carbon_qubit_noise] * len( + carbon_positions + ) + qmem = QuantumProcessor( + name=name, + num_positions=cfg.num_qubits, + mem_noise_models=mem_noise_models, + phys_instructions=phys_instructions, + ) + return qmem diff --git a/squidasm/sim/qoala/common.py b/squidasm/qoala/sim/common.py similarity index 100% rename from squidasm/sim/qoala/common.py rename to squidasm/qoala/sim/common.py diff --git a/squidasm/sim/qoala/connection.py b/squidasm/qoala/sim/connection.py similarity index 96% rename from squidasm/sim/qoala/connection.py rename to squidasm/qoala/sim/connection.py index d551d8da..9b4edf74 100644 --- a/squidasm/sim/qoala/connection.py +++ b/squidasm/qoala/sim/connection.py @@ -19,11 +19,11 @@ if TYPE_CHECKING: from netqasm.sdk.transpile import SubroutineTranspiler - from squidasm.sim.stack.host import Host + from squidasm.qoala.sim.host import Host -from squidasm.sim.stack.common import LogManager +from squidasm.qoala.sim.common import LogManager -from .context import NetSquidNetworkInfo +from ..runtime.context import NetSquidNetworkInfo class QnosConnection(BaseNetQASMConnection): diff --git a/squidasm/sim/qoala/csocket.py b/squidasm/qoala/sim/csocket.py similarity index 97% rename from squidasm/sim/qoala/csocket.py rename to squidasm/qoala/sim/csocket.py index 617f8861..c446e49f 100644 --- a/squidasm/sim/qoala/csocket.py +++ b/squidasm/qoala/sim/csocket.py @@ -8,7 +8,7 @@ from pydynaa import EventExpression if TYPE_CHECKING: - from squidasm.sim.stack.host import Host + from squidasm.qoala.sim.host import Host class ClassicalSocket(Socket): diff --git a/squidasm/sim/qoala/egp.py b/squidasm/qoala/sim/egp.py similarity index 100% rename from squidasm/sim/qoala/egp.py rename to squidasm/qoala/sim/egp.py diff --git a/squidasm/sim/qoala/globals.py b/squidasm/qoala/sim/globals.py similarity index 96% rename from squidasm/sim/qoala/globals.py rename to squidasm/qoala/sim/globals.py index c56297db..f3f0e27b 100644 --- a/squidasm/sim/qoala/globals.py +++ b/squidasm/qoala/sim/globals.py @@ -7,7 +7,7 @@ from netsquid.qubits.qubit import Qubit if TYPE_CHECKING: - from squidasm.sim.stack.stack import StackNetwork + from squidasm.qoala.sim.stack import StackNetwork T_QubitData = Dict[str, Dict[int, Qubit]] T_StateData = Dict[str, Dict[int, np.ndarray]] diff --git a/squidasm/sim/qoala/handler.py b/squidasm/qoala/sim/handler.py similarity index 97% rename from squidasm/sim/qoala/handler.py rename to squidasm/qoala/sim/handler.py index c8b1beea..b3003101 100644 --- a/squidasm/sim/qoala/handler.py +++ b/squidasm/qoala/sim/handler.py @@ -17,18 +17,18 @@ from netsquid.nodes import Node from pydynaa import EventExpression -from squidasm.sim.stack.common import ( +from squidasm.qoala.sim.common import ( AppMemory, ComponentProtocol, PhysicalQuantumMemory, PortListener, ) -from squidasm.sim.stack.netstack import Netstack, NetstackComponent -from squidasm.sim.stack.signals import SIGNAL_HOST_HAND_MSG, SIGNAL_PROC_HAND_MSG +from squidasm.qoala.sim.netstack import Netstack, NetstackComponent +from squidasm.qoala.sim.signals import SIGNAL_HOST_HAND_MSG, SIGNAL_PROC_HAND_MSG if TYPE_CHECKING: - from squidasm.sim.stack.processor import ProcessorComponent - from squidasm.sim.stack.qnos import Qnos, QnosComponent + from squidasm.qoala.sim.processor import ProcessorComponent + from squidasm.qoala.sim.qnos import Qnos, QnosComponent class HandlerComponent(Component): diff --git a/squidasm/sim/qoala/host.py b/squidasm/qoala/sim/host.py similarity index 97% rename from squidasm/sim/qoala/host.py rename to squidasm/qoala/sim/host.py index 5fd1b293..d48cae27 100644 --- a/squidasm/sim/qoala/host.py +++ b/squidasm/qoala/sim/host.py @@ -16,13 +16,13 @@ from netsquid.nodes import Node from pydynaa import EventExpression -from squidasm.run.qoala import lhr -from squidasm.sim.stack.common import ComponentProtocol, LogManager, PortListener -from squidasm.sim.stack.connection import QnosConnection -from squidasm.sim.stack.context import NetSquidContext -from squidasm.sim.stack.csocket import ClassicalSocket -from squidasm.sim.stack.program import Program, ProgramContext -from squidasm.sim.stack.signals import SIGNAL_HAND_HOST_MSG, SIGNAL_HOST_HOST_MSG +from squidasm.qoala.lang import lhr +from squidasm.qoala.runtime.context import NetSquidContext +from squidasm.qoala.runtime.program import Program, ProgramContext +from squidasm.qoala.sim.common import ComponentProtocol, LogManager, PortListener +from squidasm.qoala.sim.connection import QnosConnection +from squidasm.qoala.sim.csocket import ClassicalSocket +from squidasm.qoala.sim.signals import SIGNAL_HAND_HOST_MSG, SIGNAL_HOST_HOST_MSG class LhrProcess: diff --git a/squidasm/sim/qoala/netstack.py b/squidasm/qoala/sim/netstack.py similarity index 99% rename from squidasm/sim/qoala/netstack.py rename to squidasm/qoala/sim/netstack.py index c12c17bf..0a7bfc5b 100644 --- a/squidasm/sim/qoala/netstack.py +++ b/squidasm/qoala/sim/netstack.py @@ -33,7 +33,7 @@ from qlink_interface.interface import ReqRemoteStatePrep from pydynaa import EventExpression -from squidasm.sim.stack.common import ( +from squidasm.qoala.sim.common import ( AllocError, AppMemory, ComponentProtocol, @@ -44,15 +44,15 @@ PhysicalQuantumMemory, PortListener, ) -from squidasm.sim.stack.egp import EgpProtocol -from squidasm.sim.stack.signals import ( +from squidasm.qoala.sim.egp import EgpProtocol +from squidasm.qoala.sim.signals import ( SIGNAL_MEMORY_FREED, SIGNAL_PEER_NSTK_MSG, SIGNAL_PROC_NSTK_MSG, ) if TYPE_CHECKING: - from squidasm.sim.stack.qnos import Qnos + from squidasm.qoala.sim.qnos import Qnos PI = math.pi PI_OVER_2 = math.pi / 2 diff --git a/squidasm/sim/qoala/processor.py b/squidasm/qoala/sim/processor.py similarity index 99% rename from squidasm/sim/qoala/processor.py rename to squidasm/qoala/sim/processor.py index 0e912eea..9a5ff7c5 100644 --- a/squidasm/sim/qoala/processor.py +++ b/squidasm/qoala/sim/processor.py @@ -30,7 +30,7 @@ from netsquid.qubits import qubitapi from pydynaa import EventExpression -from squidasm.sim.stack.common import ( +from squidasm.qoala.sim.common import ( AllocError, AppMemory, ComponentProtocol, @@ -41,15 +41,15 @@ PhysicalQuantumMemory, PortListener, ) -from squidasm.sim.stack.globals import GlobalSimData -from squidasm.sim.stack.signals import ( +from squidasm.qoala.sim.globals import GlobalSimData +from squidasm.qoala.sim.signals import ( SIGNAL_HAND_PROC_MSG, SIGNAL_MEMORY_FREED, SIGNAL_NSTK_PROC_MSG, ) if TYPE_CHECKING: - from squidasm.sim.stack.qnos import Qnos + from squidasm.qoala.sim.qnos import Qnos PI = math.pi PI_OVER_2 = math.pi / 2 diff --git a/squidasm/sim/qoala/qnos.py b/squidasm/qoala/sim/qnos.py similarity index 96% rename from squidasm/sim/qoala/qnos.py rename to squidasm/qoala/sim/qnos.py index 23150be4..700acf6b 100644 --- a/squidasm/sim/qoala/qnos.py +++ b/squidasm/qoala/sim/qnos.py @@ -8,14 +8,14 @@ from netsquid.protocols import Protocol from netsquid_magic.link_layer import MagicLinkLayerProtocolWithSignaling -from squidasm.sim.stack.common import ( +from squidasm.qoala.sim.common import ( AppMemory, NVPhysicalQuantumMemory, PhysicalQuantumMemory, ) -from squidasm.sim.stack.handler import Handler, HandlerComponent -from squidasm.sim.stack.netstack import Netstack, NetstackComponent -from squidasm.sim.stack.processor import ( +from squidasm.qoala.sim.handler import Handler, HandlerComponent +from squidasm.qoala.sim.netstack import Netstack, NetstackComponent +from squidasm.qoala.sim.processor import ( GenericProcessor, NVProcessor, Processor, diff --git a/squidasm/sim/qoala/signals.py b/squidasm/qoala/sim/signals.py similarity index 100% rename from squidasm/sim/qoala/signals.py rename to squidasm/qoala/sim/signals.py diff --git a/squidasm/sim/qoala/stack.py b/squidasm/qoala/sim/stack.py similarity index 98% rename from squidasm/sim/qoala/stack.py rename to squidasm/qoala/sim/stack.py index 8a397ef3..27cc96e5 100644 --- a/squidasm/sim/qoala/stack.py +++ b/squidasm/qoala/sim/stack.py @@ -12,8 +12,8 @@ MagicLinkLayerProtocolWithSignaling, ) -from squidasm.sim.stack.host import Host, HostComponent -from squidasm.sim.stack.qnos import Qnos, QnosComponent +from squidasm.qoala.sim.host import Host, HostComponent +from squidasm.qoala.sim.qnos import Qnos, QnosComponent class ProcessingNode(Node): diff --git a/squidasm/sim/stack/host.py b/squidasm/sim/stack/host.py index 5fd1b293..48ae8832 100644 --- a/squidasm/sim/stack/host.py +++ b/squidasm/sim/stack/host.py @@ -16,7 +16,7 @@ from netsquid.nodes import Node from pydynaa import EventExpression -from squidasm.run.qoala import lhr +from squidasm.qoala.lang import lhr from squidasm.sim.stack.common import ComponentProtocol, LogManager, PortListener from squidasm.sim.stack.connection import QnosConnection from squidasm.sim.stack.context import NetSquidContext diff --git a/tests/qoala/test_parse_lhr.py b/tests/qoala/test_parse_lhr.py index 76c2dd50..efacf2f2 100644 --- a/tests/qoala/test_parse_lhr.py +++ b/tests/qoala/test_parse_lhr.py @@ -1,4 +1,4 @@ -from squidasm.run.qoala import lhr as lp +from squidasm.qoala.lang import lhr as lp from squidasm.run.stack.config import ( GenericQDeviceConfig, LinkConfig, From 11bc52dd37876874ef73b61dd57aa20158464b75 Mon Sep 17 00:00:00 2001 From: Bart van der Vecht Date: Thu, 16 Jun 2022 22:08:04 +0200 Subject: [PATCH 4/8] More reorganizing --- squidasm/qoala/arch/qoala-runtime-data.drawio | 28 ++-- squidasm/qoala/lang/lhr.py | 12 +- squidasm/qoala/lang/target.py | 4 + squidasm/qoala/runtime/config.py | 21 ++- squidasm/qoala/runtime/context.py | 41 +---- squidasm/qoala/runtime/environment.py | 124 ++++++++++++-- squidasm/qoala/runtime/program.py | 10 ++ squidasm/qoala/runtime/run.py | 32 +++- squidasm/qoala/sim/host.py | 156 +++--------------- squidasm/qoala/sim/stack.py | 6 +- 10 files changed, 215 insertions(+), 219 deletions(-) create mode 100644 squidasm/qoala/lang/target.py diff --git a/squidasm/qoala/arch/qoala-runtime-data.drawio b/squidasm/qoala/arch/qoala-runtime-data.drawio index 7e98d2be..2af679d8 100644 --- a/squidasm/qoala/arch/qoala-runtime-data.drawio +++ b/squidasm/qoala/arch/qoala-runtime-data.drawio @@ -1,6 +1,6 @@ - + - + @@ -19,46 +19,46 @@ - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/squidasm/qoala/lang/lhr.py b/squidasm/qoala/lang/lhr.py index a1cc83da..04b515ea 100644 --- a/squidasm/qoala/lang/lhr.py +++ b/squidasm/qoala/lang/lhr.py @@ -8,11 +8,10 @@ from netqasm.lang.operand import Template from netqasm.lang.parsing.text import parse_text_subroutine from netqasm.lang.subroutine import Subroutine -from netqasm.sdk.futures import Future from squidasm.qoala.runtime.program import ProgramContext, ProgramMeta -LhrValue = Union[int, Template, Future] +LhrValue = Union[int, Template] class LhrInstructionType(Enum): @@ -479,15 +478,6 @@ def parse(self) -> LhrProgram: return LhrProgram(instructions, subroutines) -class SdkProgram(abc.ABC): - @property - def meta(self) -> ProgramMeta: - raise NotImplementedError - - def compile(self, context: ProgramContext) -> LhrProgram: - raise NotImplementedError - - if __name__ == "__main__": ops = [] ops.append(SendCMsgOp("my_value")) diff --git a/squidasm/qoala/lang/target.py b/squidasm/qoala/lang/target.py new file mode 100644 index 00000000..0f5db4cb --- /dev/null +++ b/squidasm/qoala/lang/target.py @@ -0,0 +1,4 @@ +class OfflineHardwareInfo: + """Hardware made available to offline compiler.""" + + pass diff --git a/squidasm/qoala/runtime/config.py b/squidasm/qoala/runtime/config.py index 1ec04180..05fae9e1 100644 --- a/squidasm/qoala/runtime/config.py +++ b/squidasm/qoala/runtime/config.py @@ -79,9 +79,9 @@ class NVQDeviceConfig(BaseModel): carbon_rot_y: int = 500_000 carbon_rot_z: int = 500_000 electron_init: int = 2_000 - electron_rot_x: int = 5 - electron_rot_y: int = 5 - electron_rot_z: int = 5 + electron_rot_x: int = 5_000 + electron_rot_y: int = 5_000 + electron_rot_z: int = 5_000 ec_controlled_dir_x: int = 500_000 ec_controlled_dir_y: int = 500_000 measure: int = 3_700 @@ -110,6 +110,8 @@ class StackConfig(BaseModel): name: str qdevice_typ: str qdevice_cfg: Any + host_qnos_latency: float = 0.0 + instr_latency: float = 0.0 @classmethod def from_file(cls, path: str) -> StackConfig: @@ -121,6 +123,8 @@ def perfect_generic_config(cls, name: str) -> StackConfig: name=name, qdevice_typ="generic", qdevice_cfg=GenericQDeviceConfig.perfect_config(), + host_qnos_latency=0.0, + instr_latency=0.0, ) @@ -166,6 +170,8 @@ class LinkConfig(BaseModel): stack2: str typ: str cfg: Any + host_host_latency: float = 0.0 + qnos_qnos_latency: float = 0.0 @classmethod def from_file(cls, path: str) -> LinkConfig: @@ -173,7 +179,14 @@ def from_file(cls, path: str) -> LinkConfig: @classmethod def perfect_config(cls, stack1: str, stack2: str) -> LinkConfig: - return LinkConfig(stack1=stack1, stack2=stack2, typ="perfect", cfg=None) + return LinkConfig( + stack1=stack1, + stack2=stack2, + typ="perfect", + cfg=None, + host_host_latency=0.0, + qnos_qnos_latency=0.0, + ) class StackNetworkConfig(BaseModel): diff --git a/squidasm/qoala/runtime/context.py b/squidasm/qoala/runtime/context.py index dfed162b..28667536 100644 --- a/squidasm/qoala/runtime/context.py +++ b/squidasm/qoala/runtime/context.py @@ -4,22 +4,26 @@ from netqasm.sdk.network import NetworkInfo +from squidasm.qoala.runtime.environment import GlobalEnvironment + if TYPE_CHECKING: from squidasm.qoala.sim.host import Host class NetSquidNetworkInfo(NetworkInfo): + _global_env: GlobalEnvironment + @classmethod def _get_node_id(cls, node_name: str) -> int: - nodes = NetSquidContext.get_nodes() - for id, name in nodes.items(): - if name == node_name: + nodes = cls._global_env.get_nodes() + for id, info in nodes.items(): + if info.name == node_name: return id raise ValueError(f"Node with name {node_name} not found") @classmethod def _get_node_name(cls, node_id: int) -> str: - return NetSquidContext.get_nodes()[node_id] + return cls._global_env.get_nodes()[node_id].name @classmethod def get_node_id_for_app(cls, app_name: str) -> int: @@ -28,32 +32,3 @@ def get_node_id_for_app(cls, app_name: str) -> int: @classmethod def get_node_name_for_app(cls, app_name: str) -> str: raise NotImplementedError - - -class NetSquidContext: - _protocols: Dict[str, Host] = {} - _nodes: Dict[int, str] = {} - - @classmethod - def get_nodes(cls) -> Dict[int, str]: - return cls._nodes - - @classmethod - def set_nodes(cls, nodes: Dict[int, str]) -> None: - cls._nodes = nodes - - @classmethod - def add_node(cls, id: int, node: str) -> None: - cls._nodes[id] = node - - @classmethod - def get_protocols(cls) -> Dict[str, Host]: - return cls._protocols - - @classmethod - def set_protocols(cls, protocols: Dict[str, Host]) -> None: - cls._protocols = protocols - - @classmethod - def add_protocol(cls, name: str, protocol: Host) -> None: - cls._protocols[name] = protocol diff --git a/squidasm/qoala/runtime/environment.py b/squidasm/qoala/runtime/environment.py index 4b94ce17..9a9d710e 100644 --- a/squidasm/qoala/runtime/environment.py +++ b/squidasm/qoala/runtime/environment.py @@ -1,37 +1,129 @@ -from typing import Dict, Tuple +from __future__ import annotations +from dataclasses import dataclass +from typing import Dict, Tuple, Union -class GlobalNodeInfo: - pass +from squidasm.qoala.runtime.config import ( + GenericQDeviceConfig, + LinkConfig, + NVQDeviceConfig, +) +@dataclass +class GlobalNodeInfo: + """Node information available at runtime.""" + + name: str + + # total number of qubits + num_qubits: int + # number of communication qubits + num_comm_qubits: int + + # coherence times for communication qubits + comm_T1: int + comm_T2: int + + # coherence times for memory (non-communication) qubits + mem_T1: int + mem_T2: int + + @classmethod + def from_config( + cls, name: str, config: Union[GenericQDeviceConfig, NVQDeviceConfig] + ) -> GlobalNodeInfo: + if isinstance(config, GenericQDeviceConfig): + return GlobalNodeInfo( + name=name, + num_qubits=config.num_qubits, + num_comm_qubits=config.num_comm_qubits, + comm_T1=config.T1, + comm_T2=config.T2, + mem_T1=config.T1, + mem_T2=config.T2, + ) + else: + assert isinstance(config, NVQDeviceConfig) + return GlobalNodeInfo( + name=name, + num_qubits=config.num_qubits, + num_comm_qubits=1, + comm_T1=config.electron_T1, + comm_T2=config.electron_T2, + mem_T1=config.carbon_T1, + mem_T2=config.carbon_T2, + ) + + +@dataclass class GlobalLinkInfo: - pass + node_name1: str + node_name2: str + + fidelity: float + + @classmethod + def from_config( + cls, node_name1: str, node_name2: str, config: LinkConfig + ) -> GlobalLinkInfo: + if config.typ == "perfect": + return GlobalLinkInfo( + node_name1=node_name1, node_name2=node_name2, fidelity=1.0 + ) + elif config.typ == "depolarise": + return GlobalLinkInfo( + node_name1=node_name1, + node_name2=node_name2, + fidelity=config.cfg.fidelity, # type: ignore + ) + else: + raise NotImplementedError class GlobalEnvironment: - # node ID -> node info - _nodes: Dict[int, GlobalNodeInfo] = {} + def __init__(self) -> None: + # node ID -> node info + self._nodes: Dict[int, GlobalNodeInfo] = {} - # (node A ID, node B ID) -> link info - # for a pair (a, b) there exists no separate (b, a) info (it is the same) - _links: Dict[Tuple[int, int], GlobalLinkInfo] = {} + # (node A ID, node B ID) -> link info + # for a pair (a, b) there exists no separate (b, a) info (it is the same) + self._links: Dict[Tuple[int, int], GlobalLinkInfo] = {} + def get_nodes(self) -> Dict[int, GlobalNodeInfo]: + return self._nodes -class LocalNodeInfo: - pass + def set_nodes(self, nodes: Dict[int, GlobalNodeInfo]) -> None: + self._nodes = nodes + def add_node(self, id: int, node: GlobalNodeInfo) -> None: + self._nodes[id] = node -class LocalLinkInfo: - pass + def get_links(self) -> Dict[int, GlobalLinkInfo]: + return self._links + + def set_links(self, links: Dict[int, GlobalLinkInfo]) -> None: + self._links = links + + def add_link(self, id: int, link: GlobalLinkInfo) -> None: + self._links[id] = link class LocalEnvironment: - _global_env: GlobalEnvironment + def __init__(self, global_env: GlobalEnvironment, node_id: int) -> None: + self._global_env: GlobalEnvironment = global_env + + # node ID of self + self._node_id: int = node_id - # node ID of self - _node_id: int + def get_global_env(self) -> GlobalEnvironment: + return self._global_env + + def get_node_id(self) -> int: + return self._node_id class ProgramEnvironment: + """Environment interface given to a program""" + pass diff --git a/squidasm/qoala/runtime/program.py b/squidasm/qoala/runtime/program.py index 2cba790b..1076f5b5 100644 --- a/squidasm/qoala/runtime/program.py +++ b/squidasm/qoala/runtime/program.py @@ -6,6 +6,8 @@ from netqasm.sdk.connection import BaseNetQASMConnection from netqasm.sdk.epr_socket import EPRSocket +from squidasm.qoala.lang.lhr import LhrProgram + class ProgramContext: def __init__( @@ -53,3 +55,11 @@ def meta(self) -> ProgramMeta: def run(self, context: ProgramContext) -> Dict[str, Any]: raise NotImplementedError + + +@dataclass +class ProgramInstance: + program: LhrProgram + inputs: Dict[str, Any] + num_iterations: int + deadline: float diff --git a/squidasm/qoala/runtime/run.py b/squidasm/qoala/runtime/run.py index 19ee23fd..f4e2df7d 100644 --- a/squidasm/qoala/runtime/run.py +++ b/squidasm/qoala/runtime/run.py @@ -25,7 +25,8 @@ NVQDeviceConfig, StackNetworkConfig, ) -from squidasm.qoala.runtime.context import NetSquidContext +from squidasm.qoala.runtime.context import NetSquidContext, NetSquidNetworkInfo +from squidasm.qoala.runtime.environment import GlobalEnvironment, GlobalNodeInfo from squidasm.qoala.runtime.program import Program from squidasm.qoala.sim.build import build_generic_qdevice, build_nv_qdevice from squidasm.qoala.sim.globals import GlobalSimData @@ -36,7 +37,7 @@ def fidelity_to_prob_max_mixed(fid: float) -> float: return (1 - fid) * 4.0 / 3.0 -def _setup_network(config: StackNetworkConfig) -> StackNetwork: +def _setup_network(config: StackNetworkConfig, rte: GlobalEnvironment) -> StackNetwork: assert len(config.stacks) <= 2 assert len(config.links) <= 1 @@ -56,7 +57,11 @@ def _setup_network(config: StackNetworkConfig) -> StackNetwork: qdevice_cfg = GenericQDeviceConfig(**cfg.qdevice_cfg) qdevice = build_generic_qdevice(f"qdevice_{cfg.name}", cfg=qdevice_cfg) stack = NodeStack(cfg.name, qdevice_type="generic", qdevice=qdevice) - NetSquidContext.add_node(stack.node.ID, cfg.name) + + # TODO !!! + # get HW info from config + node_info = GlobalNodeInfo(cfg.name, 2, 1, 0, 0, 0, 0) + rte.add_node(stack.node.ID, node_info) stacks[cfg.name] = stack for (_, s1), (_, s2) in itertools.combinations(stacks.items(), 2): @@ -153,11 +158,22 @@ def run( :param num_times: numbers of times to run the programs, defaults to 1 :return: program results """ - network = _setup_network(config) - - NetSquidContext.set_nodes({}) - for name, stack in network.stacks.items(): - NetSquidContext.add_node(stack.node.ID, name) + # Create global runtime environment. + rte = GlobalEnvironment() + + # Build the network. Info about created nodes will be added to the runtime environment. + network = _setup_network(config, rte) + + # Add info about constructed nodes to runtime environment. + # rte.set_nodes({}) + # for name, stack in network.stacks.items(): + # # TODO !!! + # # get HW info from config + # node_info = GlobalNodeInfo(name, 2, 1, 0, 0, 0, 0) + # rte.add_node(stack.node.ID, node_info) + + # TODO: rewrite + NetSquidNetworkInfo._global_env = rte GlobalSimData.set_network(network) for name, program in programs.items(): diff --git a/squidasm/qoala/sim/host.py b/squidasm/qoala/sim/host.py index d48cae27..4c36c649 100644 --- a/squidasm/qoala/sim/host.py +++ b/squidasm/qoala/sim/host.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +from distutils.filelist import glob_to_re from typing import Any, Dict, Generator, List, Optional, Type from netqasm.backend.messages import ( @@ -18,6 +19,7 @@ from pydynaa import EventExpression from squidasm.qoala.lang import lhr from squidasm.qoala.runtime.context import NetSquidContext +from squidasm.qoala.runtime.environment import GlobalEnvironment from squidasm.qoala.runtime.program import Program, ProgramContext from squidasm.qoala.sim.common import ComponentProtocol, LogManager, PortListener from squidasm.qoala.sim.connection import QnosConnection @@ -59,10 +61,13 @@ def setup(self) -> Generator[EventExpression, None, None]: epr_sockets: Dict[int, EPRSocket] = {} for i, remote_name in enumerate(prog_meta.epr_sockets): remote_id = None - nodes = NetSquidContext.get_nodes() - for id, name in nodes.items(): - if name == remote_name: + + # TODO: rewrite + nodes = self._host._global_env.get_nodes() + for id, info in nodes.items(): + if info.name == remote_name: remote_id = id + assert remote_id is not None self._host.send_qnos_msg( bytes(OpenEPRSocketMessage(self._app_id, i, remote_id)) @@ -74,10 +79,13 @@ def setup(self) -> Generator[EventExpression, None, None]: classical_sockets: Dict[int, ClassicalSocket] = {} for i, remote_name in enumerate(prog_meta.csockets): remote_id = None - nodes = NetSquidContext.get_nodes() - for id, name in nodes.items(): - if name == remote_name: + + # TODO: rewrite + nodes = self._host._global_env.get_nodes() + for id, info in nodes.items(): + if info.name == remote_name: remote_id = id + assert remote_id is not None classical_sockets[remote_name] = ClassicalSocket( self._host, prog_meta.name, remote_name @@ -230,7 +238,12 @@ def peer_out_port(self) -> Port: class Host(ComponentProtocol): """NetSquid protocol representing a Host.""" - def __init__(self, comp: HostComponent, qdevice_type: Optional[str] = "nv") -> None: + def __init__( + self, + comp: HostComponent, + global_env: GlobalEnvironment, + qdevice_type: Optional[str] = "nv", + ) -> None: """Qnos protocol constructor. :param comp: NetSquid component representing the Host @@ -239,6 +252,8 @@ def __init__(self, comp: HostComponent, qdevice_type: Optional[str] = "nv") -> N super().__init__(name=f"{comp.name}_protocol", comp=comp) self._comp = comp + self._global_env = global_env + self.add_listener( "qnos", PortListener(self._comp.ports["qnos_in"], SIGNAL_HAND_HOST_MSG), @@ -258,7 +273,7 @@ def __init__(self, comp: HostComponent, qdevice_type: Optional[str] = "nv") -> N raise ValueError # Program that is currently being executed. - self._program: Optional[Program] = None + self._program: Optional[lhr.LhrProgram] = None # Number of times the current program still needs to be run. self._num_pending: int = 0 @@ -286,124 +301,6 @@ def send_peer_msg(self, msg: str) -> None: def receive_peer_msg(self) -> Generator[EventExpression, None, str]: return (yield from self._receive_msg("peer", SIGNAL_HOST_HOST_MSG)) - def run_sdk_program( - self, program: Program - ) -> Generator[EventExpression, None, None]: - prog_meta = program.meta - - # Register the new program (called 'application' by QNodeOS) with QNodeOS. - self.send_qnos_msg(bytes(InitNewAppMessage(max_qubits=prog_meta.max_qubits))) - app_id = yield from self.receive_qnos_msg() - self._logger.debug(f"got app id from qnos: {app_id}") - - # Set up the Connection object to be used by the program SDK code. - conn = QnosConnection( - self, - app_id, - prog_meta.name, - max_qubits=prog_meta.max_qubits, - compiler=self._compiler, - ) - - # Create EPR sockets that can be used by the program SDK code. - epr_sockets: Dict[int, EPRSocket] = {} - for i, remote_name in enumerate(prog_meta.epr_sockets): - remote_id = None - nodes = NetSquidContext.get_nodes() - for id, name in nodes.items(): - if name == remote_name: - remote_id = id - assert remote_id is not None - self.send_qnos_msg(bytes(OpenEPRSocketMessage(app_id, i, remote_id))) - epr_sockets[remote_name] = EPRSocket(remote_name, i) - epr_sockets[remote_name].conn = conn - - # Create classical sockets that can be used by the program SDK code. - classical_sockets: Dict[int, ClassicalSocket] = {} - for i, remote_name in enumerate(prog_meta.csockets): - remote_id = None - nodes = NetSquidContext.get_nodes() - for id, name in nodes.items(): - if name == remote_name: - remote_id = id - assert remote_id is not None - classical_sockets[remote_name] = ClassicalSocket( - self, prog_meta.name, remote_name - ) - - context = ProgramContext( - netqasm_connection=conn, - csockets=classical_sockets, - epr_sockets=epr_sockets, - app_id=app_id, - ) - - # Run the program by evaluating its run() method. - result = yield from program.run(context) - self._program_results.append(result) - - # Tell QNodeOS the program has finished. - self.send_qnos_msg(bytes(StopAppMessage(app_id))) - - def run_lhr_sdk_program( - self, - program: lhr.LhrProgram, - ) -> Generator[EventExpression, None, None]: - prog_meta = program.meta - - # Register the new program (called 'application' by QNodeOS) with QNodeOS. - self.send_qnos_msg(bytes(InitNewAppMessage(max_qubits=prog_meta.max_qubits))) - app_id = yield from self.receive_qnos_msg() - self._logger.debug(f"got app id from qnos: {app_id}") - - # Set up the Connection object to be used by the program SDK code. - conn = QnosConnection( - self, - app_id, - prog_meta.name, - max_qubits=prog_meta.max_qubits, - compiler=self._compiler, - ) - - # Create EPR sockets that can be used by the program SDK code. - epr_sockets: Dict[int, EPRSocket] = {} - for i, remote_name in enumerate(prog_meta.epr_sockets): - remote_id = None - nodes = NetSquidContext.get_nodes() - for id, name in nodes.items(): - if name == remote_name: - remote_id = id - assert remote_id is not None - self.send_qnos_msg(bytes(OpenEPRSocketMessage(app_id, i, remote_id))) - epr_sockets[remote_name] = EPRSocket(remote_name, i) - epr_sockets[remote_name].conn = conn - - # Create classical sockets that can be used by the program SDK code. - classical_sockets: Dict[int, ClassicalSocket] = {} - for i, remote_name in enumerate(prog_meta.csockets): - remote_id = None - nodes = NetSquidContext.get_nodes() - for id, name in nodes.items(): - if name == remote_name: - remote_id = id - assert remote_id is not None - classical_sockets[remote_name] = ClassicalSocket( - self, prog_meta.name, remote_name - ) - - context = ProgramContext( - netqasm_connection=conn, - csockets=classical_sockets, - epr_sockets=epr_sockets, - app_id=app_id, - ) - - lhr_program = program.compile(context) - self._logger.warning(f"Runnign compiled SDK program:\n{lhr_program}") - process = LhrProcess(self, lhr_program) - yield from process.run_with_context(context, 1) - self._program_results = process._program_results - def run_lhr_program( self, program: lhr.LhrProgram, num_times: int ) -> Generator[EventExpression, None, None]: @@ -422,12 +319,7 @@ def run(self) -> Generator[EventExpression, None, None]: assert self._program is not None - if isinstance(self._program, lhr.LhrProgram): - yield from self.run_lhr_program(self._program, 1) - elif isinstance(self._program, lhr.SdkProgram): - yield from self.run_lhr_sdk_program(self._program) - else: - self.run_sdk_program(self._program) + yield from self.run_lhr_program(self._program, 1) def enqueue_program(self, program: Program, num_times: int = 1): """Queue a program to be run the given number of times. diff --git a/squidasm/qoala/sim/stack.py b/squidasm/qoala/sim/stack.py index 27cc96e5..a4c30eaf 100644 --- a/squidasm/qoala/sim/stack.py +++ b/squidasm/qoala/sim/stack.py @@ -12,6 +12,7 @@ MagicLinkLayerProtocolWithSignaling, ) +from squidasm.qoala.runtime.environment import GlobalEnvironment from squidasm.qoala.sim.host import Host, HostComponent from squidasm.qoala.sim.qnos import Qnos, QnosComponent @@ -103,6 +104,7 @@ class NodeStack(Protocol): def __init__( self, name: str, + global_env: Optional[GlobalEnvironment] = None, node: Optional[ProcessingNode] = None, qdevice_type: Optional[str] = "generic", qdevice: Optional[QuantumProcessor] = None, @@ -131,6 +133,8 @@ def __init__( assert qdevice is not None self._node = ProcessingNode(name, qdevice, node_id) + self._global_env = global_env + self._host: Optional[Host] = None self._qnos: Optional[Qnos] = None @@ -138,7 +142,7 @@ def __init__( # If `use_default_components` is False, these components must be manually # created and added to this NodeStack. if use_default_components: - self._host = Host(self.host_comp, qdevice_type) + self._host = Host(self.host_comp, global_env, qdevice_type) self._qnos = Qnos(self.qnos_comp, qdevice_type) def assign_ll_protocol(self, prot: MagicLinkLayerProtocolWithSignaling) -> None: From 99213b1ff73df4c50b22f4f86bbe66015028ced8 Mon Sep 17 00:00:00 2001 From: Bart van der Vecht Date: Fri, 17 Jun 2022 11:55:37 +0200 Subject: [PATCH 5/8] Clean up host --- squidasm/qoala/lang/lhr.py | 16 ++++++++++------ squidasm/qoala/runtime/program.py | 23 ----------------------- squidasm/qoala/sim/host.py | 6 +----- 3 files changed, 11 insertions(+), 34 deletions(-) diff --git a/squidasm/qoala/lang/lhr.py b/squidasm/qoala/lang/lhr.py index 04b515ea..01304690 100644 --- a/squidasm/qoala/lang/lhr.py +++ b/squidasm/qoala/lang/lhr.py @@ -1,7 +1,7 @@ import abc from dataclasses import dataclass from enum import Enum, auto -from typing import Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union from netqasm.lang.instr import NetQASMInstruction from netqasm.lang.instr.flavour import NVFlavour @@ -9,11 +9,18 @@ from netqasm.lang.parsing.text import parse_text_subroutine from netqasm.lang.subroutine import Subroutine -from squidasm.qoala.runtime.program import ProgramContext, ProgramMeta - LhrValue = Union[int, Template] +@dataclass +class ProgramMeta: + name: str + parameters: Dict[str, Any] + csockets: List[str] + epr_sockets: List[str] + max_qubits: int + + class LhrInstructionType(Enum): CC = 0 CL = auto() @@ -362,9 +369,6 @@ def __str__(self) -> str: # return "\n".join(" " + i for i in instrs) return "\n".join(" " + str(i) for i in self.instructions) - def compile(self, context: ProgramContext) -> None: - raise NotImplementedError - class EndOfTextException(Exception): pass diff --git a/squidasm/qoala/runtime/program.py b/squidasm/qoala/runtime/program.py index 1076f5b5..f980ef98 100644 --- a/squidasm/qoala/runtime/program.py +++ b/squidasm/qoala/runtime/program.py @@ -4,7 +4,6 @@ from netqasm.sdk.classical_communication.socket import Socket from netqasm.sdk.connection import BaseNetQASMConnection -from netqasm.sdk.epr_socket import EPRSocket from squidasm.qoala.lang.lhr import LhrProgram @@ -14,12 +13,10 @@ def __init__( self, netqasm_connection: BaseNetQASMConnection, csockets: Dict[str, Socket], - epr_sockets: Dict[str, EPRSocket], app_id: int, ): self._connection = netqasm_connection self._csockets = csockets - self._epr_sockets = epr_sockets self._app_id = app_id @property @@ -30,31 +27,11 @@ def connection(self) -> BaseNetQASMConnection: def csockets(self) -> Dict[str, Socket]: return self._csockets - @property - def epr_sockets(self) -> Dict[str, EPRSocket]: - return self._epr_sockets - @property def app_id(self) -> int: return self._app_id -@dataclass -class ProgramMeta: - name: str - parameters: Dict[str, Any] - csockets: List[str] - epr_sockets: List[str] - max_qubits: int - - -class Program(abc.ABC): - @property - def meta(self) -> ProgramMeta: - raise NotImplementedError - - def run(self, context: ProgramContext) -> Dict[str, Any]: - raise NotImplementedError @dataclass diff --git a/squidasm/qoala/sim/host.py b/squidasm/qoala/sim/host.py index 4c36c649..46012ef6 100644 --- a/squidasm/qoala/sim/host.py +++ b/squidasm/qoala/sim/host.py @@ -57,8 +57,7 @@ def setup(self) -> Generator[EventExpression, None, None]: compiler=self._host._compiler, ) - # Create EPR sockets that can be used by the program SDK code. - epr_sockets: Dict[int, EPRSocket] = {} + # Open the EPR sockets required by the program. for i, remote_name in enumerate(prog_meta.epr_sockets): remote_id = None @@ -72,8 +71,6 @@ def setup(self) -> Generator[EventExpression, None, None]: self._host.send_qnos_msg( bytes(OpenEPRSocketMessage(self._app_id, i, remote_id)) ) - epr_sockets[remote_name] = EPRSocket(remote_name, i) - epr_sockets[remote_name].conn = conn # Create classical sockets that can be used by the program SDK code. classical_sockets: Dict[int, ClassicalSocket] = {} @@ -94,7 +91,6 @@ def setup(self) -> Generator[EventExpression, None, None]: self._context = ProgramContext( netqasm_connection=conn, csockets=classical_sockets, - epr_sockets=epr_sockets, app_id=self._app_id, ) From 01d35e3064ef133465e3722a693cb5a9d2b30432 Mon Sep 17 00:00:00 2001 From: Bart van der Vecht Date: Fri, 17 Jun 2022 15:17:35 +0200 Subject: [PATCH 6/8] First working version --- setup.cfg | 2 +- squidasm/qoala/lang/lhr.py | 1 - squidasm/qoala/runtime/context.py | 5 - squidasm/qoala/runtime/environment.py | 18 ++- squidasm/qoala/runtime/program.py | 32 +---- squidasm/qoala/runtime/run.py | 45 ++++--- squidasm/qoala/sim/connection.py | 23 +--- squidasm/qoala/sim/csocket.py | 16 +-- squidasm/qoala/sim/handler.py | 3 +- squidasm/qoala/sim/host.py | 187 ++++++++++++-------------- squidasm/qoala/sim/stack.py | 35 ++++- tests/qoala/test_parse_lhr.py | 29 ++-- 12 files changed, 193 insertions(+), 203 deletions(-) diff --git a/setup.cfg b/setup.cfg index 48f56d8d..1493e3a1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,7 +22,7 @@ install_requires = netsquid >=1.0.6, <2.0 netsquid-magic >=12.1, <13.0.0 netsquid-nv >=8.0, <9.0 - netqasm ==0.11.2 + netqasm >=0.11 [options.extras_require] dev = diff --git a/squidasm/qoala/lang/lhr.py b/squidasm/qoala/lang/lhr.py index 01304690..90e9479b 100644 --- a/squidasm/qoala/lang/lhr.py +++ b/squidasm/qoala/lang/lhr.py @@ -1,4 +1,3 @@ -import abc from dataclasses import dataclass from enum import Enum, auto from typing import Any, Dict, List, Optional, Union diff --git a/squidasm/qoala/runtime/context.py b/squidasm/qoala/runtime/context.py index 28667536..e75132cb 100644 --- a/squidasm/qoala/runtime/context.py +++ b/squidasm/qoala/runtime/context.py @@ -1,14 +1,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Dict - from netqasm.sdk.network import NetworkInfo from squidasm.qoala.runtime.environment import GlobalEnvironment -if TYPE_CHECKING: - from squidasm.qoala.sim.host import Host - class NetSquidNetworkInfo(NetworkInfo): _global_env: GlobalEnvironment diff --git a/squidasm/qoala/runtime/environment.py b/squidasm/qoala/runtime/environment.py index 9a9d710e..5da1baf1 100644 --- a/squidasm/qoala/runtime/environment.py +++ b/squidasm/qoala/runtime/environment.py @@ -1,13 +1,14 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Dict, Tuple, Union +from typing import Dict, List, Tuple, Union from squidasm.qoala.runtime.config import ( GenericQDeviceConfig, LinkConfig, NVQDeviceConfig, ) +from squidasm.qoala.runtime.program import ProgramInstance @dataclass @@ -93,6 +94,11 @@ def __init__(self) -> None: def get_nodes(self) -> Dict[int, GlobalNodeInfo]: return self._nodes + def get_node_id(self, name: str) -> int: + for id, node in self._nodes.items(): + if node.name == name: + return id + def set_nodes(self, nodes: Dict[int, GlobalNodeInfo]) -> None: self._nodes = nodes @@ -116,12 +122,22 @@ def __init__(self, global_env: GlobalEnvironment, node_id: int) -> None: # node ID of self self._node_id: int = node_id + self._programs: List[ProgramInstance] = [] + self._csockets: List[str] = [] + self._epr_sockets: List[str] = [] + def get_global_env(self) -> GlobalEnvironment: return self._global_env def get_node_id(self) -> int: return self._node_id + def register_program(self, program: ProgramInstance) -> None: + self._programs.append(program) + + def open_epr_socket(self) -> None: + pass + class ProgramEnvironment: """Environment interface given to a program""" diff --git a/squidasm/qoala/runtime/program.py b/squidasm/qoala/runtime/program.py index f980ef98..0a980dcf 100644 --- a/squidasm/qoala/runtime/program.py +++ b/squidasm/qoala/runtime/program.py @@ -1,39 +1,9 @@ -import abc from dataclasses import dataclass -from typing import Any, Dict, List - -from netqasm.sdk.classical_communication.socket import Socket -from netqasm.sdk.connection import BaseNetQASMConnection +from typing import Any, Dict from squidasm.qoala.lang.lhr import LhrProgram -class ProgramContext: - def __init__( - self, - netqasm_connection: BaseNetQASMConnection, - csockets: Dict[str, Socket], - app_id: int, - ): - self._connection = netqasm_connection - self._csockets = csockets - self._app_id = app_id - - @property - def connection(self) -> BaseNetQASMConnection: - return self._connection - - @property - def csockets(self) -> Dict[str, Socket]: - return self._csockets - - @property - def app_id(self) -> int: - return self._app_id - - - - @dataclass class ProgramInstance: program: LhrProgram diff --git a/squidasm/qoala/runtime/run.py b/squidasm/qoala/runtime/run.py index f4e2df7d..99949a12 100644 --- a/squidasm/qoala/runtime/run.py +++ b/squidasm/qoala/runtime/run.py @@ -25,9 +25,9 @@ NVQDeviceConfig, StackNetworkConfig, ) -from squidasm.qoala.runtime.context import NetSquidContext, NetSquidNetworkInfo +from squidasm.qoala.runtime.context import NetSquidNetworkInfo from squidasm.qoala.runtime.environment import GlobalEnvironment, GlobalNodeInfo -from squidasm.qoala.runtime.program import Program +from squidasm.qoala.runtime.program import ProgramInstance from squidasm.qoala.sim.build import build_generic_qdevice, build_nv_qdevice from squidasm.qoala.sim.globals import GlobalSimData from squidasm.qoala.sim.stack import NodeStack, StackNetwork @@ -44,24 +44,37 @@ def _setup_network(config: StackNetworkConfig, rte: GlobalEnvironment) -> StackN stacks: Dict[str, NodeStack] = {} link_prots: List[MagicLinkLayerProtocol] = [] - for cfg in config.stacks: + for node_id, cfg in enumerate(config.stacks): + # TODO !!! + # get HW info from config + node_info = GlobalNodeInfo(cfg.name, 2, 1, 0, 0, 0, 0) + rte.add_node(node_id, node_info) + if cfg.qdevice_typ == "nv": qdevice_cfg = cfg.qdevice_cfg if not isinstance(qdevice_cfg, NVQDeviceConfig): qdevice_cfg = NVQDeviceConfig(**cfg.qdevice_cfg) qdevice = build_nv_qdevice(f"qdevice_{cfg.name}", cfg=qdevice_cfg) - stack = NodeStack(cfg.name, qdevice_type="nv", qdevice=qdevice) + stack = NodeStack( + cfg.name, + global_env=rte, + qdevice_type="nv", + qdevice=qdevice, + node_id=node_id, + ) elif cfg.qdevice_typ == "generic": qdevice_cfg = cfg.qdevice_cfg if not isinstance(qdevice_cfg, GenericQDeviceConfig): qdevice_cfg = GenericQDeviceConfig(**cfg.qdevice_cfg) qdevice = build_generic_qdevice(f"qdevice_{cfg.name}", cfg=qdevice_cfg) - stack = NodeStack(cfg.name, qdevice_type="generic", qdevice=qdevice) + stack = NodeStack( + cfg.name, + global_env=rte, + qdevice_type="generic", + qdevice=qdevice, + node_id=node_id, + ) - # TODO !!! - # get HW info from config - node_info = GlobalNodeInfo(cfg.name, 2, 1, 0, 0, 0, 0) - rte.add_node(stack.node.ID, node_info) stacks[cfg.name] = stack for (_, s1), (_, s2) in itertools.combinations(stacks.items(), 2): @@ -149,7 +162,7 @@ def _run(network: StackNetwork) -> List[Dict[str, Any]]: def run( - config: StackNetworkConfig, programs: Dict[str, Program], num_times: int = 1 + config: StackNetworkConfig, programs: Dict[str, ProgramInstance], num_times: int = 1 ) -> List[Dict[str, Any]]: """Run programs on a network specified by a network configuration. @@ -164,20 +177,14 @@ def run( # Build the network. Info about created nodes will be added to the runtime environment. network = _setup_network(config, rte) - # Add info about constructed nodes to runtime environment. - # rte.set_nodes({}) - # for name, stack in network.stacks.items(): - # # TODO !!! - # # get HW info from config - # node_info = GlobalNodeInfo(name, 2, 1, 0, 0, 0, 0) - # rte.add_node(stack.node.ID, node_info) - # TODO: rewrite NetSquidNetworkInfo._global_env = rte GlobalSimData.set_network(network) + for name, program in programs.items(): - network.stacks[name].host.enqueue_program(program, num_times) + network.stacks[name]._local_env.register_program(program) + network.stacks[name].install_environment() results = _run(network) return results diff --git a/squidasm/qoala/sim/connection.py b/squidasm/qoala/sim/connection.py index 9b4edf74..72bdd9d9 100644 --- a/squidasm/qoala/sim/connection.py +++ b/squidasm/qoala/sim/connection.py @@ -4,15 +4,11 @@ from typing import TYPE_CHECKING, Callable, Generator, Optional, Type from netqasm.backend.messages import SubroutineMessage +from netqasm.lang.ir import ProtoSubroutine from netqasm.lang.subroutine import Subroutine from netqasm.sdk.build_types import GenericHardwareConfig, HardwareConfig from netqasm.sdk.builder import Builder -from netqasm.sdk.connection import ( - BaseNetQASMConnection, - NetworkInfo, - ProtoSubroutine, - T_Message, -) +from netqasm.sdk.connection import T_Message from netqasm.sdk.shared_memory import SharedMemory from pydynaa import EventExpression @@ -23,10 +19,8 @@ from squidasm.qoala.sim.common import LogManager -from ..runtime.context import NetSquidNetworkInfo - -class QnosConnection(BaseNetQASMConnection): +class QnosConnection: def __init__( self, host: Host, @@ -46,7 +40,7 @@ def __init__( self._shared_memory = None self._logger: logging.Logger = LogManager.get_stack_logger( - f"{self.__class__.__name__}({self.app_name})" + f"{self.__class__.__name__}({self._app_name})" ) if hardware_config is None: @@ -63,12 +57,6 @@ def __init__( def shared_memory(self) -> SharedMemory: return self._shared_memory - def __enter__(self) -> QnosConnection: - pass - - def __exit__(self, exc_type, exc_val, exc_tb): - self.flush() - def _commit_message( self, msg: T_Message, block: bool = True, callback: Optional[Callable] = None ) -> None: @@ -124,6 +112,3 @@ def _commit_serialized_message( self, raw_msg: bytes, block: bool = True, callback: Optional[Callable] = None ) -> None: pass - - def _get_network_info(self) -> Type[NetworkInfo]: - return NetSquidNetworkInfo diff --git a/squidasm/qoala/sim/csocket.py b/squidasm/qoala/sim/csocket.py index c446e49f..6ebd2592 100644 --- a/squidasm/qoala/sim/csocket.py +++ b/squidasm/qoala/sim/csocket.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING, Generator from netqasm.sdk.classical_communication.message import StructuredMessage -from netqasm.sdk.classical_communication.socket import Socket from pydynaa import EventExpression @@ -11,19 +10,10 @@ from squidasm.qoala.sim.host import Host -class ClassicalSocket(Socket): - def __init__( - self, - host: Host, - app_name: str, - remote_app_name: str, - socket_id: int = 0, - ): - """Socket used to communicate classical data between applications.""" - super().__init__( - app_name=app_name, remote_app_name=remote_app_name, socket_id=socket_id - ) +class ClassicalSocket: + def __init__(self, host: Host, remote_name: str): self._host = host + self._remote_name = remote_name def send(self, msg: str) -> None: """Sends a message to the remote node.""" diff --git a/squidasm/qoala/sim/handler.py b/squidasm/qoala/sim/handler.py index b3003101..7f04f0a3 100644 --- a/squidasm/qoala/sim/handler.py +++ b/squidasm/qoala/sim/handler.py @@ -200,8 +200,7 @@ def _next_app(self) -> Optional[RunningApp]: return app return None - def init_new_app(self, max_qubits: int) -> int: - app_id = self._app_counter + def init_new_app(self, app_id: int) -> int: self._app_counter += 1 self.app_memories[app_id] = AppMemory(app_id, self.physical_memory.qubit_count) self._applications[app_id] = RunningApp(app_id) diff --git a/squidasm/qoala/sim/host.py b/squidasm/qoala/sim/host.py index 46012ef6..fa7b0f43 100644 --- a/squidasm/qoala/sim/host.py +++ b/squidasm/qoala/sim/host.py @@ -1,34 +1,53 @@ from __future__ import annotations import logging -from distutils.filelist import glob_to_re from typing import Any, Dict, Generator, List, Optional, Type -from netqasm.backend.messages import ( - InitNewAppMessage, - OpenEPRSocketMessage, - StopAppMessage, -) +from netqasm.backend.messages import StopAppMessage from netqasm.lang.operand import Register from netqasm.lang.parsing.text import NetQASMSyntaxError, parse_register -from netqasm.sdk.epr_socket import EPRSocket from netqasm.sdk.transpile import NVSubroutineTranspiler, SubroutineTranspiler from netsquid.components.component import Component, Port from netsquid.nodes import Node from pydynaa import EventExpression from squidasm.qoala.lang import lhr -from squidasm.qoala.runtime.context import NetSquidContext -from squidasm.qoala.runtime.environment import GlobalEnvironment -from squidasm.qoala.runtime.program import Program, ProgramContext +from squidasm.qoala.runtime.environment import LocalEnvironment +from squidasm.qoala.runtime.program import ProgramInstance from squidasm.qoala.sim.common import ComponentProtocol, LogManager, PortListener from squidasm.qoala.sim.connection import QnosConnection from squidasm.qoala.sim.csocket import ClassicalSocket from squidasm.qoala.sim.signals import SIGNAL_HAND_HOST_MSG, SIGNAL_HOST_HOST_MSG +class ProgramContext: + def __init__( + self, + conn: QnosConnection, + csockets: Dict[str, ClassicalSocket], + app_id: int, + ): + self._conn = conn + self._csockets = csockets + self._app_id = app_id + + @property + def conn(self) -> QnosConnection: + return self._conn + + @property + def csockets(self) -> Dict[str, ClassicalSocket]: + return self._csockets + + @property + def app_id(self) -> int: + return self._app_id + + class LhrProcess: - def __init__(self, host: Host, program: lhr.LhrProgram) -> None: + def __init__( + self, host: Host, program: ProgramInstance, context: ProgramContext + ) -> None: self._host = host self._name = f"{host._comp.name}_Lhr" self._logger: logging.Logger = LogManager.get_stack_logger( @@ -37,81 +56,16 @@ def __init__(self, host: Host, program: lhr.LhrProgram) -> None: self._program = program self._program_results: List[Dict[str, Any]] = [] - def setup(self) -> Generator[EventExpression, None, None]: - program = self._program - prog_meta = program.meta - - # Register the new program (called 'application' by QNodeOS) with QNodeOS. - self._host.send_qnos_msg( - bytes(InitNewAppMessage(max_qubits=prog_meta.max_qubits)) - ) - self._app_id = yield from self._host.receive_qnos_msg() - self._logger.debug(f"got app id from qnos: {self._app_id}") - - # Set up the connection with QNodeOS. - conn = QnosConnection( - host=self._host, - app_id=self._app_id, - app_name=prog_meta.name, - max_qubits=prog_meta.max_qubits, - compiler=self._host._compiler, - ) - - # Open the EPR sockets required by the program. - for i, remote_name in enumerate(prog_meta.epr_sockets): - remote_id = None - - # TODO: rewrite - nodes = self._host._global_env.get_nodes() - for id, info in nodes.items(): - if info.name == remote_name: - remote_id = id - - assert remote_id is not None - self._host.send_qnos_msg( - bytes(OpenEPRSocketMessage(self._app_id, i, remote_id)) - ) - - # Create classical sockets that can be used by the program SDK code. - classical_sockets: Dict[int, ClassicalSocket] = {} - for i, remote_name in enumerate(prog_meta.csockets): - remote_id = None - - # TODO: rewrite - nodes = self._host._global_env.get_nodes() - for id, info in nodes.items(): - if info.name == remote_name: - remote_id = id - - assert remote_id is not None - classical_sockets[remote_name] = ClassicalSocket( - self._host, prog_meta.name, remote_name - ) - - self._context = ProgramContext( - netqasm_connection=conn, - csockets=classical_sockets, - app_id=self._app_id, - ) + self._context = context self._memory: Dict[str, Any] = {} - def run(self, num_times: int) -> Generator[EventExpression, None, None]: + def run(self, num_times: int = 1) -> Generator[EventExpression, None, None]: for _ in range(num_times): - yield from self.setup() result = yield from self.execute_program() self._program_results.append(result) - self._host.send_qnos_msg(bytes(StopAppMessage(self._app_id))) - - def run_with_context( - self, context: ProgramContext, num_times: int - ) -> Generator[EventExpression, None, None]: - self._memory: Dict[str, Any] = {} - self._context = context - for _ in range(num_times): - result = yield from self.execute_program() - self._program_results.append(result) - self._host.send_qnos_msg(bytes(StopAppMessage(context.app_id))) + self._host.send_qnos_msg(bytes(StopAppMessage(self._context.app_id))) + return self._program_results @property def context(self) -> ProgramContext: @@ -128,11 +82,11 @@ def program(self) -> ProgramContext: def execute_program(self) -> Generator[EventExpression, None, Dict[str, Any]]: context = self._context memory = self._memory - program = self._program + program = self._program.program - csockets = list(context.csockets.values()) + csockets = list(self._context.csockets.values()) csck = csockets[0] if len(csockets) > 0 else None - conn = context.connection + conn = context.conn results: Dict[str, Any] = {} @@ -178,7 +132,7 @@ def execute_program(self) -> Generator[EventExpression, None, Dict[str, Any]]: self._logger.warning( f"instantiating subroutine with values {arg_values}" ) - subrt.instantiate(conn.app_id, arg_values) + subrt.instantiate(context.app_id, arg_values) yield from conn.commit_subroutine(subrt) @@ -237,7 +191,7 @@ class Host(ComponentProtocol): def __init__( self, comp: HostComponent, - global_env: GlobalEnvironment, + local_env: LocalEnvironment, qdevice_type: Optional[str] = "nv", ) -> None: """Qnos protocol constructor. @@ -248,7 +202,7 @@ def __init__( super().__init__(name=f"{comp.name}_protocol", comp=comp) self._comp = comp - self._global_env = global_env + self._local_env = local_env self.add_listener( "qnos", @@ -268,8 +222,13 @@ def __init__( else: raise ValueError - # Program that is currently being executed. - self._program: Optional[lhr.LhrProgram] = None + # Programs that need to be executed. + self._programs: Dict[int, ProgramInstance] = {} + self._program_counter: int = 0 + + self._connections: Dict[int, QnosConnection] = {} + + self._csockets: Dict[int, Dict[str, ClassicalSocket]] = {} # Number of times the current program still needs to be run. self._num_pending: int = 0 @@ -285,6 +244,10 @@ def compiler(self) -> Optional[Type[SubroutineTranspiler]]: def compiler(self, typ: Optional[Type[SubroutineTranspiler]]) -> None: self._compiler = typ + @property + def local_env(self) -> LocalEnvironment: + return self._local_env + def send_qnos_msg(self, msg: bytes) -> None: self._comp.qnos_out_port.tx_output(msg) @@ -298,31 +261,53 @@ def receive_peer_msg(self) -> Generator[EventExpression, None, str]: return (yield from self._receive_msg("peer", SIGNAL_HOST_HOST_MSG)) def run_lhr_program( - self, program: lhr.LhrProgram, num_times: int + self, program: ProgramInstance, context: ProgramContext ) -> Generator[EventExpression, None, None]: self._logger.warning(f"Creating LHR process for program:\n{program}") - process = LhrProcess(self, program) - result = yield from process.run(num_times) + process = LhrProcess(self, program, context) + result = yield from process.run() return result def run(self) -> Generator[EventExpression, None, None]: """Run this protocol. Automatically called by NetSquid during simulation.""" # Run a single program as many times as requested. - while self._num_pending > 0: - self._logger.info(f"num pending: {self._num_pending}") - self._num_pending -= 1 + programs = list(self._programs.items()) + if len(programs) == 0: + return - assert self._program is not None + app_id, program = programs[0] - yield from self.run_lhr_program(self._program, 1) + context = ProgramContext( + conn=self._connections[app_id], + csockets=self._csockets[app_id], + app_id=app_id, + ) - def enqueue_program(self, program: Program, num_times: int = 1): - """Queue a program to be run the given number of times. + result = yield from self.run_lhr_program(program, context) + self._program_results.append(result) - NOTE: At the moment, only a single program can be queued at a time.""" - self._program = program - self._num_pending = num_times + def init_new_program(self, program: ProgramInstance) -> int: + app_id = self._program_counter + self._program_counter += 1 + self._programs[app_id] = program + + conn = QnosConnection( + host=self, + app_id=app_id, + app_name=program.program.meta.name, + max_qubits=program.program.meta.max_qubits, + compiler=self._compiler, + ) + self._connections[app_id] = conn + + self._csockets[app_id] = {} + + return app_id + + def open_csocket(self, app_id: int, remote_name: str) -> None: + assert app_id in self._csockets + self._csockets[app_id][remote_name] = ClassicalSocket(self, remote_name) def get_results(self) -> List[Dict[str, Any]]: return self._program_results diff --git a/squidasm/qoala/sim/stack.py b/squidasm/qoala/sim/stack.py index a4c30eaf..adfcddd4 100644 --- a/squidasm/qoala/sim/stack.py +++ b/squidasm/qoala/sim/stack.py @@ -12,7 +12,7 @@ MagicLinkLayerProtocolWithSignaling, ) -from squidasm.qoala.runtime.environment import GlobalEnvironment +from squidasm.qoala.runtime.environment import GlobalEnvironment, LocalEnvironment from squidasm.qoala.sim.host import Host, HostComponent from squidasm.qoala.sim.qnos import Qnos, QnosComponent @@ -134,6 +134,7 @@ def __init__( self._node = ProcessingNode(name, qdevice, node_id) self._global_env = global_env + self._local_env = LocalEnvironment(global_env, global_env.get_node_id(name)) self._host: Optional[Host] = None self._qnos: Optional[Qnos] = None @@ -142,9 +143,39 @@ def __init__( # If `use_default_components` is False, these components must be manually # created and added to this NodeStack. if use_default_components: - self._host = Host(self.host_comp, global_env, qdevice_type) + self._host = Host(self.host_comp, self._local_env, qdevice_type) self._qnos = Qnos(self.qnos_comp, qdevice_type) + def install_environment(self) -> None: + for instance in self._local_env._programs: + app_id = self._host.init_new_program(instance) + self._qnos.handler.init_new_app(app_id) + + # Open the EPR sockets required by the program. + for i, remote_name in enumerate(instance.program.meta.epr_sockets): + remote_id = None + + # TODO: rewrite + nodes = self._global_env.get_nodes() + for id, info in nodes.items(): + if info.name == remote_name: + remote_id = id + + assert remote_id is not None + self._qnos.handler.open_epr_socket(app_id, i, remote_id) + + for i, remote_name in enumerate(instance.program.meta.csockets): + remote_id = None + + # TODO: rewrite + nodes = self._global_env.get_nodes() + for id, info in nodes.items(): + if info.name == remote_name: + remote_id = id + + assert remote_id is not None + self._host.open_csocket(app_id, remote_name) + def assign_ll_protocol(self, prot: MagicLinkLayerProtocolWithSignaling) -> None: """Set the link layer protocol to use for entanglement generation. diff --git a/tests/qoala/test_parse_lhr.py b/tests/qoala/test_parse_lhr.py index efacf2f2..025ad349 100644 --- a/tests/qoala/test_parse_lhr.py +++ b/tests/qoala/test_parse_lhr.py @@ -1,11 +1,12 @@ from squidasm.qoala.lang import lhr as lp -from squidasm.run.stack.config import ( +from squidasm.qoala.runtime.config import ( GenericQDeviceConfig, LinkConfig, StackConfig, StackNetworkConfig, ) -from squidasm.run.stack.run import run +from squidasm.qoala.runtime.program import ProgramInstance +from squidasm.qoala.runtime.run import run from squidasm.sim.stack.common import LogManager from squidasm.sim.stack.program import ProgramMeta @@ -59,8 +60,10 @@ def test_run(): qdevice_cfg=GenericQDeviceConfig.perfect_config(), ) + prog_instance = ProgramInstance(parsed_program, {}, 1, 0.0) + cfg = StackNetworkConfig(stacks=[sender_stack], links=[]) - result = run(cfg, programs={"client": parsed_program}) + result = run(cfg, programs={"client": prog_instance}) print(result) @@ -94,8 +97,13 @@ def test_run_two_nodes_classical(): qdevice_cfg=GenericQDeviceConfig.perfect_config(), ) + prog_server_instance = ProgramInstance(program_server, {}, 1, 0.0) + prog_client_instance = ProgramInstance(program_client, {}, 1, 0.0) + cfg = StackNetworkConfig(stacks=[client_stack, server_stack], links=[]) - result = run(cfg, programs={"client": program_client, "server": program_server}) + result = run( + cfg, programs={"client": prog_client_instance, "server": prog_server_instance} + ) print(result) @@ -181,14 +189,19 @@ def test_run_two_nodes_epr(): typ="perfect", ) + prog_server_instance = ProgramInstance(program_server, {}, 1, 0.0) + prog_client_instance = ProgramInstance(program_client, {}, 1, 0.0) + cfg = StackNetworkConfig(stacks=[client_stack, server_stack], links=[link]) - result = run(cfg, programs={"client": program_client, "server": program_server}) + result = run( + cfg, programs={"client": prog_client_instance, "server": prog_server_instance} + ) print(result) if __name__ == "__main__": LogManager.set_log_level("DEBUG") - # test_parse() - # test_run() - # test_run_two_nodes_classical() + test_parse() + test_run() + test_run_two_nodes_classical() test_run_two_nodes_epr() From 960bf603b81d255f35f9f68a9eb783e36e3a6f19 Mon Sep 17 00:00:00 2001 From: Bart van der Vecht Date: Fri, 17 Jun 2022 15:40:35 +0200 Subject: [PATCH 7/8] Rename test file --- tests/qoala/{test_parse_lhr.py => test_lhr.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/qoala/{test_parse_lhr.py => test_lhr.py} (100%) diff --git a/tests/qoala/test_parse_lhr.py b/tests/qoala/test_lhr.py similarity index 100% rename from tests/qoala/test_parse_lhr.py rename to tests/qoala/test_lhr.py From e3353c11a807bf452a1644260dd715c5d0da6e67 Mon Sep 17 00:00:00 2001 From: Bart van der Vecht Date: Wed, 27 Jul 2022 18:24:10 +0200 Subject: [PATCH 8/8] Fix tests --- examples/lir/bqc/bqc_5_5.py | 217 --------------- examples/lir/bqc/bqc_5_5_client.lhr | 41 +++ examples/lir/bqc/bqc_5_5_nv.py | 217 --------------- examples/lir/bqc/bqc_5_5_nv_raw_lhr.py | 119 +++------ .../bqc/{bqc_5_5.lhr => bqc_5_5_server.lhr} | 0 squidasm/qoala/lang/lhr.py | 1 + squidasm/qoala/runtime/program.py | 15 +- squidasm/qoala/runtime/run.py | 2 + squidasm/qoala/sim/host.py | 36 ++- squidasm/sim/stack/host.py | 246 +----------------- 10 files changed, 109 insertions(+), 785 deletions(-) delete mode 100644 examples/lir/bqc/bqc_5_5.py create mode 100644 examples/lir/bqc/bqc_5_5_client.lhr delete mode 100644 examples/lir/bqc/bqc_5_5_nv.py rename examples/lir/bqc/{bqc_5_5.lhr => bqc_5_5_server.lhr} (100%) diff --git a/examples/lir/bqc/bqc_5_5.py b/examples/lir/bqc/bqc_5_5.py deleted file mode 100644 index 93c0499a..00000000 --- a/examples/lir/bqc/bqc_5_5.py +++ /dev/null @@ -1,217 +0,0 @@ -from __future__ import annotations - -import math -from typing import List - -from netqasm.lang.operand import Template -from netqasm.sdk.qubit import Qubit - -from squidasm.qoala.lang import lhr as lp -from squidasm.run.stack.config import ( - GenericQDeviceConfig, - LinkConfig, - StackConfig, - StackNetworkConfig, -) -from squidasm.run.stack.run import run -from squidasm.sim.stack.common import LogManager -from squidasm.sim.stack.program import ProgramContext, ProgramMeta - - -class ClientProgram(lp.SdkProgram): - PEER = "server" - - def __init__( - self, - alpha: float, - beta: float, - theta1: float, - r1: int, - ): - self._alpha = alpha - self._beta = beta - self._theta1 = theta1 - self._r1 = r1 - - @property - def meta(self) -> ProgramMeta: - return ProgramMeta( - name="client_program", - parameters={ - "alpha": self._alpha, - "beta": self._beta, - "theta1": self._theta1, - "r1": self._r1, - }, - csockets=[self.PEER], - epr_sockets=[self.PEER], - max_qubits=2, - ) - - def compile(self, context: ProgramContext) -> lp.LhrProgram: - conn = context.connection - epr_socket = context.epr_sockets[self.PEER] - - epr = epr_socket.create_keep()[0] - - epr.rot_Z(angle=self._theta1) - epr.H() - p1 = epr.measure(store_array=True) - - subrt = conn.compile() - subroutines = {"subrt": subrt} - - lhr_subrt = lp.LhrSubroutine(subrt, return_map={"p1": lp.LhrSharedMemLoc("M0")}) - instrs: List[lp.ClassicalLhrOp] = [] - instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt)) - instrs.append(lp.AssignCValueOp("p1", p1)) - - delta1 = self._alpha - self._theta1 + self._r1 * math.pi - delta1_discrete = delta1 / (math.pi / 16) - - instrs.append(lp.AssignCValueOp("delta1", delta1_discrete)) - instrs.append(lp.MultiplyConstantCValueOp("p1", "p1", 16)) - instrs.append(lp.AddCValueOp("delta1", "delta1", "p1")) - instrs.append(lp.SendCMsgOp("delta1")) - - instrs.append(lp.ReceiveCMsgOp("m1")) - - beta = math.pow(-1, self._r1) * self._beta - delta2_discrete = beta / (math.pi / 16) - instrs.append(lp.AssignCValueOp("delta2", delta2_discrete)) - instrs.append( - lp.BitConditionalMultiplyConstantCValueOp( - result="delta2", value0="delta2", value1=-1, cond="m1" - ) - ) - instrs.append(lp.SendCMsgOp("delta2")) - - instrs.append(lp.ReturnResultOp("p1")) - - return lp.LhrProgram(instrs, subroutines, meta=self.meta) - - -class ServerProgram(lp.SdkProgram): - PEER = "client" - - def __init__(self) -> None: - pass - - @property - def meta(self) -> ProgramMeta: - return ProgramMeta( - name="server_program", - parameters={}, - csockets=[self.PEER], - epr_sockets=[self.PEER], - max_qubits=2, - ) - - def compile(self, context: ProgramContext) -> lp.LhrProgram: - conn = context.connection - epr_socket = context.epr_sockets[self.PEER] - - # Create EPR Pair - epr = epr_socket.recv_keep()[0] - q = Qubit(conn) - q.H() - - epr.cphase(q) - - subrt1 = conn.compile() - subroutines = {"subrt1": subrt1} - - instrs: List[lp.ClassicalLhrOp] = [] - lhr_subrt1 = lp.LhrSubroutine(subrt1, return_map={}) - instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt1)) - - instrs.append(lp.ReceiveCMsgOp("delta1")) - - epr.rot_Z(n=Template("delta1"), d=4) - epr.H() - m1 = epr.measure(store_array=False) - - subrt2 = conn.compile() - subroutines["subrt2"] = subrt2 - - lhr_subrt2 = lp.LhrSubroutine( - subrt2, return_map={"m1": lp.LhrSharedMemLoc("M0")} - ) - instrs.append(lp.RunSubroutineOp(lp.LhrVector(["delta1"]), lhr_subrt2)) - instrs.append(lp.AssignCValueOp("m1", m1)) - instrs.append(lp.SendCMsgOp("m1")) - - instrs.append(lp.ReceiveCMsgOp("delta2")) - - q.rot_Z(n=Template("delta2"), d=4) - q.H() - m2 = q.measure(store_array=False) - subrt3 = conn.compile() - subroutines["subrt3"] = subrt3 - - lhr_subrt3 = lp.LhrSubroutine( - subrt3, return_map={"m2": lp.LhrSharedMemLoc("M0")} - ) - instrs.append(lp.RunSubroutineOp(lp.LhrVector(["delta2"]), lhr_subrt3)) - instrs.append(lp.AssignCValueOp("m2", m2)) - instrs.append(lp.ReturnResultOp("m1")) - instrs.append(lp.ReturnResultOp("m2")) - - return lp.LhrProgram(instrs, subroutines, meta=self.meta) - - -PI = math.pi -PI_OVER_2 = math.pi / 2 - - -def computation_round( - cfg: StackNetworkConfig, - num_times: int = 1, - alpha: float = 0.0, - beta: float = 0.0, - theta1: float = 0.0, -) -> None: - client_program = ClientProgram( - alpha=alpha, - beta=beta, - theta1=theta1, - r1=0, - ) - server_program = ServerProgram() - - _, server_results = run( - cfg, {"client": client_program, "server": server_program}, num_times=num_times - ) - - m2s = [result["m2"] for result in server_results] - num_zeros = len([m for m in m2s if m == 0]) - frac0 = round(num_zeros / num_times, 2) - frac1 = 1 - frac0 - print(f"dist (0, 1) = ({frac0}, {frac1})") - - -if __name__ == "__main__": - num_times = 1 - - LogManager.set_log_level("WARNING") - LogManager.log_to_file("dump.log") - - sender_stack = StackConfig( - name="client", - qdevice_typ="generic", - qdevice_cfg=GenericQDeviceConfig.perfect_config(), - ) - receiver_stack = StackConfig( - name="server", - qdevice_typ="generic", - qdevice_cfg=GenericQDeviceConfig.perfect_config(), - ) - link = LinkConfig( - stack1="client", - stack2="server", - typ="perfect", - ) - - cfg = StackNetworkConfig(stacks=[sender_stack, receiver_stack], links=[link]) - - computation_round(cfg, num_times, alpha=PI_OVER_2, beta=PI_OVER_2) diff --git a/examples/lir/bqc/bqc_5_5_client.lhr b/examples/lir/bqc/bqc_5_5_client.lhr new file mode 100644 index 00000000..0cbaf558 --- /dev/null +++ b/examples/lir/bqc/bqc_5_5_client.lhr @@ -0,0 +1,41 @@ +delta1_discrete = assign_cval() : 1 +delta2_discrete = assign_cval() : 1 + +run_subroutine(vec) : + return M0 -> p1 + NETQASM_START + set C0 0 + set C1 1 + set C2 2 + set C10 10 + set C11 20 + array C10 @0 + array C1 @1 + store C0 @1[C0] + array C11 @2 + store C0 @2[C0] + store C1 @2[C1] + set R5 0 + set R6 0 + set R7 1 + set R8 0 + create_epr C1 C0 C1 C2 C0 + wait_all @0[C0:C11] + set Q0 0 + rot_z Q0 {theta_discrete} 4 + rot_y Q0 8 4 + rot_x Q0 16 4 + meas Q0 M0 + qfree Q0 + ret_reg M0 + NETQASM_END + +p1 = mult_const(p1, 16) +delta1 = add_cval_c(delta1_discrete, p1) +send_cmsg(delta1) +m1 = recv_cmsg() + +delta2 = bcond_mult_const(delta2_discrete, -1, m1) +send_cmsg(delta2) + +return_result(p1) diff --git a/examples/lir/bqc/bqc_5_5_nv.py b/examples/lir/bqc/bqc_5_5_nv.py deleted file mode 100644 index 9e1c47ef..00000000 --- a/examples/lir/bqc/bqc_5_5_nv.py +++ /dev/null @@ -1,217 +0,0 @@ -from __future__ import annotations - -import math -from typing import List - -from netqasm.lang.operand import Template -from netqasm.sdk.qubit import Qubit - -from squidasm.qoala.lang import lhr as lp -from squidasm.run.stack.config import ( - LinkConfig, - NVQDeviceConfig, - StackConfig, - StackNetworkConfig, -) -from squidasm.run.stack.run import run -from squidasm.sim.stack.common import LogManager -from squidasm.sim.stack.program import ProgramContext, ProgramMeta - - -class ClientProgram(lp.SdkProgram): - PEER = "server" - - def __init__( - self, - alpha: float, - beta: float, - theta1: float, - r1: int, - ): - self._alpha = alpha - self._beta = beta - self._theta1 = theta1 - self._r1 = r1 - - @property - def meta(self) -> ProgramMeta: - return ProgramMeta( - name="client_program", - parameters={ - "alpha": self._alpha, - "beta": self._beta, - "theta1": self._theta1, - "r1": self._r1, - }, - csockets=[self.PEER], - epr_sockets=[self.PEER], - max_qubits=2, - ) - - def compile(self, context: ProgramContext) -> lp.LhrProgram: - conn = context.connection - epr_socket = context.epr_sockets[self.PEER] - - epr = epr_socket.create_keep()[0] - - epr.rot_Z(angle=self._theta1) - epr.H() - p1 = epr.measure(store_array=True) - - subrt = conn.compile() - subroutines = {"subrt": subrt} - - lhr_subrt = lp.LhrSubroutine(subrt, return_map={"p1": lp.LhrSharedMemLoc("M0")}) - instrs: List[lp.ClassicalLhrOp] = [] - instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt)) - instrs.append(lp.AssignCValueOp("p1", p1)) - - delta1 = self._alpha - self._theta1 + self._r1 * math.pi - delta1_discrete = delta1 / (math.pi / 16) - - instrs.append(lp.AssignCValueOp("delta1", delta1_discrete)) - instrs.append(lp.MultiplyConstantCValueOp("p1", "p1", 16)) - instrs.append(lp.AddCValueOp("delta1", "delta1", "p1")) - instrs.append(lp.SendCMsgOp("delta1")) - - instrs.append(lp.ReceiveCMsgOp("m1")) - - beta = math.pow(-1, self._r1) * self._beta - delta2_discrete = beta / (math.pi / 16) - instrs.append(lp.AssignCValueOp("delta2", delta2_discrete)) - instrs.append( - lp.BitConditionalMultiplyConstantCValueOp( - result="delta2", value0="delta2", value1=-1, cond="m1" - ) - ) - instrs.append(lp.SendCMsgOp("delta2")) - - instrs.append(lp.ReturnResultOp("p1")) - - return lp.LhrProgram(instrs, subroutines, meta=self.meta) - - -class ServerProgram(lp.SdkProgram): - PEER = "client" - - def __init__(self) -> None: - pass - - @property - def meta(self) -> ProgramMeta: - return ProgramMeta( - name="server_program", - parameters={}, - csockets=[self.PEER], - epr_sockets=[self.PEER], - max_qubits=2, - ) - - def compile(self, context: ProgramContext) -> lp.LhrProgram: - conn = context.connection - epr_socket = context.epr_sockets[self.PEER] - - # Create EPR Pair - epr = epr_socket.recv_keep()[0] - q = Qubit(conn) - q.H() - - epr.cphase(q) - - subrt1 = conn.compile() - subroutines = {"subrt1": subrt1} - - instrs: List[lp.ClassicalLhrOp] = [] - lhr_subrt1 = lp.LhrSubroutine(subrt1, return_map={}) - instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt1)) - - instrs.append(lp.ReceiveCMsgOp("delta1")) - - epr.rot_Z(n=Template("delta1"), d=4) - epr.H() - m1 = epr.measure(store_array=False) - - subrt2 = conn.compile() - subroutines["subrt2"] = subrt2 - - lhr_subrt2 = lp.LhrSubroutine( - subrt2, return_map={"m1": lp.LhrSharedMemLoc("M0")} - ) - instrs.append(lp.RunSubroutineOp(lp.LhrVector(["delta1"]), lhr_subrt2)) - instrs.append(lp.AssignCValueOp("m1", m1)) - instrs.append(lp.SendCMsgOp("m1")) - - instrs.append(lp.ReceiveCMsgOp("delta2")) - - q.rot_Z(n=Template("delta2"), d=4) - q.H() - m2 = q.measure(store_array=False) - subrt3 = conn.compile() - subroutines["subrt3"] = subrt3 - - lhr_subrt3 = lp.LhrSubroutine( - subrt3, return_map={"m2": lp.LhrSharedMemLoc("M0")} - ) - instrs.append(lp.RunSubroutineOp(lp.LhrVector(["delta2"]), lhr_subrt3)) - instrs.append(lp.AssignCValueOp("m2", m2)) - instrs.append(lp.ReturnResultOp("m1")) - instrs.append(lp.ReturnResultOp("m2")) - - return lp.LhrProgram(instrs, subroutines, meta=self.meta) - - -PI = math.pi -PI_OVER_2 = math.pi / 2 - - -def computation_round( - cfg: StackNetworkConfig, - num_times: int = 1, - alpha: float = 0.0, - beta: float = 0.0, - theta1: float = 0.0, -) -> None: - client_program = ClientProgram( - alpha=alpha, - beta=beta, - theta1=theta1, - r1=0, - ) - server_program = ServerProgram() - - _, server_results = run( - cfg, {"client": client_program, "server": server_program}, num_times=num_times - ) - - m2s = [result["m2"] for result in server_results] - num_zeros = len([m for m in m2s if m == 0]) - frac0 = round(num_zeros / num_times, 2) - frac1 = 1 - frac0 - print(f"dist (0, 1) = ({frac0}, {frac1})") - - -if __name__ == "__main__": - num_times = 1 - - LogManager.set_log_level("WARNING") - LogManager.log_to_file("dump.log") - - sender_stack = StackConfig( - name="client", - qdevice_typ="nv", - qdevice_cfg=NVQDeviceConfig.perfect_config(), - ) - receiver_stack = StackConfig( - name="server", - qdevice_typ="nv", - qdevice_cfg=NVQDeviceConfig.perfect_config(), - ) - link = LinkConfig( - stack1="client", - stack2="server", - typ="perfect", - ) - - cfg = StackNetworkConfig(stacks=[sender_stack, receiver_stack], links=[link]) - - computation_round(cfg, num_times, alpha=PI_OVER_2, beta=PI_OVER_2) diff --git a/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py b/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py index aa1e2c18..49c52367 100644 --- a/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py +++ b/examples/lir/bqc/bqc_5_5_nv_raw_lhr.py @@ -4,90 +4,19 @@ import os from typing import List +from netqasm.sdk.epr_socket import EPRSocket + from squidasm.qoala.lang import lhr as lp -from squidasm.run.stack.config import ( +from squidasm.qoala.runtime.config import ( LinkConfig, NVQDeviceConfig, StackConfig, StackNetworkConfig, ) -from squidasm.run.stack.run import run -from squidasm.sim.stack.common import LogManager -from squidasm.sim.stack.program import ProgramContext, ProgramMeta - - -class ClientProgram(lp.SdkProgram): - PEER = "server" - - def __init__( - self, - alpha: float, - beta: float, - theta1: float, - r1: int, - ): - self._alpha = alpha - self._beta = beta - self._theta1 = theta1 - self._r1 = r1 - - @property - def meta(self) -> ProgramMeta: - return ProgramMeta( - name="client_program", - parameters={ - "alpha": self._alpha, - "beta": self._beta, - "theta1": self._theta1, - "r1": self._r1, - }, - csockets=[self.PEER], - epr_sockets=[self.PEER], - max_qubits=2, - ) - - def compile(self, context: ProgramContext) -> lp.LhrProgram: - conn = context.connection - epr_socket = context.epr_sockets[self.PEER] - - epr = epr_socket.create_keep()[0] - - epr.rot_Z(angle=self._theta1) - epr.H() - _ = epr.measure(store_array=True) # p1 - - subrt = conn.compile() - subroutines = {"subrt": subrt} - - lhr_subrt = lp.LhrSubroutine(subrt, return_map={"p1": lp.LhrSharedMemLoc("M0")}) - instrs: List[lp.ClassicalLhrOp] = [] - instrs.append(lp.RunSubroutineOp(lp.LhrVector([]), lhr_subrt)) - # instrs.append(lp.AssignCValueOp("p1", p1)) - - delta1 = self._alpha - self._theta1 + self._r1 * math.pi - delta1_discrete = delta1 / (math.pi / 16) - - instrs.append(lp.AssignCValueOp("delta1", delta1_discrete)) - instrs.append(lp.MultiplyConstantCValueOp("p1", "p1", 16)) - instrs.append(lp.AddCValueOp("delta1", "delta1", "p1")) - instrs.append(lp.SendCMsgOp("delta1")) - - instrs.append(lp.ReceiveCMsgOp("m1")) - - beta = math.pow(-1, self._r1) * self._beta - delta2_discrete = beta / (math.pi / 16) - instrs.append(lp.AssignCValueOp("delta2", delta2_discrete)) - instrs.append( - lp.BitConditionalMultiplyConstantCValueOp( - result="delta2", value0="delta2", value1=-1, cond="m1" - ) - ) - instrs.append(lp.SendCMsgOp("delta2")) - - instrs.append(lp.ReturnResultOp("p1")) - - return lp.LhrProgram(instrs, subroutines, meta=self.meta) - +from squidasm.qoala.runtime.program import ProgramContext, ProgramInstance, SdkProgram +from squidasm.qoala.runtime.run import run +from squidasm.qoala.sim.common import LogManager +from squidasm.qoala.sim.netstack import EprSocket PI = math.pi PI_OVER_2 = math.pi / 2 @@ -100,18 +29,25 @@ def computation_round( beta: float = 0.0, theta1: float = 0.0, ) -> None: - client_program = ClientProgram( - alpha=alpha, - beta=beta, - theta1=theta1, - r1=0, + # TODO: use alpha, beta to calculate delta1_discrete etc. + + client_lhr_file = os.path.join(os.path.dirname(__file__), "bqc_5_5_client.lhr") + with open(client_lhr_file) as file: + client_lhr_text = file.read() + client_program = lp.LhrParser(client_lhr_text).parse() + client_program.meta = lp.ProgramMeta( + name="client_program", + parameters=["alpha", "beta", "theta1", "r1"], + csockets=["server"], + epr_sockets=["server"], + max_qubits=2, ) - # server_program = ServerProgram() - lhr_file = os.path.join(os.path.dirname(__file__), "bqc_5_5.lhr") - with open(lhr_file) as file: - lhr_text = file.read() - server_program = lp.LhrParser(lhr_text).parse() - server_program.meta = ProgramMeta( + + server_lhr_file = os.path.join(os.path.dirname(__file__), "bqc_5_5_server.lhr") + with open(server_lhr_file) as file: + server_lhr_text = file.read() + server_program = lp.LhrParser(server_lhr_text).parse() + server_program.meta = lp.ProgramMeta( name="server_program", parameters={}, csockets=["client"], @@ -119,8 +55,11 @@ def computation_round( max_qubits=2, ) + client_instance = ProgramInstance(client_program, {"theta_discrete": 0}, 1, 0) + server_instance = ProgramInstance(server_program, {}, 1, 0) + _, server_results = run( - cfg, {"client": client_program, "server": server_program}, num_times=num_times + cfg, {"client": client_instance, "server": server_instance}, num_times=num_times ) m2s = [result["m2"] for result in server_results] diff --git a/examples/lir/bqc/bqc_5_5.lhr b/examples/lir/bqc/bqc_5_5_server.lhr similarity index 100% rename from examples/lir/bqc/bqc_5_5.lhr rename to examples/lir/bqc/bqc_5_5_server.lhr diff --git a/squidasm/qoala/lang/lhr.py b/squidasm/qoala/lang/lhr.py index 90e9479b..01304690 100644 --- a/squidasm/qoala/lang/lhr.py +++ b/squidasm/qoala/lang/lhr.py @@ -1,3 +1,4 @@ +import abc from dataclasses import dataclass from enum import Enum, auto from typing import Any, Dict, List, Optional, Union diff --git a/squidasm/qoala/runtime/program.py b/squidasm/qoala/runtime/program.py index 0a980dcf..22a520ef 100644 --- a/squidasm/qoala/runtime/program.py +++ b/squidasm/qoala/runtime/program.py @@ -1,12 +1,23 @@ +import abc from dataclasses import dataclass -from typing import Any, Dict +from typing import Any, Dict, Union from squidasm.qoala.lang.lhr import LhrProgram +class ProgramContext(abc.ABC): + pass + + +class SdkProgram(abc.ABC): + @abc.abstractmethod + def compile(self, context: ProgramContext) -> LhrProgram: + raise NotImplementedError + + @dataclass class ProgramInstance: - program: LhrProgram + program: Union[LhrProgram, SdkProgram] inputs: Dict[str, Any] num_iterations: int deadline: float diff --git a/squidasm/qoala/runtime/run.py b/squidasm/qoala/runtime/run.py index 99949a12..62c59093 100644 --- a/squidasm/qoala/runtime/run.py +++ b/squidasm/qoala/runtime/run.py @@ -184,6 +184,8 @@ def run( for name, program in programs.items(): network.stacks[name]._local_env.register_program(program) + + for name in programs.keys(): network.stacks[name].install_environment() results = _run(network) diff --git a/squidasm/qoala/sim/host.py b/squidasm/qoala/sim/host.py index fa7b0f43..63e01d67 100644 --- a/squidasm/qoala/sim/host.py +++ b/squidasm/qoala/sim/host.py @@ -13,14 +13,14 @@ from pydynaa import EventExpression from squidasm.qoala.lang import lhr from squidasm.qoala.runtime.environment import LocalEnvironment -from squidasm.qoala.runtime.program import ProgramInstance +from squidasm.qoala.runtime.program import ProgramContext, ProgramInstance, SdkProgram from squidasm.qoala.sim.common import ComponentProtocol, LogManager, PortListener from squidasm.qoala.sim.connection import QnosConnection from squidasm.qoala.sim.csocket import ClassicalSocket from squidasm.qoala.sim.signals import SIGNAL_HAND_HOST_MSG, SIGNAL_HOST_HOST_MSG -class ProgramContext: +class HostProgramContext(ProgramContext): def __init__( self, conn: QnosConnection, @@ -46,7 +46,7 @@ def app_id(self) -> int: class LhrProcess: def __init__( - self, host: Host, program: ProgramInstance, context: ProgramContext + self, host: Host, program: ProgramInstance, context: HostProgramContext ) -> None: self._host = host self._name = f"{host._comp.name}_Lhr" @@ -68,7 +68,7 @@ def run(self, num_times: int = 1) -> Generator[EventExpression, None, None]: return self._program_results @property - def context(self) -> ProgramContext: + def context(self) -> HostProgramContext: return self._context @property @@ -76,7 +76,7 @@ def memory(self) -> Dict[str, Any]: return self._memory @property - def program(self) -> ProgramContext: + def program(self) -> ProgramInstance: return self._program def execute_program(self) -> Generator[EventExpression, None, Dict[str, Any]]: @@ -88,6 +88,9 @@ def execute_program(self) -> Generator[EventExpression, None, Dict[str, Any]]: csck = csockets[0] if len(csockets) > 0 else None conn = context.conn + for name, value in self._program.inputs.items(): + memory[name] = value + results: Dict[str, Any] = {} for instr in program.instructions: @@ -102,19 +105,21 @@ def execute_program(self) -> Generator[EventExpression, None, Dict[str, Any]]: memory[instr.results[0]] = msg self._logger.info(f"received msg {msg}") elif isinstance(instr, lhr.AddCValueOp): - arg0 = memory[instr.arguments[0]] - arg1 = memory[instr.arguments[1]] + arg0 = int(memory[instr.arguments[0]]) + arg1 = int(memory[instr.arguments[1]]) memory[instr.results[0]] = arg0 + arg1 elif isinstance(instr, lhr.MultiplyConstantCValueOp): arg0 = memory[instr.arguments[0]] - arg1 = instr.arguments[1] + arg1 = int(instr.arguments[1]) memory[instr.results[0]] = arg0 * arg1 elif isinstance(instr, lhr.BitConditionalMultiplyConstantCValueOp): arg0 = memory[instr.arguments[0]] - arg1 = instr.arguments[1] + arg1 = int(instr.arguments[1]) cond = memory[instr.arguments[2]] if cond == 1: memory[instr.results[0]] = arg0 * arg1 + else: + memory[instr.results[0]] = arg0 elif isinstance(instr, lhr.AssignCValueOp): value = instr.attributes[0] # if isinstance(value, str) and value.startswith("RegFuture__"): @@ -261,7 +266,7 @@ def receive_peer_msg(self) -> Generator[EventExpression, None, str]: return (yield from self._receive_msg("peer", SIGNAL_HOST_HOST_MSG)) def run_lhr_program( - self, program: ProgramInstance, context: ProgramContext + self, program: ProgramInstance, context: HostProgramContext ) -> Generator[EventExpression, None, None]: self._logger.warning(f"Creating LHR process for program:\n{program}") process = LhrProcess(self, program, context) @@ -276,16 +281,19 @@ def run(self) -> Generator[EventExpression, None, None]: if len(programs) == 0: return - app_id, program = programs[0] + app_id, prog_instance = programs[0] - context = ProgramContext( + context = HostProgramContext( conn=self._connections[app_id], csockets=self._csockets[app_id], app_id=app_id, ) - result = yield from self.run_lhr_program(program, context) - self._program_results.append(result) + if isinstance(prog_instance.program, SdkProgram): + prog_instance.program = prog_instance.program.compile(context) + assert isinstance(prog_instance.program, lhr.LhrProgram) + + yield from self.run_lhr_program(prog_instance, context) def init_new_program(self, program: ProgramInstance) -> int: app_id = self._program_counter diff --git a/squidasm/sim/stack/host.py b/squidasm/sim/stack/host.py index 48ae8832..d71b5325 100644 --- a/squidasm/sim/stack/host.py +++ b/squidasm/sim/stack/host.py @@ -16,7 +16,6 @@ from netsquid.nodes import Node from pydynaa import EventExpression -from squidasm.qoala.lang import lhr from squidasm.sim.stack.common import ComponentProtocol, LogManager, PortListener from squidasm.sim.stack.connection import QnosConnection from squidasm.sim.stack.context import NetSquidContext @@ -25,177 +24,6 @@ from squidasm.sim.stack.signals import SIGNAL_HAND_HOST_MSG, SIGNAL_HOST_HOST_MSG -class LhrProcess: - def __init__(self, host: Host, program: lhr.LhrProgram) -> None: - self._host = host - self._name = f"{host._comp.name}_Lhr" - self._logger: logging.Logger = LogManager.get_stack_logger( - f"{self.__class__.__name__}({self._name})" - ) - self._program = program - self._program_results: List[Dict[str, Any]] = [] - - def setup(self) -> Generator[EventExpression, None, None]: - program = self._program - prog_meta = program.meta - - # Register the new program (called 'application' by QNodeOS) with QNodeOS. - self._host.send_qnos_msg( - bytes(InitNewAppMessage(max_qubits=prog_meta.max_qubits)) - ) - self._app_id = yield from self._host.receive_qnos_msg() - self._logger.debug(f"got app id from qnos: {self._app_id}") - - # Set up the connection with QNodeOS. - conn = QnosConnection( - host=self._host, - app_id=self._app_id, - app_name=prog_meta.name, - max_qubits=prog_meta.max_qubits, - compiler=self._host._compiler, - ) - - # Create EPR sockets that can be used by the program SDK code. - epr_sockets: Dict[int, EPRSocket] = {} - for i, remote_name in enumerate(prog_meta.epr_sockets): - remote_id = None - nodes = NetSquidContext.get_nodes() - for id, name in nodes.items(): - if name == remote_name: - remote_id = id - assert remote_id is not None - self._host.send_qnos_msg( - bytes(OpenEPRSocketMessage(self._app_id, i, remote_id)) - ) - epr_sockets[remote_name] = EPRSocket(remote_name, i) - epr_sockets[remote_name].conn = conn - - # Create classical sockets that can be used by the program SDK code. - classical_sockets: Dict[int, ClassicalSocket] = {} - for i, remote_name in enumerate(prog_meta.csockets): - remote_id = None - nodes = NetSquidContext.get_nodes() - for id, name in nodes.items(): - if name == remote_name: - remote_id = id - assert remote_id is not None - classical_sockets[remote_name] = ClassicalSocket( - self._host, prog_meta.name, remote_name - ) - - self._context = ProgramContext( - netqasm_connection=conn, - csockets=classical_sockets, - epr_sockets=epr_sockets, - app_id=self._app_id, - ) - - self._memory: Dict[str, Any] = {} - - def run(self, num_times: int) -> Generator[EventExpression, None, None]: - for _ in range(num_times): - yield from self.setup() - result = yield from self.execute_program() - self._program_results.append(result) - self._host.send_qnos_msg(bytes(StopAppMessage(self._app_id))) - - def run_with_context( - self, context: ProgramContext, num_times: int - ) -> Generator[EventExpression, None, None]: - self._memory: Dict[str, Any] = {} - self._context = context - for _ in range(num_times): - result = yield from self.execute_program() - self._program_results.append(result) - self._host.send_qnos_msg(bytes(StopAppMessage(context.app_id))) - - @property - def context(self) -> ProgramContext: - return self._context - - @property - def memory(self) -> Dict[str, Any]: - return self._memory - - @property - def program(self) -> ProgramContext: - return self._program - - def execute_program(self) -> Generator[EventExpression, None, Dict[str, Any]]: - context = self._context - memory = self._memory - program = self._program - - csockets = list(context.csockets.values()) - csck = csockets[0] if len(csockets) > 0 else None - conn = context.connection - - results: Dict[str, Any] = {} - - for instr in program.instructions: - self._logger.info(f"Interpreting LHR instruction {instr}") - if isinstance(instr, lhr.SendCMsgOp): - value = memory[instr.arguments[0]] - self._logger.info(f"sending msg {value}") - csck.send(value) - elif isinstance(instr, lhr.ReceiveCMsgOp): - msg = yield from csck.recv() - msg = int(msg) - memory[instr.results[0]] = msg - self._logger.info(f"received msg {msg}") - elif isinstance(instr, lhr.AddCValueOp): - arg0 = memory[instr.arguments[0]] - arg1 = memory[instr.arguments[1]] - memory[instr.results[0]] = arg0 + arg1 - elif isinstance(instr, lhr.MultiplyConstantCValueOp): - arg0 = memory[instr.arguments[0]] - arg1 = instr.arguments[1] - memory[instr.results[0]] = arg0 * arg1 - elif isinstance(instr, lhr.BitConditionalMultiplyConstantCValueOp): - arg0 = memory[instr.arguments[0]] - arg1 = instr.arguments[1] - cond = memory[instr.arguments[2]] - if cond == 1: - memory[instr.results[0]] = arg0 * arg1 - elif isinstance(instr, lhr.AssignCValueOp): - value = instr.attributes[0] - # if isinstance(value, str) and value.startswith("RegFuture__"): - # reg_str = value[len("RegFuture__") :] - memory[instr.results[0]] = instr.attributes[0] - elif isinstance(instr, lhr.RunSubroutineOp): - arg_vec: lhr.LhrVector = instr.arguments[0] - args = arg_vec.values - lhr_subrt: lhr.LhrSubroutine = instr.attributes[0] - subrt = lhr_subrt.subroutine - self._logger.info(f"executing subroutine {subrt}") - - arg_values = {arg: memory[arg] for arg in args} - - self._logger.warning( - f"instantiating subroutine with values {arg_values}" - ) - subrt.instantiate(conn.app_id, arg_values) - - yield from conn.commit_subroutine(subrt) - - for key, mem_loc in lhr_subrt.return_map.items(): - try: - reg: Register = parse_register(mem_loc.loc) - value = conn.shared_memory.get_register(reg) - self._logger.debug( - f"writing shared memory value {value} from location " - f"{mem_loc} to variable {key}" - ) - memory[key] = value - except NetQASMSyntaxError: - pass - elif isinstance(instr, lhr.ReturnResultOp): - value = instr.arguments[0] - results[value] = int(memory[value]) - - return results - - class HostComponent(Component): """NetSquid compmonent representing a Host. @@ -345,73 +173,6 @@ def run_sdk_program( # Tell QNodeOS the program has finished. self.send_qnos_msg(bytes(StopAppMessage(app_id))) - def run_lhr_sdk_program( - self, - program: lhr.LhrProgram, - ) -> Generator[EventExpression, None, None]: - prog_meta = program.meta - - # Register the new program (called 'application' by QNodeOS) with QNodeOS. - self.send_qnos_msg(bytes(InitNewAppMessage(max_qubits=prog_meta.max_qubits))) - app_id = yield from self.receive_qnos_msg() - self._logger.debug(f"got app id from qnos: {app_id}") - - # Set up the Connection object to be used by the program SDK code. - conn = QnosConnection( - self, - app_id, - prog_meta.name, - max_qubits=prog_meta.max_qubits, - compiler=self._compiler, - ) - - # Create EPR sockets that can be used by the program SDK code. - epr_sockets: Dict[int, EPRSocket] = {} - for i, remote_name in enumerate(prog_meta.epr_sockets): - remote_id = None - nodes = NetSquidContext.get_nodes() - for id, name in nodes.items(): - if name == remote_name: - remote_id = id - assert remote_id is not None - self.send_qnos_msg(bytes(OpenEPRSocketMessage(app_id, i, remote_id))) - epr_sockets[remote_name] = EPRSocket(remote_name, i) - epr_sockets[remote_name].conn = conn - - # Create classical sockets that can be used by the program SDK code. - classical_sockets: Dict[int, ClassicalSocket] = {} - for i, remote_name in enumerate(prog_meta.csockets): - remote_id = None - nodes = NetSquidContext.get_nodes() - for id, name in nodes.items(): - if name == remote_name: - remote_id = id - assert remote_id is not None - classical_sockets[remote_name] = ClassicalSocket( - self, prog_meta.name, remote_name - ) - - context = ProgramContext( - netqasm_connection=conn, - csockets=classical_sockets, - epr_sockets=epr_sockets, - app_id=app_id, - ) - - lhr_program = program.compile(context) - self._logger.warning(f"Runnign compiled SDK program:\n{lhr_program}") - process = LhrProcess(self, lhr_program) - yield from process.run_with_context(context, 1) - self._program_results = process._program_results - - def run_lhr_program( - self, program: lhr.LhrProgram, num_times: int - ) -> Generator[EventExpression, None, None]: - self._logger.warning(f"Creating LHR process for program:\n{program}") - process = LhrProcess(self, program) - result = yield from process.run(num_times) - return result - def run(self) -> Generator[EventExpression, None, None]: """Run this protocol. Automatically called by NetSquid during simulation.""" @@ -422,12 +183,7 @@ def run(self) -> Generator[EventExpression, None, None]: assert self._program is not None - if isinstance(self._program, lhr.LhrProgram): - yield from self.run_lhr_program(self._program, 1) - elif isinstance(self._program, lhr.SdkProgram): - yield from self.run_lhr_sdk_program(self._program) - else: - self.run_sdk_program(self._program) + yield from self.run_sdk_program(self._program) def enqueue_program(self, program: Program, num_times: int = 1): """Queue a program to be run the given number of times.