spatial-computing 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spatial/__init__.py +6 -0
- spatial/ai/__init__.py +18 -0
- spatial/ai/kvcache.py +81 -0
- spatial/cli.py +135 -0
- spatial/compute/__init__.py +3 -0
- spatial/compute/cost.py +69 -0
- spatial/compute/detect.py +139 -0
- spatial/compute/monitor.py +111 -0
- spatial/compute/scheduler.py +168 -0
- spatial/compute/taskqueue.py +107 -0
- spatial/compute/worker.py +89 -0
- spatial/compute/workloads.py +148 -0
- spatial/core/__init__.py +1 -0
- spatial/core/energy.py +101 -0
- spatial/core/ledger.py +76 -0
- spatial/core/model.py +82 -0
- spatial/core/protocol.py +167 -0
- spatial/core/registry.py +59 -0
- spatial/holographic/__init__.py +8 -0
- spatial/holographic/cgh.py +40 -0
- spatial/holographic/model.py +49 -0
- spatial/intelligence/__init__.py +6 -0
- spatial/intelligence/predictor.py +51 -0
- spatial/memory/__init__.py +5 -0
- spatial/memory/fararray.py +44 -0
- spatial/memory/pageserver.py +141 -0
- spatial/memory/secure.py +100 -0
- spatial/memory/shared.py +43 -0
- spatial/memory/tiered.py +355 -0
- spatial/q/__init__.py +4 -0
- spatial/q/__main__.py +9 -0
- spatial/q/backends/__init__.py +122 -0
- spatial/q/codegen.py +153 -0
- spatial/q/compiler.py +276 -0
- spatial/q/ir.py +121 -0
- spatial/q/lexer.py +61 -0
- spatial/q/parser.py +330 -0
- spatial/q/qir.py +128 -0
- spatial/q/runtime.py +274 -0
- spatial/q/sim.py +218 -0
- spatial/runtime/__init__.py +3 -0
- spatial/runtime/envs.py +74 -0
- spatial/runtime/inspect.py +48 -0
- spatial/runtime/microvm.py +125 -0
- spatial/runtime/planner.py +155 -0
- spatial/runtime/runner.py +108 -0
- spatial/runtime/toolchain.py +35 -0
- spatial/scientific/__init__.py +8 -0
- spatial/scientific/jobs.py +80 -0
- spatial/simulation/__init__.py +7 -0
- spatial/simulation/heat.py +48 -0
- spatial/simulation/nbody.py +31 -0
- spatial_computing-0.5.0.dist-info/METADATA +112 -0
- spatial_computing-0.5.0.dist-info/RECORD +58 -0
- spatial_computing-0.5.0.dist-info/WHEEL +5 -0
- spatial_computing-0.5.0.dist-info/entry_points.txt +3 -0
- spatial_computing-0.5.0.dist-info/licenses/LICENSE +202 -0
- spatial_computing-0.5.0.dist-info/top_level.txt +1 -0
spatial/q/parser.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""SPATIAL Q recursive-descent parser. AST nodes are tuples: (kind, ..., line).
|
|
2
|
+
|
|
3
|
+
See docs/07_SPATIAL_Q_SPEC.md for the grammar.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
|
|
9
|
+
from .lexer import SQSyntaxError, Tok, lex
|
|
10
|
+
|
|
11
|
+
GATES = { # name -> (n_params, n_qubits)
|
|
12
|
+
"h": (0, 1), "x": (0, 1), "y": (0, 1), "z": (0, 1), "s": (0, 1), "sdg": (0, 1),
|
|
13
|
+
"t": (0, 1), "tdg": (0, 1), "sx": (0, 1),
|
|
14
|
+
"rx": (1, 1), "ry": (1, 1), "rz": (1, 1), "p": (1, 1),
|
|
15
|
+
"cx": (0, 2), "cz": (0, 2), "swap": (0, 2), "cp": (1, 2), "crz": (1, 2),
|
|
16
|
+
"ccx": (0, 3),
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Kernel:
|
|
22
|
+
name: str
|
|
23
|
+
params: list[str]
|
|
24
|
+
body: list
|
|
25
|
+
line: int
|
|
26
|
+
ptypes: list = field(default_factory=list) # per-parameter annotation; "qubit" marks a qubit argument
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class TaskDef:
|
|
31
|
+
name: str
|
|
32
|
+
params: list[str]
|
|
33
|
+
target: str # cpu | worker | any
|
|
34
|
+
body: list
|
|
35
|
+
line: int
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class Program:
|
|
40
|
+
kernels: dict[str, Kernel] = field(default_factory=dict)
|
|
41
|
+
tasks: dict[str, TaskDef] = field(default_factory=dict)
|
|
42
|
+
main: list | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Parser:
|
|
46
|
+
def __init__(self, src: str):
|
|
47
|
+
self.t = lex(src)
|
|
48
|
+
self.i = 0
|
|
49
|
+
|
|
50
|
+
# ---- token helpers
|
|
51
|
+
@property
|
|
52
|
+
def cur(self) -> Tok:
|
|
53
|
+
return self.t[self.i]
|
|
54
|
+
|
|
55
|
+
def err(self, msg: str):
|
|
56
|
+
raise SQSyntaxError(f"line {self.cur.line}:{self.cur.col}: {msg} (found {self.cur.val!r})")
|
|
57
|
+
|
|
58
|
+
def at(self, kind: str, val=None) -> bool:
|
|
59
|
+
return self.cur.kind == kind and (val is None or self.cur.val == val)
|
|
60
|
+
|
|
61
|
+
def eat(self, kind: str, val=None) -> Tok:
|
|
62
|
+
if not self.at(kind, val):
|
|
63
|
+
self.err(f"expected {val or kind}")
|
|
64
|
+
self.i += 1
|
|
65
|
+
return self.t[self.i - 1]
|
|
66
|
+
|
|
67
|
+
def opt(self, kind: str, val=None) -> bool:
|
|
68
|
+
if self.at(kind, val):
|
|
69
|
+
self.i += 1
|
|
70
|
+
return True
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
# ---- declarations
|
|
74
|
+
def program(self) -> Program:
|
|
75
|
+
p = Program()
|
|
76
|
+
while not self.at("eof"):
|
|
77
|
+
if self.at("kw", "quantum"):
|
|
78
|
+
line = self.eat("kw").line
|
|
79
|
+
name = self.eat("id").val
|
|
80
|
+
params = self.params()
|
|
81
|
+
ptypes = self._ptypes
|
|
82
|
+
p.kernels[name] = Kernel(name, params, self.block(True), line, ptypes)
|
|
83
|
+
elif self.at("kw", "task"):
|
|
84
|
+
line = self.eat("kw").line
|
|
85
|
+
name = self.eat("id").val
|
|
86
|
+
params = self.params()
|
|
87
|
+
target = self.eat("id").val if self.opt("kw", "on") else "cpu"
|
|
88
|
+
if target not in ("cpu", "worker", "any"):
|
|
89
|
+
self.err("task target must be cpu, worker or any")
|
|
90
|
+
p.tasks[name] = TaskDef(name, params, target, self.block(False), line)
|
|
91
|
+
elif self.at("kw", "main"):
|
|
92
|
+
self.eat("kw")
|
|
93
|
+
if p.main is not None:
|
|
94
|
+
self.err("duplicate main")
|
|
95
|
+
p.main = self.block(False)
|
|
96
|
+
else:
|
|
97
|
+
self.err("expected 'quantum', 'task' or 'main'")
|
|
98
|
+
if p.main is None:
|
|
99
|
+
raise SQSyntaxError("program has no main block")
|
|
100
|
+
return p
|
|
101
|
+
|
|
102
|
+
def params(self) -> list[str]:
|
|
103
|
+
self.eat("op", "(")
|
|
104
|
+
out, types = [], []
|
|
105
|
+
while not self.at("op", ")"):
|
|
106
|
+
out.append(self.eat("id").val)
|
|
107
|
+
types.append(None)
|
|
108
|
+
if self.opt("op", ":"): # annotation; `qubit` makes the parameter a qubit argument
|
|
109
|
+
if self.cur.kind not in ("id", "kw"):
|
|
110
|
+
self.err("expected a type name")
|
|
111
|
+
types[-1] = self.cur.val
|
|
112
|
+
self.i += 1
|
|
113
|
+
if not self.opt("op", ","):
|
|
114
|
+
break
|
|
115
|
+
self.eat("op", ")")
|
|
116
|
+
self._ptypes = types
|
|
117
|
+
return out
|
|
118
|
+
|
|
119
|
+
def block(self, q: bool) -> list:
|
|
120
|
+
self.eat("op", "{")
|
|
121
|
+
out = []
|
|
122
|
+
while not self.at("op", "}"):
|
|
123
|
+
out.append(self.stmt(q))
|
|
124
|
+
self.eat("op", "}")
|
|
125
|
+
return out
|
|
126
|
+
|
|
127
|
+
# ---- statements
|
|
128
|
+
def stmt(self, q: bool):
|
|
129
|
+
c = self.cur
|
|
130
|
+
if c.kind == "kw":
|
|
131
|
+
if c.val == "qubit":
|
|
132
|
+
if not q:
|
|
133
|
+
self.err("'qubit' is only allowed inside a quantum kernel")
|
|
134
|
+
self.eat("kw")
|
|
135
|
+
name = self.eat("id").val
|
|
136
|
+
self.eat("op", "[")
|
|
137
|
+
size = self.expr()
|
|
138
|
+
self.eat("op", "]")
|
|
139
|
+
self.eat("op", ";")
|
|
140
|
+
return ("qubit", name, size, c.line)
|
|
141
|
+
if c.val == "let":
|
|
142
|
+
self.eat("kw")
|
|
143
|
+
name = self.eat("id").val
|
|
144
|
+
self.eat("op", "=")
|
|
145
|
+
e = self.expr()
|
|
146
|
+
self.eat("op", ";")
|
|
147
|
+
return ("let", name, e, c.line)
|
|
148
|
+
if c.val == "for":
|
|
149
|
+
self.eat("kw")
|
|
150
|
+
var = self.eat("id").val
|
|
151
|
+
self.eat("kw", "in")
|
|
152
|
+
lo = self.expr()
|
|
153
|
+
self.eat("op", "..")
|
|
154
|
+
hi = self.expr()
|
|
155
|
+
return ("for", var, lo, hi, self.block(q), c.line)
|
|
156
|
+
if c.val == "while":
|
|
157
|
+
self.eat("kw")
|
|
158
|
+
cond = self.expr()
|
|
159
|
+
return ("while", cond, self.block(q), c.line)
|
|
160
|
+
if c.val == "if":
|
|
161
|
+
self.eat("kw")
|
|
162
|
+
cond = self.expr()
|
|
163
|
+
then = self.block(q)
|
|
164
|
+
els = self.block(q) if self.opt("kw", "else") else []
|
|
165
|
+
return ("if", cond, then, els, c.line)
|
|
166
|
+
if c.val == "return":
|
|
167
|
+
self.eat("kw")
|
|
168
|
+
e = self.expr()
|
|
169
|
+
self.eat("op", ";")
|
|
170
|
+
return ("return", e, c.line)
|
|
171
|
+
if c.val == "print":
|
|
172
|
+
self.eat("kw")
|
|
173
|
+
es = [self.expr()]
|
|
174
|
+
while self.opt("op", ","):
|
|
175
|
+
es.append(self.expr())
|
|
176
|
+
self.eat("op", ";")
|
|
177
|
+
return ("print", es, c.line)
|
|
178
|
+
if c.val == "parallel":
|
|
179
|
+
if q:
|
|
180
|
+
self.err("'parallel' is not allowed inside a quantum kernel")
|
|
181
|
+
self.eat("kw")
|
|
182
|
+
return ("parallel", self.block(False), c.line)
|
|
183
|
+
if c.val == "reset" and q:
|
|
184
|
+
self.eat("kw")
|
|
185
|
+
qa = self.qarg()
|
|
186
|
+
self.eat("op", ";")
|
|
187
|
+
return ("reset", qa, c.line)
|
|
188
|
+
if q and c.kind == "id" and c.val in GATES:
|
|
189
|
+
self.eat("id")
|
|
190
|
+
params = []
|
|
191
|
+
if self.opt("op", "("):
|
|
192
|
+
while not self.at("op", ")"):
|
|
193
|
+
params.append(self.expr())
|
|
194
|
+
if not self.opt("op", ","):
|
|
195
|
+
break
|
|
196
|
+
self.eat("op", ")")
|
|
197
|
+
qargs = [self.qarg()]
|
|
198
|
+
while self.opt("op", ","):
|
|
199
|
+
qargs.append(self.qarg())
|
|
200
|
+
self.eat("op", ";")
|
|
201
|
+
np_, nq = GATES[c.val]
|
|
202
|
+
if len(params) != np_:
|
|
203
|
+
raise SQSyntaxError(f"line {c.line}: gate {c.val} takes {np_} parameter(s), got {len(params)}")
|
|
204
|
+
if len(qargs) != nq:
|
|
205
|
+
raise SQSyntaxError(f"line {c.line}: gate {c.val} acts on {nq} qubit(s), got {len(qargs)}")
|
|
206
|
+
return ("gate", c.val, params, qargs, c.line)
|
|
207
|
+
if q and c.kind == "id" and self.t[self.i + 1].kind == "op" and self.t[self.i + 1].val == "(":
|
|
208
|
+
self.eat("id") # sub-kernel call: name(args...);
|
|
209
|
+
args = self.call_args()
|
|
210
|
+
self.eat("op", ";")
|
|
211
|
+
return ("kcall", c.val, args, c.line)
|
|
212
|
+
if c.kind == "id" and self.t[self.i + 1].kind == "op" and self.t[self.i + 1].val == "=":
|
|
213
|
+
name = self.eat("id").val
|
|
214
|
+
self.eat("op", "=")
|
|
215
|
+
e = self.expr()
|
|
216
|
+
self.eat("op", ";")
|
|
217
|
+
return ("assign", name, e, c.line)
|
|
218
|
+
e = self.expr()
|
|
219
|
+
self.eat("op", ";")
|
|
220
|
+
return ("expr", e, c.line)
|
|
221
|
+
|
|
222
|
+
def qarg(self):
|
|
223
|
+
name = self.eat("id").val
|
|
224
|
+
idx = None
|
|
225
|
+
if self.opt("op", "["):
|
|
226
|
+
idx = self.expr()
|
|
227
|
+
self.eat("op", "]")
|
|
228
|
+
return (name, idx)
|
|
229
|
+
|
|
230
|
+
# ---- expressions (precedence climbing)
|
|
231
|
+
_BIN = [("or",), ("and",), ("==", "!=", "<", "<=", ">", ">="), ("+", "-"), ("*", "/", "%")]
|
|
232
|
+
|
|
233
|
+
def expr(self, level: int = 0):
|
|
234
|
+
if level == len(self._BIN):
|
|
235
|
+
return self.unary()
|
|
236
|
+
left = self.expr(level + 1)
|
|
237
|
+
while self.cur.kind in ("op", "kw") and self.cur.val in self._BIN[level]:
|
|
238
|
+
op = self.cur.val
|
|
239
|
+
self.i += 1
|
|
240
|
+
left = ("bin", op, left, self.expr(level + 1))
|
|
241
|
+
return left
|
|
242
|
+
|
|
243
|
+
def unary(self):
|
|
244
|
+
if self.opt("op", "-"):
|
|
245
|
+
return ("neg", self.unary())
|
|
246
|
+
if self.opt("op", "!") or self.opt("kw", "not"):
|
|
247
|
+
return ("not", self.unary())
|
|
248
|
+
return self.postfix()
|
|
249
|
+
|
|
250
|
+
def postfix(self):
|
|
251
|
+
e = self.primary()
|
|
252
|
+
while self.opt("op", "["):
|
|
253
|
+
idx = self.expr()
|
|
254
|
+
self.eat("op", "]")
|
|
255
|
+
e = ("index", e, idx)
|
|
256
|
+
return e
|
|
257
|
+
|
|
258
|
+
def call_args(self) -> list:
|
|
259
|
+
self.eat("op", "(")
|
|
260
|
+
args = []
|
|
261
|
+
while not self.at("op", ")"):
|
|
262
|
+
args.append(self.expr())
|
|
263
|
+
if not self.opt("op", ","):
|
|
264
|
+
break
|
|
265
|
+
self.eat("op", ")")
|
|
266
|
+
return args
|
|
267
|
+
|
|
268
|
+
def primary(self):
|
|
269
|
+
c = self.cur
|
|
270
|
+
if c.kind == "num":
|
|
271
|
+
self.i += 1
|
|
272
|
+
return ("num", c.val)
|
|
273
|
+
if c.kind == "str":
|
|
274
|
+
self.i += 1
|
|
275
|
+
return ("str", c.val)
|
|
276
|
+
if c.kind == "kw":
|
|
277
|
+
if c.val in ("true", "false"):
|
|
278
|
+
self.i += 1
|
|
279
|
+
return ("num", c.val == "true")
|
|
280
|
+
if c.val == "measure":
|
|
281
|
+
self.i += 1
|
|
282
|
+
return ("measure", self.qarg(), c.line)
|
|
283
|
+
if c.val == "sample":
|
|
284
|
+
self.i += 1
|
|
285
|
+
name = self.eat("id").val
|
|
286
|
+
args = self.call_args()
|
|
287
|
+
shots = self.expr(3) if self.opt("kw", "shots") else ("num", 1024)
|
|
288
|
+
backend = self.eat("id").val if self.opt("kw", "on") else "auto"
|
|
289
|
+
return ("sample", name, args, shots, backend, c.line)
|
|
290
|
+
if c.val == "expect":
|
|
291
|
+
self.i += 1
|
|
292
|
+
pauli = self.eat("str").val
|
|
293
|
+
self.eat("kw", "of")
|
|
294
|
+
name = self.eat("id").val
|
|
295
|
+
args = self.call_args()
|
|
296
|
+
backend = self.eat("id").val if self.opt("kw", "on") else "auto"
|
|
297
|
+
return ("expect", pauli, name, args, backend, c.line)
|
|
298
|
+
if c.val == "workload":
|
|
299
|
+
self.i += 1
|
|
300
|
+
self.eat("op", "(")
|
|
301
|
+
wname = self.eat("str").val
|
|
302
|
+
kwargs = {}
|
|
303
|
+
while self.opt("op", ","):
|
|
304
|
+
k = self.eat("id").val
|
|
305
|
+
self.eat("op", "=")
|
|
306
|
+
kwargs[k] = self.expr()
|
|
307
|
+
self.eat("op", ")")
|
|
308
|
+
return ("workload", wname, kwargs, c.line)
|
|
309
|
+
if c.kind == "id":
|
|
310
|
+
self.i += 1
|
|
311
|
+
if self.at("op", "("):
|
|
312
|
+
return ("call", c.val, self.call_args(), c.line)
|
|
313
|
+
return ("var", c.val, c.line)
|
|
314
|
+
if self.opt("op", "("):
|
|
315
|
+
e = self.expr()
|
|
316
|
+
self.eat("op", ")")
|
|
317
|
+
return e
|
|
318
|
+
if self.opt("op", "["):
|
|
319
|
+
items = []
|
|
320
|
+
while not self.at("op", "]"):
|
|
321
|
+
items.append(self.expr())
|
|
322
|
+
if not self.opt("op", ","):
|
|
323
|
+
break
|
|
324
|
+
self.eat("op", "]")
|
|
325
|
+
return ("list", items)
|
|
326
|
+
self.err("expected an expression")
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def parse(src: str) -> Program:
|
|
330
|
+
return Parser(src).program()
|
spatial/q/qir.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""QIR export for SQIR circuits (base and adaptive profiles).
|
|
2
|
+
|
|
3
|
+
Gates outside the common QIS set are lowered first (exact up to a global phase):
|
|
4
|
+
sx -> rx(pi/2) p(t) -> rz(t)
|
|
5
|
+
cp(t) a,b -> rz(t/2) a; cx a,b; rz(-t/2) b; cx a,b; rz(t/2) b
|
|
6
|
+
crz(t) c,t -> rz(t/2) t; cx c,t; rz(-t/2) t; cx c,t
|
|
7
|
+
Static circuits are exported in the base profile; circuits with mid-circuit measurement
|
|
8
|
+
or feed-forward in the adaptive profile.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import math
|
|
13
|
+
import struct
|
|
14
|
+
|
|
15
|
+
from .ir import Circuit, Op
|
|
16
|
+
|
|
17
|
+
_QIS = {"h": "h", "x": "x", "y": "y", "z": "z", "s": "s", "sdg": "s__adj", "t": "t", "tdg": "t__adj",
|
|
18
|
+
"rx": "rx", "ry": "ry", "rz": "rz", "cx": "cnot", "cz": "cz", "swap": "swap", "ccx": "ccx"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class QirExportError(Exception):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def lower(c: Circuit) -> Circuit:
|
|
26
|
+
"""Rewrite the circuit with gates of the QIS set only (measure/reset/conditions kept)."""
|
|
27
|
+
out: list[Op] = []
|
|
28
|
+
for o in c.ops:
|
|
29
|
+
k = o.cond
|
|
30
|
+
if o.name in ("measure", "reset"):
|
|
31
|
+
out.append(o)
|
|
32
|
+
elif o.name == "sx":
|
|
33
|
+
out.append(Op("rx", o.qubits, (math.pi / 2,), cond=k))
|
|
34
|
+
elif o.name == "p":
|
|
35
|
+
out.append(Op("rz", o.qubits, o.params, cond=k))
|
|
36
|
+
elif o.name == "cp":
|
|
37
|
+
(t,), (a, b) = o.params, o.qubits
|
|
38
|
+
out += [Op("rz", (a,), (t / 2,), cond=k), Op("cx", (a, b), cond=k), Op("rz", (b,), (-t / 2,), cond=k),
|
|
39
|
+
Op("cx", (a, b), cond=k), Op("rz", (b,), (t / 2,), cond=k)]
|
|
40
|
+
elif o.name == "crz":
|
|
41
|
+
(t,), (a, b) = o.params, o.qubits
|
|
42
|
+
out += [Op("rz", (b,), (t / 2,), cond=k), Op("cx", (a, b), cond=k), Op("rz", (b,), (-t / 2,), cond=k),
|
|
43
|
+
Op("cx", (a, b), cond=k)]
|
|
44
|
+
elif o.name in _QIS:
|
|
45
|
+
out.append(o)
|
|
46
|
+
else:
|
|
47
|
+
raise QirExportError(f"gate {o.name} has no QIR lowering")
|
|
48
|
+
return Circuit(c.name, c.n_qubits, c.n_cbits, out)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _q(i: int) -> str:
|
|
52
|
+
return "ptr null" if i == 0 else f"ptr inttoptr (i64 {i} to ptr)"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _dbl(x: float) -> str:
|
|
56
|
+
return "double 0x" + struct.pack(">d", float(x)).hex().upper() # exact IEEE-754 literal
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def to_qir(c: Circuit) -> str:
|
|
60
|
+
"""LLVM IR text (opaque pointers). Static circuits use the QIR base profile; circuits with
|
|
61
|
+
mid-circuit measurement or feed-forward use the adaptive profile (results read back with
|
|
62
|
+
read_result and conditional branches). Every qubit is measured at the end."""
|
|
63
|
+
low = lower(c)
|
|
64
|
+
dynamic = any(o.name in ("measure", "reset") or o.cond for o in low.ops)
|
|
65
|
+
body, decls, tmp, blk = [], set(), 0, 0
|
|
66
|
+
n, m = c.n_qubits, c.n_cbits
|
|
67
|
+
|
|
68
|
+
def gate(o: Op) -> str:
|
|
69
|
+
if o.name == "measure":
|
|
70
|
+
decls.add("declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1")
|
|
71
|
+
return f" call void @__quantum__qis__mz__body({_q(o.qubits[0])}, {_q(o.cbit)})"
|
|
72
|
+
if o.name == "reset":
|
|
73
|
+
decls.add("declare void @__quantum__qis__reset__body(ptr) #1")
|
|
74
|
+
return f" call void @__quantum__qis__reset__body({_q(o.qubits[0])})"
|
|
75
|
+
fn = f"__quantum__qis__{_QIS[o.name]}__body"
|
|
76
|
+
decls.add(f"declare void @{fn}({', '.join(['double'] * len(o.params) + ['ptr'] * len(o.qubits))})")
|
|
77
|
+
return f" call void @{fn}({', '.join([_dbl(p) for p in o.params] + [_q(q) for q in o.qubits])})"
|
|
78
|
+
|
|
79
|
+
i = 0
|
|
80
|
+
while i < len(low.ops):
|
|
81
|
+
o = low.ops[i]
|
|
82
|
+
if not o.cond:
|
|
83
|
+
body.append(gate(o))
|
|
84
|
+
i += 1
|
|
85
|
+
continue
|
|
86
|
+
j = i # consecutive ops under the same condition share one branch
|
|
87
|
+
while j < len(low.ops) and low.ops[j].cond == o.cond:
|
|
88
|
+
j += 1
|
|
89
|
+
decls.add("declare i1 @__quantum__qis__read_result__body(ptr)")
|
|
90
|
+
acc = None
|
|
91
|
+
for bit, val in o.cond:
|
|
92
|
+
body.append(f" %t{tmp} = call i1 @__quantum__qis__read_result__body({_q(bit)})")
|
|
93
|
+
cur = f"%t{tmp}"
|
|
94
|
+
tmp += 1
|
|
95
|
+
if val == 0:
|
|
96
|
+
body.append(f" %t{tmp} = xor i1 {cur}, true")
|
|
97
|
+
cur = f"%t{tmp}"
|
|
98
|
+
tmp += 1
|
|
99
|
+
if acc is not None:
|
|
100
|
+
body.append(f" %t{tmp} = and i1 {acc}, {cur}")
|
|
101
|
+
cur = f"%t{tmp}"
|
|
102
|
+
tmp += 1
|
|
103
|
+
acc = cur
|
|
104
|
+
body += [f" br i1 {acc}, label %then{blk}, label %cont{blk}", f"then{blk}:"]
|
|
105
|
+
body += [gate(x) for x in low.ops[i:j]]
|
|
106
|
+
body += [f" br label %cont{blk}", f"cont{blk}:"]
|
|
107
|
+
blk += 1
|
|
108
|
+
i = j
|
|
109
|
+
for q in range(n): # final read-out uses result ids after the mid-circuit ones
|
|
110
|
+
body.append(f" call void @__quantum__qis__mz__body({_q(q)}, {_q(m + q)})")
|
|
111
|
+
body.append(f" call void @__quantum__rt__array_record_output(i64 {n}, ptr null)")
|
|
112
|
+
for q in range(n):
|
|
113
|
+
body.append(f" call void @__quantum__rt__result_record_output({_q(m + q)}, ptr null)")
|
|
114
|
+
decls |= {"declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1",
|
|
115
|
+
"declare void @__quantum__rt__array_record_output(i64, ptr)",
|
|
116
|
+
"declare void @__quantum__rt__result_record_output(ptr, ptr)"}
|
|
117
|
+
profile = "adaptive_profile" if dynamic else "base_profile"
|
|
118
|
+
return "\n".join([
|
|
119
|
+
f"; SPATIAL Q kernel '{c.name}' exported to QIR ({profile})",
|
|
120
|
+
"define void @main() #0 {", "entry:", *body, " ret void", "}", "", *sorted(decls), "",
|
|
121
|
+
f'attributes #0 = {{ "entry_point" "output_labeling_schema" "qir_profiles"="{profile}" '
|
|
122
|
+
f'"required_num_qubits"="{n}" "required_num_results"="{m + n}" }}',
|
|
123
|
+
'attributes #1 = { "irreversible" }', "",
|
|
124
|
+
"!llvm.module.flags = !{!0, !1, !2, !3}",
|
|
125
|
+
'!0 = !{i32 1, !"qir_major_version", i32 1}',
|
|
126
|
+
'!1 = !{i32 7, !"qir_minor_version", i32 0}',
|
|
127
|
+
'!2 = !{i32 1, !"dynamic_qubit_management", i1 false}',
|
|
128
|
+
'!3 = !{i32 1, !"dynamic_result_management", i1 false}', ""])
|