From 58f7bfef830ee7cbabb78a7e098384c475fc0820 Mon Sep 17 00:00:00 2001 From: John Long Date: Tue, 18 Nov 2025 14:03:18 -0500 Subject: [PATCH 1/6] reshuffled some things, structure up for debate --- src/bloqade/gemini/__init__.py | 2 +- src/bloqade/gemini/logical/__init__.py | 4 +++ src/bloqade/gemini/logical/_dialect.py | 3 +++ src/bloqade/gemini/logical/_interface.py | 30 ++++++++++++++++++++++ src/bloqade/gemini/{ => logical}/groups.py | 4 +-- src/bloqade/gemini/logical/stmts.py | 19 ++++++++++++++ test/gemini/test_logical_validation.py | 18 ++++++------- 7 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 src/bloqade/gemini/logical/__init__.py create mode 100644 src/bloqade/gemini/logical/_dialect.py create mode 100644 src/bloqade/gemini/logical/_interface.py rename src/bloqade/gemini/{ => logical}/groups.py (96%) create mode 100644 src/bloqade/gemini/logical/stmts.py diff --git a/src/bloqade/gemini/__init__.py b/src/bloqade/gemini/__init__.py index c03aeeb7..96b315d7 100644 --- a/src/bloqade/gemini/__init__.py +++ b/src/bloqade/gemini/__init__.py @@ -1 +1 @@ -from .groups import logical as logical +from . import logical as logical diff --git a/src/bloqade/gemini/logical/__init__.py b/src/bloqade/gemini/logical/__init__.py new file mode 100644 index 00000000..187ae361 --- /dev/null +++ b/src/bloqade/gemini/logical/__init__.py @@ -0,0 +1,4 @@ +from . import stmts as stmts +from .groups import kernel as kernel +from ._dialect import dialect as dialect +from ._interface import terminal_measure as terminal_measure diff --git a/src/bloqade/gemini/logical/_dialect.py b/src/bloqade/gemini/logical/_dialect.py new file mode 100644 index 00000000..960012dc --- /dev/null +++ b/src/bloqade/gemini/logical/_dialect.py @@ -0,0 +1,3 @@ +from kirin import ir + +dialect = ir.Dialect("gemini.logical") diff --git a/src/bloqade/gemini/logical/_interface.py b/src/bloqade/gemini/logical/_interface.py new file mode 100644 index 00000000..f93b3b27 --- /dev/null +++ b/src/bloqade/gemini/logical/_interface.py @@ -0,0 +1,30 @@ +from typing import TypeVar + +from kirin import lowering +from kirin.dialects import ilist + +from bloqade.types import Qubit, MeasurementResult + +from .stmts import TerminalLogicalMeasurement + +Len = TypeVar("Len", bound=int) +CodeN = TypeVar("CodeN", bound=int) + + +@lowering.wraps(TerminalLogicalMeasurement) +def terminal_measure( + qubits: ilist.IList[Qubit, Len], +) -> ilist.IList[ilist.IList[MeasurementResult, CodeN], Len]: + """Perform measurements on a list of logical qubits. + + Measurements are returned as a nested list where each member list + contains the individual measurement results for the constituent physical qubits per logical qubit. + + Args: + qubits (IList[Qubit, Len]): The list of logical qubits to measure. + + Returns: + IList[IList[MeasurementResult, CodeN], Len]: A nested list containing the measurement results, + where each inner list corresponds to the measurements of the physical qubits that make up each logical qubit. + """ + ... diff --git a/src/bloqade/gemini/groups.py b/src/bloqade/gemini/logical/groups.py similarity index 96% rename from src/bloqade/gemini/groups.py rename to src/bloqade/gemini/logical/groups.py index 90441099..4d65538c 100644 --- a/src/bloqade/gemini/groups.py +++ b/src/bloqade/gemini/logical/groups.py @@ -11,11 +11,11 @@ from bloqade.validation import KernelValidation from bloqade.rewrite.passes import AggressiveUnroll -from .analysis import GeminiLogicalValidationAnalysis +from ..analysis import GeminiLogicalValidationAnalysis @ir.dialect_group(structural_no_opt.union([gate, py.constant, qubit, func, ilist])) -def logical(self): +def kernel(self): """Compile a function to a Gemini logical kernel.""" def run_pass( diff --git a/src/bloqade/gemini/logical/stmts.py b/src/bloqade/gemini/logical/stmts.py new file mode 100644 index 00000000..e5e27463 --- /dev/null +++ b/src/bloqade/gemini/logical/stmts.py @@ -0,0 +1,19 @@ +from kirin import ir, types, lowering +from kirin.decl import info, statement +from kirin.dialects import ilist + +from bloqade.types import QubitType, MeasurementResultType + +from ._dialect import dialect + +Len = types.TypeVar("Len", bound=types.Int) +CodeN = types.TypeVar("CodeN", bound=types.Int) + + +@statement(dialect=dialect) +class TerminalLogicalMeasurement(ir.Statement): + traits = frozenset({lowering.FromPythonCall()}) + qubits: ir.SSAValue = info.argument(ilist.IListType[QubitType, Len]) + result: ir.ResultValue = info.result( + ilist.IListType[ilist.IListType[MeasurementResultType, CodeN], Len] + ) diff --git a/test/gemini/test_logical_validation.py b/test/gemini/test_logical_validation.py index 046b2ac4..ec4e3d11 100644 --- a/test/gemini/test_logical_validation.py +++ b/test/gemini/test_logical_validation.py @@ -9,7 +9,7 @@ def test_if_stmt_invalid(): - @gemini.logical(verify=False) + @gemini.logical.kernel(verify=False) def main(): q = squin.qalloc(3) @@ -42,7 +42,7 @@ def main(): def test_for_loop(): - @gemini.logical + @gemini.logical.kernel def valid_loop(): q = squin.qalloc(3) @@ -53,7 +53,7 @@ def valid_loop(): with pytest.raises(ir.ValidationError): - @gemini.logical + @gemini.logical.kernel def invalid_loop(n: int): q = squin.qalloc(3) @@ -64,11 +64,11 @@ def invalid_loop(n: int): def test_func(): - @gemini.logical + @gemini.logical.kernel def sub_kernel(q: Qubit): squin.x(q) - @gemini.logical + @gemini.logical.kernel def main(): q = squin.qalloc(3) sub_kernel(q[0]) @@ -77,14 +77,14 @@ def main(): with pytest.raises(ValidationErrorGroup): - @gemini.logical(inline=False) + @gemini.logical.kernel(inline=False) def invalid(): q = squin.qalloc(3) sub_kernel(q[0]) def test_clifford_gates(): - @gemini.logical + @gemini.logical.kernel def main(): q = squin.qalloc(2) squin.u3(0.123, 0.253, 1.2, q[0]) @@ -94,7 +94,7 @@ def main(): with pytest.raises(ir.ValidationError): - @gemini.logical(no_raise=False) + @gemini.logical.kernel(no_raise=False) def invalid(): q = squin.qalloc(2) @@ -113,7 +113,7 @@ def test_multiple_errors(): did_error = False try: - @gemini.logical + @gemini.logical.kernel def main(n: int): q = squin.qalloc(3) m = squin.qubit.measure(q[0]) From 724b0196324984cbb536fd4b824a3960582d6964 Mon Sep 17 00:00:00 2001 From: John Long Date: Thu, 20 Nov 2025 10:17:25 -0500 Subject: [PATCH 2/6] add docstring to original statement --- src/bloqade/gemini/logical/stmts.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/bloqade/gemini/logical/stmts.py b/src/bloqade/gemini/logical/stmts.py index e5e27463..e1a5684c 100644 --- a/src/bloqade/gemini/logical/stmts.py +++ b/src/bloqade/gemini/logical/stmts.py @@ -12,6 +12,19 @@ @statement(dialect=dialect) class TerminalLogicalMeasurement(ir.Statement): + """Perform measurements on a list of logical qubits. + + Measurements are returned as a nested list where each member list + contains the individual measurement results for the constituent physical qubits per logical qubit. + + Args: + qubits (IList[QubitType, Len]): The list of logical qubits + + Returns: + IList[IList[MeasurementResultType, CodeN], Len]: A nested list containing the measurement results, + where each inner list corresponds to the measurements of the physical qubits that make up each logical qubit. + """ + traits = frozenset({lowering.FromPythonCall()}) qubits: ir.SSAValue = info.argument(ilist.IListType[QubitType, Len]) result: ir.ResultValue = info.result( From d2d70276415514123bdb053c6b76af682ac614a5 Mon Sep 17 00:00:00 2001 From: John Long Date: Thu, 20 Nov 2025 16:19:03 -0500 Subject: [PATCH 3/6] reshuffle to try and avoid import errors --- src/bloqade/gemini/analysis/__init__.py | 3 --- .../gemini/analysis/logical_validation/__init__.py | 3 ++- src/bloqade/gemini/{logical => }/groups.py | 8 +++++--- src/bloqade/gemini/logical/__init__.py | 2 +- src/bloqade/gemini/logical/_dialect.py | 2 +- src/bloqade/gemini/logical/_interface.py | 4 ++-- src/bloqade/gemini/logical/stmts.py | 2 +- test/gemini/test_logical_validation.py | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) rename src/bloqade/gemini/{logical => }/groups.py (90%) diff --git a/src/bloqade/gemini/analysis/__init__.py b/src/bloqade/gemini/analysis/__init__.py index 8c94d180..e69de29b 100644 --- a/src/bloqade/gemini/analysis/__init__.py +++ b/src/bloqade/gemini/analysis/__init__.py @@ -1,3 +0,0 @@ -from .logical_validation.analysis import ( - GeminiLogicalValidationAnalysis as GeminiLogicalValidationAnalysis, -) diff --git a/src/bloqade/gemini/analysis/logical_validation/__init__.py b/src/bloqade/gemini/analysis/logical_validation/__init__.py index 1b289d8c..dc0d77ef 100644 --- a/src/bloqade/gemini/analysis/logical_validation/__init__.py +++ b/src/bloqade/gemini/analysis/logical_validation/__init__.py @@ -1 +1,2 @@ -from . import impls as impls, analysis as analysis # NOTE: register methods +from . import impls as impls # NOTE: register methods +from .analysis import GeminiLogicalValidationAnalysis as GeminiLogicalValidationAnalysis diff --git a/src/bloqade/gemini/logical/groups.py b/src/bloqade/gemini/groups.py similarity index 90% rename from src/bloqade/gemini/logical/groups.py rename to src/bloqade/gemini/groups.py index 4d65538c..2f915816 100644 --- a/src/bloqade/gemini/logical/groups.py +++ b/src/bloqade/gemini/groups.py @@ -8,13 +8,15 @@ from kirin.passes.inline import InlinePass from bloqade.squin import gate, qubit +from bloqade.gemini import logical from bloqade.validation import KernelValidation from bloqade.rewrite.passes import AggressiveUnroll +from bloqade.gemini.analysis.logical_validation import GeminiLogicalValidationAnalysis -from ..analysis import GeminiLogicalValidationAnalysis - -@ir.dialect_group(structural_no_opt.union([gate, py.constant, qubit, func, ilist])) +@ir.dialect_group( + structural_no_opt.union([gate, py.constant, qubit, func, ilist, logical]) +) def kernel(self): """Compile a function to a Gemini logical kernel.""" diff --git a/src/bloqade/gemini/logical/__init__.py b/src/bloqade/gemini/logical/__init__.py index 187ae361..b4b67983 100644 --- a/src/bloqade/gemini/logical/__init__.py +++ b/src/bloqade/gemini/logical/__init__.py @@ -1,4 +1,4 @@ from . import stmts as stmts -from .groups import kernel as kernel +from ..groups import kernel as kernel from ._dialect import dialect as dialect from ._interface import terminal_measure as terminal_measure diff --git a/src/bloqade/gemini/logical/_dialect.py b/src/bloqade/gemini/logical/_dialect.py index 960012dc..6be7cae9 100644 --- a/src/bloqade/gemini/logical/_dialect.py +++ b/src/bloqade/gemini/logical/_dialect.py @@ -1,3 +1,3 @@ from kirin import ir -dialect = ir.Dialect("gemini.logical") +dialect = ir.Dialect("logical") diff --git a/src/bloqade/gemini/logical/_interface.py b/src/bloqade/gemini/logical/_interface.py index f93b3b27..e44d8817 100644 --- a/src/bloqade/gemini/logical/_interface.py +++ b/src/bloqade/gemini/logical/_interface.py @@ -1,4 +1,4 @@ -from typing import TypeVar +from typing import Any, TypeVar from kirin import lowering from kirin.dialects import ilist @@ -14,7 +14,7 @@ @lowering.wraps(TerminalLogicalMeasurement) def terminal_measure( qubits: ilist.IList[Qubit, Len], -) -> ilist.IList[ilist.IList[MeasurementResult, CodeN], Len]: +) -> ilist.IList[ilist.IList[MeasurementResult, Any], Len]: """Perform measurements on a list of logical qubits. Measurements are returned as a nested list where each member list diff --git a/src/bloqade/gemini/logical/stmts.py b/src/bloqade/gemini/logical/stmts.py index e1a5684c..dffa67aa 100644 --- a/src/bloqade/gemini/logical/stmts.py +++ b/src/bloqade/gemini/logical/stmts.py @@ -28,5 +28,5 @@ class TerminalLogicalMeasurement(ir.Statement): traits = frozenset({lowering.FromPythonCall()}) qubits: ir.SSAValue = info.argument(ilist.IListType[QubitType, Len]) result: ir.ResultValue = info.result( - ilist.IListType[ilist.IListType[MeasurementResultType, CodeN], Len] + ilist.IListType[ilist.IListType[MeasurementResultType, types.Any], Len] ) diff --git a/test/gemini/test_logical_validation.py b/test/gemini/test_logical_validation.py index ec4e3d11..7bb583f0 100644 --- a/test/gemini/test_logical_validation.py +++ b/test/gemini/test_logical_validation.py @@ -4,8 +4,8 @@ from bloqade import squin, gemini from bloqade.types import Qubit from bloqade.validation import KernelValidation -from bloqade.gemini.analysis import GeminiLogicalValidationAnalysis from bloqade.validation.kernel_validation import ValidationErrorGroup +from bloqade.gemini.analysis.logical_validation import GeminiLogicalValidationAnalysis def test_if_stmt_invalid(): From 6d5c786b5042061c526d25f14d698f8dbcc55a27 Mon Sep 17 00:00:00 2001 From: John Long Date: Thu, 20 Nov 2025 16:49:44 -0500 Subject: [PATCH 4/6] fix obnoxious circular import problem --- src/bloqade/gemini/__init__.py | 2 +- src/bloqade/gemini/dialects/__init__.py | 1 + src/bloqade/gemini/{ => dialects}/logical/__init__.py | 2 +- src/bloqade/gemini/{ => dialects}/logical/_dialect.py | 0 src/bloqade/gemini/{ => dialects}/logical/_interface.py | 0 src/bloqade/gemini/{ => dialects/logical}/groups.py | 5 +++-- src/bloqade/gemini/{ => dialects}/logical/stmts.py | 0 test/gemini/test_logical_validation.py | 3 +++ 8 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 src/bloqade/gemini/dialects/__init__.py rename src/bloqade/gemini/{ => dialects}/logical/__init__.py (77%) rename src/bloqade/gemini/{ => dialects}/logical/_dialect.py (100%) rename src/bloqade/gemini/{ => dialects}/logical/_interface.py (100%) rename src/bloqade/gemini/{ => dialects/logical}/groups.py (97%) rename src/bloqade/gemini/{ => dialects}/logical/stmts.py (100%) diff --git a/src/bloqade/gemini/__init__.py b/src/bloqade/gemini/__init__.py index 96b315d7..e5708280 100644 --- a/src/bloqade/gemini/__init__.py +++ b/src/bloqade/gemini/__init__.py @@ -1 +1 @@ -from . import logical as logical +from .dialects import logical as logical diff --git a/src/bloqade/gemini/dialects/__init__.py b/src/bloqade/gemini/dialects/__init__.py new file mode 100644 index 00000000..96b315d7 --- /dev/null +++ b/src/bloqade/gemini/dialects/__init__.py @@ -0,0 +1 @@ +from . import logical as logical diff --git a/src/bloqade/gemini/logical/__init__.py b/src/bloqade/gemini/dialects/logical/__init__.py similarity index 77% rename from src/bloqade/gemini/logical/__init__.py rename to src/bloqade/gemini/dialects/logical/__init__.py index b4b67983..187ae361 100644 --- a/src/bloqade/gemini/logical/__init__.py +++ b/src/bloqade/gemini/dialects/logical/__init__.py @@ -1,4 +1,4 @@ from . import stmts as stmts -from ..groups import kernel as kernel +from .groups import kernel as kernel from ._dialect import dialect as dialect from ._interface import terminal_measure as terminal_measure diff --git a/src/bloqade/gemini/logical/_dialect.py b/src/bloqade/gemini/dialects/logical/_dialect.py similarity index 100% rename from src/bloqade/gemini/logical/_dialect.py rename to src/bloqade/gemini/dialects/logical/_dialect.py diff --git a/src/bloqade/gemini/logical/_interface.py b/src/bloqade/gemini/dialects/logical/_interface.py similarity index 100% rename from src/bloqade/gemini/logical/_interface.py rename to src/bloqade/gemini/dialects/logical/_interface.py diff --git a/src/bloqade/gemini/groups.py b/src/bloqade/gemini/dialects/logical/groups.py similarity index 97% rename from src/bloqade/gemini/groups.py rename to src/bloqade/gemini/dialects/logical/groups.py index 2f915816..dea43e91 100644 --- a/src/bloqade/gemini/groups.py +++ b/src/bloqade/gemini/dialects/logical/groups.py @@ -8,14 +8,15 @@ from kirin.passes.inline import InlinePass from bloqade.squin import gate, qubit -from bloqade.gemini import logical from bloqade.validation import KernelValidation from bloqade.rewrite.passes import AggressiveUnroll from bloqade.gemini.analysis.logical_validation import GeminiLogicalValidationAnalysis +from ._dialect import dialect + @ir.dialect_group( - structural_no_opt.union([gate, py.constant, qubit, func, ilist, logical]) + structural_no_opt.union([gate, py.constant, qubit, func, ilist, dialect]) ) def kernel(self): """Compile a function to a Gemini logical kernel.""" diff --git a/src/bloqade/gemini/logical/stmts.py b/src/bloqade/gemini/dialects/logical/stmts.py similarity index 100% rename from src/bloqade/gemini/logical/stmts.py rename to src/bloqade/gemini/dialects/logical/stmts.py diff --git a/test/gemini/test_logical_validation.py b/test/gemini/test_logical_validation.py index 7bb583f0..4f92761c 100644 --- a/test/gemini/test_logical_validation.py +++ b/test/gemini/test_logical_validation.py @@ -40,6 +40,9 @@ def main(): validator.run(main, no_raise=False) +test_if_stmt_invalid() + + def test_for_loop(): @gemini.logical.kernel From b56f9f077a3f690feb31f544c82c843bcdf1acde Mon Sep 17 00:00:00 2001 From: John Long Date: Thu, 20 Nov 2025 16:54:02 -0500 Subject: [PATCH 5/6] add test --- test/gemini/test_logical_validation.py | 29 +++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/test/gemini/test_logical_validation.py b/test/gemini/test_logical_validation.py index 4f92761c..34917778 100644 --- a/test/gemini/test_logical_validation.py +++ b/test/gemini/test_logical_validation.py @@ -40,9 +40,6 @@ def main(): validator.run(main, no_raise=False) -test_if_stmt_invalid() - - def test_for_loop(): @gemini.logical.kernel @@ -112,6 +109,32 @@ def invalid(): invalid.print(analysis=frame.entries) +def test_terminal_measurement(): + @gemini.logical.kernel(verify=False) + def main(): + q = squin.qalloc(3) + m = gemini.logical.terminal_measure(q) + return m + + main.print() + + with pytest.raises(ir.ValidationError): + + @gemini.logical.kernel(no_raise=False) + def invalid(): + q = squin.qalloc(3) + squin.x(q[0]) + m = gemini.logical.terminal_measure(q) + another_m = gemini.logical.terminal_measure(q) + return m, another_m + + frame, _ = GeminiLogicalValidationAnalysis(invalid.dialects).run_no_raise( + invalid + ) + + invalid.print(analysis=frame.entries) + + def test_multiple_errors(): did_error = False try: From 136ed03b401f59921123246ff2f6e6d06b205297 Mon Sep 17 00:00:00 2001 From: John Long Date: Thu, 20 Nov 2025 17:04:47 -0500 Subject: [PATCH 6/6] add back the physical qubit number per patch type - accidentally removed it during dev --- src/bloqade/gemini/dialects/logical/_interface.py | 4 ++-- src/bloqade/gemini/dialects/logical/stmts.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bloqade/gemini/dialects/logical/_interface.py b/src/bloqade/gemini/dialects/logical/_interface.py index e44d8817..f93b3b27 100644 --- a/src/bloqade/gemini/dialects/logical/_interface.py +++ b/src/bloqade/gemini/dialects/logical/_interface.py @@ -1,4 +1,4 @@ -from typing import Any, TypeVar +from typing import TypeVar from kirin import lowering from kirin.dialects import ilist @@ -14,7 +14,7 @@ @lowering.wraps(TerminalLogicalMeasurement) def terminal_measure( qubits: ilist.IList[Qubit, Len], -) -> ilist.IList[ilist.IList[MeasurementResult, Any], Len]: +) -> ilist.IList[ilist.IList[MeasurementResult, CodeN], Len]: """Perform measurements on a list of logical qubits. Measurements are returned as a nested list where each member list diff --git a/src/bloqade/gemini/dialects/logical/stmts.py b/src/bloqade/gemini/dialects/logical/stmts.py index dffa67aa..e1a5684c 100644 --- a/src/bloqade/gemini/dialects/logical/stmts.py +++ b/src/bloqade/gemini/dialects/logical/stmts.py @@ -28,5 +28,5 @@ class TerminalLogicalMeasurement(ir.Statement): traits = frozenset({lowering.FromPythonCall()}) qubits: ir.SSAValue = info.argument(ilist.IListType[QubitType, Len]) result: ir.ResultValue = info.result( - ilist.IListType[ilist.IListType[MeasurementResultType, types.Any], Len] + ilist.IListType[ilist.IListType[MeasurementResultType, CodeN], Len] )