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.
Files changed (58) hide show
  1. spatial/__init__.py +6 -0
  2. spatial/ai/__init__.py +18 -0
  3. spatial/ai/kvcache.py +81 -0
  4. spatial/cli.py +135 -0
  5. spatial/compute/__init__.py +3 -0
  6. spatial/compute/cost.py +69 -0
  7. spatial/compute/detect.py +139 -0
  8. spatial/compute/monitor.py +111 -0
  9. spatial/compute/scheduler.py +168 -0
  10. spatial/compute/taskqueue.py +107 -0
  11. spatial/compute/worker.py +89 -0
  12. spatial/compute/workloads.py +148 -0
  13. spatial/core/__init__.py +1 -0
  14. spatial/core/energy.py +101 -0
  15. spatial/core/ledger.py +76 -0
  16. spatial/core/model.py +82 -0
  17. spatial/core/protocol.py +167 -0
  18. spatial/core/registry.py +59 -0
  19. spatial/holographic/__init__.py +8 -0
  20. spatial/holographic/cgh.py +40 -0
  21. spatial/holographic/model.py +49 -0
  22. spatial/intelligence/__init__.py +6 -0
  23. spatial/intelligence/predictor.py +51 -0
  24. spatial/memory/__init__.py +5 -0
  25. spatial/memory/fararray.py +44 -0
  26. spatial/memory/pageserver.py +141 -0
  27. spatial/memory/secure.py +100 -0
  28. spatial/memory/shared.py +43 -0
  29. spatial/memory/tiered.py +355 -0
  30. spatial/q/__init__.py +4 -0
  31. spatial/q/__main__.py +9 -0
  32. spatial/q/backends/__init__.py +122 -0
  33. spatial/q/codegen.py +153 -0
  34. spatial/q/compiler.py +276 -0
  35. spatial/q/ir.py +121 -0
  36. spatial/q/lexer.py +61 -0
  37. spatial/q/parser.py +330 -0
  38. spatial/q/qir.py +128 -0
  39. spatial/q/runtime.py +274 -0
  40. spatial/q/sim.py +218 -0
  41. spatial/runtime/__init__.py +3 -0
  42. spatial/runtime/envs.py +74 -0
  43. spatial/runtime/inspect.py +48 -0
  44. spatial/runtime/microvm.py +125 -0
  45. spatial/runtime/planner.py +155 -0
  46. spatial/runtime/runner.py +108 -0
  47. spatial/runtime/toolchain.py +35 -0
  48. spatial/scientific/__init__.py +8 -0
  49. spatial/scientific/jobs.py +80 -0
  50. spatial/simulation/__init__.py +7 -0
  51. spatial/simulation/heat.py +48 -0
  52. spatial/simulation/nbody.py +31 -0
  53. spatial_computing-0.5.0.dist-info/METADATA +112 -0
  54. spatial_computing-0.5.0.dist-info/RECORD +58 -0
  55. spatial_computing-0.5.0.dist-info/WHEEL +5 -0
  56. spatial_computing-0.5.0.dist-info/entry_points.txt +3 -0
  57. spatial_computing-0.5.0.dist-info/licenses/LICENSE +202 -0
  58. 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}', ""])