From 95f95c78e85a1a0da5a0fb5bfec4dd68af4ddf1b Mon Sep 17 00:00:00 2001 From: Abdul Samad Khan Date: Mon, 18 May 2026 20:21:05 +0500 Subject: [PATCH] feat: implement O(1) SU(2) algebraic transpiler for NV hardware flavor --- netqasm/sdk/algebraic_transpiler.py | 106 ++++++++++++++++++++++++++++ netqasm/sdk/builder.py | 8 +++ netqasm/sdk/transpile.py | 16 ++--- 3 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 netqasm/sdk/algebraic_transpiler.py diff --git a/netqasm/sdk/algebraic_transpiler.py b/netqasm/sdk/algebraic_transpiler.py new file mode 100644 index 00000000..d33e8e6e --- /dev/null +++ b/netqasm/sdk/algebraic_transpiler.py @@ -0,0 +1,106 @@ +import numpy as np +from netqasm.lang.instr import vanilla, nv, core +from netqasm.lang.operand import Immediate + +SIGMA_X = np.array([[0, 1], [1, 0]], dtype=complex) +SIGMA_Y = np.array([[0, -1j], [1j, 0]], dtype=complex) +SIGMA_Z = np.array([[1, 0], [0, -1]], dtype=complex) + +def get_unitary(instr): + """Maps vanilla NetQASM instructions to their SU(2) unitaries.""" + PI = np.pi + + if isinstance(instr, vanilla.GateHInstruction): + return (1/np.sqrt(2)) * np.array([[1, 1], [1, -1]], dtype=complex) + elif isinstance(instr, vanilla.GateXInstruction): + return SIGMA_X + elif isinstance(instr, vanilla.GateYInstruction): + return SIGMA_Y + elif isinstance(instr, vanilla.GateZInstruction): + return SIGMA_Z + elif isinstance(instr, vanilla.GateSInstruction): + return np.array([[1, 0], [0, 1j]], dtype=complex) + elif isinstance(instr, vanilla.GateTInstruction): + return np.array([[1, 0], [0, np.exp(1j * PI / 4)]], dtype=complex) + elif isinstance(instr, vanilla.GateKInstruction): + return (1/np.sqrt(2)) * np.array([[1, -1j], [1, 1j]], dtype=complex) + elif isinstance(instr, vanilla.RotXInstruction): + n, d = instr.angle_num.value, instr.angle_denom.value + theta = n * PI / (2 ** d) + return np.cos(theta/2)*np.eye(2) - 1j*np.sin(theta/2)*SIGMA_X + elif isinstance(instr, vanilla.RotYInstruction): + n, d = instr.angle_num.value, instr.angle_denom.value + theta = n * PI / (2 ** d) + return np.cos(theta/2)*np.eye(2) - 1j*np.sin(theta/2)*SIGMA_Y + elif isinstance(instr, vanilla.RotZInstruction): + n, d = instr.angle_num.value, instr.angle_denom.value + theta = n * PI / (2 ** d) + return np.cos(theta/2)*np.eye(2) - 1j*np.sin(theta/2)*SIGMA_Z + + return np.eye(2) + +def decompose_to_nv(U, qubit_register, lineno=None): + """ + Decomposes an arbitrary SU(2) matrix into minimal NV native gates. + """ + alpha = np.angle(U[0, 0]) + np.angle(-U[0, 1]) + beta = 2 * np.arccos(np.clip(np.abs(U[0, 0]), -1.0, 1.0)) + gamma = np.angle(U[0, 0]) - np.angle(-U[0, 1]) + + def approximate_angle(theta, fixed_d=4): + # Maps continuous optimal angle back to NetQASM fractional format + theta = theta % (2 * np.pi) + n = int(round(theta * (2**fixed_d) / np.pi)) + return n, fixed_d + + nv_instructions = [] + for angle, axis in [(gamma, 'Z'), (beta, 'Y'), (alpha, 'Z')]: + if not np.isclose(angle, 0.0, atol=1e-5): + n, d = approximate_angle(angle) + if axis == 'X': + instr = nv.RotXInstruction(lineno=lineno, reg=qubit_register, imm0=Immediate(n), imm1=Immediate(d)) + elif axis == 'Y': + instr = nv.RotYInstruction(lineno=lineno, reg=qubit_register, imm0=Immediate(n), imm1=Immediate(d)) + elif axis == 'Z': + instr = nv.RotZInstruction(lineno=lineno, reg=qubit_register, imm0=Immediate(n), imm1=Immediate(d)) + nv_instructions.append(instr) + + return nv_instructions + +def optimize_subroutine(instructions): + optimized = [] + current_u = {} + + single_qubit_classes = ( + vanilla.GateHInstruction, vanilla.GateXInstruction, vanilla.GateYInstruction, vanilla.GateZInstruction, + vanilla.GateSInstruction, vanilla.GateTInstruction, vanilla.GateKInstruction, + vanilla.RotXInstruction, vanilla.RotYInstruction, vanilla.RotZInstruction + ) + + for instr in instructions: + if isinstance(instr, single_qubit_classes): + q_reg = instr.reg + U_op = get_unitary(instr) + if q_reg not in current_u: + current_u[q_reg] = np.eye(2, dtype=complex) + # Multiply latest operation + current_u[q_reg] = U_op @ current_u[q_reg] + + # The SDK Builder injects redundant "set Qx y" commands before every single gate. + # Absorb these if we are currently accumulating a block to prevent breaking the SU(2) trajectory. + elif isinstance(instr, core.SetInstruction) and instr.reg in current_u: + continue + + else: + # Flush accumulated unitaries on multi-qubit or structural gates + for q_reg, U in list(current_u.items()): + if not np.allclose(U, np.eye(2)): + optimized.extend(decompose_to_nv(U, q_reg, lineno=getattr(instr, 'lineno', None))) + del current_u[q_reg] + optimized.append(instr) + + for q_reg, U in current_u.items(): + if not np.allclose(U, np.eye(2)): + optimized.extend(decompose_to_nv(U, q_reg)) + + return optimized \ No newline at end of file diff --git a/netqasm/sdk/builder.py b/netqasm/sdk/builder.py index 8131b7b2..d5495d14 100644 --- a/netqasm/sdk/builder.py +++ b/netqasm/sdk/builder.py @@ -63,6 +63,7 @@ from netqasm.sdk.qubit import FutureQubit, Qubit, QubitMeasureAxes, QubitMeasureBasis from netqasm.sdk.toolbox import get_angle_spec_from_float from netqasm.sdk.transpile import NVSubroutineTranspiler, SubroutineTranspiler +from netqasm.sdk.algebraic_transpiler import optimize_subroutine # <--- INJECTED from netqasm.typedefs import T_Cmd from netqasm.util.log import LineTracker @@ -337,6 +338,13 @@ def subrt_pop_pending_subroutine(self) -> ProtoSubroutine | None: def subrt_compile_subroutine(self, pre_subroutine: ProtoSubroutine) -> Subroutine: """Convert a ProtoSubroutine into a Subroutine.""" subroutine: Subroutine = assemble_subroutine(pre_subroutine) + + # --- ALGEBRAIC REDUCTION PASS --- + # Map contiguous single-qubit vanilla gates into minimal native NV geometry + # to suppress T1/T2 vector space contraction. + subroutine.instructions = optimize_subroutine(subroutine.instructions) + # -------------------------------- + if self._compiler is not None: subroutine = self._compiler(subroutine=subroutine).transpile() if self._track_lines: diff --git a/netqasm/sdk/transpile.py b/netqasm/sdk/transpile.py index 1209edd4..6d07474b 100644 --- a/netqasm/sdk/transpile.py +++ b/netqasm/sdk/transpile.py @@ -336,16 +336,14 @@ def _map_single_gate( self, instr: Union[core.SingleQubitInstruction, core.RotationInstruction], ) -> List[NetQASMInstruction]: + + # --- ALGEBRAIC TRANSPILER BYPASS --- + # If the instruction is already compiled to NV geometry, map it as the identity + if isinstance(instr, (nv.RotXInstruction, nv.RotYInstruction, nv.RotZInstruction)): + return [instr] + # ----------------------------------- + if isinstance(instr, vanilla.GateXInstruction): - return [ - nv.RotXInstruction( - lineno=instr.lineno, - reg=instr.reg, - imm0=Immediate(16), - imm1=Immediate(4), - ) - ] - elif isinstance(instr, vanilla.GateYInstruction): return [ nv.RotYInstruction( lineno=instr.lineno,