Skip to content

Commit a5a7d90

Browse files
authored
Feat/guppy gen v2 (#181)
* Initial implementation
1 parent a313bcc commit a5a7d90

File tree

66 files changed

+18217
-637
lines changed

Some content is hidden

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

66 files changed

+18217
-637
lines changed

Cargo.lock

Lines changed: 143 additions & 137 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ decoder-cache-clean: ## Clean decoder download cache
223223
pytest: ## Run tests on the Python package (not including optional dependencies). ASSUMES: previous build command
224224
uv run pytest ./python/tests/ --doctest-modules -m "not optional_dependency"
225225
uv run pytest ./python/pecos-rslib/tests/
226+
uv run pytest ./python/slr-tests/ -m "not optional_dependency"
226227

227228
.PHONY: pytest-dep
228229
pytest-dep: ## Run tests on the Python package only for optional dependencies. ASSUMES: previous build command

crates/pecos-qasm/tests/general_noise_builder_test.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,11 @@ fn test_general_noise_builder_with_pauli_models() {
7373

7474
let noise_model = NoiseModelType::General(Box::new(noise_builder));
7575

76-
let results = qasm_sim(qasm).noise(noise_model).run(1000).unwrap();
76+
let results = qasm_sim(qasm)
77+
.seed(42)
78+
.noise(noise_model)
79+
.run(1000)
80+
.unwrap();
7781

7882
let shot_map = results.try_as_shot_map().unwrap();
7983
let values = shot_map.try_bits_as_u64("c").unwrap();
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"id": "df9aa642-5320-4134-a41d-b6f45f626245",
7+
"metadata": {},
8+
"outputs": [
9+
{
10+
"name": "stdout",
11+
"output_type": "stream",
12+
"text": [
13+
"from __future__ import annotations\n",
14+
"\n",
15+
"from typing import no_type_check\n",
16+
"\n",
17+
"from guppylang.decorator import guppy\n",
18+
"from guppylang.std import quantum\n",
19+
"from guppylang.std.quantum import qubit\n",
20+
"from guppylang.std.quantum.functional import (reset, h, x, y, z, s, t, sdg, tdg, cx, cy, cz)\n",
21+
"from guppylang.std.builtins import array, owned, result, py\n",
22+
"\n",
23+
"@guppy.struct\n",
24+
"@no_type_check\n",
25+
"class steane_struct:\n",
26+
" c: array[bool, 32]\n",
27+
" d: array[qubit, 7]\n",
28+
" flag_x: array[bool, 3]\n",
29+
" flags: array[bool, 3]\n",
30+
" flags_z: array[bool, 3]\n",
31+
" last_raw_syn_x: array[bool, 32]\n",
32+
" last_raw_syn_z: array[bool, 32]\n",
33+
" raw_meas: array[bool, 7]\n",
34+
" scratch: array[bool, 32]\n",
35+
" syn_meas: array[bool, 32]\n",
36+
" syn_x: array[bool, 3]\n",
37+
" syn_z: array[bool, 3]\n",
38+
" syndromes: array[bool, 3]\n",
39+
" verify_prep: array[bool, 32]\n",
40+
"\n",
41+
"\n",
42+
"# === Qubit Allocation Optimization Report ===\n",
43+
"# Array: c_d (size: 7)\n",
44+
"# Strategy: pre_allocate\n",
45+
"# Array: c_a (size: 3)\n",
46+
"# Strategy: pre_allocate\n",
47+
"# - Element 1 allocated but not used in operations\n",
48+
"# - Element 2 allocated but not used in operations\n",
49+
"# - Some elements are reused after consumption\n",
50+
"\n",
51+
"@guppy\n",
52+
"@no_type_check\n",
53+
"def main() -> None:\n",
54+
" # Pre-allocate ancilla array c_a (used as function parameter)\n",
55+
" c_a = array(quantum.qubit() for _ in range(3))\n",
56+
" c_d = array(quantum.qubit() for _ in range(7))\n",
57+
" c_c = array(False for _ in range(32))\n",
58+
" c_syn_meas = array(False for _ in range(32))\n",
59+
" c_last_raw_syn_x = array(False for _ in range(32))\n",
60+
" c_last_raw_syn_z = array(False for _ in range(32))\n",
61+
" c_scratch = array(False for _ in range(32))\n",
62+
" c_flag_x = array(False for _ in range(3))\n",
63+
" c_flags_z = array(False for _ in range(3))\n",
64+
" c_flags = array(False for _ in range(3))\n",
65+
" c_raw_meas = array(False for _ in range(7))\n",
66+
" c_syn_x = array(False for _ in range(3))\n",
67+
" c_syn_z = array(False for _ in range(3))\n",
68+
" c_syndromes = array(False for _ in range(3))\n",
69+
" c_verify_prep = array(False for _ in range(32))\n",
70+
" c = steane_struct(c_c, c_d, c_flag_x, c_flags, c_flags_z, c_last_raw_syn_x, c_last_raw_syn_z, c_raw_meas, c_scratch, c_syn_meas, c_syn_x, c_syn_z, c_syndromes, c_verify_prep)\n",
71+
" # Skip unpacking c_d - consumed by struct constructor\n",
72+
" c, c_a = steane_prep_rus(c, c_a)\n",
73+
" # Decompose struct c for cleanup\n",
74+
" c_c_final, c_d_final, c_flag_x_final, c_flags_final, c_flags_z_final, c_last_raw_syn_x_final, c_last_raw_syn_z_final, c_raw_meas_final, c_scratch_final, c_syn_meas_final, c_syn_x_final, c_syn_z_final, c_syndromes_final, c_verify_prep_final = steane_decompose(c)\n",
75+
" result(\"c_c\", c_c_final)\n",
76+
" result(\"c_syn_meas\", c_syn_meas_final)\n",
77+
" result(\"c_last_raw_syn_x\", c_last_raw_syn_x_final)\n",
78+
" result(\"c_last_raw_syn_z\", c_last_raw_syn_z_final)\n",
79+
" result(\"c_scratch\", c_scratch_final)\n",
80+
" result(\"c_flag_x\", c_flag_x_final)\n",
81+
" result(\"c_flags_z\", c_flags_z_final)\n",
82+
" result(\"c_flags\", c_flags_final)\n",
83+
" result(\"c_raw_meas\", c_raw_meas_final)\n",
84+
" result(\"c_syn_x\", c_syn_x_final)\n",
85+
" result(\"c_syn_z\", c_syn_z_final)\n",
86+
" result(\"c_syndromes\", c_syndromes_final)\n",
87+
" result(\"c_verify_prep\", c_verify_prep_final)\n",
88+
" # Discard quantum fields from c\n",
89+
" quantum.discard_array(c_d_final)\n",
90+
" # Note: struct c contains unconsumed quantum arrays\n",
91+
" # Discard c_a\n",
92+
" quantum.discard_array(c_a)\n",
93+
"\n",
94+
"@guppy\n",
95+
"@no_type_check\n",
96+
"def steane_decompose(c: steane_struct @owned) -> tuple[array[bool, 32], array[qubit, 7], array[bool, 3], array[bool, 3], array[bool, 3], array[bool, 32], array[bool, 32], array[bool, 7], array[bool, 32], array[bool, 32], array[bool, 3], array[bool, 3], array[bool, 3], array[bool, 32]]:\n",
97+
" return c.c, c.d, c.flag_x, c.flags, c.flags_z, c.last_raw_syn_x, c.last_raw_syn_z, c.raw_meas, c.scratch, c.syn_meas, c.syn_x, c.syn_z, c.syndromes, c.verify_prep\n",
98+
"\n",
99+
"@guppy\n",
100+
"@no_type_check\n",
101+
"def steane_discard(c: steane_struct @owned) -> None:\n",
102+
" quantum.discard_array(c.d)\n",
103+
"\n",
104+
"@guppy\n",
105+
"@no_type_check\n",
106+
"def steane_prep_rus(c: steane_struct @owned, c_a: array[quantum.qubit, 3] @owned) -> tuple[steane_struct, array[quantum.qubit, 3]]:\n",
107+
" # Unpack ancilla array c_a to avoid MoveOutOfSubscriptError with @owned\n",
108+
" c_a_0, c_a_1, c_a_2 = c_a\n",
109+
" c_a = array(c_a_0, c_a_1, c_a_2)\n",
110+
" c, c_a = steane_prep_encoding_ft_zero(c, c_a)\n",
111+
" for _ in range(2):\n",
112+
" # Extract condition variable to avoid @owned struct field access in loop\n",
113+
" verify_prep_0_extracted = c.verify_prep[0]\n",
114+
" if verify_prep_0_extracted:\n",
115+
" c, c_a = steane_prep_encoding_ft_zero(c, c_a)\n",
116+
" log_zero_rot()\n",
117+
" return c, c_a\n",
118+
"\n",
119+
"@guppy\n",
120+
"@no_type_check\n",
121+
"def steane_prep_encoding_ft_zero(c: steane_struct @owned, c_a: array[quantum.qubit, 3] @owned) -> tuple[steane_struct, array[quantum.qubit, 3]]:\n",
122+
" # Unpack ancilla array c_a to avoid MoveOutOfSubscriptError with @owned\n",
123+
" c_a_0, c_a_1, c_a_2 = c_a\n",
124+
" for i in range(0, 7):\n",
125+
" quantum.reset(c.d[i])\n",
126+
" quantum.reset(c_a_0)\n",
127+
" c = steane_prep_encoding_non_ft_zero(c)\n",
128+
" c_a = array(c_a_0, c_a_1, c_a_2)\n",
129+
" c, c_a = steane_prep_zero_verify(c, c_a)\n",
130+
" return c, c_a\n",
131+
"\n",
132+
"@guppy\n",
133+
"@no_type_check\n",
134+
"def log_zero_rot() -> None:\n",
135+
" pass\n",
136+
"\n",
137+
"@guppy\n",
138+
"@no_type_check\n",
139+
"def steane_prep_encoding_non_ft_zero(c: steane_struct @owned) -> steane_struct:\n",
140+
" quantum.h(c.d[0])\n",
141+
" quantum.h(c.d[4])\n",
142+
" quantum.h(c.d[6])\n",
143+
" quantum.cx(c.d[4], c.d[5])\n",
144+
" quantum.cx(c.d[0], c.d[1])\n",
145+
" quantum.cx(c.d[6], c.d[3])\n",
146+
" quantum.cx(c.d[4], c.d[2])\n",
147+
" quantum.cx(c.d[6], c.d[5])\n",
148+
" quantum.cx(c.d[0], c.d[3])\n",
149+
" quantum.cx(c.d[4], c.d[1])\n",
150+
" quantum.cx(c.d[3], c.d[2])\n",
151+
" return c\n",
152+
"\n",
153+
"@guppy\n",
154+
"@no_type_check\n",
155+
"def steane_prep_zero_verify(c: steane_struct @owned, c_a: array[quantum.qubit, 3] @owned) -> tuple[steane_struct, array[quantum.qubit, 3]]:\n",
156+
" # Unpack ancilla array c_a to avoid MoveOutOfSubscriptError with @owned\n",
157+
" c_a_0, c_a_1, c_a_2 = c_a\n",
158+
" # verification step\n",
159+
" quantum.cx(c.d[5], c_a_0)\n",
160+
" quantum.cx(c.d[1], c_a_0)\n",
161+
" quantum.cx(c.d[3], c_a_0)\n",
162+
" c.verify_prep[0] = quantum.measure(c_a_0)\n",
163+
" c_a_0 = quantum.qubit()\n",
164+
" return c, array(c_a_0, c_a_1, c_a_2)\n"
165+
]
166+
}
167+
],
168+
"source": [
169+
"from pecos.qeclib.steane.steane_class import Steane\n",
170+
"from pecos.slr import Main, SlrConverter\n",
171+
"\n",
172+
"prog = Main(\n",
173+
" c := Steane(\"c\"),\n",
174+
" c.pz(),\n",
175+
")\n",
176+
"\n",
177+
"print(SlrConverter(prog).guppy())"
178+
]
179+
},
180+
{
181+
"cell_type": "code",
182+
"execution_count": 2,
183+
"id": "4d68f6f5-4688-4d3b-bcd5-326088981c6c",
184+
"metadata": {},
185+
"outputs": [],
186+
"source": [
187+
"hugr = SlrConverter(prog).hugr()"
188+
]
189+
},
190+
{
191+
"cell_type": "code",
192+
"execution_count": null,
193+
"id": "96b4e356-c240-407f-8df9-8a49045710cd",
194+
"metadata": {},
195+
"outputs": [],
196+
"source": []
197+
}
198+
],
199+
"metadata": {
200+
"kernelspec": {
201+
"display_name": "Python 3 (ipykernel)",
202+
"language": "python",
203+
"name": "python3"
204+
},
205+
"language_info": {
206+
"codemirror_mode": {
207+
"name": "ipython",
208+
"version": 3
209+
},
210+
"file_extension": ".py",
211+
"mimetype": "text/x-python",
212+
"name": "python",
213+
"nbconvert_exporter": "python",
214+
"pygments_lexer": "ipython3",
215+
"version": "3.12.8"
216+
}
217+
},
218+
"nbformat": 4,
219+
"nbformat_minor": 5
220+
}

pyproject.toml

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
name = "pecos-workspace"
33
version = "0.7.0.dev4"
44
dependencies = [
5-
"wasmer~=1.1.0",
6-
"wasmer-compiler-cranelift~=1.1.0",
5+
# Note: guppylang is an optional dependency in quantum-pecos
6+
# Don't include it here as a direct dependency
77
]
88

99
[tool.uv.workspace]
@@ -12,17 +12,20 @@ members = [
1212
"python/quantum-pecos"
1313
]
1414

15+
[tool.uv.sources]
16+
# guppylang = { path = "../guppylang" } # Commented out to use packaged version
17+
1518
[dependency-groups]
1619
dev = [
17-
"maturin>=1.2,<2.0", # For building (should match build requirements)
18-
"setuptools>=62.6", # Build system
19-
"pre-commit", # Git hooks
20-
"black", # Code formatting
21-
"ruff", # Fast Python linting
22-
"mkdocs", # Documentation framework
23-
"mkdocs-material", # Material theme for MkDocs
20+
"maturin>=1.2,<2.0", # For building (should match build requirements)
21+
"setuptools>=62.6", # Build system
22+
"pre-commit", # Git hooks
23+
"black", # Code formatting
24+
"ruff", # Fast Python linting
25+
"mkdocs", # Documentation framework
26+
"mkdocs-material", # Material theme for MkDocs
2427
"mkdocstrings[python]", # Code documentation extraction
25-
"markdown-exec[ansi]", # Executable markdown blocks
28+
"markdown-exec[ansi]", # Executable markdown blocks
2629
# Runtime dependencies for development
2730
"numpy>=1.15.0",
2831
"scipy>=1.1.0",
@@ -31,6 +34,7 @@ dev = [
3134
"phir>=0.3.3",
3235
# WebAssembly runtimes for testing
3336
"wasmtime>=13.0",
37+
"jupyter>=1.1.1",
3438
]
3539
test = [ # pinning testing environment
3640
"pytest==8.3.3", # 8.3.4 seems to be causing errors
@@ -41,6 +45,11 @@ test = [ # pinning testing environment
4145
[tool.uv]
4246
default-groups = ["dev", "test"]
4347

48+
# Override dependencies to ensure correct versions
49+
override-dependencies = [
50+
"hugr==0.13.0",
51+
]
52+
4453
[tool.pytest.ini_options]
4554
markers = [
4655
"optional_dependency: mark a test as using one or more optional dependencies",
-41.4 KB
Binary file not shown.

python/quantum-pecos/pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ repository = "https://github.com/PECOS-packages/PECOS"
5858
qir = [
5959
"llvmlite==0.43.0; python_version < '3.13'"
6060
]
61+
guppy = [
62+
"guppylang>=0.21.0", # Install guppylang first
63+
"selene-sim~=0.2.0", # Then selene-sim (dependency of guppylang)
64+
"hugr>=0.13.0,<0.14", # Use stable version compatible with guppylang
65+
]
6166
projectq = [ # State-vector sims using ProjectQ
6267
"pybind11>=2.2.3",
6368
"projectq>=0.5",
@@ -82,6 +87,7 @@ all = [
8287
"quantum-pecos[wasm-all]",
8388
"quantum-pecos[visualization]",
8489
"quantum-pecos[qir]",
90+
"quantum-pecos[guppy]",
8591
]
8692
# The following only work for some environments/Python versions:
8793
qulacs = [ # State-vector sims using Qulacs

python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ def __init__(
7373
ValueError: If provided ancilla register has fewer than 3 qubits.
7474
"""
7575
super().__init__()
76+
# Set the source class for code generation
77+
self.source_class = self.__class__.__name__
7678
self.check_indices = [[2, 1, 3, 0], [5, 2, 1, 4], [6, 5, 2, 3]]
7779

7880
self.d = QReg(f"{name}_d", 7)

python/quantum-pecos/src/pecos/slr/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from pecos.slr.block import Block
1313
from pecos.slr.cond_block import If, Repeat
14+
from pecos.slr.loop_block import For, While
1415
from pecos.slr.main import Main
1516
from pecos.slr.main import Main as SLR # noqa: N814
1617
from pecos.slr.misc import Barrier, Comment, Parallel, Permute
@@ -24,6 +25,7 @@
2425
"Block",
2526
"CReg",
2627
"Comment",
28+
"For",
2729
"If",
2830
"Main",
2931
"Parallel",
@@ -33,4 +35,5 @@
3335
"Repeat",
3436
"SlrConverter",
3537
"Vars",
38+
"While",
3639
]

python/quantum-pecos/src/pecos/slr/block.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,19 @@
1717
class Block(Node):
1818
"""A collection of other operations and blocks."""
1919

20-
def __init__(self, *args, ops=None, vargs=None, allow_no_ops=True) -> None:
20+
def __init__(
21+
self,
22+
*args,
23+
ops=None,
24+
vargs=None,
25+
allow_no_ops=True,
26+
block_name=None,
27+
) -> None:
2128
self.ops = []
2229
self.vars = Vars()
30+
# Preserve the original block type name for code generation
31+
self.block_name = block_name or self.__class__.__name__
32+
self.block_module = self.__class__.__module__
2333

2434
if args and ops:
2535
msg = "Can not use both *args for ops and the ops keyword argument."

0 commit comments

Comments
 (0)