Skip to content

Commit 1c6e5e0

Browse files
authored
L1 text intermediate representation (IR) (#1824)
# Qualtran-L1 Intermediate Representation The L1 intermediate representation (IR) for Qualtran is a host-language-agnostic interchange format for hierarchical composite quantum programs. The Python `qualtran` library (Qualtran-L2) can compile to Qualtran-L1. - L1 quantum functions are represented as text vs. L2 bloqs represented as in-memory Python objects. - a L1 `*.qlt` file contains a full hierarchical record of the quantum funciton vs. "lazy" evaluation of decompositions in L2. - Compile-time classical parameters are filled in and static in L1 vs. templated families of quantum subroutines via bloq classes in L2. <img width="1144" height="1091" alt="image" src="https://github.com/user-attachments/assets/53baf72c-e6fe-4f84-8c9d-b4d8219567bc" /> <img width="1137" height="1010" alt="image" src="https://github.com/user-attachments/assets/0454a3af-bbd1-4791-89b3-9af75b94e7f0" />
1 parent aa47606 commit 1c6e5e0

File tree

18 files changed

+2520
-17
lines changed

18 files changed

+2520
-17
lines changed

dev_tools/qualtran_dev_tools/make_reference_docs/_make.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,12 @@
7878
]
7979
MINIMAL = True
8080

81-
DEFINED_IN_CONTAINER_EXCEPTIONS = ['qualtran.dtype', 'qualtran.exception', 'qualtran.cirq_interop']
81+
DEFINED_IN_CONTAINER_EXCEPTIONS = [
82+
'qualtran.dtype',
83+
'qualtran.exception',
84+
'qualtran.cirq_interop',
85+
'qualtran.l1.nodes',
86+
]
8287

8388

8489
def _get_all_aliases(obj: Union[griffe.Object, griffe.Alias]) -> Set[str]:

qualtran/_infra/composite_bloq.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454

5555
from qualtran.bloqs.bookkeeping.auto_partition import Unused
5656
from qualtran.cirq_interop._cirq_to_bloq import CirqQuregInT, CirqQuregT
57+
from qualtran.drawing import WireSymbol
5758
from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator
5859
from qualtran.simulation.classical_sim import ClassicalValT
5960
from qualtran.symbolics import SymbolicInt
@@ -141,12 +142,20 @@ class CompositeBloq(Bloq):
141142
should correspond to the dangling `Soquets` in the `cxns`.
142143
bloq_instances: Optionally specify the unique bloq instances. Otherwise, deduce them from
143144
the connections.
145+
decomposed_from: Optionally include a reference to the bloq from whence this
146+
`CompositeBloq` was decomposed. This can be used for debugging and error
147+
reporting, but is never used for deriving any properties.
148+
bloq_key: An optional string key for this bloq. This can be used for debugging and
149+
error reporting, but is never used for deriving any properties.
144150
"""
145151

146152
connections: Tuple[Connection, ...] = attrs.field(converter=_to_tuple)
147153
signature: Signature
148154
bloq_instances: FrozenSet[BloqInstance] = attrs.field(converter=_to_set)
149155

156+
decomposed_from: Optional[Bloq] = attrs.field(default=None, kw_only=True)
157+
bloq_key: Optional[str] = attrs.field(default=None, kw_only=True)
158+
150159
@bloq_instances.default
151160
def _default_bloq_instances(self):
152161
return {
@@ -524,7 +533,23 @@ def debug_text(self) -> str:
524533
delimited_gens = ('\n' + '-' * 20 + '\n').join(gen_texts)
525534
return delimited_gens
526535

536+
def wire_symbol(
537+
self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple()
538+
) -> 'WireSymbol':
539+
from qualtran.drawing import Text
540+
541+
if reg is None:
542+
if self.decomposed_from is not None:
543+
return self.decomposed_from.wire_symbol(None)
544+
if self.bloq_key is not None:
545+
return Text(self.bloq_key)
546+
return Text('cbloq')
547+
548+
return super().wire_symbol(reg, idx)
549+
527550
def __str__(self):
551+
if self.bloq_key is not None:
552+
return self.bloq_key
528553
return f'CompositeBloq([{len(self.bloq_instances)} subbloqs...])'
529554

530555

@@ -554,6 +579,9 @@ def _binst_to_cxns(
554579
binst: Union[BloqInstance, DanglingT], binst_graph: nx.DiGraph
555580
) -> Tuple[List[Connection], List[Connection]]:
556581
"""Helper method to extract all predecessor and successor Connections for a binst."""
582+
if binst not in binst_graph.nodes:
583+
return [], []
584+
557585
pred_cxns: List[Connection] = []
558586
for pred in binst_graph.pred[binst]:
559587
pred_cxns.extend(binst_graph.edges[pred, binst]['cxns'])

qualtran/l1/L1-Text-IR.ipynb

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "83e446f6-4f99-4a93-a854-0423e4421b35",
6+
"metadata": {},
7+
"source": [
8+
"# Qualtran-L1 Intermediate Representation\n",
9+
"\n",
10+
"The L1 intermediate representation (IR) for Qualtran is a host-language-agnostic interchange format for hierarchical composite quantum programs. The Python `qualtran` library (Qualtran-L2) can compile to Qualtran-L1.\n",
11+
"\n",
12+
" - L1 quantum functions are represented as text vs. L2 bloqs represented as in-memory Python objects.\n",
13+
" - a L1 `*.qlt` file contains a full hierarchical record of the quantum funciton vs. \"lazy\" evaluation of decompositions in L2.\n",
14+
" - Compile-time classical parameters are filled in and static in L1 vs. templated families of quantum subroutines via bloq classes in L2."
15+
]
16+
},
17+
{
18+
"cell_type": "markdown",
19+
"id": "7d56515d-2d3f-4cc2-97c6-5efe02eb3b84",
20+
"metadata": {},
21+
"source": [
22+
"## Example usage"
23+
]
24+
},
25+
{
26+
"cell_type": "code",
27+
"execution_count": null,
28+
"id": "ab390b71-0792-4a48-af09-27463f7d3390",
29+
"metadata": {},
30+
"outputs": [],
31+
"source": [
32+
"import qualtran as qlt\n",
33+
"import qualtran.dtype as qdt"
34+
]
35+
},
36+
{
37+
"cell_type": "markdown",
38+
"id": "667a66b5-8e1c-4d20-b3a4-17a499a8b512",
39+
"metadata": {},
40+
"source": [
41+
"### `dump_l1`\n",
42+
"\n",
43+
"This function will take a bloq and recursively \"compile\" it to the static L1 textual representation. \n",
44+
"\n",
45+
" - The hierarchical structure of the program definition is maintained.\n",
46+
" - Each bloq object is included as a `qdef` quantum function declaration with a static quantum signature (enclosed in square brackets).\n",
47+
" - Bloq objects with a defined decomposition include a `qdef` quantum function definition with a static function body (enclosed in curly braces) encoding the composition.\n",
48+
" - Bloq objects without a defined decomposition are included as `extern qdef` declarations *only*. These must include the `from` clause, which provides the L1-objectstrng used to *link* the extern qdefs with an in-memory Python bloq object."
49+
]
50+
},
51+
{
52+
"cell_type": "code",
53+
"execution_count": null,
54+
"id": "4f412be1-1377-4524-a124-fd51383ac335",
55+
"metadata": {},
56+
"outputs": [],
57+
"source": [
58+
"from qualtran.bloqs.arithmetic import BitwiseNot\n",
59+
"from qualtran.l1 import dump_l1\n",
60+
"\n",
61+
"l1_code = dump_l1(BitwiseNot(qdt.QUInt(4)))\n",
62+
"print(l1_code)"
63+
]
64+
},
65+
{
66+
"cell_type": "markdown",
67+
"id": "b8cf9e9d-e528-4925-872b-ed598ba6928c",
68+
"metadata": {},
69+
"source": [
70+
"### `dump_root_l1`\n",
71+
"\n",
72+
"This convenience function is provided to only dump one level of decomposition and declare each of the immediate callees as extern qdefs."
73+
]
74+
},
75+
{
76+
"cell_type": "code",
77+
"execution_count": null,
78+
"id": "d6f7696e-b255-434b-a8d7-ff767a0e050c",
79+
"metadata": {},
80+
"outputs": [],
81+
"source": [
82+
"from qualtran.bloqs.arithmetic import Negate\n",
83+
"from qualtran.l1 import dump_root_l1\n",
84+
"\n",
85+
"negate = Negate(qdt.QUInt(8))\n",
86+
"root_l1_code = dump_root_l1(negate)\n",
87+
"print(root_l1_code)"
88+
]
89+
},
90+
{
91+
"cell_type": "markdown",
92+
"id": "9ac23592-9f55-4ef0-9675-2c2c97f82a6c",
93+
"metadata": {},
94+
"source": [
95+
"### `load_module`"
96+
]
97+
},
98+
{
99+
"cell_type": "code",
100+
"execution_count": null,
101+
"id": "76769eeb-a777-4bf3-9466-05efda846cf5",
102+
"metadata": {},
103+
"outputs": [],
104+
"source": [
105+
"from qualtran.l1 import load_module\n",
106+
"module = load_module(\"\"\"\n",
107+
"# Qualtran-L1\n",
108+
"# 1.0.0\n",
109+
"\n",
110+
"qdef Negate[x: QUInt(8)] {\n",
111+
" x2 = BitwiseNot(8)[x=x] \n",
112+
" x3 = AddK(k=1)[x=x2] \n",
113+
" return [x=x3] \n",
114+
"}\n",
115+
"\n",
116+
"extern qdef BitwiseNot(8)\n",
117+
"from qualtran.bloqs.arithmetic.BitwiseNot(QUInt(8))\n",
118+
"[x: QUInt(8)]\n",
119+
"\n",
120+
"extern qdef AddK(k=1)\n",
121+
"from qualtran.bloqs.arithmetic.AddK(QUInt(8), 1)\n",
122+
"[x: QUInt(8)]\"\"\")\n",
123+
"module.keys()"
124+
]
125+
},
126+
{
127+
"cell_type": "code",
128+
"execution_count": null,
129+
"id": "0d98758f-8efd-42bb-a918-6842074be601",
130+
"metadata": {},
131+
"outputs": [],
132+
"source": [
133+
"module['Negate'].bloq_instances"
134+
]
135+
},
136+
{
137+
"cell_type": "code",
138+
"execution_count": null,
139+
"id": "72e18029-f45f-4a31-8cf9-1f5ce72698ef",
140+
"metadata": {},
141+
"outputs": [],
142+
"source": [
143+
"type(module['Negate']), type(negate)"
144+
]
145+
},
146+
{
147+
"cell_type": "code",
148+
"execution_count": null,
149+
"id": "8b7a75c5-7002-4294-8171-7edaecdccd55",
150+
"metadata": {},
151+
"outputs": [],
152+
"source": [
153+
"module['Negate'].call_classically(x=1)"
154+
]
155+
}
156+
],
157+
"metadata": {
158+
"kernelspec": {
159+
"display_name": "Python 3 (ipykernel)",
160+
"language": "python",
161+
"name": "python3"
162+
},
163+
"language_info": {
164+
"codemirror_mode": {
165+
"name": "ipython",
166+
"version": 3
167+
},
168+
"file_extension": ".py",
169+
"mimetype": "text/x-python",
170+
"name": "python",
171+
"nbconvert_exporter": "python",
172+
"pygments_lexer": "ipython3",
173+
"version": "3.13.9"
174+
}
175+
},
176+
"nbformat": 4,
177+
"nbformat_minor": 5
178+
}

qualtran/l1/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from ._eval import eval_cvalue_node
16-
from ._parse import parse_objectstring
17-
from ._parse_eval import load_objectstring
15+
from ._ast_to_code import L1ASTPrinter
16+
from ._ast_visitor_base import L1VisitorBase
17+
from ._eval import eval_cvalue_node, eval_module
18+
from ._parse import dump_ast, parse_module, parse_objectstring
19+
from ._parse_eval import load_bloq, load_module, load_objectstring
1820
from ._to_cobject_node import dump_objectstring, to_cobject_node
21+
from ._to_l1 import dump_l1, dump_root_l1, L1ModuleBuilder
22+
from ._vm import StandardQualtranArchitectureAgnosticVirtualMachine

0 commit comments

Comments
 (0)