Skip to content

Commit 0e578fc

Browse files
Roger-luokaihsinweinbe58johnzl-777
authored
move qasm2 from kirin-circuit (#1)
This PR moves the QASM2 implementation into this new namespace package with some minor cleanups: - qrack is no longer living with dialects, it is in independent module so we can install it conditionally with extra `qrack` - implement qrack interpreter for QASM2 Co-authored-by: Roger-luo <[email protected]> Co-authored-by: kaihsin <[email protected]> Co-authored-by: weinbe58 <[email protected]> Co-authored-by: johnzl-777 <[email protected]>
1 parent 39bd1c8 commit 0e578fc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+5017
-31
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
- name: Set up Python ${{ matrix.python-version }}
3131
run: uv python install ${{ matrix.python-version }}
3232
- name: Install the project
33-
run: uv sync --all-extras --dev
33+
run: uv sync --all-extras --dev --index="https://${{ secrets.JFROG_USER}}:${{ secrets.JFROG_TOKEN }}@quera.jfrog.io/artifactory/api/pypi/kirin/simple/ https://${{ secrets.JFROG_USER}}:${{ secrets.JFROG_TOKEN }}@quera.jfrog.io/artifactory/api/pypi/quera-pypi-algo/simple/"
3434
- name: Run tests
3535
# For example, using `pytest`
3636
run: uv run just coverage

.github/workflows/devdoc.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
enable-cache: true
2525
cache-dependency-glob: "uv.lock"
2626
- name: Install Documentation dependencies
27-
run: uv sync --group doc
27+
run: uv sync --group doc --index="https://${{ secrets.JFROG_USER}}:${{ secrets.JFROG_TOKEN }}@quera.jfrog.io/artifactory/api/pypi/kirin/simple/ https://${{ secrets.JFROG_USER}}:${{ secrets.JFROG_TOKEN }}@quera.jfrog.io/artifactory/api/pypi/quera-pypi-algo/simple/"
2828
- name: Set up build cache
2929
uses: actions/cache@v4
3030
id: cache

.github/workflows/doc.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
enable-cache: true
2525
cache-dependency-glob: "uv.lock"
2626
- name: Install Documentation dependencies
27-
run: uv sync --group doc
27+
run: uv sync --group doc --index="https://${{ secrets.JFROG_USER}}:${{ secrets.JFROG_TOKEN }}@quera.jfrog.io/artifactory/api/pypi/kirin/simple/ https://${{ secrets.JFROG_USER}}:${{ secrets.JFROG_TOKEN }}@quera.jfrog.io/artifactory/api/pypi/quera-pypi-algo/simple/"
2828
- name: Set up build cache
2929
uses: actions/cache@v4
3030
id: cache

.github/workflows/pub_doc.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
enable-cache: true
2525
cache-dependency-glob: "uv.lock"
2626
- name: Install Documentation dependencies
27-
run: uv sync --group doc
27+
run: uv sync --group doc --index="https://${{ secrets.JFROG_USER}}:${{ secrets.JFROG_TOKEN }}@quera.jfrog.io/artifactory/api/pypi/kirin/simple/ https://${{ secrets.JFROG_USER}}:${{ secrets.JFROG_TOKEN }}@quera.jfrog.io/artifactory/api/pypi/quera-pypi-algo/simple/"
2828
- name: Set up build cache
2929
uses: actions/cache@v4
3030
id: cache

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,4 @@ main.py
185185
.ruff_cache
186186
.python-version
187187
!package.json
188+
!src/**/**/main.py

.pre-commit-config.yaml

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# See https://pre-commit.com for more information
2+
# See https://pre-commit.com/hooks.html for more hooks
3+
repos:
4+
- repo: https://github.com/pre-commit/pre-commit-hooks
5+
rev: v5.0.0
6+
hooks:
7+
- id: check-yaml
8+
args: ['--unsafe']
9+
- id: end-of-file-fixer
10+
- id: trailing-whitespace
11+
- repo: https://github.com/pycqa/isort
12+
rev: 5.13.2
13+
hooks:
14+
- id: isort
15+
name: isort (python)
16+
- repo: https://github.com/psf/black
17+
rev: 24.10.0
18+
hooks:
19+
- id: black
20+
- repo: https://github.com/charliermarsh/ruff-pre-commit
21+
# Ruff version.
22+
rev: "v0.9.2"
23+
hooks:
24+
- id: ruff

justfile

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
coverage-run:
2+
coverage run -m pytest test
3+
4+
coverage-xml:
5+
coverage xml
6+
7+
coverage-html:
8+
coverage html
9+
10+
coverage-report:
11+
coverage report
12+
13+
coverage-open:
14+
open htmlcov/index.html
15+
16+
coverage: coverage-run coverage-xml coverage-report
17+
18+
doc:
19+
mkdocs serve
20+
21+
doc-build:
22+
mkdocs build

pyproject.toml

+10-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@ authors = [
1111
]
1212
requires-python = ">=3.10"
1313
dependencies = [
14-
"kirin-toolchain>=0.9.0",
14+
"kirin-toolchain>=0.9.1",
15+
]
16+
17+
[project.optional-dependencies]
18+
qasm2 = [
19+
"lark>=1.2.2",
20+
]
21+
pyqrack-cpu = [
22+
"pyqrack-cpu>=1.34.9",
1523
]
1624

1725
[build-system]
@@ -89,4 +97,4 @@ exclude = [
8997
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
9098

9199
[tool.coverage.run]
92-
include = ["src/kirin/*"]
100+
include = ["src/bloqade/*"]
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from . import impls as impls
2+
from .lattice import (
3+
Address as Address,
4+
NotQubit as NotQubit,
5+
AddressReg as AddressReg,
6+
AnyAddress as AnyAddress,
7+
AddressQubit as AddressQubit,
8+
AddressTuple as AddressTuple,
9+
)
10+
from .analysis import AddressAnalysis as AddressAnalysis
+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from typing import TypeVar, Iterable
2+
3+
from kirin import ir, types, interp
4+
from bloqade.types import QubitType
5+
from kirin.analysis import Forward, const
6+
from kirin.dialects import cf, py, func, ilist
7+
from kirin.exceptions import InterpreterError
8+
from kirin.analysis.forward import ForwardFrame
9+
10+
from .lattice import Address
11+
12+
13+
class AddressAnalysis(Forward[Address]):
14+
keys = ["qubit.address"]
15+
lattice = Address
16+
17+
def __init__(
18+
self,
19+
dialects: ir.DialectGroup | Iterable[ir.Dialect],
20+
*,
21+
fuel: int | None = None,
22+
save_all_ssa: bool = False,
23+
max_depth: int = 128,
24+
max_python_recursion_depth: int = 8192,
25+
):
26+
super().__init__(
27+
dialects,
28+
fuel=fuel,
29+
save_all_ssa=save_all_ssa,
30+
max_depth=max_depth,
31+
max_python_recursion_depth=max_python_recursion_depth,
32+
)
33+
self.next_address: int = 0
34+
self.constprop_results: dict[ir.SSAValue, const.JointResult] = {}
35+
36+
def clear(self):
37+
self.next_address = 0
38+
self.constprop_results.clear()
39+
40+
@property
41+
def qubit_count(self) -> int:
42+
return self.next_address
43+
44+
T = TypeVar("T")
45+
46+
def get_const_value(self, typ: type[T], value: ir.SSAValue) -> T:
47+
if isinstance(value.type, types.Hinted) and isinstance(
48+
value.type.data, const.Value
49+
):
50+
data = value.type.data.data
51+
if isinstance(data, typ):
52+
return value.type.data.data
53+
raise InterpreterError(
54+
f"Expected constant value <type = {typ}>, got {data}"
55+
)
56+
raise InterpreterError(f"Expected constant value <type = {typ}>, got {value}")
57+
58+
def run_stmt_fallback(
59+
self, frame: ForwardFrame[Address, None], stmt: ir.Statement
60+
) -> tuple[Address, ...] | interp.SpecialResult[Address]:
61+
return tuple(
62+
(
63+
self.lattice.top()
64+
if result.type.is_subseteq(QubitType)
65+
else self.lattice.bottom()
66+
)
67+
for result in stmt.results
68+
)
69+
70+
def should_exec_stmt(self, stmt: ir.Statement):
71+
return (
72+
stmt.has_trait(ir.ConstantLike)
73+
or stmt.dialect in self.dialects.data
74+
or isinstance(
75+
stmt,
76+
(
77+
func.Return,
78+
func.Invoke,
79+
py.tuple.New,
80+
ilist.New,
81+
py.GetItem,
82+
py.Alias,
83+
py.Add,
84+
cf.Branch,
85+
cf.ConditionalBranch,
86+
),
87+
)
88+
)
89+
90+
def run_method(
91+
self, method: ir.Method, args: tuple[Address, ...]
92+
) -> Address | interp.Err[Address]:
93+
if len(self.state.frames) >= self.max_depth:
94+
raise InterpreterError("maximum recursion depth exceeded")
95+
# NOTE: we do not support dynamic calls here, thus no need to propagate method object
96+
return self.run_callable(method.code, (self.lattice.bottom(),) + args)

src/bloqade/analysis/address/impls.py

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""
2+
qubit.address method table for a few builtin dialects.
3+
"""
4+
5+
from kirin import interp
6+
from kirin.dialects import cf, py, func, ilist
7+
8+
from .lattice import NotQubit, AddressReg, AddressQubit, AddressTuple
9+
from .analysis import AddressAnalysis
10+
11+
12+
@py.binop.dialect.register(key="qubit.address")
13+
class PyBinOp(interp.MethodTable):
14+
15+
@interp.impl(py.Add)
16+
def add(self, interp: AddressAnalysis, frame: interp.Frame, stmt: py.Add):
17+
lhs = frame.get(stmt.lhs)
18+
rhs = frame.get(stmt.rhs)
19+
20+
if isinstance(lhs, AddressTuple) and isinstance(rhs, AddressTuple):
21+
return (AddressTuple(data=lhs.data + rhs.data),)
22+
else:
23+
return (NotQubit(),)
24+
25+
26+
@py.tuple.dialect.register(key="qubit.address")
27+
class PyTuple(interp.MethodTable):
28+
@interp.impl(py.tuple.New)
29+
def new_tuple(
30+
self,
31+
interp: AddressAnalysis,
32+
frame: interp.Frame,
33+
stmt: py.tuple.New,
34+
):
35+
return (AddressTuple(frame.get_values(stmt.args)),)
36+
37+
38+
@ilist.dialect.register(key="qubit.address")
39+
class IList(interp.MethodTable):
40+
@interp.impl(ilist.New)
41+
def new_ilist(
42+
self,
43+
interp: AddressAnalysis,
44+
frame: interp.Frame,
45+
stmt: ilist.New,
46+
):
47+
return (AddressTuple(frame.get_values(stmt.args)),)
48+
49+
50+
@py.list.dialect.register(key="qubit.address")
51+
class PyList(interp.MethodTable):
52+
@interp.impl(py.list.New)
53+
def new_ilist(
54+
self,
55+
interp: AddressAnalysis,
56+
frame: interp.Frame,
57+
stmt: py.list.New,
58+
):
59+
return (AddressTuple(frame.get_values(stmt.args)),)
60+
61+
62+
@py.indexing.dialect.register(key="qubit.address")
63+
class PyIndexing(interp.MethodTable):
64+
@interp.impl(py.GetItem)
65+
def getitem(self, interp: AddressAnalysis, frame: interp.Frame, stmt: py.GetItem):
66+
idx = interp.get_const_value(int, stmt.index)
67+
obj = frame.get(stmt.obj)
68+
if isinstance(obj, AddressTuple):
69+
return (obj.data[idx],)
70+
elif isinstance(obj, AddressReg):
71+
return (AddressQubit(obj.data[idx]),)
72+
else:
73+
return (NotQubit(),)
74+
75+
76+
@py.assign.dialect.register(key="qubit.address")
77+
class PyAssign(interp.MethodTable):
78+
@interp.impl(py.Alias)
79+
def alias(self, interp: AddressAnalysis, frame: interp.Frame, stmt: py.Alias):
80+
return (frame.get(stmt.value),)
81+
82+
83+
@func.dialect.register(key="qubit.address")
84+
class Func(interp.MethodTable):
85+
@interp.impl(func.Return)
86+
def return_(self, _: AddressAnalysis, frame: interp.Frame, stmt: func.Return):
87+
return interp.ReturnValue(frame.get(stmt.value))
88+
89+
90+
@cf.dialect.register(key="qubit.address")
91+
class Cf(cf.typeinfer.TypeInfer):
92+
# NOTE: cf just re-use the type infer method table
93+
# it's the same process as type infer.
94+
pass
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from typing import Sequence, final
2+
from dataclasses import dataclass
3+
4+
from kirin.lattice import (
5+
SingletonMeta,
6+
BoundedLattice,
7+
SimpleJoinMixin,
8+
SimpleMeetMixin,
9+
)
10+
11+
12+
@dataclass
13+
class Address(
14+
SimpleJoinMixin["Address"],
15+
SimpleMeetMixin["Address"],
16+
BoundedLattice["Address"],
17+
):
18+
19+
@classmethod
20+
def bottom(cls) -> "Address":
21+
return NotQubit()
22+
23+
@classmethod
24+
def top(cls) -> "Address":
25+
return AnyAddress()
26+
27+
28+
@final
29+
@dataclass
30+
class NotQubit(Address, metaclass=SingletonMeta):
31+
32+
def is_subseteq(self, other: Address) -> bool:
33+
return True
34+
35+
36+
@final
37+
@dataclass
38+
class AnyAddress(Address, metaclass=SingletonMeta):
39+
40+
def is_subseteq(self, other: Address) -> bool:
41+
return isinstance(other, AnyAddress)
42+
43+
44+
@final
45+
@dataclass
46+
class AddressTuple(Address):
47+
data: tuple[Address, ...]
48+
49+
def is_subseteq(self, other: Address) -> bool:
50+
if isinstance(other, AddressTuple):
51+
return all(a.is_subseteq(b) for a, b in zip(self.data, other.data))
52+
return False
53+
54+
55+
@final
56+
@dataclass
57+
class AddressReg(Address):
58+
data: Sequence[int]
59+
60+
def is_subseteq(self, other: Address) -> bool:
61+
if isinstance(other, AddressReg):
62+
return self.data == other.data
63+
return False
64+
65+
66+
@final
67+
@dataclass
68+
class AddressQubit(Address):
69+
data: int
70+
71+
def is_subseteq(self, other: Address) -> bool:
72+
if isinstance(other, AddressQubit):
73+
return self.data == other.data
74+
return False

0 commit comments

Comments
 (0)