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
@@ -0,0 +1,125 @@
1
+ """Linux micro-VM runner (QEMU). Boots a kernel + generated initramfs containing a static
2
+ Linux ELF, runs it, captures its output from the serial console, powers off.
3
+
4
+ accel="hvf" : hardware virtualization (same ISA as host) -> near-native CPU speed
5
+ accel="tcg" : full software emulation -> the cost of cross-ISA execution
6
+
7
+ No network device, no disk, no host folder is attached to the guest: it only sees the
8
+ files packed in its initramfs. Guest RAM is a hard cap (-m).
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import gzip
13
+ import os
14
+ import shutil
15
+ import subprocess
16
+ import tempfile
17
+ import time
18
+ from dataclasses import dataclass
19
+ from pathlib import Path
20
+
21
+ VENDOR = Path(__file__).resolve().parents[2] / "vendor"
22
+ KERNEL = VENDOR / "vmlinuz-virt"
23
+ BUSYBOX = VENDOR / "busybox-x86_64"
24
+
25
+
26
+ def _cpio_entry(name: str, mode: int, data: bytes = b"", rdev: tuple[int, int] = (0, 0), ino: int = 0) -> bytes:
27
+ n = name.encode() + b"\0"
28
+ hdr = "070701" + "".join(f"{v:08X}" for v in (
29
+ ino, mode, 0, 0, 1, 0, len(data), 0, 0, rdev[0], rdev[1], len(n), 0))
30
+ out = hdr.encode() + n
31
+ out += b"\0" * (-len(out) % 4)
32
+ out += data + b"\0" * (-len(data) % 4)
33
+ return out
34
+
35
+
36
+ def build_initramfs(app: bytes, args: list[str], out_path: str, files: dict | None = None,
37
+ app_name: str = "app") -> int:
38
+ """newc cpio archive written directly (no root needed for the device node)."""
39
+ init = "\n".join([
40
+ "#!/bin/sh",
41
+ "/bin/busybox mount -t devtmpfs dev /dev 2>/dev/null",
42
+ "/bin/busybox mount -t proc proc /proc 2>/dev/null",
43
+ "echo SPATIAL_BEGIN",
44
+ f"/{app_name} " + " ".join(args),
45
+ "echo SPATIAL_EXIT $?",
46
+ "/bin/busybox poweroff -f",
47
+ ""]).encode()
48
+ S_DIR, S_REG, S_CHR, S_LNK = 0o040755, 0o100755, 0o020600, 0o120777
49
+ parts = [
50
+ _cpio_entry("bin", S_DIR, ino=1), _cpio_entry("dev", S_DIR, ino=2), _cpio_entry("proc", S_DIR, ino=3),
51
+ _cpio_entry("dev/console", S_CHR, rdev=(5, 1), ino=4),
52
+ _cpio_entry("bin/busybox", S_REG, BUSYBOX.read_bytes(), ino=5),
53
+ _cpio_entry("bin/sh", S_LNK, b"busybox", ino=6),
54
+ _cpio_entry("init", S_REG, init, ino=7),
55
+ _cpio_entry(app_name, S_REG, app, ino=8),
56
+ _cpio_entry("data", S_DIR, ino=9),
57
+ ]
58
+ for i, (fname, content) in enumerate((files or {}).items()):
59
+ if "/" in fname or fname in ("", ".", ".."):
60
+ raise ValueError(f"input file name must be a plain name, got {fname!r}")
61
+ parts.append(_cpio_entry(f"data/{fname}", 0o100444, content, ino=10 + i))
62
+ parts.append(_cpio_entry("TRAILER!!!", 0))
63
+ blob = gzip.compress(b"".join(parts), compresslevel=1)
64
+ Path(out_path).write_bytes(blob)
65
+ return len(blob)
66
+
67
+
68
+ @dataclass
69
+ class VmResult:
70
+ ok: bool
71
+ accel: str
72
+ stdout: str # what the app printed
73
+ exit_code: int | None
74
+ total_wall_s: float # QEMU start -> QEMU exit (boot + app + shutdown)
75
+ boot_s: float | None # QEMU start -> app start marker
76
+ raw_tail: str = ""
77
+
78
+
79
+ def available() -> dict:
80
+ return {"qemu": shutil.which("qemu-system-x86_64"), "kernel": KERNEL.exists(), "busybox": BUSYBOX.exists()}
81
+
82
+
83
+ def run_elf(elf_path: str, args: list[str] | None = None, accel: str = "hvf", mem_mb: int = 256,
84
+ cpus: int = 1, timeout_s: float = 180.0, files: dict | None = None,
85
+ app_name: str = "app") -> VmResult:
86
+ """`files` ({name: bytes}) are copied read-only into the guest under /data. They are the
87
+ only host data the guest can see; nothing is written back to the host."""
88
+ qemu = shutil.which("qemu-system-x86_64")
89
+ if not (qemu and KERNEL.exists() and BUSYBOX.exists()):
90
+ raise RuntimeError(f"micro-VM prerequisites missing: {available()}")
91
+ with tempfile.TemporaryDirectory(prefix="spatial-vm-") as td:
92
+ initrd = os.path.join(td, "initramfs.cpio.gz")
93
+ build_initramfs(Path(elf_path).read_bytes(), args or [], initrd, files, app_name)
94
+ cmd = [qemu, "-accel", accel, "-cpu", "host" if accel == "hvf" else "max",
95
+ "-m", str(mem_mb), "-smp", str(cpus), "-kernel", str(KERNEL), "-initrd", initrd,
96
+ "-append", "console=ttyS0 quiet panic=-1 rdinit=/init",
97
+ "-nodefaults", "-no-reboot", "-display", "none", "-serial", "stdio"]
98
+ t0 = time.perf_counter()
99
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL, text=True,
100
+ errors="replace")
101
+ lines, boot_s, code, capture, app_out = [], None, None, False, []
102
+ try:
103
+ for line in p.stdout:
104
+ line = line.rstrip("\r\n")
105
+ lines.append(line)
106
+ if line.strip() == "SPATIAL_BEGIN":
107
+ boot_s = time.perf_counter() - t0
108
+ capture = True
109
+ elif line.startswith("SPATIAL_EXIT"):
110
+ code = int(line.split()[1])
111
+ capture = False
112
+ elif capture:
113
+ app_out.append(line)
114
+ if time.perf_counter() - t0 > timeout_s:
115
+ break
116
+ p.wait(timeout=max(1.0, timeout_s - (time.perf_counter() - t0)))
117
+ except subprocess.TimeoutExpired:
118
+ pass
119
+ finally:
120
+ if p.poll() is None:
121
+ p.kill()
122
+ p.wait()
123
+ total = time.perf_counter() - t0
124
+ return VmResult(ok=code == 0, accel=accel, stdout="\n".join(app_out), exit_code=code,
125
+ total_wall_s=total, boot_s=boot_s, raw_tail="\n".join(lines[-15:]))
@@ -0,0 +1,155 @@
1
+ """Spatial Runtime Engine planner + executor.
2
+
3
+ plan(artifact, host): ordered list of execution routes, each marked feasible or not WITH the
4
+ reason. execute(): runs the first feasible route and returns the app output and timings.
5
+ No route is reported as feasible unless its prerequisites were detected on this host.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import shutil
11
+ import subprocess
12
+ import time
13
+ from dataclasses import dataclass, field
14
+ from pathlib import Path
15
+
16
+ from . import microvm
17
+ from .inspect import Artifact, inspect
18
+
19
+
20
+ @dataclass
21
+ class Route:
22
+ name: str
23
+ feasible: bool
24
+ reason: str
25
+ expected_overhead: str = ""
26
+ isolation: str = ""
27
+
28
+
29
+ @dataclass
30
+ class Execution:
31
+ route: str
32
+ ok: bool
33
+ stdout: str
34
+ wall_s: float
35
+ extra: dict = field(default_factory=dict)
36
+
37
+
38
+ def find_wine() -> str | None:
39
+ """Wine on PATH, or a user-level unpacked build under ~/.local/share/spatial (no system install)."""
40
+ w = shutil.which("wine64") or shutil.which("wine")
41
+ if w:
42
+ return w
43
+ base = Path.home() / ".local" / "share" / "spatial"
44
+ for cand in sorted(base.glob("**/bin/wine")) if base.exists() else []:
45
+ if os.access(cand, os.X_OK):
46
+ return str(cand)
47
+ return None
48
+
49
+
50
+ def host_caps(node=None) -> dict:
51
+ import platform
52
+ caps = {"os": {"Darwin": "macos", "Linux": "linux", "Windows": "windows"}.get(platform.system(), "?"),
53
+ "arch": {"AMD64": "x86_64", "arm64": "aarch64"}.get(platform.machine(), platform.machine()),
54
+ "node": shutil.which("node"), "wine": find_wine(),
55
+ "docker": shutil.which("docker") or shutil.which("podman"),
56
+ "qemu_x86": shutil.which("qemu-system-x86_64"),
57
+ "microvm": all(microvm.available().values())}
58
+ try:
59
+ caps["hvf"] = subprocess.run(["sysctl", "-n", "kern.hv_support"], capture_output=True,
60
+ text=True, timeout=5).stdout.strip() == "1"
61
+ except Exception:
62
+ caps["hvf"] = False
63
+ return caps
64
+
65
+
66
+ def plan(art: Artifact, caps: dict | None = None) -> list[Route]:
67
+ caps = caps or host_caps()
68
+ R: list[Route] = []
69
+ same_arch = caps["arch"] in art.arch or ("arm64" in art.arch and caps["arch"] == "aarch64")
70
+ if art.format == "macho":
71
+ ok = caps["os"] == "macos" and same_arch
72
+ R.append(Route("native", ok, "same OS and CPU" if ok else f"host is {caps['os']}/{caps['arch']}, binary is macOS/{art.arch}",
73
+ "none", "none (host process)"))
74
+ elif art.format == "elf":
75
+ if caps["os"] == "linux" and same_arch:
76
+ R.append(Route("native", True, "same OS and CPU", "none", "none"))
77
+ hv = caps["microvm"] and caps["hvf"] and "x86_64" in art.arch and caps["arch"] == "x86_64"
78
+ R.append(Route("microvm-hvf", hv,
79
+ "Linux x86_64 guest under Hypervisor.framework" if hv else
80
+ "needs QEMU + kernel + HVF and an x86_64 guest on an x86_64 host",
81
+ "VM boot (~1-2 s) + near-native CPU", "hardware VM, no network, no host files"))
82
+ tcg = caps["microvm"] and "x86_64" in art.arch
83
+ R.append(Route("microvm-tcg", tcg,
84
+ "software emulation (works without HVF or across CPU families)" if tcg else
85
+ "no kernel/emulator available for this guest architecture",
86
+ "5-30x slower CPU", "emulated VM"))
87
+ R.append(Route("container", bool(caps["docker"]) and same_arch,
88
+ "OCI runtime found" if caps["docker"] else "no Docker/Podman installed",
89
+ "Linux VM shared by containers on macOS", "namespace isolation inside a VM"))
90
+ elif art.format == "pe":
91
+ R.append(Route("native", caps["os"] == "windows" and same_arch,
92
+ "same OS" if caps["os"] == "windows" else "host is not Windows", "none", "none"))
93
+ R.append(Route("wine", bool(caps["wine"]),
94
+ "Wine found" if caps["wine"] else
95
+ "Wine is not installed (not installed automatically: unsigned third-party build, needs user consent)",
96
+ "API translation, no CPU emulation on x86_64", "host process (weak)"))
97
+ R.append(Route("windows-vm", False,
98
+ "no Windows image/licence on this machine; would run under QEMU+HVF",
99
+ "full guest OS (>=4 GB RAM, >=20 GB disk)", "hardware VM"))
100
+ R.append(Route("remote-windows", False, "no remote Windows host configured", "network latency", "remote machine"))
101
+ elif art.format == "wasm":
102
+ ok = bool(caps["node"])
103
+ R.append(Route("wasm-node-wasi", ok, "Node.js WASI runtime found" if ok else "no WASM runtime",
104
+ "JIT, typically 1.1-2x native", "WASI sandbox (no files/network unless granted)"))
105
+ elif art.format == "script":
106
+ R.append(Route("native-interpreter", True, f"interpreter: {art.detail}", "none", "none"))
107
+ else:
108
+ R.append(Route("none", False, "unrecognised format"))
109
+ return R
110
+
111
+
112
+ _WASI_JS = """
113
+ const { WASI } = require('node:wasi'); const fs = require('node:fs');
114
+ process.removeAllListeners('warning');
115
+ const wasi = new WASI({ version: 'preview1', args: ['app', ...process.argv.slice(3)], env: {} });
116
+ (async () => {
117
+ const m = await WebAssembly.compile(fs.readFileSync(process.argv[2]));
118
+ const i = await WebAssembly.instantiate(m, wasi.getImportObject());
119
+ wasi.start(i);
120
+ })();
121
+ """
122
+
123
+
124
+ def execute(path: str, args: list[str] | None = None, route: str | None = None, timeout_s: float = 300) -> Execution:
125
+ art = inspect(path)
126
+ routes = plan(art)
127
+ chosen = next((r for r in routes if r.feasible and (route is None or r.name == route)), None)
128
+ if chosen is None:
129
+ why = "; ".join(f"{r.name}: {r.reason}" for r in routes)
130
+ return Execution(route or "none", False, "", 0.0, {"not_runnable": why, "format": art.format})
131
+ args = args or []
132
+ t0 = time.perf_counter()
133
+ if chosen.name in ("native", "native-interpreter"):
134
+ r = subprocess.run([path, *args], capture_output=True, text=True, timeout=timeout_s)
135
+ return Execution(chosen.name, r.returncode == 0, r.stdout.strip(), time.perf_counter() - t0)
136
+ if chosen.name.startswith("microvm-"):
137
+ v = microvm.run_elf(path, args, accel=chosen.name.split("-")[1], timeout_s=timeout_s)
138
+ return Execution(chosen.name, v.ok, v.stdout.strip(), v.total_wall_s,
139
+ {"boot_s": v.boot_s, "exit_code": v.exit_code, "tail": "" if v.ok else v.raw_tail})
140
+ if chosen.name == "wasm-node-wasi":
141
+ js = Path(os.environ.get("TMPDIR", "/tmp")) / f"spatial-wasi-{os.getpid()}.js"
142
+ js.write_text(_WASI_JS)
143
+ try:
144
+ r = subprocess.run(["node", "--no-warnings", str(js), path, *args], capture_output=True,
145
+ text=True, timeout=timeout_s)
146
+ finally:
147
+ js.unlink(missing_ok=True)
148
+ return Execution(chosen.name, r.returncode == 0, r.stdout.strip(), time.perf_counter() - t0,
149
+ {"stderr": r.stderr[-300:]})
150
+ if chosen.name == "wine":
151
+ env = {**os.environ, "WINEDEBUG": "-all", "WINEPREFIX": str(Path.home() / ".local/share/spatial/wineprefix")}
152
+ r = subprocess.run([find_wine(), path, *args], capture_output=True, text=True, timeout=timeout_s, env=env)
153
+ return Execution(chosen.name, r.returncode == 0, r.stdout.strip(), time.perf_counter() - t0,
154
+ {"stderr": r.stderr[-300:]})
155
+ return Execution(chosen.name, False, "", 0.0, {"not_runnable": "route has no executor yet"})
@@ -0,0 +1,108 @@
1
+ """Remote execution: a runner that accepts a Linux ELF over SWP/1 and runs it ONLY inside a
2
+ micro-VM (no network, no disk, no host files, RAM capped). This is the one place where
3
+ code crosses the wire, which is why the sandbox is not optional and cannot be disabled
4
+ by the caller.
5
+
6
+ Run: spatial serve runtime --token-file <path>
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import secrets
12
+ import subprocess
13
+ import sys
14
+ import tempfile
15
+ from pathlib import Path
16
+
17
+ from ..core.protocol import RpcClient, RpcServer
18
+ from . import microvm
19
+ from .inspect import inspect
20
+
21
+ MAX_ELF = 64 * 1024 * 1024
22
+ MAX_TIMEOUT = 300.0
23
+
24
+
25
+ def _handler(h: dict, blob: bytes):
26
+ op = h.get("op")
27
+ if op == "ping":
28
+ return {"pong": True, "microvm": microvm.available()}, b""
29
+ if op != "run_elf":
30
+ raise ValueError(f"unknown op {op!r}")
31
+ if len(blob) > MAX_ELF:
32
+ raise ValueError("binary too large")
33
+ args = [str(a) for a in h.get("args", [])]
34
+ if any(c in a for a in args for c in "\n;&|`$<>"):
35
+ raise ValueError("argument contains shell metacharacters")
36
+ with tempfile.NamedTemporaryFile(prefix="spatial-remote-", suffix=".elf", delete=False) as f:
37
+ f.write(blob)
38
+ path = f.name
39
+ try:
40
+ art = inspect(path)
41
+ if art.format != "elf" or "x86_64" not in art.arch:
42
+ raise ValueError(f"runner only accepts Linux x86_64 ELF binaries, got {art.format}/{art.arch}")
43
+ accel = "hvf" if h.get("accel", "hvf") == "hvf" else "tcg"
44
+ r = microvm.run_elf(path, args, accel=accel, mem_mb=min(int(h.get("mem_mb", 256)), 1024),
45
+ timeout_s=min(float(h.get("timeout_s", 120)), MAX_TIMEOUT))
46
+ finally:
47
+ os.unlink(path)
48
+ return {"run_ok": r.ok, "exit_code": r.exit_code, "stdout": r.stdout, "boot_s": r.boot_s,
49
+ "total_wall_s": r.total_wall_s, "accel": r.accel}, b""
50
+
51
+
52
+ def serve(token: bytes, host: str = "127.0.0.1", port: int = 0, allow_remote: bool = False) -> RpcServer:
53
+ return RpcServer(_handler, token, host=host, port=port, allow_remote=allow_remote)
54
+
55
+
56
+ def run_remote(client: RpcClient, elf_path: str, args: list[str] | None = None, **opts) -> dict:
57
+ rh, _ = client.call({"op": "run_elf", "args": args or [], **opts}, Path(elf_path).read_bytes())
58
+ return rh
59
+
60
+
61
+ class LocalRunnerProcess:
62
+ """Runner in a separate OS process on loopback (stand-in for a remote host)."""
63
+
64
+ def __init__(self):
65
+ self.token = secrets.token_bytes(32)
66
+ self._tf = Path(os.environ.get("TMPDIR", "/tmp")) / f"spatial-rt-{os.getpid()}-{secrets.token_hex(4)}"
67
+ self._tf.write_bytes(self.token)
68
+ os.chmod(self._tf, 0o600)
69
+ self.proc = subprocess.Popen([sys.executable, "-m", "spatial.runtime.runner", "--token-file", str(self._tf)],
70
+ cwd=Path(__file__).resolve().parents[2], stdout=subprocess.PIPE, text=True)
71
+ line = self.proc.stdout.readline()
72
+ if not line.startswith("PORT "):
73
+ raise RuntimeError(f"runner failed to start: {line!r}")
74
+ self.port = int(line.split()[1])
75
+
76
+ def client(self) -> RpcClient:
77
+ return RpcClient("127.0.0.1", self.port, self.token, timeout=MAX_TIMEOUT + 30)
78
+
79
+ def stop(self):
80
+ self.proc.terminate()
81
+ try:
82
+ self.proc.wait(timeout=5)
83
+ except subprocess.TimeoutExpired:
84
+ self.proc.kill()
85
+ self._tf.unlink(missing_ok=True)
86
+
87
+ def __enter__(self):
88
+ return self
89
+
90
+ def __exit__(self, *a):
91
+ self.stop()
92
+
93
+
94
+ def main():
95
+ import argparse
96
+ ap = argparse.ArgumentParser()
97
+ ap.add_argument("--token-file", required=True)
98
+ ap.add_argument("--host", default="127.0.0.1")
99
+ ap.add_argument("--port", type=int, default=0)
100
+ ap.add_argument("--allow-remote", action="store_true")
101
+ a = ap.parse_args()
102
+ srv = serve(Path(a.token_file).read_bytes(), a.host, a.port, a.allow_remote)
103
+ print(f"PORT {srv.port}", flush=True)
104
+ srv.serve_forever()
105
+
106
+
107
+ if __name__ == "__main__":
108
+ main()
@@ -0,0 +1,35 @@
1
+ """Cross-compilation of one C source to several executable formats (zig cc from PyPI `ziglang`).
2
+ Used to produce REAL foreign binaries for the runtime tests instead of mock files."""
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ TARGETS = {
10
+ "macos-x86_64": (None, "bench-macos"), # host clang
11
+ "linux-x86_64": ("x86_64-linux-musl", "bench-linux.elf"),
12
+ "windows-x86_64": ("x86_64-windows-gnu", "bench-windows.exe"),
13
+ "wasm32-wasi": ("wasm32-wasi", "bench.wasm"),
14
+ }
15
+ FLAGS = ["-O2", "-ffp-contract=off"] # no FMA contraction: identical IEEE results everywhere
16
+
17
+
18
+ def build(src: str, out_dir: str, target: str) -> Path:
19
+ triple, name = TARGETS[target]
20
+ out = Path(out_dir) / name
21
+ out.parent.mkdir(parents=True, exist_ok=True)
22
+ if triple is None:
23
+ cmd = ["clang", *FLAGS, src, "-o", str(out)]
24
+ else:
25
+ cmd = [sys.executable, "-m", "ziglang", "cc", "-target", triple, *FLAGS, src, "-o", str(out)]
26
+ if "linux" in triple:
27
+ cmd.insert(-2, "-static")
28
+ r = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
29
+ if r.returncode != 0:
30
+ raise RuntimeError(f"build {target} failed: {r.stderr[-800:]}")
31
+ return out
32
+
33
+
34
+ def build_all(src: str, out_dir: str) -> dict[str, Path]:
35
+ return {t: build(src, out_dir, t) for t in TARGETS}
@@ -0,0 +1,8 @@
1
+ """Spatial Scientific & Industrial Engine — FIRST COMPONENTS ONLY.
2
+
3
+ A JobSpec describes a numerical job with a validation target; the engine runs it and
4
+ checks the answer against an independent reference. One reference job is implemented
5
+ (Monte Carlo option pricing vs Black-Scholes) plus an RK4 ODE job checked against its
6
+ analytic solution. No industrial solver is claimed.
7
+ """
8
+ from .jobs import JobSpec, run_job, european_call_mc, black_scholes_call, harmonic_rk4 # noqa: F401
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from dataclasses import dataclass, field
5
+ from typing import Callable
6
+
7
+ import numpy as np
8
+
9
+ from ..compute.monitor import Measure
10
+ from ..core.model import Measurement
11
+
12
+
13
+ def black_scholes_call(s0: float, k: float, r: float, sigma: float, t: float) -> float:
14
+ d1 = (math.log(s0 / k) + (r + sigma * sigma / 2) * t) / (sigma * math.sqrt(t))
15
+ d2 = d1 - sigma * math.sqrt(t)
16
+ cdf = lambda x: 0.5 * (1 + math.erf(x / math.sqrt(2)))
17
+ return s0 * cdf(d1) - k * math.exp(-r * t) * cdf(d2)
18
+
19
+
20
+ def european_call_mc(s0: float, k: float, r: float, sigma: float, t: float, paths: int,
21
+ seed: int = 0) -> tuple[float, float]:
22
+ """Returns (price, standard error). Antithetic variates."""
23
+ rng = np.random.default_rng(seed)
24
+ acc = acc2 = 0.0
25
+ n = 0
26
+ left = paths // 2
27
+ drift, vol = (r - sigma * sigma / 2) * t, sigma * math.sqrt(t)
28
+ disc = math.exp(-r * t)
29
+ while left > 0:
30
+ m = min(left, 1_000_000)
31
+ z = rng.standard_normal(m)
32
+ pay = 0.5 * disc * (np.maximum(s0 * np.exp(drift + vol * z) - k, 0)
33
+ + np.maximum(s0 * np.exp(drift - vol * z) - k, 0))
34
+ acc += float(pay.sum())
35
+ acc2 += float((pay * pay).sum())
36
+ n += m
37
+ left -= m
38
+ mean = acc / n
39
+ return mean, math.sqrt(max(acc2 / n - mean * mean, 0) / n)
40
+
41
+
42
+ def harmonic_rk4(omega: float, t_end: float, steps: int) -> tuple[float, float]:
43
+ """x'' = -omega^2 x, x(0)=1, v(0)=0, integrated with RK4. Returns (x(t_end), error bound).
44
+ The bound is the RK4 global error scale (omega*dt)^4 * omega*t_end, used as the tolerance."""
45
+ dt = t_end / steps
46
+ x, v = 1.0, 0.0
47
+ f = lambda x, v: (v, -omega * omega * x)
48
+ for _ in range(steps):
49
+ k1 = f(x, v)
50
+ k2 = f(x + dt / 2 * k1[0], v + dt / 2 * k1[1])
51
+ k3 = f(x + dt / 2 * k2[0], v + dt / 2 * k2[1])
52
+ k4 = f(x + dt * k3[0], v + dt * k3[1])
53
+ x += dt / 6 * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0])
54
+ v += dt / 6 * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1])
55
+ return x, (omega * dt) ** 4 * omega * t_end
56
+
57
+
58
+ @dataclass
59
+ class JobSpec:
60
+ name: str
61
+ sector: str
62
+ fn: Callable[[], tuple[float, float]] # returns (value, standard error)
63
+ reference: float | None = None
64
+ tolerance_sigmas: float = 4.0
65
+ tags: list[str] = field(default_factory=list)
66
+
67
+
68
+ def run_job(job: JobSpec) -> Measurement:
69
+ out = {}
70
+ with Measure("scientific", job.name, "local_numpy", power=False) as m:
71
+ out["v"], out["se"] = job.fn()
72
+ if m.ok:
73
+ m.metrics.update(value=out["v"], std_error=out["se"], reference=job.reference)
74
+ if job.reference is not None:
75
+ dev = abs(out["v"] - job.reference) / max(out["se"], 1e-300)
76
+ m.metrics["deviation_sigmas"] = dev
77
+ m.ok = dev <= job.tolerance_sigmas
78
+ if not m.ok:
79
+ m.notes = "validation against reference failed"
80
+ return m
@@ -0,0 +1,7 @@
1
+ """Spatial Simulation Engine — FIRST COMPONENTS ONLY.
2
+
3
+ An explicit 2-D heat-diffusion solver (NumPy and Numba variants producing the same field)
4
+ and a symplectic N-body integrator. No multiphysics, no digital twin.
5
+ """
6
+ from .heat import step_numpy, step_numba, simulate, stable_dt # noqa: F401
7
+ from . import nbody # noqa: F401
@@ -0,0 +1,48 @@
1
+ """Explicit finite-difference heat equation u_t = alpha * laplacian(u), insulated borders."""
2
+ from __future__ import annotations
3
+
4
+ import numpy as np
5
+
6
+ try:
7
+ from numba import njit
8
+ HAVE_NUMBA = True
9
+ except Exception: # pragma: no cover
10
+ HAVE_NUMBA = False
11
+
12
+
13
+ def stable_dt(alpha: float, dx: float) -> float:
14
+ """Largest stable time step for the explicit scheme in 2-D (CFL): dx^2 / (4 alpha)."""
15
+ return dx * dx / (4 * alpha)
16
+
17
+
18
+ def step_numpy(u: np.ndarray, c: float) -> np.ndarray:
19
+ """c = alpha*dt/dx^2 (must be <= 0.25). Neumann (zero-flux) boundaries via edge padding."""
20
+ p = np.pad(u, 1, mode="edge")
21
+ return u + c * (p[:-2, 1:-1] + p[2:, 1:-1] + p[1:-1, :-2] + p[1:-1, 2:] - 4 * u)
22
+
23
+
24
+ if HAVE_NUMBA:
25
+ @njit(cache=True)
26
+ def step_numba(u, c):
27
+ n, m = u.shape
28
+ out = np.empty_like(u)
29
+ for i in range(n):
30
+ for j in range(m):
31
+ up = u[i - 1, j] if i > 0 else u[i, j]
32
+ dn = u[i + 1, j] if i < n - 1 else u[i, j]
33
+ lf = u[i, j - 1] if j > 0 else u[i, j]
34
+ rt = u[i, j + 1] if j < m - 1 else u[i, j]
35
+ out[i, j] = u[i, j] + c * (up + dn + lf + rt - 4 * u[i, j])
36
+ return out
37
+ else: # pragma: no cover
38
+ step_numba = step_numpy
39
+
40
+
41
+ def simulate(u0: np.ndarray, c: float, steps: int, backend: str = "numpy") -> np.ndarray:
42
+ if c > 0.25:
43
+ raise ValueError("unstable: c = alpha*dt/dx^2 must be <= 0.25")
44
+ f = step_numba if backend == "numba" else step_numpy
45
+ u = u0.astype(np.float64)
46
+ for _ in range(steps):
47
+ u = f(u, c)
48
+ return u
@@ -0,0 +1,31 @@
1
+ """Gravitational N-body with a leapfrog (kick-drift-kick) integrator. Symplectic: total
2
+ energy oscillates but does not drift, which the test checks."""
3
+ from __future__ import annotations
4
+
5
+ import numpy as np
6
+
7
+
8
+ def accelerations(pos: np.ndarray, mass: np.ndarray, soft: float = 1e-3) -> np.ndarray:
9
+ d = pos[None, :, :] - pos[:, None, :]
10
+ r2 = (d ** 2).sum(-1) + soft ** 2
11
+ np.fill_diagonal(r2, np.inf)
12
+ return (d * (mass[None, :, None] / r2[..., None] ** 1.5)).sum(1)
13
+
14
+
15
+ def energy(pos, vel, mass, soft: float = 1e-3) -> float:
16
+ kin = 0.5 * (mass * (vel ** 2).sum(-1)).sum()
17
+ d = pos[None] - pos[:, None]
18
+ r = np.sqrt((d ** 2).sum(-1) + soft ** 2)
19
+ iu = np.triu_indices(len(mass), 1)
20
+ return float(kin - (mass[iu[0]] * mass[iu[1]] / r[iu]).sum())
21
+
22
+
23
+ def leapfrog(pos, vel, mass, dt: float, steps: int, soft: float = 1e-3):
24
+ pos, vel = pos.copy(), vel.copy()
25
+ a = accelerations(pos, mass, soft)
26
+ for _ in range(steps):
27
+ vel += 0.5 * dt * a
28
+ pos += dt * vel
29
+ a = accelerations(pos, mass, soft)
30
+ vel += 0.5 * dt * a
31
+ return pos, vel