Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install the project
run: uv sync --all-extras --dev
run: uv sync --upgrade --dev --group dev-linux
- name: Run tests
# For example, using `pytest`
run: uv run just coverage
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/devdoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Install Documentation dependencies
run: uv sync --group doc
run: uv sync --upgrade --dev --group doc --group dev-linux
- name: Set up build cache
uses: actions/cache@v4
id: cache
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Install Documentation dependencies
run: uv sync --group doc
run: uv sync --upgrade --dev --group doc --group dev-linux
- name: Set up build cache
uses: actions/cache@v4
id: cache
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pub_doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Install Documentation dependencies
run: uv sync --group doc
run: uv sync --upgrade --dev --group doc --group dev-linux
- name: Set up build cache
uses: actions/cache@v4
id: cache
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
# Install a specific version of uv.
version: "0.5.5"
- name: Install the project
run: uv sync --all-extras --dev
run: uv sync --all-extras
- name: Build distribution 📦
run: uv build
- name: Store the distribution packages
Expand Down
1 change: 1 addition & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ AttributeIDSupressMenu = "AttributeIDSupressMenu"
Braket = "Braket"
mch = "mch"
IY = "IY"
ket = "ket"
20 changes: 20 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,26 @@ doc = [
examples = [
"networkx>=3.4.2",
]
dev-linux = [
"cirq-core[contrib]>=1.4.1",
"lark>=1.2.2",
"pyqrack-cpu>=1.38.2",
"qbraid>=0.9.5",
"ffmpeg>=1.4",
"matplotlib>=3.10.0",
"pyqt5>=5.15.11",
"tqdm>=4.67.1",
]
dev-mac-arm = [
"cirq-core[contrib]>=1.4.1",
"ffmpeg>=1.4",
"lark>=1.2.2",
"matplotlib>=3.10.0",
"pyqrack>=1.38.2",
"pyqt5>=5.15.11",
"qbraid>=0.9.5",
"tqdm>=4.67.1",
]

[tool.isort]
profile = "black"
Expand Down
18 changes: 18 additions & 0 deletions src/bloqade/pyqrack/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from .reg import (
CBitRef as CBitRef,
CRegister as CRegister,
PyQrackReg as PyQrackReg,
QubitState as QubitState,
Measurement as Measurement,
PyQrackQubit as PyQrackQubit,
)
from .base import (
StackMemory as StackMemory,
DynamicMemory as DynamicMemory,
PyQrackInterpreter as PyQrackInterpreter,
)

# 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 .target import PyQrack as PyQrack
131 changes: 131 additions & 0 deletions src/bloqade/pyqrack/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import abc
import typing
from dataclasses import field, dataclass
from unittest.mock import Mock

import numpy as np
from kirin.interp import Interpreter
from typing_extensions import Self
from kirin.interp.exceptions import InterpreterError

from bloqade.pyqrack.reg import Measurement

if typing.TYPE_CHECKING:
from pyqrack import QrackSimulator

Check warning on line 14 in src/bloqade/pyqrack/base.py

View check run for this annotation

Codecov / codecov/patch

src/bloqade/pyqrack/base.py#L14

Added line #L14 was not covered by tests


class PyQrackOptions(typing.TypedDict):
qubitCount: int
isTensorNetwork: bool
isSchmidtDecomposeMulti: bool
isSchmidtDecompose: bool
isStabilizerHybrid: bool
isBinaryDecisionTree: bool
isPaged: bool
isCpuGpuHybrid: bool
isOpenCL: bool


def _default_pyqrack_args() -> PyQrackOptions:
return PyQrackOptions(
qubitCount=-1,
isTensorNetwork=False,
isSchmidtDecomposeMulti=True,
isSchmidtDecompose=True,
isStabilizerHybrid=True,
isBinaryDecisionTree=True,
isPaged=True,
isCpuGpuHybrid=True,
isOpenCL=True,
)


@dataclass
class MemoryABC(abc.ABC):
pyqrack_options: PyQrackOptions = field(default_factory=_default_pyqrack_args)
sim_reg: "QrackSimulator" = field(init=False)

@abc.abstractmethod
def allocate(self, n_qubits: int) -> tuple[int, ...]:
"""Allocate `n_qubits` qubits and return their ids."""
...

Check warning on line 51 in src/bloqade/pyqrack/base.py

View check run for this annotation

Codecov / codecov/patch

src/bloqade/pyqrack/base.py#L51

Added line #L51 was not covered by tests

def reset(self):
"""Reset the memory, releasing all qubits."""
from pyqrack import QrackSimulator

# do not reset the simulator it might be used by
# results of the simulation
self.sim_reg = QrackSimulator(**self.pyqrack_options)


@dataclass
class MockMemory(MemoryABC):
"""Mock memory for testing purposes."""

allocated: int = field(init=False, default=0)

def allocate(self, n_qubits: int):
allocated = self.allocated + n_qubits
result = tuple(range(self.allocated, allocated))
self.allocated = allocated
return result

def reset(self):
self.allocated = 0
self.sim_reg = Mock()


@dataclass
class StackMemory(MemoryABC):
total: int = field(kw_only=True)
allocated: int = field(init=False, default=0)

def allocate(self, n_qubits: int):
curr_allocated = self.allocated
self.allocated += n_qubits

if self.allocated > self.total:
raise InterpreterError(

Check warning on line 89 in src/bloqade/pyqrack/base.py

View check run for this annotation

Codecov / codecov/patch

src/bloqade/pyqrack/base.py#L89

Added line #L89 was not covered by tests
f"qubit allocation exceeds memory, "
f"{self.total} qubits, "
f"{self.allocated} allocated"
)

return tuple(range(curr_allocated, self.allocated))

def reset(self):
super().reset()
self.allocated = 0


@dataclass
class DynamicMemory(MemoryABC):
def __post_init__(self):
self.reset()

if self.sim_reg.is_tensor_network:
raise ValueError("DynamicMemory does not support tensor networks")

Check warning on line 108 in src/bloqade/pyqrack/base.py

View check run for this annotation

Codecov / codecov/patch

src/bloqade/pyqrack/base.py#L108

Added line #L108 was not covered by tests

def allocate(self, n_qubits: int):
start = self.sim_reg.num_qubits()
for i in range(start, start + n_qubits):
self.sim_reg.allocate_qubit(i)

return tuple(range(start, start + n_qubits))


@dataclass
class PyQrackInterpreter(Interpreter):
keys = ["pyqrack", "main"]
memory: MemoryABC = field(kw_only=True)
rng_state: np.random.Generator = field(
default_factory=np.random.default_rng, kw_only=True
)
loss_m_result: Measurement = field(default=Measurement.One, kw_only=True)
"""The value of a measurement result when a qubit is lost."""

def initialize(self) -> Self:
super().initialize()
self.memory.reset() # reset allocated qubits
return self
Empty file.
103 changes: 103 additions & 0 deletions src/bloqade/pyqrack/noise/native.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from typing import TYPE_CHECKING, List

from kirin import interp

from bloqade.noise import native
from bloqade.pyqrack import PyQrackInterpreter, reg

if TYPE_CHECKING:
from pyqrack import QrackSimulator

Check warning on line 9 in src/bloqade/pyqrack/noise/native.py

View check run for this annotation

Codecov / codecov/patch

src/bloqade/pyqrack/noise/native.py#L9

Added line #L9 was not covered by tests


@native.dialect.register(key="pyqrack")
class PyQrackMethods(interp.MethodTable):
def apply_pauli_error(
self,
interp: PyQrackInterpreter,
qarg: reg.PyQrackQubit,
px: float,
py: float,
pz: float,
):
p = [1 - (px + py + pz), px, py, pz]

assert all(0 <= x <= 1 for x in p), "Invalid Pauli error probabilities"

which = interp.rng_state.choice(["i", "x", "y", "z"], p=p)

if which == "i":
return

getattr(qarg.sim_reg, which)(qarg.addr)

@interp.impl(native.PauliChannel)
def single_qubit_error_channel(
self,
interp: PyQrackInterpreter,
frame: interp.Frame,
stmt: native.PauliChannel,
):
qargs: List[reg.PyQrackQubit] = frame.get(stmt.qargs)

active_qubits = (qarg for qarg in qargs if qarg.is_active())

for qarg in active_qubits:
self.apply_pauli_error(interp, qarg, stmt.px, stmt.py, stmt.pz)

return ()

@interp.impl(native.CZPauliChannel)
def cz_pauli_channel(
self,
interp: PyQrackInterpreter,
frame: interp.Frame,
stmt: native.CZPauliChannel,
):

qargs: List[reg.PyQrackQubit] = frame.get(stmt.qargs)
ctrls: List[reg.PyQrackQubit] = frame.get(stmt.ctrls)

if stmt.paired:
valid_pairs = (
(ctrl, qarg)
for ctrl, qarg in zip(ctrls, qargs)
if ctrl.is_active() and qarg.is_active()
)
else:
valid_pairs = (
(ctrl, qarg)
for ctrl, qarg in zip(ctrls, qargs)
if ctrl.is_active() ^ qarg.is_active()
)

for ctrl, qarg in valid_pairs:
if ctrl.is_active():
self.apply_pauli_error(
interp, ctrl, stmt.px_ctrl, stmt.py_ctrl, stmt.pz_ctrl
)

if qarg.is_active():
self.apply_pauli_error(
interp, qarg, stmt.px_qarg, stmt.py_qarg, stmt.pz_qarg
)

return ()

@interp.impl(native.AtomLossChannel)
def atom_loss_channel(
self,
interp: PyQrackInterpreter,
frame: interp.Frame,
stmt: native.AtomLossChannel,
):
qargs: List[reg.PyQrackQubit["QrackSimulator"]] = frame.get(stmt.qargs)

active_qubits = (qarg for qarg in qargs if qarg.is_active())

for qarg in active_qubits:
if interp.rng_state.uniform() <= stmt.prob:
sim_reg = qarg.ref.sim_reg
sim_reg.force_m(qarg.addr, 0)
qarg.drop()

return ()
Empty file.
Loading