From 8f2068448aff10cd8d44959691d06be3e43d772e Mon Sep 17 00:00:00 2001 From: David Plankensteiner Date: Mon, 14 Apr 2025 16:49:17 +0200 Subject: [PATCH 1/7] Reorder UGate arguments to match wrapper function arg order The wrapper was already in use in a bunch of places with this order, but using named arguments so it never errored. Still this should be consistent and I stuck with the order of the wrapper as it is already used. --- src/bloqade/qasm2/dialects/glob.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bloqade/qasm2/dialects/glob.py b/src/bloqade/qasm2/dialects/glob.py index 04defbfa..a5f92307 100644 --- a/src/bloqade/qasm2/dialects/glob.py +++ b/src/bloqade/qasm2/dialects/glob.py @@ -14,10 +14,10 @@ class UGate(ir.Statement): name = "ugate" traits = frozenset({lowering.FromPythonCall()}) - registers: ir.SSAValue = info.argument(ilist.IListType[QRegType]) theta: ir.SSAValue = info.argument(types.Float) phi: ir.SSAValue = info.argument(types.Float) lam: ir.SSAValue = info.argument(types.Float) + registers: ir.SSAValue = info.argument(ilist.IListType[QRegType]) @dialect.register(key="qasm2.schedule.dag") From 5878705fc9db19a1f52716448a249ce8b7094547 Mon Sep 17 00:00:00 2001 From: David Plankensteiner Date: Tue, 15 Apr 2025 10:49:43 +0200 Subject: [PATCH 2/7] Make PyQrackReg iterable by throwing IndexError in getitem method --- src/bloqade/pyqrack/reg.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bloqade/pyqrack/reg.py b/src/bloqade/pyqrack/reg.py index f78c4d5f..d12d74c0 100644 --- a/src/bloqade/pyqrack/reg.py +++ b/src/bloqade/pyqrack/reg.py @@ -72,6 +72,8 @@ def drop(self, pos: int): self.qubit_state[pos] = QubitState.Lost def __getitem__(self, pos: int): + if not 0 <= pos < self.size: + raise IndexError("Qubit index out of bounds of register.") return PyQrackQubit(self, pos) From 9e72ff32a7095455def7bb20480feb8edc330f43 Mon Sep 17 00:00:00 2001 From: David Plankensteiner Date: Tue, 15 Apr 2025 10:51:13 +0200 Subject: [PATCH 3/7] Implement pyqrack interpreter method for glob.u gate --- src/bloqade/pyqrack/qasm2/core.py | 20 ++++++++++- test/pyqrack/test_target.py | 56 +++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/bloqade/pyqrack/qasm2/core.py b/src/bloqade/pyqrack/qasm2/core.py index f9f7a1f0..4f95ac07 100644 --- a/src/bloqade/pyqrack/qasm2/core.py +++ b/src/bloqade/pyqrack/qasm2/core.py @@ -1,4 +1,7 @@ +from typing import Any + from kirin import interp +from kirin.dialects import ilist from bloqade.pyqrack.reg import ( CBitRef, @@ -9,7 +12,7 @@ PyQrackQubit, ) from bloqade.pyqrack.base import PyQrackInterpreter -from bloqade.qasm2.dialects import core +from bloqade.qasm2.dialects import core, glob @core.dialect.register(key="pyqrack") @@ -77,3 +80,18 @@ def creg_eq( return (False,) return (all(left is right for left, right in zip(lhs, rhs)),) + + @interp.impl(glob.UGate) + def ugate(self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: glob.UGate): + registers: ilist.IList[PyQrackReg, Any] = frame.get(stmt.registers) + theta, phi, lam = ( + frame.get(stmt.theta), + frame.get(stmt.phi), + frame.get(stmt.lam), + ) + + for qreg in registers: + for qarg in qreg: + if qarg.is_active(): + interp.memory.sim_reg.u(qarg.addr, theta, phi, lam) + return () diff --git a/test/pyqrack/test_target.py b/test/pyqrack/test_target.py index e4774cfa..4ebadc36 100644 --- a/test/pyqrack/test_target.py +++ b/test/pyqrack/test_target.py @@ -38,3 +38,59 @@ def ghz(): assert math.isclose(out[0].real, val, abs_tol=abs_tol) assert math.isclose(out[-1].real, val, abs_tol=abs_tol) assert all(math.isclose(ele.real, 0.0, abs_tol=abs_tol) for ele in out[1:-1]) + + +def test_target_glob(): + @qasm2.extended + def global_h(): + q = qasm2.qreg(3) + + # rotate around Y by pi/2, i.e. perform a hadamard + qasm2.glob.u(math.pi / 2.0, 0, 0, [q]) + + return q + + target = PyQrack(3) + q = target.run(global_h) + + assert isinstance(q, reg.PyQrackReg) + + out = q.sim_reg.out_ket() + + # remove global phase introduced by pyqrack + phase = out[0] / abs(out[0]) + out = [ele / phase for ele in out] + + for element in out: + assert math.isclose(element.real, 1 / math.sqrt(8), abs_tol=2.2e-7) + assert math.isclose(element.imag, 0, abs_tol=2.2e-7) + + @qasm2.extended + def multiple_registers(): + q1 = qasm2.qreg(2) + q2 = qasm2.qreg(2) + q3 = qasm2.qreg(2) + + # hadamard on first register + qasm2.glob.u(math.pi / 2.0, 0, 0, [q1]) + + # apply hadamard to the other two + qasm2.glob.u(math.pi / 2.0, 0, 0, [q2, q3]) + + # rotate all of them back down + qasm2.glob.u(-math.pi / 2.0, 0, 0, [q1, q2, q3]) + + return q1 + + target = PyQrack(6) + q1 = target.run(multiple_registers) + + assert isinstance(q1, reg.PyQrackReg) + + out = q1.sim_reg.out_ket() + + assert out[0] == 1 + for i in range(1, len(out)): + assert out[i] == 0 + + assert True From 7f0396578af9d76ab4eec00cbe5382ce76764cb3 Mon Sep 17 00:00:00 2001 From: David Plankensteiner Date: Tue, 15 Apr 2025 11:07:46 +0200 Subject: [PATCH 4/7] Fix pyright issue in core & reg --- src/bloqade/pyqrack/reg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bloqade/pyqrack/reg.py b/src/bloqade/pyqrack/reg.py index d12d74c0..f579f258 100644 --- a/src/bloqade/pyqrack/reg.py +++ b/src/bloqade/pyqrack/reg.py @@ -33,7 +33,7 @@ class CBitRef: pos: int """The position of this bit in the classical register.""" - def set_value(self, value: bool): + def set_value(self, value: Measurement): self.ref[self.pos] = value def get_value(self): From 93ef3c887ef1a5fc64b2f7fed9b7be69733a00dc Mon Sep 17 00:00:00 2001 From: David Plankensteiner Date: Tue, 15 Apr 2025 11:31:27 +0200 Subject: [PATCH 5/7] Fix order of glob.u decorator yet again (to match original UGate class args) --- src/bloqade/qasm2/dialects/glob.py | 2 +- src/bloqade/qasm2/glob.py | 2 +- test/pyqrack/test_target.py | 23 +++++++++++++++++++---- test/qasm2/passes/test_heuristic_noise.py | 2 +- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/bloqade/qasm2/dialects/glob.py b/src/bloqade/qasm2/dialects/glob.py index a5f92307..04defbfa 100644 --- a/src/bloqade/qasm2/dialects/glob.py +++ b/src/bloqade/qasm2/dialects/glob.py @@ -14,10 +14,10 @@ class UGate(ir.Statement): name = "ugate" traits = frozenset({lowering.FromPythonCall()}) + registers: ir.SSAValue = info.argument(ilist.IListType[QRegType]) theta: ir.SSAValue = info.argument(types.Float) phi: ir.SSAValue = info.argument(types.Float) lam: ir.SSAValue = info.argument(types.Float) - registers: ir.SSAValue = info.argument(ilist.IListType[QRegType]) @dialect.register(key="qasm2.schedule.dag") diff --git a/src/bloqade/qasm2/glob.py b/src/bloqade/qasm2/glob.py index c8c98f99..4049e5fc 100644 --- a/src/bloqade/qasm2/glob.py +++ b/src/bloqade/qasm2/glob.py @@ -11,7 +11,7 @@ @wraps(glob.UGate) def u( - theta: float, phi: float, lam: float, registers: ilist.IList[QReg, Any] | list + registers: ilist.IList[QReg, Any] | list, theta: float, phi: float, lam: float ) -> None: """Apply a U gate to all qubits in the input registers. diff --git a/test/pyqrack/test_target.py b/test/pyqrack/test_target.py index 4ebadc36..04b9763f 100644 --- a/test/pyqrack/test_target.py +++ b/test/pyqrack/test_target.py @@ -46,7 +46,7 @@ def global_h(): q = qasm2.qreg(3) # rotate around Y by pi/2, i.e. perform a hadamard - qasm2.glob.u(math.pi / 2.0, 0, 0, [q]) + qasm2.glob.u([q], math.pi / 2.0, 0, 0) return q @@ -72,13 +72,28 @@ def multiple_registers(): q3 = qasm2.qreg(2) # hadamard on first register - qasm2.glob.u(math.pi / 2.0, 0, 0, [q1]) + qasm2.glob.u( + [q1], + math.pi / 2.0, + 0, + 0, + ) # apply hadamard to the other two - qasm2.glob.u(math.pi / 2.0, 0, 0, [q2, q3]) + qasm2.glob.u( + [q2, q3], + math.pi / 2.0, + 0, + 0, + ) # rotate all of them back down - qasm2.glob.u(-math.pi / 2.0, 0, 0, [q1, q2, q3]) + qasm2.glob.u( + [q1, q2, q3], + -math.pi / 2.0, + 0, + 0, + ) return q1 diff --git a/test/qasm2/passes/test_heuristic_noise.py b/test/qasm2/passes/test_heuristic_noise.py index 8265eedc..e183c4b4 100644 --- a/test/qasm2/passes/test_heuristic_noise.py +++ b/test/qasm2/passes/test_heuristic_noise.py @@ -265,7 +265,7 @@ def test_global_noise(): def test_method(): q0 = qasm2.qreg(1) q1 = qasm2.qreg(1) - glob.UGate([q0, q1], 0.1, 0.2, 0.3) + qasm2.glob.u([q0, q1], 0.1, 0.2, 0.3) px = 0.01 py = 0.01 From 0dd6b312148c6eaae883de476fc32d220252c34e Mon Sep 17 00:00:00 2001 From: David Plankensteiner Date: Tue, 15 Apr 2025 17:37:19 +0200 Subject: [PATCH 6/7] Move new method to separate method table --- src/bloqade/pyqrack/__init__.py | 2 +- src/bloqade/pyqrack/qasm2/core.py | 21 +-------------------- src/bloqade/pyqrack/qasm2/glob.py | 26 ++++++++++++++++++++++++++ test/pyqrack/test_target.py | 3 +++ 4 files changed, 31 insertions(+), 21 deletions(-) create mode 100644 src/bloqade/pyqrack/qasm2/glob.py diff --git a/src/bloqade/pyqrack/__init__.py b/src/bloqade/pyqrack/__init__.py index 8fb757e1..4c6ebf59 100644 --- a/src/bloqade/pyqrack/__init__.py +++ b/src/bloqade/pyqrack/__init__.py @@ -14,5 +14,5 @@ # NOTE: The following import is for registering the method tables from .noise import native as native -from .qasm2 import uop as uop, core as core, parallel as parallel +from .qasm2 import uop as uop, core as core, glob as glob, parallel as parallel from .target import PyQrack as PyQrack diff --git a/src/bloqade/pyqrack/qasm2/core.py b/src/bloqade/pyqrack/qasm2/core.py index 4f95ac07..45f4baa1 100644 --- a/src/bloqade/pyqrack/qasm2/core.py +++ b/src/bloqade/pyqrack/qasm2/core.py @@ -1,7 +1,4 @@ -from typing import Any - from kirin import interp -from kirin.dialects import ilist from bloqade.pyqrack.reg import ( CBitRef, @@ -12,12 +9,11 @@ PyQrackQubit, ) from bloqade.pyqrack.base import PyQrackInterpreter -from bloqade.qasm2.dialects import core, glob +from bloqade.qasm2.dialects import core @core.dialect.register(key="pyqrack") class PyQrackMethods(interp.MethodTable): - @interp.impl(core.QRegNew) def qreg_new( self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: core.QRegNew @@ -80,18 +76,3 @@ def creg_eq( return (False,) return (all(left is right for left, right in zip(lhs, rhs)),) - - @interp.impl(glob.UGate) - def ugate(self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: glob.UGate): - registers: ilist.IList[PyQrackReg, Any] = frame.get(stmt.registers) - theta, phi, lam = ( - frame.get(stmt.theta), - frame.get(stmt.phi), - frame.get(stmt.lam), - ) - - for qreg in registers: - for qarg in qreg: - if qarg.is_active(): - interp.memory.sim_reg.u(qarg.addr, theta, phi, lam) - return () diff --git a/src/bloqade/pyqrack/qasm2/glob.py b/src/bloqade/pyqrack/qasm2/glob.py new file mode 100644 index 00000000..730f71b6 --- /dev/null +++ b/src/bloqade/pyqrack/qasm2/glob.py @@ -0,0 +1,26 @@ +from typing import Any + +from kirin import interp +from kirin.dialects import ilist + +from bloqade.pyqrack.reg import PyQrackReg +from bloqade.pyqrack.base import PyQrackInterpreter +from bloqade.qasm2.dialects import glob + + +@glob.dialect.register(key="pyqrack") +class PyQrackMethods(interp.MethodTable): + @interp.impl(glob.UGate) + def ugate(self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: glob.UGate): + registers: ilist.IList[PyQrackReg, Any] = frame.get(stmt.registers) + theta, phi, lam = ( + frame.get(stmt.theta), + frame.get(stmt.phi), + frame.get(stmt.lam), + ) + + for qreg in registers: + for qarg in qreg: + if qarg.is_active(): + interp.memory.sim_reg.u(qarg.addr, theta, phi, lam) + return () diff --git a/test/pyqrack/test_target.py b/test/pyqrack/test_target.py index 04b9763f..58d3413e 100644 --- a/test/pyqrack/test_target.py +++ b/test/pyqrack/test_target.py @@ -109,3 +109,6 @@ def multiple_registers(): assert out[i] == 0 assert True + + +test_target_glob() From e13a264ad61704713990315b837fdb43842428b4 Mon Sep 17 00:00:00 2001 From: David Plankensteiner Date: Tue, 15 Apr 2025 17:38:52 +0200 Subject: [PATCH 7/7] Add TODO to clean up PyQrackReg --- src/bloqade/pyqrack/reg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bloqade/pyqrack/reg.py b/src/bloqade/pyqrack/reg.py index f579f258..38838e2e 100644 --- a/src/bloqade/pyqrack/reg.py +++ b/src/bloqade/pyqrack/reg.py @@ -46,7 +46,7 @@ class QubitState(enum.Enum): @dataclass(frozen=True) -class PyQrackReg(QReg): +class PyQrackReg(QReg): # TODO: clean up implementation with list base class """Simulation runtime value of a quantum register.""" size: int